Repository: liferay/liferay-docs Branch: master Commit: 23b94b964916 Files: 2539 Total size: 72.3 MB Directory structure: gitextract_40y6mjuc/ ├── .gitattributes ├── .gitignore ├── README.markdown ├── bin/ │ ├── convert-markdown.sh │ ├── convert.bat │ ├── convert.sh │ ├── migrate.py │ ├── update-liferay-learn-links.sh │ ├── update-markdown-sidebar-syntax-win.sh │ └── update-markdown-sidebar-syntax.sh ├── book/ │ ├── developer/ │ │ ├── appdev.aux │ │ ├── appdev.tex │ │ ├── customization.aux │ │ ├── customization.tex │ │ ├── frameworks.aux │ │ ├── frameworks.tex │ │ ├── publish.tex │ │ ├── reference.aux │ │ ├── reference.tex │ │ ├── tutorials.aux │ │ └── tutorials.tex │ ├── developing-liferay-dxp-72.tex │ ├── readme.md │ ├── user/ │ │ ├── deployment.aux │ │ ├── deployment.tex │ │ ├── user.aux │ │ └── user.tex │ └── using-liferay-dxp-72.tex ├── build-common.xml ├── build.properties ├── build.xml ├── code/ │ ├── liferay-book-utils/ │ │ ├── .pydevproject │ │ └── src/ │ │ ├── fix-latex.py │ │ ├── format-ebook.py │ │ └── merge-books.py │ ├── liferay-doc-utils/ │ │ ├── .gitignore │ │ ├── build.xml │ │ ├── lib/ │ │ │ ├── ant-1-8-2.jar │ │ │ ├── com.liferay.knowledge.base.markdown.converter.api.jar │ │ │ ├── com.liferay.knowledge.base.markdown.converter.impl.jar │ │ │ ├── commons-io-2.0.1.jar │ │ │ ├── freemarker.jar │ │ │ ├── jaxen-1.1.6.jar │ │ │ ├── jdom-2.0.6.jar │ │ │ ├── one-jar-appgen-0.97.jar │ │ │ ├── serializer-2.7.2.jar │ │ │ ├── xalan-2.7.2.jar │ │ │ ├── xercesImpl.jar │ │ │ └── xml-apis.jar │ │ └── src/ │ │ └── com/ │ │ └── liferay/ │ │ └── documentation/ │ │ ├── movedclassreporter/ │ │ │ ├── BasicClassInfo.java │ │ │ ├── MovedClassInfo.java │ │ │ ├── MovedClassReporterMain.java │ │ │ ├── TemplateProcessor.java │ │ │ └── dependencies/ │ │ │ └── moved-classes.ftl │ │ └── util/ │ │ ├── AddTOCTask.java │ │ ├── CheckArticleImagesTask.java │ │ ├── CheckHeadersTask.java │ │ ├── CheckImageUtil.java │ │ ├── CheckImagesTask.java │ │ ├── CheckIntrosTask.java │ │ ├── CheckLatestCommitTask.java │ │ ├── CheckLinksTask.java │ │ ├── CleanImages.java │ │ ├── ConcatMarkdownTask.java │ │ ├── ConvertHeadersTask.java │ │ ├── ConvertLinksTask.java │ │ ├── ConvertSidebarsTask.java │ │ ├── DistDiffTask.java │ │ ├── DocsUtil.java │ │ ├── FindParentIntros.java │ │ ├── GitCompare.java │ │ ├── MarkdownToHtml.java │ │ ├── NumberHeadersTask.java │ │ └── NumberImagesTask.java │ └── sample-content-portlet-lr-workspace/ │ ├── .gitignore │ ├── build.gradle │ ├── configs/ │ │ ├── common/ │ │ │ └── .touch │ │ ├── dev/ │ │ │ └── portal-ext.properties │ │ ├── local/ │ │ │ └── portal-ext.properties │ │ ├── prod/ │ │ │ ├── osgi/ │ │ │ │ └── configs/ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.cfg │ │ │ └── portal-ext.properties │ │ └── uat/ │ │ ├── osgi/ │ │ │ └── configs/ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.cfg │ │ └── portal-ext.properties │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── modules/ │ │ ├── .touch │ │ └── com.liferay.docs.samplecontent/ │ │ ├── README.markdown │ │ ├── bnd.bnd │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── liferay/ │ │ │ └── docs/ │ │ │ └── samplecontent/ │ │ │ └── portlet/ │ │ │ └── SampleContentPortlet.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── resources/ │ │ │ ├── init.jsp │ │ │ └── view.jsp │ │ ├── content/ │ │ │ └── Language.properties │ │ ├── organizations.json │ │ ├── user-groups.json │ │ └── users.json │ ├── settings.gradle │ └── themes/ │ └── .touch ├── copyright.txt ├── en/ │ ├── build.xml │ ├── deployment/ │ │ ├── articles/ │ │ │ ├── 01-deploying-liferay/ │ │ │ │ ├── 01-deploying-liferay-intro.markdown │ │ │ │ ├── 02-obtaining-liferay.markdown │ │ │ │ ├── 03-preparing-for-install.markdown │ │ │ │ ├── 04-installing-liferay.markdown │ │ │ │ ├── 05-installing-liferay-on-tomcat.markdown │ │ │ │ ├── 06-installing-liferay-portal-on-wildfly.markdown │ │ │ │ ├── 12-setting-up-marketplace-and-portal-security.markdown │ │ │ │ └── 14-document-repository-configuration/ │ │ │ │ ├── 00-document-repository-configuration-intro.markdown │ │ │ │ ├── 01-using-simple-file-system-store.markdown │ │ │ │ ├── 02-using-advanced-file-system-store.markdown │ │ │ │ ├── 03-using-s3-store.markdown │ │ │ │ └── 04-using-the-dbstore.markdown │ │ │ ├── 02-configuring-liferay/ │ │ │ │ ├── 01-configuring-liferay-portal-intro.markdown │ │ │ │ ├── 02-configuring-mail.markdown │ │ │ │ ├── 03-locales-and-encoding.markdown │ │ │ │ ├── 04-liferay-portal-clustering/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-point-all-nodes-to-the-same-liferay-portal-database.markdown │ │ │ │ │ ├── 03-configure-documents-and-media-the-same-for-all-nodes.markdown │ │ │ │ │ ├── 04-clustering-search.markdown │ │ │ │ │ ├── 05-enabling-cluster-link.markdown │ │ │ │ │ └── 06-auto-deploy-to-all-nodes.markdown │ │ │ │ ├── 05-updating-a-cluster/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-using-rolling-restarts.markdown │ │ │ │ │ └── 03-other-cluster-update-techniques.markdown │ │ │ │ ├── 06-configuring-remote-staging-clustered.markdown │ │ │ │ └── 07-content-delivery-network.markdown │ │ │ ├── 03-installing-a-search-engine/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-elasticsearch/ │ │ │ │ │ ├── 01-elasticsearch-intro.markdown │ │ │ │ │ ├── 02-preparing-to-install.markdown │ │ │ │ │ ├── 03-installing-elasticsearch.markdown │ │ │ │ │ ├── 04-configuring-the-connector.markdown │ │ │ │ │ ├── 05-advanced-configuration.markdown │ │ │ │ │ ├── 06-elasticsearch-connector-settings-reference.markdown │ │ │ │ │ ├── 07-security.markdown │ │ │ │ │ ├── 08-backing-up-es.markdown │ │ │ │ │ ├── 09-upgrading-to-elasticsearch_65.markdown │ │ │ │ │ ├── 10-upgrading-to-elasticsearch_68.markdown │ │ │ │ │ └── 11-upgrading-to-elasticsearch-73.markdown │ │ │ │ └── 03-solr/ │ │ │ │ ├── 01-installing-solr-intro.markdown │ │ │ │ ├── 02-installing-solr-basic.markdown │ │ │ │ ├── 03-installing-solr-solrcloud.markdown │ │ │ │ └── 04-solr-settings.markdown │ │ │ ├── 04-securing-liferay/ │ │ │ │ ├── 01-liferay-security-intro.markdown │ │ │ │ ├── 02-logging-in-to-liferay.markdown │ │ │ │ ├── 03-service-access-policies.markdown │ │ │ │ ├── 04-auth-verifiers.markdown │ │ │ │ ├── 05-ldap/ │ │ │ │ │ ├── 01-ldap-servers-intro.markdown │ │ │ │ │ └── 02-configuring-ldap.markdown │ │ │ │ ├── 06-token-based-authentication.markdown │ │ │ │ ├── 07-openid-connect-authentication.markdown │ │ │ │ ├── 08-openam-authentication.markdown │ │ │ │ ├── 09-cas-authentication.markdown │ │ │ │ ├── 11-ntlm-authentication.markdown │ │ │ │ ├── 12-openid-authentication.markdown │ │ │ │ ├── 13-kerberos.markdown │ │ │ │ ├── 14-cors.markdown │ │ │ │ ├── 15-antisamy.markdown │ │ │ │ └── 15-oauth2/ │ │ │ │ ├── 01-oauth2-intro.markdown │ │ │ │ ├── 02-scopes.markdown │ │ │ │ └── 03-authorizing-access.markdown │ │ │ ├── 05-upgrading-to-liferay-7-2/ │ │ │ │ ├── 01-upgrading-to-liferay-7-2-intro.markdown │ │ │ │ ├── 02-planning-for-deprecated-apps.markdown │ │ │ │ ├── 03-test-ugrading-a-liferay-backup-copy/ │ │ │ │ │ ├── 01-test-upgrading-a-liferay-backup-copy-intro.markdown │ │ │ │ │ ├── 02-pruning-the-database.markdown │ │ │ │ │ ├── 03-example-removing-intermediate-journal-article-versions.markdown │ │ │ │ │ └── 04-upgrading-your-test-server-and-database.markdown │ │ │ │ ├── 04-preparing-to-upgrade-the-liferay-database.markdown │ │ │ │ ├── 05-preparing-a-new-liferay-server.markdown │ │ │ │ ├── 06-upgrading-the-liferay-database/ │ │ │ │ │ ├── 01-upgrading-the-liferay-database-intro.markdown │ │ │ │ │ ├── 02-tuning-for-the-data-upgrade.markdown │ │ │ │ │ ├── 03-configuring-the-data-upgrade.markdown │ │ │ │ │ ├── 04-upgrading-the-core-using-the-upgrade-tool.markdown │ │ │ │ │ └── 05-upgrading-modules-using-gogo-shell.markdown │ │ │ │ ├── 07-executing-post-upgrade-tasks.markdown │ │ │ │ ├── 08-upgrading-a-sharded-environment.markdown │ │ │ │ ├── 09-migrating-from-audience-targeting/ │ │ │ │ │ ├── 01-migrating-from-audience-targeting-intro.markdown │ │ │ │ │ ├── 02-migrating-user-segments.markdown │ │ │ │ │ └── 03-manually-migrating-from-audience-targeting.markdown │ │ │ │ ├── 98-deprecated-apps-in-7-2-what-to-do.markdown │ │ │ │ └── 99-apps-in-maintenance-mode.markdown │ │ │ ├── 06-maintaining-liferay/ │ │ │ │ ├── 01-maintaining-liferay-intro.markdown │ │ │ │ └── 02-backing-up-a-liferay-installation.markdown │ │ │ ├── 07-monitoring-liferay/ │ │ │ │ ├── 01-monitoring-liferay-intro.markdown │ │ │ │ └── 02-monitoring-gc-and-the-jvm.markdown │ │ │ ├── 100-reference/ │ │ │ │ ├── 01-deployment-reference-intro.markdown │ │ │ │ ├── 02-liferay-home.markdown │ │ │ │ ├── 03-portal-properties.markdown │ │ │ │ ├── 04-system-properties.markdown │ │ │ │ └── 05-database-templates.markdown │ │ │ └── 99-troubleshooting-deployments/ │ │ │ ├── 01-troubleshooting-deployments-intro.markdown │ │ │ ├── database-not-ready.markdown │ │ │ ├── sort-order-changed-with-different-database.markdown │ │ │ └── using-files-to-config-components.markdown │ │ ├── articles-dxp/ │ │ │ ├── 01-deploying-liferay/ │ │ │ │ ├── 07-installing-liferay-portal-on-jboss-eap.markdown │ │ │ │ ├── 08-installing-liferay-on-weblogic.markdown │ │ │ │ ├── 09-installing-liferay-on-websphere.markdown │ │ │ │ ├── 11-activating-liferay-dxp.markdown │ │ │ │ └── 13-trial-plugin-installation.markdown │ │ │ ├── 02-configuring-liferay/ │ │ │ │ ├── 04-liferay-portal-clustering/ │ │ │ │ │ └── 04-clustering-search.markdown │ │ │ │ └── 08-dxp-configuration-and-tuning/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ └── 02-jvm-tuning.markdown │ │ │ ├── 03-installing-a-search-engine/ │ │ │ │ └── 03-enterprise-search/ │ │ │ │ ├── 01-les-intro.markdown │ │ │ │ ├── 02-monitoring/ │ │ │ │ │ └── 01-monitoring-intro.markdown │ │ │ │ └── 03-ltr/ │ │ │ │ ├── 01-learning-to-rank-intro.markdown │ │ │ │ └── 02-configuring-learning-to-rank.markdown │ │ │ ├── 04-securing-liferay/ │ │ │ │ └── 10-saml/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-setting-up-identity-provider.markdown │ │ │ │ ├── 03-registering-a-service-provider.markdown │ │ │ │ ├── 04-setting-up-service-provider.markdown │ │ │ │ ├── 05-configuring-sp-and-idp-connections.markdown │ │ │ │ └── 06-configuring-saml.markdown │ │ │ ├── 06-maintaining-liferay/ │ │ │ │ └── 01-patching-liferay-dxp/ │ │ │ │ ├── 01-patching-liferay-dxp-intro.markdown │ │ │ │ ├── 02-patching-basics/ │ │ │ │ │ └── 01-patching-basics-intro.markdown │ │ │ │ ├── 03-using-the-patching-tool/ │ │ │ │ │ ├── 01-using-the-patching-tool-intro.markdown │ │ │ │ │ ├── 02-installing-patches.markdown │ │ │ │ │ └── 03-working-with-patches.markdown │ │ │ │ ├── 04-configuring-the-patching-tool/ │ │ │ │ │ ├── 01-configuring-the-patching-tool-intro.markdown │ │ │ │ │ ├── 02-patching-tool-basic-configuration.markdown │ │ │ │ │ ├── 03-patching-tool-advanced-configuration.markdown │ │ │ │ │ └── 04-installing-patches-on-the-liferay-war.markdown │ │ │ │ └── 05-keeping-up-with-fix-packs/ │ │ │ │ └── 01-keeping-up-with-fix-packs-intro.markdown │ │ │ ├── 08-lcs/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-getting-started.markdown │ │ │ │ ├── 03-lcs-preconfiguration.markdown │ │ │ │ ├── 04-registration.markdown │ │ │ │ ├── 05-using-lcs/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-lcs-property-storage.markdown │ │ │ │ │ ├── 03-managing-lcs-users.markdown │ │ │ │ │ ├── 04-lcs-dashboard.markdown │ │ │ │ │ ├── 05-lcs-environments.markdown │ │ │ │ │ ├── 06-lcs-servers.markdown │ │ │ │ │ ├── 07-lcs-account.markdown │ │ │ │ │ ├── 08-lcs-subscriptions.markdown │ │ │ │ │ └── 09-lcs-tokens.markdown │ │ │ │ └── 06-lcs-troubleshooting.markdown │ │ │ └── 100-reference/ │ │ │ ├── 06-comparing-patch-levels.markdown │ │ │ └── 07-patching-tool-configuration-properties.markdown │ │ ├── build.xml │ │ └── drawings/ │ │ ├── README.markdown │ │ ├── kerberos.odg │ │ ├── oauth-flow.odg │ │ └── patch-war-file-folder-structure.odg │ ├── developer/ │ │ ├── appdev/ │ │ │ ├── articles/ │ │ │ │ ├── 01-application-development/ │ │ │ │ │ └── 01-application-development-intro.markdown │ │ │ │ ├── 02-web-front-ends/ │ │ │ │ │ ├── 01-web-front-ends-intro.markdown │ │ │ │ │ ├── 02-angular-widget/ │ │ │ │ │ │ └── 01-developing-an-angular-application-intro.markdown │ │ │ │ │ ├── 03-react-widget/ │ │ │ │ │ │ └── 01-developing-react-application-intro.markdown │ │ │ │ │ ├── 04-vue-widget/ │ │ │ │ │ │ └── 01-developing-a-vue-application-intro.markdown │ │ │ │ │ ├── 05-liferay-mvc-portlet/ │ │ │ │ │ │ ├── 01-liferay-mvc-portlet-intro.markdown │ │ │ │ │ │ ├── 02-creating-an-mvc-portlet.markdown │ │ │ │ │ │ ├── 03-writing-mvc-portlet-controller-code.markdown │ │ │ │ │ │ ├── 04-configuring-the-view-layer.markdown │ │ │ │ │ │ ├── 05-mvc-action-command.markdown │ │ │ │ │ │ ├── 06-mvc-render-command.markdown │ │ │ │ │ │ └── 07-mvc-resource-command.markdown │ │ │ │ │ ├── 06-portletmvc4pring/ │ │ │ │ │ │ ├── 01-portlet-mvc-for-spring-intro.markdown │ │ │ │ │ │ ├── 02-developing-a-portlet-using-portletmvc4spring.markdown │ │ │ │ │ │ └── 99-migrating-to-portletmvc4spring.markdown │ │ │ │ │ ├── 07-jsf-portlet/ │ │ │ │ │ │ ├── 01-jsf-portlet-intro.markdown │ │ │ │ │ │ └── 02-developing-a-jsf-portlet-application.markdown │ │ │ │ │ └── 08-bean-portlet/ │ │ │ │ │ ├── 01-bean-portlet-intro.markdown │ │ │ │ │ └── 02-creating-a-bean-portlet.markdown │ │ │ │ ├── 03-service-builder/ │ │ │ │ │ ├── 01-service-builder-intro.markdown │ │ │ │ │ ├── 02-creating-a-service-builder-project/ │ │ │ │ │ │ └── 02-creating-a-service-builder-project-intro.markdown │ │ │ │ │ ├── 03-creating-o-r-map/ │ │ │ │ │ │ ├── 01-creating-the-service-xml-file-intro.markdown │ │ │ │ │ │ ├── 03-defining-global-service-info/ │ │ │ │ │ │ │ └── 01-defining-global-service-information-intro.markdown │ │ │ │ │ │ ├── 04-defining-service-entities/ │ │ │ │ │ │ │ └── 01-defining-service-entities-intro.markdown │ │ │ │ │ │ ├── 05-defining-columns-and-attribs/ │ │ │ │ │ │ │ └── 01-defining-the-columns-attributes-for-each-service-entity-intro.markdown │ │ │ │ │ │ ├── 06-defining-relationships-between-entities/ │ │ │ │ │ │ │ └── 01-defining-relationships-between-service-entities-intro.markdown │ │ │ │ │ │ ├── 07-defining-service-entity-instance-order/ │ │ │ │ │ │ │ └── 01-defining-ordering-of-service-entity-instances-intro.markdown │ │ │ │ │ │ ├── 08-defining-service-entity-finder-methods/ │ │ │ │ │ │ │ └── 01-defining-service-entity-finder-methods-intro.markdown │ │ │ │ │ │ ├── 09-running-service-builder/ │ │ │ │ │ │ │ └── 01-running-service-builder-and-understanding-the-generated-code-intro.markdown │ │ │ │ │ │ ├── 10-understanding-the-code-service-builder-generates/ │ │ │ │ │ │ │ └── 01-understanding-the-code-generated-by-service-builder-intro.markdown │ │ │ │ │ │ ├── 11-iterative-development/ │ │ │ │ │ │ │ └── 01-iterative-development-intro.markdown │ │ │ │ │ │ ├── 12-customizing-model-entities-with-model-hints/ │ │ │ │ │ │ │ └── 01-customizing-model-entities-with-model-hints-intro.markdown │ │ │ │ │ │ ├── 13-configuring-service-properties/ │ │ │ │ │ │ │ └── 01-configuring-service-properties-intro.markdown │ │ │ │ │ │ ├── 98-connecting-service-builder-to-an-external-database/ │ │ │ │ │ │ │ ├── 01-connecting-service-builder-to-an-external-database-intro.markdown │ │ │ │ │ │ │ ├── 02-datasource-connect-using-a-datasourceprovider.markdown │ │ │ │ │ │ │ └── 03-datasource-connect-using-spring-beans.markdown │ │ │ │ │ │ └── 99-migrating-a-service-builder-module-from-spring-di-to-osgi-ds/ │ │ │ │ │ │ └── 01-migrating-a-service-builder-module-from-spring-di-to-osgi-ds-intro.markdown │ │ │ │ │ ├── 04-business-logic-with-service-builder/ │ │ │ │ │ │ ├── 01-business-logic-with-service-builder-intro.markdown │ │ │ │ │ │ ├── 02-creating-local-services/ │ │ │ │ │ │ │ ├── 01-implementing-an-add-method-intro.markdown │ │ │ │ │ │ │ ├── 02-implementing-an-update-method.markdown │ │ │ │ │ │ │ ├── 03-implementing-methods-to-get-and-count-entities.markdown │ │ │ │ │ │ │ ├── 04-implementing-any-other-business-logic.markdown │ │ │ │ │ │ │ └── 05-integrating-with-liferay-frameworks.markdown │ │ │ │ │ │ ├── 03-invoking-local-services.markdown │ │ │ │ │ │ └── 04-invoking-services-from-spring-service-builder-code.markdown │ │ │ │ │ └── 09-advanced-queries/ │ │ │ │ │ ├── 01-advanced-query-intro.markdown │ │ │ │ │ ├── 02-custom-sql.markdown │ │ │ │ │ ├── 03-defining-a-custom-finder-method.markdown │ │ │ │ │ ├── 03-dynamic-query.markdown │ │ │ │ │ ├── 05-accessing-your-custom-finder-method-from-the-service-layer.markdown │ │ │ │ │ └── 06-actionable-dynamic-query.markdown │ │ │ │ ├── 04-rest-builder/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-generating-apis-rest-builder.markdown │ │ │ │ └── 99-troubleshooting-application-development-issues/ │ │ │ │ ├── 01-troubleshooting-application-development-issues-intro.markdown │ │ │ │ ├── adjusting-module-logging.markdown │ │ │ │ ├── artifact-versions-dependencies.markdown │ │ │ │ ├── bsn-naming-syntax-issues.markdown │ │ │ │ ├── calling-osgi-services-instead-of-static-service-utils.markdown │ │ │ │ ├── connecting-to-jndi-data-sources.markdown │ │ │ │ ├── detecting-unresolved-osgi-components.markdown │ │ │ │ ├── disabling-cache-on-table-mapper-tables.markdown │ │ │ │ ├── implementing-logging.markdown │ │ │ │ ├── optional-imports.markdown │ │ │ │ ├── resolving-bundle-requirements.markdown │ │ │ │ ├── resolving-cnfe-and-cdnfe-in-osgi-bundles.markdown │ │ │ │ ├── system-check.markdown │ │ │ │ └── troubleshooting-front-end-dev.markdown │ │ │ ├── build.xml │ │ │ └── code/ │ │ │ ├── angular-guestbook/ │ │ │ │ ├── after/ │ │ │ │ │ └── my-angular-guestbook/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── .npmbuildrc │ │ │ │ │ ├── .npmbundlerrc │ │ │ │ │ ├── README.md │ │ │ │ │ ├── assets/ │ │ │ │ │ │ ├── .placeholder │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ ├── add-entry.component.html │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ └── view-guestbook.component.html │ │ │ │ │ │ └── css/ │ │ │ │ │ │ ├── add-entry.component.css │ │ │ │ │ │ ├── app.component.css │ │ │ │ │ │ ├── styles.css │ │ │ │ │ │ └── view-guestbook.component.css │ │ │ │ │ ├── package.json │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── app/ │ │ │ │ │ │ │ ├── add-entry/ │ │ │ │ │ │ │ │ ├── add-entry.component.spec.ts │ │ │ │ │ │ │ │ └── add-entry.component.ts │ │ │ │ │ │ │ ├── app-routing.module.ts │ │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ ├── dynamic.loader.ts │ │ │ │ │ │ │ ├── entry.service.spec.ts │ │ │ │ │ │ │ ├── entry.service.ts │ │ │ │ │ │ │ ├── guestbook-entry.ts │ │ │ │ │ │ │ ├── in-memory-data.service.spec.ts │ │ │ │ │ │ │ ├── in-memory-data.service.ts │ │ │ │ │ │ │ ├── mock-guestbook-entries.ts │ │ │ │ │ │ │ └── view-guestbook/ │ │ │ │ │ │ │ ├── view-guestbook.component.spec.ts │ │ │ │ │ │ │ └── view-guestbook.component.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ │ └── types/ │ │ │ │ │ │ └── LiferayParams.ts │ │ │ │ │ └── tsconfig.json │ │ │ │ └── before/ │ │ │ │ └── angular-guestbook/ │ │ │ │ ├── .editorconfig │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── angular.json │ │ │ │ ├── e2e/ │ │ │ │ │ ├── protractor.conf.js │ │ │ │ │ ├── src/ │ │ │ │ │ │ ├── app.e2e-spec.ts │ │ │ │ │ │ └── app.po.ts │ │ │ │ │ └── tsconfig.e2e.json │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── add-entry/ │ │ │ │ │ │ │ ├── add-entry.component.css │ │ │ │ │ │ │ ├── add-entry.component.html │ │ │ │ │ │ │ ├── add-entry.component.spec.ts │ │ │ │ │ │ │ └── add-entry.component.ts │ │ │ │ │ │ ├── app-routing.module.ts │ │ │ │ │ │ ├── app.component.css │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ ├── app.component.spec.ts │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ ├── entry.service.spec.ts │ │ │ │ │ │ ├── entry.service.ts │ │ │ │ │ │ ├── guestbook-entry.ts │ │ │ │ │ │ ├── in-memory-data.service.spec.ts │ │ │ │ │ │ ├── in-memory-data.service.ts │ │ │ │ │ │ ├── mock-guestbook-entries.ts │ │ │ │ │ │ └── view-guestbook/ │ │ │ │ │ │ ├── view-guestbook.component.css │ │ │ │ │ │ ├── view-guestbook.component.html │ │ │ │ │ │ ├── view-guestbook.component.spec.ts │ │ │ │ │ │ └── view-guestbook.component.ts │ │ │ │ │ ├── assets/ │ │ │ │ │ │ └── .gitkeep │ │ │ │ │ ├── browserslist │ │ │ │ │ ├── environments/ │ │ │ │ │ │ ├── environment.prod.ts │ │ │ │ │ │ └── environment.ts │ │ │ │ │ ├── index.html │ │ │ │ │ ├── karma.conf.js │ │ │ │ │ ├── main.ts │ │ │ │ │ ├── polyfills.ts │ │ │ │ │ ├── styles.css │ │ │ │ │ ├── test.ts │ │ │ │ │ ├── tsconfig.app.json │ │ │ │ │ ├── tsconfig.spec.json │ │ │ │ │ └── tslint.json │ │ │ │ ├── tsconfig.json │ │ │ │ └── tslint.json │ │ │ ├── react-guestbook/ │ │ │ │ ├── after/ │ │ │ │ │ └── react-guestbook-app-migrated/ │ │ │ │ │ ├── .babelrc │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── .npmbuildrc │ │ │ │ │ ├── .npmbundlerrc │ │ │ │ │ ├── README.md │ │ │ │ │ ├── assets/ │ │ │ │ │ │ ├── .placeholder │ │ │ │ │ │ └── css/ │ │ │ │ │ │ ├── App.css │ │ │ │ │ │ ├── index.css │ │ │ │ │ │ └── styles.css │ │ │ │ │ ├── package.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── App.js │ │ │ │ │ ├── App.test.js │ │ │ │ │ ├── add-entry.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── serviceWorker.js │ │ │ │ │ └── view-guestbook.js │ │ │ │ └── before/ │ │ │ │ └── my-react-guestbook-app/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── package.json │ │ │ │ ├── public/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── manifest.json │ │ │ │ └── src/ │ │ │ │ ├── App.css │ │ │ │ ├── App.js │ │ │ │ ├── App.test.js │ │ │ │ ├── add-entry.js │ │ │ │ ├── index.css │ │ │ │ ├── index.js │ │ │ │ ├── serviceWorker.js │ │ │ │ └── view-guestbook.js │ │ │ └── vue-guestbook/ │ │ │ ├── after/ │ │ │ │ └── vue-guestbook-migrated/ │ │ │ │ ├── .babelrc │ │ │ │ ├── .gitignore │ │ │ │ ├── .npmbuildrc │ │ │ │ ├── .npmbundlerrc │ │ │ │ ├── README.md │ │ │ │ ├── assets/ │ │ │ │ │ ├── .placeholder │ │ │ │ │ └── css/ │ │ │ │ │ ├── custom.css │ │ │ │ │ └── styles.css │ │ │ │ ├── package.json │ │ │ │ └── src/ │ │ │ │ ├── Api.js │ │ │ │ ├── App.vue │ │ │ │ ├── components/ │ │ │ │ │ ├── AddEntry.vue │ │ │ │ │ └── ViewGuestbook.vue │ │ │ │ └── index.js │ │ │ └── before/ │ │ │ └── vue-guestbook/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── babel.config.js │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ └── index.html │ │ │ └── src/ │ │ │ ├── Api.js │ │ │ ├── App.vue │ │ │ ├── components/ │ │ │ │ ├── AddEntry.vue │ │ │ │ └── ViewGuestbook.vue │ │ │ └── main.js │ │ ├── build.xml │ │ ├── customization/ │ │ │ ├── articles/ │ │ │ │ ├── 01-liferay-customization-intro.markdown │ │ │ │ ├── 02-fundamentals/ │ │ │ │ │ ├── 01-fundamentals-intro.markdown │ │ │ │ │ ├── 02-configuring-dependencies/ │ │ │ │ │ │ ├── 01-configuring-dependencies-intro.markdown │ │ │ │ │ │ ├── 02-finding-artifacts.markdown │ │ │ │ │ │ ├── 03-specifying-dependencies.markdown │ │ │ │ │ │ ├── 04-resolving-third-party-library-package-dependencies.markdown │ │ │ │ │ │ └── 05-understanding-excluded-jars.markdown │ │ │ │ │ ├── 03-felix-gogo-shell/ │ │ │ │ │ │ └── 01-felix-gogo-shell-intro.markdown │ │ │ │ │ ├── 04-importing-packages/ │ │ │ │ │ │ └── 01-importing-packages-intro.markdown │ │ │ │ │ ├── 05-exporting-packages/ │ │ │ │ │ │ └── 01-exporting-packages-intro.markdown │ │ │ │ │ ├── 06-semantic-versioning/ │ │ │ │ │ │ └── 01-semantic-versioning-intro.markdown │ │ │ │ │ └── 07-deploying-wars-wab-generator/ │ │ │ │ │ └── 01-deploying-wars-wab-generator-intro.markdown │ │ │ │ ├── 03-architecture/ │ │ │ │ │ ├── 01-architecture-intro.markdown │ │ │ │ │ ├── 02-classloader-hierarchy.markdown │ │ │ │ │ ├── 03-portal-startup-phases.markdown │ │ │ │ │ ├── 04-benefits-of-modularity.markdown │ │ │ │ │ ├── 05-osgi-and-modularity.markdown │ │ │ │ │ ├── 06-module-lifecycle.markdown │ │ │ │ │ ├── 06-ui-concepts/ │ │ │ │ │ │ ├── 01-ui-architecture-intro.markdown │ │ │ │ │ │ ├── 02-theme-components.markdown │ │ │ │ │ │ └── 03-understanding-the-page-layout.markdown │ │ │ │ │ ├── 07-bundle-classloading-flow.markdown │ │ │ │ │ └── 08-finding-extension-points.markdown │ │ │ │ ├── 100-troubleshooting-customizations/ │ │ │ │ │ ├── 01-troubleshooting-customizations-intro.markdown │ │ │ │ │ ├── jsp-fragment-unresolved-requirement.markdown │ │ │ │ │ ├── jsp-fragments.markdown │ │ │ │ │ └── using-osgi-services-from-ext-plugins.markdown │ │ │ │ ├── 110-contributing/ │ │ │ │ │ └── 01-contributing-to-liferay-portal-intro.markdown │ │ │ │ ├── 50-creating-model-listeners/ │ │ │ │ │ └── 01-creating-model-listeners-intro.markdown │ │ │ │ ├── 50-customizing-jsps/ │ │ │ │ │ ├── 01-overriding-jsps-intro.markdown │ │ │ │ │ ├── 02-customizing-jsps-with-dynamic-includes.markdown │ │ │ │ │ ├── 03-jsp-overrides-using-portlet-filters.markdown │ │ │ │ │ ├── 04-jsp-overrides-using-fragments.markdown │ │ │ │ │ ├── 05-core-jsp-overrides-using-customjspbag.markdown │ │ │ │ │ └── 06-overriding-inline-content-using-jsps.markdown │ │ │ │ ├── 50-customizing-widgets/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-implementing-widget-templates.markdown │ │ │ │ ├── 50-dynamic-includes/ │ │ │ │ │ ├── 01-dynamic-include-extension-points-intro.markdown │ │ │ │ │ ├── 02-wysiwyg-editor-dynamic-includes.markdown │ │ │ │ │ ├── 03-top-head-jsp-dynamic-include.markdown │ │ │ │ │ ├── 04-top-js-dynamic-include.markdown │ │ │ │ │ └── 05-bottom-jsp-dynamic-include.markdown │ │ │ │ ├── 50-lifecycle-events/ │ │ │ │ │ └── 01-waiting-on-lifecycle-events-intro.markdown │ │ │ │ ├── 50-liferay-forms/ │ │ │ │ │ ├── 01-forms-intro.markdown │ │ │ │ │ ├── 02-form-storage-adapters.markdown │ │ │ │ │ └── 03-creating-form-storage-adapters.markdown │ │ │ │ ├── 50-overriding-language-keys/ │ │ │ │ │ ├── 01-overriding-language-keys-intro.markdown │ │ │ │ │ ├── 02-overriding-liferays-language-keys.markdown │ │ │ │ │ └── 03-overriding-a-modules-language-keys.markdown │ │ │ │ ├── 50-overriding-liferay-services/ │ │ │ │ │ └── 01-overriding-liferay-services-intro.markdown │ │ │ │ ├── 50-overriding-lpkg-files/ │ │ │ │ │ └── 01-overriding-lpkg-files-intro.markdown │ │ │ │ ├── 50-overriding-mvc-commands/ │ │ │ │ │ ├── 01-overriding-mvc-commands-intro.markdown │ │ │ │ │ ├── 02-adding-logic-to-mvc-commands.markdown │ │ │ │ │ ├── 03-overriding-mvcrendercommands.markdown │ │ │ │ │ ├── 04-override-mvcactioncommands.markdown │ │ │ │ │ └── 05-override-mvcresourcecommands.markdown │ │ │ │ ├── 50-overriding-osgi-services/ │ │ │ │ │ ├── 01-overriding-osgi-services-intro.markdown │ │ │ │ │ ├── 02-examining-an-service-to-override.markdown │ │ │ │ │ ├── 03-creating-a-custom-osgi-service.markdown │ │ │ │ │ └── 04-reconfiguring-components-to-use-your-osgi-service-reference.markdown │ │ │ │ ├── 50-portlet-filters/ │ │ │ │ │ └── 01-portlet-filters-intro.markdown │ │ │ │ ├── 50-product-navigation/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-customizing-the-product-menu/ │ │ │ │ │ │ ├── 01-customizing-the-product-menu-intro.markdown │ │ │ │ │ │ ├── 02-adding-custom-panel-categories.markdown │ │ │ │ │ │ └── 03-adding-custom-panel-apps.markdown │ │ │ │ │ ├── 03-customizing-the-control-menu/ │ │ │ │ │ │ ├── 01-customizing-the-control-menu-intro.markdown │ │ │ │ │ │ ├── 02-customizing-the-control-menu.markdown │ │ │ │ │ │ └── 03-defining-icons-tooltips.markdown │ │ │ │ │ ├── 04-extending-the-simulation-menu/ │ │ │ │ │ │ └── 01-extending-the-simulation-menu-intro.markdown │ │ │ │ │ └── 05-providing-the-user-personal-bar/ │ │ │ │ │ ├── 01-customizing-the-user-personal-menu-intro.markdown │ │ │ │ │ ├── 02-using-a-custom-portlet-as-the-user-personal-bar.markdown │ │ │ │ │ └── 03-customizing-the-personal-menu.markdown │ │ │ │ └── 99-customization-with-ext/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-customizing-core-functionality-with-ext/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-an-ext-plugin.markdown │ │ │ │ │ ├── 03-anatomy-of-an-ext-plugin.markdown │ │ │ │ │ ├── 04-developing-an-ext-plugin.markdown │ │ │ │ │ ├── 05-deploying-an-ext-plugin.markdown │ │ │ │ │ └── 06-redeploying-an-ext-plugin.markdown │ │ │ │ └── 03-customizing-osgi-modules-with-ext/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-creating-an-ext-module.markdown │ │ │ │ ├── 03-developing-an-ext-module.markdown │ │ │ │ └── 04-deploying-an-ext-module.markdown │ │ │ ├── articles-dxp/ │ │ │ │ ├── 50-workflow/ │ │ │ │ │ ├── 01-workflow-intro.markdown │ │ │ │ │ └── 02-sla-calendars.markdown │ │ │ │ └── 99-customization-with-ext/ │ │ │ │ ├── 02-customizing-core-functionality-with-ext/ │ │ │ │ │ └── 01-intro.markdown │ │ │ │ ├── 04-extending-core-classes-using-spring-with-ext-plugins.markdown │ │ │ │ ├── 05-overriding-core-classes-with-ext-plugins.markdown │ │ │ │ ├── 06-adding-to-the-web-xml-with-ext-plugins.markdown │ │ │ │ └── 07-modifying-the-web-xml-with-ext-plugins.markdown │ │ │ └── build.xml │ │ ├── frameworks/ │ │ │ ├── articles/ │ │ │ │ ├── 01-frameworks-intro.markdown │ │ │ │ ├── application-security/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-application-permissions/ │ │ │ │ │ │ ├── 01-application-permissions-intro.markdown │ │ │ │ │ │ ├── 02-defining-permissions.markdown │ │ │ │ │ │ ├── 03-registering-permissions.markdown │ │ │ │ │ │ ├── 04-associate-permissions-resources.markdown │ │ │ │ │ │ ├── 05-check-permissions.markdown │ │ │ │ │ │ └── 06-using-portal-roles-in-a-portlet.markdown │ │ │ │ │ ├── 04-authentication-pipelines/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-auto-login.markdown │ │ │ │ │ │ ├── 03-authentication-pipelines.markdown │ │ │ │ │ │ └── 04-custom-login-portlet.markdown │ │ │ │ │ └── 06-service-access-policies.markdown │ │ │ │ ├── assets/ │ │ │ │ │ ├── 01-asset-framework-intro.markdown │ │ │ │ │ ├── 02-adding-updating-and-deleting-assets.markdown │ │ │ │ │ ├── 03-rendering-an-asset/ │ │ │ │ │ │ ├── 01-creating-asset-renderer-intro.markdown │ │ │ │ │ │ ├── 02-jsp-templates-asset-renderer.markdown │ │ │ │ │ │ └── 03-creating-factory-asset-renderer.markdown │ │ │ │ │ ├── 04-entering-and-displaying-tags-and-categories.markdown │ │ │ │ │ ├── 05-relating-assets.markdown │ │ │ │ │ └── 06-implementing-asset-priority.markdown │ │ │ │ ├── back-end-frameworks/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-portlet-providers.markdown │ │ │ │ │ ├── 03-retrieving-portlets.markdown │ │ │ │ │ ├── 04-enabling-accessing-scopes.markdown │ │ │ │ │ └── 05-message-bus/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-destination.markdown │ │ │ │ │ ├── 03-message-bus-event-listeners.markdown │ │ │ │ │ ├── 04-registering-message-listeners.markdown │ │ │ │ │ ├── 05-creating-a-message.markdown │ │ │ │ │ ├── 06-sending-a-message.markdown │ │ │ │ │ └── 07-sending-messages-cluster.markdown │ │ │ │ ├── cache-configuration/ │ │ │ │ │ ├── 01-cache-configuration-intro.markdown │ │ │ │ │ ├── 02-overriding-cache.markdown │ │ │ │ │ └── 03-caching-data.markdown │ │ │ │ ├── collaboration/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── 01-item-selector/ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ ├── 01-selecting-entities.markdown │ │ │ │ │ │ ├── 02-creating-criterion-return-types.markdown │ │ │ │ │ │ └── 03-creating-custom-views.markdown │ │ │ │ │ ├── 02-documents-media-api/ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ ├── 01-creating-entities/ │ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ │ ├── 01-creating-files.markdown │ │ │ │ │ │ │ ├── 02-creating-folders.markdown │ │ │ │ │ │ │ └── 03-creating-file-shortcuts.markdown │ │ │ │ │ │ ├── 02-deleting-entities/ │ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ │ ├── 01-deleting-files.markdown │ │ │ │ │ │ │ ├── 02-deleting-file-versions.markdown │ │ │ │ │ │ │ ├── 03-deleting-file-shortcuts.markdown │ │ │ │ │ │ │ ├── 04-deleting-folders.markdown │ │ │ │ │ │ │ └── 05-moving-to-recycle-bin.markdown │ │ │ │ │ │ ├── 03-updating-entities/ │ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ │ ├── 01-updating-files.markdown │ │ │ │ │ │ │ ├── 02-updating-folders.markdown │ │ │ │ │ │ │ └── 03-updating-file-shortcuts.markdown │ │ │ │ │ │ ├── 04-file-checkout/ │ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ │ ├── 01-file-checkout.markdown │ │ │ │ │ │ │ ├── 02-file-checkin.markdown │ │ │ │ │ │ │ └── 03-cancel-checkout.markdown │ │ │ │ │ │ ├── 05-copying-moving-entities/ │ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ │ ├── 01-copying-folders.markdown │ │ │ │ │ │ │ └── 02-moving-folders-files.markdown │ │ │ │ │ │ └── 06-getting-entities/ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ ├── 01-getting-files.markdown │ │ │ │ │ │ ├── 02-getting-folders.markdown │ │ │ │ │ │ └── 03-getting-multiple-types.markdown │ │ │ │ │ ├── 03-adaptive-media/ │ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ │ ├── 01-displaying-adapted-images.markdown │ │ │ │ │ │ ├── 02-finding-adapted-images.markdown │ │ │ │ │ │ └── 03-image-scaler.markdown │ │ │ │ │ └── 05-social-api/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── 01-applying-social-bookmarks.markdown │ │ │ │ │ ├── 02-creating-social-bookmarks.markdown │ │ │ │ │ ├── 03-adding-comments.markdown │ │ │ │ │ ├── 05-rating-assets.markdown │ │ │ │ │ ├── 09-rating-type-selection.markdown │ │ │ │ │ ├── 10-customizing-rating-transformation.markdown │ │ │ │ │ └── 11-flagging-assets.markdown │ │ │ │ ├── configuration/ │ │ │ │ │ ├── 01-configuration-intro.markdown │ │ │ │ │ ├── 02-creating-a-configuration-interface.markdown │ │ │ │ │ ├── 03-categorizing-configurations.markdown │ │ │ │ │ ├── 04-scoping-configurations.markdown │ │ │ │ │ ├── 05-reading-configurations-configuration-provider.markdown │ │ │ │ │ ├── 06-reading-configurations-portlet.markdown │ │ │ │ │ ├── 07-reading-configurations-component.markdown │ │ │ │ │ ├── 08-customizing-the-ui/ │ │ │ │ │ │ ├── 01-customizing-the-configuration-user-interface-intro.markdown │ │ │ │ │ │ ├── 02-configuration-form-renderer.markdown │ │ │ │ │ │ └── 03-configuration-forms.markdown │ │ │ │ │ ├── 10-upgrading-a-legacy-app.markdown │ │ │ │ │ └── 11-dynamically-populating-select-fields.markdown │ │ │ │ ├── content-publication-management/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-export-import/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-developing-staged-models/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-generating-staged-models-using-service-builder.markdown │ │ │ │ │ │ │ └── 03-creating-staged-models-manually.markdown │ │ │ │ │ │ ├── 03-developing-data-handlers/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-creating-portlet-data-handlers.markdown │ │ │ │ │ │ │ └── 03-creating-staged-model-data-handlers.markdown │ │ │ │ │ │ ├── 04-providing-entity-specific-local-services-for-export-import/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-implementing-the-staged-model-repository-framework.markdown │ │ │ │ │ │ │ └── 03-using-the-staged-model-repository-framework.markdown │ │ │ │ │ │ ├── 05-using-the-export-import-lifecycle-listener-framework.markdown │ │ │ │ │ │ └── 06-initiating-new-export-import-processes.markdown │ │ │ │ │ └── 03-staging/ │ │ │ │ │ └── 01-intro.markdown │ │ │ │ ├── dependency-injection/ │ │ │ │ │ ├── 01-dependency-injection-intro.markdown │ │ │ │ │ ├── 02-cdi-dependency-injection.markdown │ │ │ │ │ ├── declarative-services.markdown │ │ │ │ │ ├── osgi-cdi-integration/ │ │ │ │ │ │ ├── 01-osgi-cdi-integration-intro.markdown │ │ │ │ │ │ ├── 02-publishing-cdi-beans-as-osgi-services.markdown │ │ │ │ │ │ └── 03-using-osgi-services-in-a-bean.markdown │ │ │ │ │ ├── service-trackers-for-osgi-services.markdown │ │ │ │ │ └── using-a-service-tracker.markdown │ │ │ │ ├── friendly-urls/ │ │ │ │ │ ├── 01-friendly-urls-intro.markdown │ │ │ │ │ └── 02-creating-friendly-urls.markdown │ │ │ │ ├── front-end-development/ │ │ │ │ │ ├── 01-front-end-dev-intro.markdown │ │ │ │ │ ├── 02-themes/ │ │ │ │ │ │ ├── 00-themes-intro.markdown │ │ │ │ │ │ ├── 03-developing-themes/ │ │ │ │ │ │ │ ├── 01-developing-themes-intro.markdown │ │ │ │ │ │ │ ├── 02-using-developer-mode-with-themes.markdown │ │ │ │ │ │ │ ├── 03-building-themes.markdown │ │ │ │ │ │ │ ├── 04-deploying-and-applying-themes.markdown │ │ │ │ │ │ │ ├── 07-configuring-your-themes-app-server.markdown │ │ │ │ │ │ │ ├── 09-automatically-deploying-theme-changes.markdown │ │ │ │ │ │ │ ├── 10-creating-a-thumbnail-preview-for-your-theme.markdown │ │ │ │ │ │ │ ├── 11-creating-color-schemes-for-your-theme.markdown │ │ │ │ │ │ │ ├── 12-making-configurable-theme-settings.markdown │ │ │ │ │ │ │ └── 13-using-font-awesome-glyph-icons-in-your-theme.markdown │ │ │ │ │ │ ├── 04-extending-themes/ │ │ │ │ │ │ │ ├── 01-extending-themes-intro.markdown │ │ │ │ │ │ │ ├── 02-installing-themelets-in-your-theme.markdown │ │ │ │ │ │ │ ├── 03-injecting-additional-context-variables-into-your-templates.markdown │ │ │ │ │ │ │ ├── 04-packaging-independent-ui-resources-for-your-site.markdown │ │ │ │ │ │ │ ├── 05-changing-the-base-theme.markdown │ │ │ │ │ │ │ ├── 06-copying-an-existing-themes-files.markdown │ │ │ │ │ │ │ ├── 07-listing-your-themes-extensions.markdown │ │ │ │ │ │ │ └── 08-overwriting-and-extending-liferay-theme-tasks.markdown │ │ │ │ │ │ ├── 05-clay-and-themes/ │ │ │ │ │ │ │ ├── 01-clay-themes-intro.markdown │ │ │ │ │ │ │ ├── 02-customizing-atlas-and-clay-themes.markdown │ │ │ │ │ │ │ ├── 03-integrating-third-party-themes-with-clay.markdown │ │ │ │ │ │ │ ├── 04-using-clay-icons-in-your-theme.markdown │ │ │ │ │ │ │ └── 04-using-clay-mixins-in-your-theme.markdown │ │ │ │ │ │ ├── 06-portlets-and-themes/ │ │ │ │ │ │ │ ├── 01-theming-portlets-intro.markdown │ │ │ │ │ │ │ └── 02-embedding-portlets-in-themes/ │ │ │ │ │ │ │ ├── 01-embedding-portlets-in-themes-intro.markdown │ │ │ │ │ │ │ ├── 02-embedding-portlets-in-themes-by-entity-type.markdown │ │ │ │ │ │ │ ├── 03-embedding-portlets-in-a-theme-by-name.markdown │ │ │ │ │ │ │ └── 04-setting-portlet-preferences-for-embedded-portlets.markdown │ │ │ │ │ │ ├── 07-managing-theme-resources/ │ │ │ │ │ │ │ ├── 01-resources-importer-intro.markdown │ │ │ │ │ │ │ ├── 02-creating-a-sitemap/ │ │ │ │ │ │ │ │ ├── 01-creating-a-sitemap-intro.markdown │ │ │ │ │ │ │ │ ├── 02-defining-layout-templates-and-pages-in-a-sitemap.markdown │ │ │ │ │ │ │ │ ├── 04-defining-portlets-in-a-sitemap.markdown │ │ │ │ │ │ │ │ └── 05-retrieving-portlet-ids-with-gogo-shell.markdown │ │ │ │ │ │ │ ├── 03-preparing-and-organizing-web-content/ │ │ │ │ │ │ │ │ └── 01-preparing-and-organizing-web-content-for-the-importer-intro.markdown │ │ │ │ │ │ │ ├── 04-defining-assets/ │ │ │ │ │ │ │ │ └── 01-defining-assets-intro.markdown │ │ │ │ │ │ │ ├── 05-where-to-import-resources/ │ │ │ │ │ │ │ │ └── 01-specifying-where-to-import-resources-intro.markdown │ │ │ │ │ │ │ └── 06-creating-a-theme-lar/ │ │ │ │ │ │ │ └── 01-creating-an-archive-of-your-sites-themes-and-resources-intro.markdown │ │ │ │ │ │ └── 10-troubleshooting-themes/ │ │ │ │ │ │ └── 01-troubleshooting-themes-intro.markdown │ │ │ │ │ ├── 03-layout-templates/ │ │ │ │ │ │ ├── 00-layout-templates-intro.markdown │ │ │ │ │ │ ├── 01-creating-custom-layout-template-thumbnail-previews.markdown │ │ │ │ │ │ └── 02-including-layout-templates-with-a-theme.markdown │ │ │ │ │ ├── 05-creating-js-widgets-with-js-tooling/ │ │ │ │ │ │ ├── 01-creating-widgets-with-javascript-tooling-intro.markdown │ │ │ │ │ │ ├── 02-configuring-system-and-instance-settings-for-your-widget.markdown │ │ │ │ │ │ ├── 04-localizing-your-widget.markdown │ │ │ │ │ │ ├── 05-using-translation-features.markdown │ │ │ │ │ │ └── 06-setting-portlet-properties-for-your-widget.markdown │ │ │ │ │ └── 07-javascript-module-loaders/ │ │ │ │ │ ├── 01-javascript-module-loaders-intro.markdown │ │ │ │ │ ├── 02-loading-amd-modules/ │ │ │ │ │ │ └── 01-loading-amd-modules-in-liferay-intro.markdown │ │ │ │ │ ├── 03-using-external-libraries/ │ │ │ │ │ │ └── 01-using-external-libraries-intro.markdown │ │ │ │ │ └── 04-loading-modules-with-aui-script-tag/ │ │ │ │ │ ├── 01-loading-modules-with-aui-script-intro.markdown │ │ │ │ │ ├── 02-loading-aui-modules-with-aui.markdown │ │ │ │ │ ├── 03-loading-es2015-and-metal-modules-with-aui-script-tag.markdown │ │ │ │ │ ├── 04-loading-aui-es2015-metal-modules-with-aui.markdown │ │ │ │ │ └── 05-loading-bundled-npm-modules-in-your-portlets.markdown │ │ │ │ ├── info-framework/ │ │ │ │ │ ├── 01-the-info-framework-intro.markdown │ │ │ │ │ ├── 02-creating-information-list-provider.markdown │ │ │ │ │ ├── 03-creating-information-item-renderer.markdown │ │ │ │ │ └── 04-custom-application-providers.markdown │ │ │ │ ├── liferay-forms/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-form-serialization/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-serilaizing-forms.markdown │ │ │ │ ├── localization/ │ │ │ │ │ ├── 01-localization-intro.markdown │ │ │ │ │ ├── 02-localizing-your-application.markdown │ │ │ │ │ ├── 03-localization-settings.markdown │ │ │ │ │ ├── 04-creating-language-module.markdown │ │ │ │ │ ├── 05-using-language-module.markdown │ │ │ │ │ └── 06-automatically-generating-translations.markdown │ │ │ │ ├── portlets/ │ │ │ │ │ ├── 01-portlets-intro.markdown │ │ │ │ │ ├── 07-using-javascript-in-your-portlets/ │ │ │ │ │ │ ├── 01-using-javascript-in-your-portlets-intro.markdown │ │ │ │ │ │ ├── 02-esplus-modules/ │ │ │ │ │ │ │ └── 01-using-esplus-modules-in-your-portlets-intro.markdown │ │ │ │ │ │ ├── 03-using-npm-in-your-portlets/ │ │ │ │ │ │ │ ├── 01-npm-intro.markdown │ │ │ │ │ │ │ ├── 02-formatting-for-amd/ │ │ │ │ │ │ │ │ └── 01-formatting-your-npm-modules-for-amd-intro.markdown │ │ │ │ │ │ │ ├── 03-migrating-bundler-projects/ │ │ │ │ │ │ │ │ ├── 01-migrating-bundler-projects-intro.markdown │ │ │ │ │ │ │ │ ├── 02-migrating-plain-js-billboard-jquery-metal-js-react-vue-bundler.markdown │ │ │ │ │ │ │ │ ├── 03-migrating-angular-bundler.markdown │ │ │ │ │ │ │ │ └── 04-migrating-to-the-new-mode.markdown │ │ │ │ │ │ │ ├── 04-custom-loaders/ │ │ │ │ │ │ │ │ └── 01-creating-custom-loaders-for-the-bundler-intro.markdown │ │ │ │ │ │ │ └── 08-npmresolver-api/ │ │ │ │ │ │ │ ├── 01-using-npmresolver-api-intro.markdown │ │ │ │ │ │ │ ├── 02-obtaining-osgi-bundle-npm-package-descriptors.markdown │ │ │ │ │ │ │ └── 03-obtaining-dependency-npm-package-descriptors.markdown │ │ │ │ │ │ └── 09-spa/ │ │ │ │ │ │ ├── 01-spa-intro.markdown │ │ │ │ │ │ ├── 02-configuring-spa-system-settings.markdown │ │ │ │ │ │ ├── 03-disabling-spa.markdown │ │ │ │ │ │ ├── 04-specifying-how-resources-are-loaded-during-spa-navigation.markdown │ │ │ │ │ │ └── 05-detaching-global-listeners.markdown │ │ │ │ │ └── 08-applying-clay-styles-to-your-app/ │ │ │ │ │ ├── 01-applying-clay-styles-intro.markdown │ │ │ │ │ ├── 02-clay-navigation-patterns/ │ │ │ │ │ │ └── 01-applying-clay-patterns-to-your-navigation-bar-intro.markdown │ │ │ │ │ ├── 03-clay-management-toolbar/ │ │ │ │ │ │ ├── 01-implementing-the-management-toolbar-intro.markdown │ │ │ │ │ │ ├── 02-configuring-view-types/ │ │ │ │ │ │ │ ├── 01-configuring-mgmt-bar-view-types-intro.markdown │ │ │ │ │ │ │ ├── 02-implementing-the-card-view.markdown │ │ │ │ │ │ │ ├── 03-implementing-the-list-view.markdown │ │ │ │ │ │ │ ├── 04-implementing-the-table-view.markdown │ │ │ │ │ │ │ └── 05-updating-the-search-iterator.markdown │ │ │ │ │ │ └── 03-filtering-and-sorting/ │ │ │ │ │ │ └── 01-filtering-and-sorting-items-with-the-management-toolbar-intro.markdown │ │ │ │ │ ├── 04-app-title-and-back-url/ │ │ │ │ │ │ └── 01-configuring-your-apps-title-and-back-link-intro.markdown │ │ │ │ │ ├── 05-add-button-pattern/ │ │ │ │ │ │ └── 01-using-the-add-button-pattern-intro.markdown │ │ │ │ │ ├── 06-configuring-admin-apps/ │ │ │ │ │ │ └── 01-configuring-your-admin-apps-actions-menus-intro.markdown │ │ │ │ │ └── 07-empty-results-message/ │ │ │ │ │ └── 01-setting-empty-results-messages-intro.markdown │ │ │ │ ├── search/ │ │ │ │ │ ├── 01-search-framework-intro.markdown │ │ │ │ │ ├── 02-aggregations/ │ │ │ │ │ │ ├── 01-aggregations-intro.markdown │ │ │ │ │ │ ├── 02-base-search-aggregations-code.markdown │ │ │ │ │ │ └── 03-statistical-aggregations.markdown │ │ │ │ │ ├── 02-indexing-framework/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-indexing.markdown │ │ │ │ │ │ ├── 03-searching.markdown │ │ │ │ │ │ ├── 04-results.markdown │ │ │ │ │ │ └── 05-registrar.markdown │ │ │ │ │ └── 03-queries-and-filters/ │ │ │ │ │ ├── 01-queries-and-filters-intro.markdown │ │ │ │ │ └── 02-building-search-queries-and-filters.markdown │ │ │ │ ├── segmentation-and-personalization/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-segment-management.markdown │ │ │ │ │ ├── 03-request-context-contributor.markdown │ │ │ │ │ └── 04-segment-criteria-contributors.markdown │ │ │ │ ├── service-context/ │ │ │ │ │ └── 01-understanding-service-context-intro.markdown │ │ │ │ ├── testing/ │ │ │ │ │ └── 01-injecting-service-components-into-tests-intro.markdown │ │ │ │ ├── upgrade-processes/ │ │ │ │ │ ├── 01-upgrade-processes-intro.markdown │ │ │ │ │ ├── 02-creating-an-upgrade-process-for-your-application.markdown │ │ │ │ │ ├── 03-upgrade-processes-for-former-service-builder-plugins.markdown │ │ │ │ │ └── 04-upgrading-data-schemas-in-development.markdown │ │ │ │ ├── user-associated-data/ │ │ │ │ │ ├── 01-managing-uad-intro.markdown │ │ │ │ │ ├── 02-implementing-uad.markdown │ │ │ │ │ ├── 03-gdpr-deletion-features.markdown │ │ │ │ │ └── 04-filtering-and-searching.markdown │ │ │ │ ├── web-experience-management/ │ │ │ │ │ ├── 01-web-experience-management-intro.markdown │ │ │ │ │ ├── 02-page-fragments/ │ │ │ │ │ │ ├── 01-page-fragments-intro.markdown │ │ │ │ │ │ ├── 02-developing-a-fragment.markdown │ │ │ │ │ │ ├── 03-making-a-fragment-configurable.markdown │ │ │ │ │ │ ├── 04-managing-fragments-and-collections.markdown │ │ │ │ │ │ ├── 05-page-fragments-desktop-tools.markdown │ │ │ │ │ │ ├── 06-creating-contributed-fragment-collection.markdown │ │ │ │ │ │ └── 07-including-default-resources-in-fragments.markdown │ │ │ │ │ ├── 03-supporting-custom-content-types-in-pages/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-mapping-a-content-type-to-a-display-page-template.markdown │ │ │ │ │ │ ├── 03-specifying-the-fields-of-a-custom-content-type.markdown │ │ │ │ │ │ ├── 04-providing-friendly-urls-for-a-custom-content-type.markdown │ │ │ │ │ │ └── 05-integrating-display-pages-into-content-creation.markdown │ │ │ │ │ ├── 04-screen-navigation/ │ │ │ │ │ │ ├── 01-screen-navigation-framework-intro.markdown │ │ │ │ │ │ ├── 02-screen-navigation-custom-apps.markdown │ │ │ │ │ │ └── 03-customizing-liferay-apps.markdown │ │ │ │ │ └── 05-developing-a-fragment-renderer/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-creating-a-fragment-renderer.markdown │ │ │ │ ├── web-services/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-headless-apis/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-discover-api.markdown │ │ │ │ │ │ ├── 03-invoke-service.markdown │ │ │ │ │ │ ├── 04-authenticated-requests.markdown │ │ │ │ │ │ ├── 05-collections/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-getting-collections.markdown │ │ │ │ │ │ │ ├── 03-pagination.markdown │ │ │ │ │ │ │ └── 04-collection-to-elements.markdown │ │ │ │ │ │ ├── 06-api-formats.markdown │ │ │ │ │ │ ├── 07-openapi-profiles.markdown │ │ │ │ │ │ ├── 08-filter-and-sort.markdown │ │ │ │ │ │ ├── 09-restrict-properties.markdown │ │ │ │ │ │ ├── 10-multipart.markdown │ │ │ │ │ │ ├── 11-how-to-get-site-id.markdown │ │ │ │ │ │ ├── 12-filtrable-properties.markdown │ │ │ │ │ │ └── 13-using-rest-apis.markdown │ │ │ │ │ ├── 03-jax/ │ │ │ │ │ │ ├── 01-jaxrs-intro.markdown │ │ │ │ │ │ └── 02-jax-ws.markdown │ │ │ │ │ ├── 04-graphql/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-discover-api.markdown │ │ │ │ │ │ ├── 03-invoke-service.markdown │ │ │ │ │ │ ├── 04-authenticated-requests.markdown │ │ │ │ │ │ ├── 05-collections.markdown │ │ │ │ │ │ ├── 06-mutations.markdown │ │ │ │ │ │ ├── 07-fragments-and-node-pattern.markdown │ │ │ │ │ │ ├── 08-language-negociation.markdown │ │ │ │ │ │ ├── 09-filter-and-sort.markdown │ │ │ │ │ │ ├── 10-multipart.markdown │ │ │ │ │ │ └── 11-using-graphql-apis.markdown │ │ │ │ │ └── 05-rest-builder/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-how-to-install.markdown │ │ │ │ │ ├── 03-openapi.markdown │ │ │ │ │ ├── 04-develop.markdown │ │ │ │ │ ├── 05-how-to-add-features-for-managing-collections.markdown │ │ │ │ │ ├── 06-scaffolding.markdown │ │ │ │ │ ├── 07-support-for-oneOf-anyOf-and-allOf.markdown │ │ │ │ │ └── 08-good-practices-and-conventions.markdown │ │ │ │ ├── workflow/ │ │ │ │ │ ├── 01-workflow-intro.markdown │ │ │ │ │ └── 02-workflow-framework.markdown │ │ │ │ └── wysiwyg-editors/ │ │ │ │ ├── 01-wysiwyg-editors-intro.markdown │ │ │ │ ├── 02-wysiwyg-editor-in-portlet/ │ │ │ │ │ └── 01-adding-a-wysiwyg-editor-to-a-portlet-intro.markdown │ │ │ │ ├── 03-modifying-editor-configuration/ │ │ │ │ │ └── 01-modifying-an-editors-configuration-intro.markdown │ │ │ │ ├── 04-alloy-editor/ │ │ │ │ │ ├── 01-alloy-editor-intro.markdown │ │ │ │ │ ├── 02-adding-buttons-to-the-alloyeditor/ │ │ │ │ │ │ ├── 01-adding-buttons-to-the-alloy-editor-intro.markdown │ │ │ │ │ │ ├── 02-preparing-and-configuring-your-module.markdown │ │ │ │ │ │ ├── 03-adding-buttons-to-the-alloyeditor-add-toolbar.markdown │ │ │ │ │ │ └── 04-adding-buttons-to-the-alloyeditor-styles-toolbar.markdown │ │ │ │ │ └── 04-embedding-content/ │ │ │ │ │ └── 01-embedding-content-in-the-editor-intro.markdown │ │ │ │ └── 05-adding-new-editor-behaviors/ │ │ │ │ └── 01-adding-new-behavior-to-an-editor-intro.markdown │ │ │ └── build.xml │ │ ├── reference/ │ │ │ ├── articles/ │ │ │ │ ├── 01-intro/ │ │ │ │ │ └── 01-reference-intro.markdown │ │ │ │ ├── 02-breaking-changes.markdown │ │ │ │ ├── 02-cdi-portlet-predefined-beans.markdown │ │ │ │ ├── 02-classes-moved/ │ │ │ │ │ └── 01-classes-moved-from-portal-service-jar-intro.markdown │ │ │ │ ├── 02-export-import-and-staging/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── decision-to-implement-staging.markdown │ │ │ │ │ └── liferay-archive-file.markdown │ │ │ │ ├── 02-front-end/ │ │ │ │ │ ├── 01-front-end-intro.markdown │ │ │ │ │ ├── 02-freemarker-macros/ │ │ │ │ │ │ └── 01-liferay-freemarker-macros-intro.markdown │ │ │ │ │ ├── 03-front-end-taglibs/ │ │ │ │ │ │ ├── 01-front-end-taglibs-intro.markdown │ │ │ │ │ │ ├── 02-theme-objects/ │ │ │ │ │ │ │ └── 01-liferay-theme-objects-intro.markdown │ │ │ │ │ │ ├── 03-portlet-objects/ │ │ │ │ │ │ │ └── 01-liferay-portlet-objects-intro.markdown │ │ │ │ │ │ ├── 04-liferay-ui-taglibs/ │ │ │ │ │ │ │ ├── 01-using-liferay-ui-taglibs-intro.markdown │ │ │ │ │ │ │ ├── 02-liferay-ui-icons.markdown │ │ │ │ │ │ │ ├── 03-liferay-ui-icon-lists.markdown │ │ │ │ │ │ │ ├── 04-liferay-ui-icon-menus.markdown │ │ │ │ │ │ │ ├── 05-liferay-ui-tabs.markdown │ │ │ │ │ │ │ └── 06-liferay-ui-icon-help.markdown │ │ │ │ │ │ ├── 05-liferay-frontend-taglibs/ │ │ │ │ │ │ │ ├── 01-using-liferay-frontend-taglibs-intro.markdown │ │ │ │ │ │ │ ├── 02-add-menu/ │ │ │ │ │ │ │ │ └── 01-liferay-frontend-add-menu-intro.markdown │ │ │ │ │ │ │ ├── 03-cards/ │ │ │ │ │ │ │ │ └── 01-liferay-frontend-cards-intro.markdown │ │ │ │ │ │ │ ├── 04-info-bar/ │ │ │ │ │ │ │ │ └── 01-liferay-frontend-info-bar-intro.markdown │ │ │ │ │ │ │ └── 05-liferay-frontend-management-bar/ │ │ │ │ │ │ │ ├── 01-liferay-frontend-management-bar-intro.markdown │ │ │ │ │ │ │ ├── 02-including-actions-in-the-management-bar.markdown │ │ │ │ │ │ │ └── 03-disabling-the-management-bar.markdown │ │ │ │ │ │ ├── 06-liferay-util-taglibs/ │ │ │ │ │ │ │ ├── 01-using-liferay-util-taglibs-intro.markdown │ │ │ │ │ │ │ ├── 02-liferay-util-body-bottom.markdown │ │ │ │ │ │ │ ├── 03-liferay-util-body-top.markdown │ │ │ │ │ │ │ ├── 04-liferay-util-buffer.markdown │ │ │ │ │ │ │ ├── 05-liferay-util-dynamic-include.markdown │ │ │ │ │ │ │ ├── 06-liferay-util-get-url.markdown │ │ │ │ │ │ │ ├── 07-liferay-util-html-bottom.markdown │ │ │ │ │ │ │ ├── 08-liferay-util-html-top.markdown │ │ │ │ │ │ │ ├── 09-liferay-util-include.markdown │ │ │ │ │ │ │ ├── 10-liferay-util-param.markdown │ │ │ │ │ │ │ └── 11-liferay-util-whitespace-remover.markdown │ │ │ │ │ │ ├── 07-clay-taglibs/ │ │ │ │ │ │ │ ├── 01-using-clay-taglibs-intro.markdown │ │ │ │ │ │ │ ├── 02-clay-alerts.markdown │ │ │ │ │ │ │ ├── 03-clay-badges.markdown │ │ │ │ │ │ │ ├── 04-clay-buttons.markdown │ │ │ │ │ │ │ ├── 05-clay-cards.markdown │ │ │ │ │ │ │ ├── 06-clay-dropdown-and-action-menus.markdown │ │ │ │ │ │ │ ├── 07-clay-form-elements.markdown │ │ │ │ │ │ │ ├── 08-clay-icons.markdown │ │ │ │ │ │ │ ├── 09-clay-links-and-labels.markdown │ │ │ │ │ │ │ ├── 10-clay-management-toolbar.markdown │ │ │ │ │ │ │ ├── 11-clay-navigation-bars.markdown │ │ │ │ │ │ │ ├── 12-clay-progress-bars.markdown │ │ │ │ │ │ │ └── 13-clay-stickers.markdown │ │ │ │ │ │ ├── 08-chart-taglibs/ │ │ │ │ │ │ │ ├── 01-using-chart-taglibs-intro.markdown │ │ │ │ │ │ │ ├── 02-bar-charts.markdown │ │ │ │ │ │ │ ├── 03-line-charts.markdown │ │ │ │ │ │ │ ├── 04-scatter-charts.markdown │ │ │ │ │ │ │ ├── 05-spline-charts.markdown │ │ │ │ │ │ │ ├── 06-step-charts.markdown │ │ │ │ │ │ │ ├── 07-combination-charts.markdown │ │ │ │ │ │ │ ├── 08-donut-charts.markdown │ │ │ │ │ │ │ ├── 09-gauge-charts.markdown │ │ │ │ │ │ │ ├── 10-pie-charts.markdown │ │ │ │ │ │ │ ├── 11-geomap-charts.markdown │ │ │ │ │ │ │ ├── 12-predictive-charts.markdown │ │ │ │ │ │ │ └── 13-refreshing-charts-to-reflect-real-time-data.markdown │ │ │ │ │ │ └── 09-aui-taglibs/ │ │ │ │ │ │ ├── 01-using-aui-taglibs-intro.markdown │ │ │ │ │ │ └── 02-building-forms-with-aui-tags.markdown │ │ │ │ │ ├── 04-liferay-npm-bundler/ │ │ │ │ │ │ ├── 01-liferay-npm-bundler-intro.markdown │ │ │ │ │ │ ├── 03-npmbundlerrc-structure.markdown │ │ │ │ │ │ ├── 04-how-the-default-preset-configures-the-liferay-npm-bundler.markdown │ │ │ │ │ │ ├── 05-the-structure-of-osgi-bundles-npm.markdown │ │ │ │ │ │ ├── 06-how-portal-publishes-npm-packages.markdown │ │ │ │ │ │ ├── 07-how-the-bundler-formats-js-modules.markdown │ │ │ │ │ │ ├── 08-how-liferay-amd-loader-configuration-is-exported.markdown │ │ │ │ │ │ ├── 09-changes-between-bundler-1.x-and-2.x.markdown │ │ │ │ │ │ ├── 10-understanding-loader-rules.markdown │ │ │ │ │ │ └── 11-default-bundler-loaders.markdown │ │ │ │ │ ├── 05-liferay-js-apis/ │ │ │ │ │ │ ├── 01-liferay-js-apis-intro.markdown │ │ │ │ │ │ ├── 02-theme-display.markdown │ │ │ │ │ │ ├── 03-working-with-urls-in-js.markdown │ │ │ │ │ │ ├── 04-javascript-utilities.markdown │ │ │ │ │ │ ├── 05-invoking-liferay-services.markdown │ │ │ │ │ │ ├── 06-handling-ajax-requests.markdown │ │ │ │ │ │ └── 07-working-with-addresses.markdown │ │ │ │ │ ├── 06-freemarker-taglib-macros/ │ │ │ │ │ │ └── 01-freemarker-taglib-macros-intro.markdown │ │ │ │ │ ├── 07-npm-environment/ │ │ │ │ │ │ └── 01-setting-up-your-npm-environment-intro.markdown │ │ │ │ │ ├── 08-sitemap-page-configuration/ │ │ │ │ │ │ └── 01-sitemap-pages-configuraiton-options-intro.markdown │ │ │ │ │ ├── 09-ckeditor-plugins/ │ │ │ │ │ │ └── 01-ckeditor-plugin-reference-guide-intro.markdown │ │ │ │ │ ├── 11-portlet-ids/ │ │ │ │ │ │ └── 01-fully-qualified-portlet-ids-intro.markdown │ │ │ │ │ ├── 12-spa-lifecycle-events/ │ │ │ │ │ │ └── 01-spa-lifecycle-events-intro.markdown │ │ │ │ │ ├── 13-theme-anatomy/ │ │ │ │ │ │ └── 01-theme-anatomy-reference-guide-intro.markdown │ │ │ │ │ └── 14-freemarker-variables/ │ │ │ │ │ └── 01-freemarker-variables-intro.markdown │ │ │ │ ├── 02-gradle-plugins/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── app-javadoc-builder-gradle-plugin.markdown │ │ │ │ │ ├── baseline-gradle-plugin.markdown │ │ │ │ │ ├── change-log-builder-gradle-plugin.markdown │ │ │ │ │ ├── css-builder-gradle-plugin.markdown │ │ │ │ │ ├── db-support-gradle-plugin.markdown │ │ │ │ │ ├── dependency-checker-gradle-plugin.markdown │ │ │ │ │ ├── deployment-helper-gradle-plugin.markdown │ │ │ │ │ ├── go-gradle-plugin.markdown │ │ │ │ │ ├── gulp-gradle-plugin.markdown │ │ │ │ │ ├── jasper-jspc-gradle-plugin.markdown │ │ │ │ │ ├── javadoc-formatter-gradle-plugin.markdown │ │ │ │ │ ├── js-module-config-generator-gradle-plugin.markdown │ │ │ │ │ ├── js-transpiler-gradle-plugin.markdown │ │ │ │ │ ├── jsdoc-gradle-plugin.markdown │ │ │ │ │ ├── lang-builder-gradle-plugin.markdown │ │ │ │ │ ├── maven-plugin-builder-gradle-plugin.markdown │ │ │ │ │ ├── node-gradle-plugin.markdown │ │ │ │ │ ├── rest-builder-gradle-plugin.markdown │ │ │ │ │ ├── service-builder-gradle-plugin.markdown │ │ │ │ │ ├── source-formatter-gradle-plugin.markdown │ │ │ │ │ ├── soy-gradle-plugin.markdown │ │ │ │ │ ├── target-platform-gradle-plugin.markdown │ │ │ │ │ ├── theme-builder-gradle-plugin.markdown │ │ │ │ │ ├── tld-formatter-gradle-plugin.markdown │ │ │ │ │ ├── tlddoc-builder-gradle-plugin.markdown │ │ │ │ │ ├── whip-gradle-plugin.markdown │ │ │ │ │ ├── wsdd-builder-gradle-plugin.markdown │ │ │ │ │ ├── wsdl-builder-gradle-plugin.markdown │ │ │ │ │ ├── xml-formatter-gradle-plugin.markdown │ │ │ │ │ └── xsd-builder-gradle-plugin.markdown │ │ │ │ ├── 02-item-selector-criterion-and-return-types.markdown │ │ │ │ ├── 02-java-apis.markdown │ │ │ │ ├── 02-liferay-faces/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-liferay-faces-version-scheme.markdown │ │ │ │ │ ├── 03-understanding-liferay-faces-bridge.markdown │ │ │ │ │ ├── 04-understanding-liferay-faces-alloy.markdown │ │ │ │ │ └── 05-understanding-liferay-faces-portal.markdown │ │ │ │ ├── 02-maven-plugins/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── bundle-support-plugin.markdown │ │ │ │ │ ├── css-builder-plugin.markdown │ │ │ │ │ ├── db-support-plugin.markdown │ │ │ │ │ ├── deployment-helper-plugin.markdown │ │ │ │ │ ├── javadoc-formatter-plugin.markdown │ │ │ │ │ ├── lang-builder-plugin.markdown │ │ │ │ │ ├── rest-builder-plugin.markdown │ │ │ │ │ ├── service-builder-plugin.markdown │ │ │ │ │ ├── source-formatter-plugin.markdown │ │ │ │ │ ├── theme-builder-plugin.markdown │ │ │ │ │ ├── tld-formatter-plugin.markdown │ │ │ │ │ ├── wsdd-builder-plugin.markdown │ │ │ │ │ └── xml-formatter-plugin.markdown │ │ │ │ ├── 02-meaningful-schema-versioning.markdown │ │ │ │ ├── 02-portlet-3-opt-in.markdown │ │ │ │ ├── 02-portlet-descriptor-to-osgi-service-property-map.markdown │ │ │ │ ├── 02-portletmvc4spring/ │ │ │ │ │ ├── 01-portletmvc4spring-intro.markdown │ │ │ │ │ ├── 02-portletmvc4spring-project-anatomy.markdown │ │ │ │ │ ├── 03-portletmvc4spring-annotations.markdown │ │ │ │ │ └── 99-portletmvc4spring-configuration-files.markdown │ │ │ │ ├── 02-project-templates/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── activator-template.markdown │ │ │ │ │ ├── api-template.markdown │ │ │ │ │ ├── control-menu-entry-template.markdown │ │ │ │ │ ├── form-field-template.markdown │ │ │ │ │ ├── fragment-template.markdown │ │ │ │ │ ├── freemarker-portlet-template.markdown │ │ │ │ │ ├── layout-template.markdown │ │ │ │ │ ├── modules-ext-template.markdown │ │ │ │ │ ├── mvc-portlet-template.markdown │ │ │ │ │ ├── panel-app-template.markdown │ │ │ │ │ ├── portlet-configuration-icon-template.markdown │ │ │ │ │ ├── portlet-provider-template.markdown │ │ │ │ │ ├── portlet-toolbar-contributor-template.markdown │ │ │ │ │ ├── rest-template.markdown │ │ │ │ │ ├── service-builder-template.markdown │ │ │ │ │ ├── service-template.markdown │ │ │ │ │ ├── service-wrapper-template.markdown │ │ │ │ │ ├── simulation-panel-entry-template.markdown │ │ │ │ │ ├── social-bookmark.markdown │ │ │ │ │ ├── spring-mvc-portlet-template.markdown │ │ │ │ │ ├── template-context-contributor-template.markdown │ │ │ │ │ ├── theme-contributor-template.markdown │ │ │ │ │ ├── theme-template.markdown │ │ │ │ │ ├── war-core-ext.markdown │ │ │ │ │ ├── war-hook-template.markdown │ │ │ │ │ └── war-mvc-portlet-template.markdown │ │ │ │ ├── 02-sample-projects/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-apps/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── greedy-policy-option-portlet.markdown │ │ │ │ │ │ ├── kotlin-portlet.markdown │ │ │ │ │ │ ├── service-builder-samples/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── service-builder-adq.markdown │ │ │ │ │ │ │ ├── service-builder-jdbc.markdown │ │ │ │ │ │ │ └── service-builder-jndi.markdown │ │ │ │ │ │ ├── shared-language-keys.markdown │ │ │ │ │ │ ├── simulation-panel-app.markdown │ │ │ │ │ │ └── workflow-samples/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-workflow-asset.markdown │ │ │ │ │ │ └── 03-workflow-basic.markdown │ │ │ │ │ ├── 03-extensions/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── control-menu-entry.markdown │ │ │ │ │ │ ├── document-action.markdown │ │ │ │ │ │ ├── gogo-shell-command.markdown │ │ │ │ │ │ ├── index-settings-contributor.markdown │ │ │ │ │ │ ├── indexer-post-processor.markdown │ │ │ │ │ │ ├── model-listener.markdown │ │ │ │ │ │ ├── screen-name-validator.markdown │ │ │ │ │ │ └── servlet.markdown │ │ │ │ │ ├── 04-overrides/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── module-jsp-override.markdown │ │ │ │ │ │ └── resource-bundle-override.markdown │ │ │ │ │ ├── 05-themes/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── simple-theme.markdown │ │ │ │ │ │ ├── template-context-contributor.markdown │ │ │ │ │ │ └── theme-contributor.markdown │ │ │ │ │ └── 06-ext/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── login-web-ext.markdown │ │ │ │ ├── 02-segmentation-and-personalization/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ └── 02-defining-segment-criteria.markdown │ │ │ │ ├── 02-third-party-packages-portal-exports.markdown │ │ │ │ ├── 02-tooling/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── 01-creating-a-project.markdown │ │ │ │ │ ├── 02-deploying-a-project.markdown │ │ │ │ │ ├── blade-cli/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-installing-blade-cli.markdown │ │ │ │ │ │ ├── 03-installing-blade-cli-with-proxy-requirements.markdown │ │ │ │ │ │ ├── 04-managing-your-liferay-server-with-blade-cli.markdown │ │ │ │ │ │ ├── 05-generating-project-samples-with-blade-cli.markdown │ │ │ │ │ │ ├── 06-updating-blade-cli.markdown │ │ │ │ │ │ ├── 07-converting-plugins-sdk-projects-with-blade-cli.markdown │ │ │ │ │ │ ├── 08-extending-blade-cli/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-creating-custom-commands-for-blade-cli.markdown │ │ │ │ │ │ │ ├── 03-creating-custom-project-templates-for-blade-cli.markdown │ │ │ │ │ │ │ ├── 04-installing-new-extensions-for-blade-cli.markdown │ │ │ │ │ │ │ └── 05-creating-a-blade-profile.markdown │ │ │ │ │ │ └── 100-common-errors-with-blade-cli-installation.markdown │ │ │ │ │ ├── dev-studio/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-installing-dev-studio.markdown │ │ │ │ │ │ ├── 03-setting-proxy-requirements-for-dev-studio.markdown │ │ │ │ │ │ ├── 04-installing-a-server-in-dev-studio.markdown │ │ │ │ │ │ ├── 05-importing-projects-in-dev-studio.markdown │ │ │ │ │ │ ├── 06-using-the-gogo-shell-in-dev-studio.markdown │ │ │ │ │ │ ├── 07-searching-liferay-portal-source-with-dev-studio.markdown │ │ │ │ │ │ ├── 08-debugging-liferay-portal-source-with-dev-studio.markdown │ │ │ │ │ │ ├── 09-updating-dev-studio.markdown │ │ │ │ │ │ ├── 100-gradle-in-dev-studio.markdown │ │ │ │ │ │ └── 101-maven-in-dev-studio.markdown │ │ │ │ │ ├── intellij/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-installing-liferay-intellij-plugin.markdown │ │ │ │ │ │ ├── 03-installing-a-server-in-intellij-idea.markdown │ │ │ │ │ │ └── 04-updating-liferay-intellij-plugin.markdown │ │ │ │ │ ├── liferay-js-generator/ │ │ │ │ │ │ ├── 01-js-generator-intro.markdown │ │ │ │ │ │ ├── 02-installing-the-js-generator-and-creating-js-portlets.markdown │ │ │ │ │ │ ├── 04-understanding-the-js-portlet-extender-configuration.markdown │ │ │ │ │ │ ├── 05-configuration-json-options.markdown │ │ │ │ │ │ └── 06-adapting-apps-for-liferay.markdown │ │ │ │ │ ├── liferay-workspace/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-installing-liferay-workspace.markdown │ │ │ │ │ │ ├── 03-creating-a-liferay-workspace.markdown │ │ │ │ │ │ ├── 04-importing-a-liferay-workspace-into-an-ide.markdown │ │ │ │ │ │ ├── 05-setting-proxy-requirements-for-liferay-workspace.markdown │ │ │ │ │ │ ├── 06-adding-a-liferay-bundle-to-workspace.markdown │ │ │ │ │ │ ├── 07-setting-environment-configurations-for-liferay-workspace.markdown │ │ │ │ │ │ ├── 08-building-nodejs-themes-in-liferay-workspace.markdown │ │ │ │ │ │ ├── 09-building-gradle-maven-themes-in-liferay-workspace.markdown │ │ │ │ │ │ ├── 10-managing-the-target-platform/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-setting-the-target-platform.markdown │ │ │ │ │ │ │ ├── 03-targeting-a-platform-outside-of-workspace.markdown │ │ │ │ │ │ │ └── 04-targetinging-a-platform-with-maven.markdown │ │ │ │ │ │ ├── 11-validating-modules-against-the-target-platform/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-adding-a-third-party-librarys-capabilities-to-the-resolvers-capabilities.markdown │ │ │ │ │ │ │ ├── 03-skipping-the-resolving-process-for-a-module.markdown │ │ │ │ │ │ │ ├── 04-depending-on-a-customized-distribution-of-liferay.markdown │ │ │ │ │ │ │ ├── 05-including-the-resolver-in-your-gradle-build.markdown │ │ │ │ │ │ │ ├── 06-how-to-resolve-common-output-errors-reported-by-the-resolve-task.markdown │ │ │ │ │ │ │ └── 99-validating-modules-outside-of-workspace.markdown │ │ │ │ │ │ ├── 12-leveraging-docker/ │ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ │ ├── 02-creating-a-liferay-docker-container.markdown │ │ │ │ │ │ │ ├── 03-configuring-a-docker-container.markdown │ │ │ │ │ │ │ └── 04-building-a-custom-docker-image.markdown │ │ │ │ │ │ ├── 13-updating-liferay-workspace.markdown │ │ │ │ │ │ └── 14-updating-default-plugins-provided-by-liferay-workspace.markdown │ │ │ │ │ ├── maven/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-installing-remote-liferay-maven-artifacts.markdown │ │ │ │ │ │ ├── 04-creating-a-maven-repository.markdown │ │ │ │ │ │ ├── 05-configuring-local-maven-settings-to-access-repositories.markdown │ │ │ │ │ │ ├── 06-deploying-liferay-maven-artifacts-to-a-repository.markdown │ │ │ │ │ │ ├── 07-building-an-osgi-module-jar-with-maven.markdown │ │ │ │ │ │ ├── 08-building-themes-in-a-maven-project.markdown │ │ │ │ │ │ ├── 09-compiling-sass-files-in-a-maven-project.markdown │ │ │ │ │ │ ├── 10-using-service-builder-in-a-maven-project.markdown │ │ │ │ │ │ └── 99-upgrading-your-maven-build-environment.markdown │ │ │ │ │ ├── theme-generator/ │ │ │ │ │ │ ├── 01-theme-generator-intro.markdown │ │ │ │ │ │ ├── 02-installing-the-theme-generator-and-creating-themes.markdown │ │ │ │ │ │ ├── 03-generating-layout-templates.markdown │ │ │ │ │ │ └── 05-generating-themelets.markdown │ │ │ │ │ └── upgrade-planner/ │ │ │ │ │ ├── 01-liferay-upgrade-planner-intro.markdown │ │ │ │ │ └── 02-using-upgrade-planner-with-proxy-requirements.markdown │ │ │ │ ├── 02-web-experience-management/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-page-fragments/ │ │ │ │ │ │ ├── 01-fragment-specific-tags-intro.markdown │ │ │ │ │ │ ├── 02-fragment-configuration-types.markdown │ │ │ │ │ │ └── 03-escaping-fragment-configuration-text-values.markdown │ │ │ │ │ └── 03-asset-display-page-taglib.markdown │ │ │ │ └── 02-workflow/ │ │ │ │ ├── 01-creating-workflow-definitions-intro.markdown │ │ │ │ ├── 02-workflow-nodes.markdown │ │ │ │ ├── 03-tasks.markdown │ │ │ │ └── 04-notifications.markdown │ │ │ ├── articles-dxp/ │ │ │ │ ├── 02-classes-moved/ │ │ │ │ │ └── 01-classes-moved-from-portal-service-jar-intro.markdown │ │ │ │ └── 02-tooling/ │ │ │ │ └── dev-studio/ │ │ │ │ └── 02-installing-dev-studio.markdown │ │ │ ├── build.xml │ │ │ └── code/ │ │ │ └── adapted-react-app/ │ │ │ └── my-react-guestbook-app/ │ │ │ ├── .gitignore │ │ │ ├── .npmbuildrc │ │ │ ├── .npmbundlerrc │ │ │ ├── README.md │ │ │ ├── features/ │ │ │ │ └── localization/ │ │ │ │ └── Language.properties │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── manifest.json │ │ │ └── src/ │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── add-entry.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── serviceWorker.js │ │ │ └── view-guestbook.js │ │ └── tutorials/ │ │ ├── articles/ │ │ │ ├── 01-tutorials-intro/ │ │ │ │ └── 01-tutorials-intro.markdown │ │ │ ├── 02-developing-a-web-application/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-setting-up-liferay-development/ │ │ │ │ │ └── 01-setting-up-liferay-development-intro.markdown │ │ │ │ ├── 03-generating-the-backend/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── 01-what-is-service-builder.markdown │ │ │ │ │ ├── 02-generating-layers.markdown │ │ │ │ │ └── 03-implementing-service-methods.markdown │ │ │ │ ├── 04-building-the-web-front-end/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-the-web-project.markdown │ │ │ │ │ ├── 03-defining-component-metadata-properties.markdown │ │ │ │ │ ├── 04-portlet-keys.markdown │ │ │ │ │ ├── 05-integrating-the-back-end.markdown │ │ │ │ │ ├── 06-creating-a-button.markdown │ │ │ │ │ ├── 07-generating-portlet-urls.markdown │ │ │ │ │ ├── 08-linking-to-another-page.markdown │ │ │ │ │ ├── 09-forms-and-action-urls.markdown │ │ │ │ │ ├── 10-implementing-portlet-actions.markdown │ │ │ │ │ ├── 11-displaying-guestbook-entries.markdown │ │ │ │ │ └── 12-fitting-it-all-together.markdown │ │ │ │ ├── 05-writing-admin-portlet/ │ │ │ │ │ ├── 01-writing-the-guestbook-admin-application-intro.markdown │ │ │ │ │ ├── 02-creating-the-classes.markdown │ │ │ │ │ ├── 03-updating-metadata.markdown │ │ │ │ │ ├── 04-updating-your-service-layer.markdown │ │ │ │ │ ├── 05-defining-portlet-actions.markdown │ │ │ │ │ ├── 06-adding-tabs-to-the-guestbook-portlet.markdown │ │ │ │ │ └── 07-creating-a-user-interface.markdown │ │ │ │ ├── 06-displaying-message-errors/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-language-keys.markdown │ │ │ │ │ ├── 03-adding-error-messages.markdown │ │ │ │ │ └── 04-adding-messages-jsp.markdown │ │ │ │ ├── 07-permissions/ │ │ │ │ │ ├── 01-permissions-intro.markdown │ │ │ │ │ ├── 02-defining-permissions.markdown │ │ │ │ │ ├── 03-registering-permissions-database.markdown │ │ │ │ │ ├── 04-registering-permissions-container.markdown │ │ │ │ │ ├── 05-assigning-permissions-to-resources.markdown │ │ │ │ │ └── 06-checking-permissions-jsps.markdown │ │ │ │ ├── 08-search-and-indexing/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-enabling-search-and-indexing-for-guestbooks/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-understanding-search-and-indexing.markdown │ │ │ │ │ │ ├── 03-registering-search-services.markdown │ │ │ │ │ │ ├── 04-indexing-guestbooks.markdown │ │ │ │ │ │ ├── 05-querying-guestbook.markdown │ │ │ │ │ │ ├── 06-summarizing-search-documents.markdown │ │ │ │ │ │ └── 07-handling-indexing-in-the-guestbook-service-layer.markdown │ │ │ │ │ ├── 03-enabling-search-and-indexing-for-guestbook-entries/ │ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ │ ├── 02-registering-search-services.markdown │ │ │ │ │ │ ├── 03-indexing-entries.markdown │ │ │ │ │ │ ├── 04-querying-entries.markdown │ │ │ │ │ │ ├── 05-summarizing-search-documents.markdown │ │ │ │ │ │ └── 06-handling-indexing-in-the-entry-service-layer.markdown │ │ │ │ │ └── 04-updating-your-user-interface-for-search/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-adding-a-search-bar-to-the-guestbook-portlet.markdown │ │ │ │ │ └── 03-creating-a-search-results-jsp-for-the-guestbook-portlet.markdown │ │ │ │ ├── 09-assets/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-enabling-assets-at-the-service-layer/ │ │ │ │ │ │ ├── 01-enabling-assets-at-the-service-layer-intro.markdown │ │ │ │ │ │ ├── 02-handling-assets-at-the-guestbook-service-layer.markdown │ │ │ │ │ │ └── 03-handling-assets-at-the-entry-service-layer.markdown │ │ │ │ │ ├── 03-implementing-asset-renderers/ │ │ │ │ │ │ ├── 01-implementing-asset-renderers-intro.markdown │ │ │ │ │ │ ├── 02-implementing-a-guestbook-asset-renderer.markdown │ │ │ │ │ │ └── 03-implementing-an-entry-asset-renderer.markdown │ │ │ │ │ └── 04-adding-asset-features-to-your-user-interface/ │ │ │ │ │ ├── 01-adding-asset-features-to-your-user-interface-intro.markdown │ │ │ │ │ ├── 02-creating-jsps-for-displaying-custom-assets-in-the-asset-publisher.markdown │ │ │ │ │ ├── 03-enabling-tags-categories-and-assets-for-guestbooks.markdown │ │ │ │ │ ├── 04-enabling-tags-categories-and-assets-for-entries.markdown │ │ │ │ │ └── 05-enabling-comments-and-ratings-for-entries.markdown │ │ │ │ └── 10-workflow/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-supporting-workflow-status/ │ │ │ │ │ ├── 01-enabling-workflow-service-layer-intro.markdown │ │ │ │ │ ├── 02-setting-guestbook-status.markdown │ │ │ │ │ ├── 03-setting-entry-status.markdown │ │ │ │ │ └── 04-finding-entities-by-status.markdown │ │ │ │ ├── 03-workflow-handlers/ │ │ │ │ │ ├── 01-implementing-workflow-handlers-intro.markdown │ │ │ │ │ ├── 02-guestbook-workflow-handlers.markdown │ │ │ │ │ └── 03-entry-workflow-handlers.markdown │ │ │ │ └── 04-displaying-approved-items/ │ │ │ │ ├── 01-displaying-approved-workflow-items-intro.markdown │ │ │ │ ├── 02-displaying-status-guestbook-admin.markdown │ │ │ │ └── 03-displaying-approved-entries.markdown │ │ │ ├── 03-upgrading-code-to-liferay-7.2/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-upgrading-your-development-environment.markdown │ │ │ │ ├── 03-migrating-plugins-sdk-projects-to-liferay-workspace.markdown │ │ │ │ ├── 04-upgrading-build-dependencies.markdown │ │ │ │ ├── 05-fixing-upgrade-problems/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-resolving-a-projects-dependencies.markdown │ │ │ │ │ └── 03-resolving-breaking-changes.markdown │ │ │ │ ├── 06-upgrading-service-builder-services/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-removing-legacy-files.markdown │ │ │ │ │ ├── 03-converting-a-service-builder-module-from-spring-di-to-osgi-ds.markdown │ │ │ │ │ └── 04-rebuilding-services.markdown │ │ │ │ ├── 07-upgrading-customization-plugins/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-upgrading-customization-modules.markdown │ │ │ │ │ ├── 03-upgrading-core-jsp-hooks.markdown │ │ │ │ │ ├── 04-upgrading-portlet-jsp-hooks.markdown │ │ │ │ │ ├── 05-upgrading-service-wrapper-hooks.markdown │ │ │ │ │ ├── 06-upgrading-core-language-key-hooks.markdown │ │ │ │ │ ├── 07-upgrading-portlet-language-key-hooks.markdown │ │ │ │ │ ├── 08-upgrading-model-listener-hooks.markdown │ │ │ │ │ ├── 09-upgrading-event-actions-hooks.markdown │ │ │ │ │ ├── 10-upgrading-servlet-filter-hooks.markdown │ │ │ │ │ ├── 11-upgrading-portal-property-hooks.markdown │ │ │ │ │ └── 12-upgrading-struts-action-hooks.markdown │ │ │ │ ├── 08-upgrading-themes/ │ │ │ │ │ ├── 01-upgrading-a-theme-to-7-2-intro.markdown │ │ │ │ │ ├── 02-upgrading-6.2-themes-7.2/ │ │ │ │ │ │ ├── 01-upgrading-6.2-themes-to-7.1-intro.markdown │ │ │ │ │ │ ├── 02-setting-up-the-development-environment/ │ │ │ │ │ │ │ ├── 01-setting-up-the-development-environment-intro.markdown │ │ │ │ │ │ │ ├── 02-installing-the-theme-generator.markdown │ │ │ │ │ │ │ └── 03-importing-the-theme-into-the-toolkit.markdown │ │ │ │ │ │ ├── 03-running-the-upgrade-task/ │ │ │ │ │ │ │ └── 01-running-the-theme-upgrade-task-for-6.2-themes-intro.markdown │ │ │ │ │ │ ├── 04-updating-6.2-css-code/ │ │ │ │ │ │ │ ├── 01-updating-6.2-css-code-intro.markdown │ │ │ │ │ │ │ ├── 02-updating-bootstrap-rules-and-font-awesome-imports.markdown │ │ │ │ │ │ │ └── 03-updating-6.2-responsiveness.markdown │ │ │ │ │ │ ├── 05-updating-theme-templates/ │ │ │ │ │ │ │ ├── 01-updating-6.2-theme-templates-intro.markdown │ │ │ │ │ │ │ ├── 02-updating-6.2-portal-normal-theme-template.markdown │ │ │ │ │ │ │ ├── 03-updating-6.2-navigation-theme-template.markdown │ │ │ │ │ │ │ └── 04-updating-6.2-init-custom-theme-template.markdown │ │ │ │ │ │ ├── 06-updating-the-resources-importer-configuration-and-content/ │ │ │ │ │ │ │ ├── 01-updating-the-importer-intro.markdown │ │ │ │ │ │ │ ├── 02-updating-package-properties.markdown │ │ │ │ │ │ │ ├── 03-updating-web-content.markdown │ │ │ │ │ │ │ └── 04-updating-the-sitemap.markdown │ │ │ │ │ │ └── 07-applying-clay-design-patterns/ │ │ │ │ │ │ └── 01-applying-clay-design-patterns-intro.markdown │ │ │ │ │ ├── 03-upgrading-7.0-themes-7.2/ │ │ │ │ │ │ ├── 01-upgrading-7.0-themes-intro.markdown │ │ │ │ │ │ ├── 02-running-the-upgrade-task/ │ │ │ │ │ │ │ └── 01-running-the-theme-upgrade-task-intro.markdown │ │ │ │ │ │ ├── 03-updating-7.0-css-code/ │ │ │ │ │ │ │ ├── 01-updating-7.0-css-code-intro.markdown │ │ │ │ │ │ │ ├── 02-renaming-7-0-css-files.markdown │ │ │ │ │ │ │ ├── 03-updating-7.0-variables.markdown │ │ │ │ │ │ │ └── 04-updating-7.0-imports.markdown │ │ │ │ │ │ ├── 04-updating-7.0-theme-templates/ │ │ │ │ │ │ │ ├── 01-updating-7.0-theme-templates-intro.markdown │ │ │ │ │ │ │ └── 02-updating-7.0-theme-templates-to-7.2.markdown │ │ │ │ │ │ └── 05-bootstrap-lexicon-compatibility-layer.markdown │ │ │ │ │ └── 04-upgrading-7.1-themes-7.2/ │ │ │ │ │ └── 01-upgrading-7-1-themes-7-2-intro.markdown │ │ │ │ ├── 09-upgrading-layout-templates/ │ │ │ │ │ ├── 01-upgrading-layout-templates-to-7.2-intro.markdown │ │ │ │ │ ├── 02-upgrading-6.2-layouts-7.2/ │ │ │ │ │ │ └── 01-upgrading-6-2-layout-templates-intro.markdown │ │ │ │ │ └── 03-upgrading-7.0-7.1-layouts-7.2/ │ │ │ │ │ └── 01-upgrading-7-0-layout-templates-intro.markdown │ │ │ │ ├── 10-upgrading-frameworks-and-features/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-upgrading-jndi-data-source-usage.markdown │ │ │ │ │ ├── 03-upgrading-service-builder-service-invocation.markdown │ │ │ │ │ ├── 04-upgrading-service-builder.markdown │ │ │ │ │ └── 05-migrating-off-of-velocity-templates.markdown │ │ │ │ ├── 11-upgrading-portlets/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-upgrading-a-genericportlet.markdown │ │ │ │ │ ├── 03-upgrading-a-liferay-mvc-portlet.markdown │ │ │ │ │ ├── 04-upgrading-a-liferay-jsf-portlet.markdown │ │ │ │ │ ├── 05-upgrading-a-servlet-based-portlet.markdown │ │ │ │ │ ├── 06-upgrading-a-spring-portlet-mvc-portlet.markdown │ │ │ │ │ └── 07-upgrading-a-struts-1-portlet.markdown │ │ │ │ ├── 12-upgrading-web-plugins.markdown │ │ │ │ └── 13-upgrading-ext-plugins.markdown │ │ │ └── 04-creating-a-theme/ │ │ │ ├── 01-creating-a-theme-intro.markdown │ │ │ ├── 02-setting-up-the-theme.markdown │ │ │ ├── 03-updating-the-header-and-logo.markdown │ │ │ ├── 04-customizing-the-navigation.markdown │ │ │ ├── 05-defining-the-footer-and-navigation.markdown │ │ │ └── 06-adding-a-color-scheme.markdown │ │ ├── build.xml │ │ ├── code/ │ │ │ ├── guestbook/ │ │ │ │ ├── 04-web-front-end/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ └── portlet/ │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ └── content/ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── 05-admin-portlet/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ └── portlet/ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ └── content/ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── 06-messages/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ └── portlet/ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ └── content/ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── 07-permissions/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ ├── GuestbookConstants.java │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ └── GuestbookPortletResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ ├── portlet.properties │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ │ ├── portlet/ │ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ ├── GuestbookEntryPermission.java │ │ │ │ │ │ │ ├── GuestbookModelPermission.java │ │ │ │ │ │ │ └── GuestbookPermission.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ ├── content/ │ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ │ └── portlet.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── 08-search/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ ├── GuestbookConstants.java │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ └── GuestbookPortletResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ ├── search/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexer.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexerImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryKeywordQueryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelDocumentContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelSummaryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntrySearchRegistrar.java │ │ │ │ │ │ │ │ │ ├── GuestbookKeywordQueryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelDocumentContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelSummaryContributor.java │ │ │ │ │ │ │ │ │ └── GuestbookSearchRegistrar.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ ├── portlet.properties │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ │ ├── portlet/ │ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ ├── GuestbookEntryPermission.java │ │ │ │ │ │ │ ├── GuestbookModelPermission.java │ │ │ │ │ │ │ └── GuestbookPermission.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ ├── view.jsp │ │ │ │ │ │ │ │ └── view_search.jsp │ │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ ├── content/ │ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ │ └── portlet.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── 09-assets/ │ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ │ ├── .blade.properties │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── configs/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── dev/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── docker/ │ │ │ │ │ │ │ └── .touch │ │ │ │ │ │ ├── local/ │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ ├── prod/ │ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ │ └── uat/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── gradlew │ │ │ │ │ ├── gradlew.bat │ │ │ │ │ ├── modules/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ │ ├── GuestbookConstants.java │ │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ │ └── src/ │ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ │ └── GuestbookPortletResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ │ ├── search/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexer.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexerImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryKeywordQueryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelDocumentContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelSummaryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntrySearchRegistrar.java │ │ │ │ │ │ │ │ │ ├── GuestbookKeywordQueryContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelDocumentContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ │ ├── GuestbookModelSummaryContributor.java │ │ │ │ │ │ │ │ │ └── GuestbookSearchRegistrar.java │ │ │ │ │ │ │ │ └── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ │ ├── portlet.properties │ │ │ │ │ │ │ └── service.properties │ │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ │ ├── portlet/ │ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ │ └── web/ │ │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ │ ├── asset/ │ │ │ │ │ │ │ │ ├── GuestbookAssetRenderer.java │ │ │ │ │ │ │ │ ├── GuestbookAssetRendererFactory.java │ │ │ │ │ │ │ │ ├── GuestbookEntryAssetRenderer.java │ │ │ │ │ │ │ │ └── GuestbookEntryAssetRendererFactory.java │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ ├── GuestbookEntryPermission.java │ │ │ │ │ │ │ ├── GuestbookModelPermission.java │ │ │ │ │ │ │ └── GuestbookPermission.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ │ ├── asset/ │ │ │ │ │ │ │ │ ├── entry/ │ │ │ │ │ │ │ │ │ └── full_content.jsp │ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ │ └── full_content.jsp │ │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ │ ├── view.jsp │ │ │ │ │ │ │ │ ├── view_entry.jsp │ │ │ │ │ │ │ │ └── view_search.jsp │ │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ │ └── init.jsp │ │ │ │ │ │ ├── content/ │ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ │ └── portlet.properties │ │ │ │ │ └── settings.gradle │ │ │ │ └── 10-workflow/ │ │ │ │ └── com-liferay-docs-guestbook/ │ │ │ │ ├── .blade.properties │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── configs/ │ │ │ │ │ ├── common/ │ │ │ │ │ │ └── .touch │ │ │ │ │ ├── dev/ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── docker/ │ │ │ │ │ │ └── .touch │ │ │ │ │ ├── local/ │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ ├── prod/ │ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ │ └── portal-ext.properties │ │ │ │ │ └── uat/ │ │ │ │ │ ├── osgi/ │ │ │ │ │ │ └── configs/ │ │ │ │ │ │ └── com.liferay.portal.search.elasticsearch.configuration.ElasticsearchConfiguration.config │ │ │ │ │ └── portal-ext.properties │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ ├── gradlew │ │ │ │ ├── gradlew.bat │ │ │ │ ├── modules/ │ │ │ │ │ └── guestbook/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── guestbook-api/ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── constants/ │ │ │ │ │ │ │ ├── GuestbookConstants.java │ │ │ │ │ │ │ └── GuestbookPortletKeys.java │ │ │ │ │ │ ├── exception/ │ │ │ │ │ │ │ ├── EntryEmailException.java │ │ │ │ │ │ │ ├── EntryMessageException.java │ │ │ │ │ │ │ ├── EntryNameException.java │ │ │ │ │ │ │ ├── GuestbookEntryEmailException.java │ │ │ │ │ │ │ ├── GuestbookEntryMessageException.java │ │ │ │ │ │ │ ├── GuestbookEntryNameException.java │ │ │ │ │ │ │ ├── GuestbookNameException.java │ │ │ │ │ │ │ ├── NoSuchGuestbookEntryException.java │ │ │ │ │ │ │ └── NoSuchGuestbookException.java │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ ├── Guestbook.java │ │ │ │ │ │ │ ├── GuestbookEntry.java │ │ │ │ │ │ │ ├── GuestbookEntryModel.java │ │ │ │ │ │ │ ├── GuestbookEntrySoap.java │ │ │ │ │ │ │ ├── GuestbookEntryWrapper.java │ │ │ │ │ │ │ ├── GuestbookModel.java │ │ │ │ │ │ │ ├── GuestbookSoap.java │ │ │ │ │ │ │ └── GuestbookWrapper.java │ │ │ │ │ │ └── service/ │ │ │ │ │ │ ├── GuestbookEntryLocalService.java │ │ │ │ │ │ ├── GuestbookEntryLocalServiceUtil.java │ │ │ │ │ │ ├── GuestbookEntryLocalServiceWrapper.java │ │ │ │ │ │ ├── GuestbookEntryService.java │ │ │ │ │ │ ├── GuestbookEntryServiceUtil.java │ │ │ │ │ │ ├── GuestbookEntryServiceWrapper.java │ │ │ │ │ │ ├── GuestbookLocalService.java │ │ │ │ │ │ ├── GuestbookLocalServiceUtil.java │ │ │ │ │ │ ├── GuestbookLocalServiceWrapper.java │ │ │ │ │ │ ├── GuestbookService.java │ │ │ │ │ │ ├── GuestbookServiceUtil.java │ │ │ │ │ │ ├── GuestbookServiceWrapper.java │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ ├── GuestbookEntryPersistence.java │ │ │ │ │ │ ├── GuestbookEntryUtil.java │ │ │ │ │ │ ├── GuestbookPersistence.java │ │ │ │ │ │ └── GuestbookUtil.java │ │ │ │ │ ├── guestbook-service/ │ │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── service.xml │ │ │ │ │ │ └── src/ │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ │ └── security/ │ │ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ │ │ ├── GuestbookEntryModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ ├── GuestbookModelResourcePermissionRegistrar.java │ │ │ │ │ │ │ │ └── GuestbookPortletResourcePermissionRegistrar.java │ │ │ │ │ │ │ ├── model/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookBaseImpl.java │ │ │ │ │ │ │ │ ├── GuestbookCacheModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntryBaseImpl.java │ │ │ │ │ │ │ │ ├── GuestbookEntryCacheModel.java │ │ │ │ │ │ │ │ ├── GuestbookEntryImpl.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModelImpl.java │ │ │ │ │ │ │ │ ├── GuestbookImpl.java │ │ │ │ │ │ │ │ └── GuestbookModelImpl.java │ │ │ │ │ │ │ ├── search/ │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexer.java │ │ │ │ │ │ │ │ ├── GuestbookEntryBatchReindexerImpl.java │ │ │ │ │ │ │ │ ├── GuestbookEntryKeywordQueryContributor.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModelDocumentContributor.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ ├── GuestbookEntryModelSummaryContributor.java │ │ │ │ │ │ │ │ ├── GuestbookEntrySearchRegistrar.java │ │ │ │ │ │ │ │ ├── GuestbookKeywordQueryContributor.java │ │ │ │ │ │ │ │ ├── GuestbookModelDocumentContributor.java │ │ │ │ │ │ │ │ ├── GuestbookModelIndexerWriterContributor.java │ │ │ │ │ │ │ │ ├── GuestbookModelSummaryContributor.java │ │ │ │ │ │ │ │ └── GuestbookSearchRegistrar.java │ │ │ │ │ │ │ ├── service/ │ │ │ │ │ │ │ │ ├── base/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceBaseImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceBaseImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceBaseImpl.java │ │ │ │ │ │ │ │ ├── http/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceHttp.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceSoap.java │ │ │ │ │ │ │ │ │ ├── GuestbookServiceHttp.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceSoap.java │ │ │ │ │ │ │ │ ├── impl/ │ │ │ │ │ │ │ │ │ ├── GuestbookEntryLocalServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookEntryServiceImpl.java │ │ │ │ │ │ │ │ │ ├── GuestbookLocalServiceImpl.java │ │ │ │ │ │ │ │ │ └── GuestbookServiceImpl.java │ │ │ │ │ │ │ │ └── persistence/ │ │ │ │ │ │ │ │ └── impl/ │ │ │ │ │ │ │ │ ├── GuestbookEntryPersistenceImpl.java │ │ │ │ │ │ │ │ ├── GuestbookPersistenceImpl.java │ │ │ │ │ │ │ │ └── constants/ │ │ │ │ │ │ │ │ └── GBPersistenceConstants.java │ │ │ │ │ │ │ └── workflow/ │ │ │ │ │ │ │ ├── GuestbookEntryWorkflowHandler.java │ │ │ │ │ │ │ └── GuestbookWorkflowHandler.java │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ ├── module-hbm.xml │ │ │ │ │ │ │ ├── portlet-model-hints.xml │ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ │ └── sql/ │ │ │ │ │ │ │ ├── indexes.sql │ │ │ │ │ │ │ ├── sequences.sql │ │ │ │ │ │ │ └── tables.sql │ │ │ │ │ │ ├── portlet.properties │ │ │ │ │ │ └── service.properties │ │ │ │ │ └── guestbook-web/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── bnd.bnd │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ └── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── liferay/ │ │ │ │ │ │ └── docs/ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ ├── application/ │ │ │ │ │ │ │ └── list/ │ │ │ │ │ │ │ └── GuestbookAdminPanelApp.java │ │ │ │ │ │ ├── portlet/ │ │ │ │ │ │ │ ├── GuestbookAdminPortlet.java │ │ │ │ │ │ │ └── GuestbookPortlet.java │ │ │ │ │ │ └── web/ │ │ │ │ │ │ └── internal/ │ │ │ │ │ │ ├── asset/ │ │ │ │ │ │ │ ├── GuestbookAssetRenderer.java │ │ │ │ │ │ │ ├── GuestbookAssetRendererFactory.java │ │ │ │ │ │ │ ├── GuestbookEntryAssetRenderer.java │ │ │ │ │ │ │ └── GuestbookEntryAssetRendererFactory.java │ │ │ │ │ │ └── security/ │ │ │ │ │ │ └── permission/ │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ ├── GuestbookEntryPermission.java │ │ │ │ │ │ ├── GuestbookModelPermission.java │ │ │ │ │ │ └── GuestbookPermission.java │ │ │ │ │ └── resources/ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ ├── resource-actions/ │ │ │ │ │ │ │ └── default.xml │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ ├── asset/ │ │ │ │ │ │ │ ├── entry/ │ │ │ │ │ │ │ │ └── full_content.jsp │ │ │ │ │ │ │ └── guestbook/ │ │ │ │ │ │ │ └── full_content.jsp │ │ │ │ │ │ ├── css/ │ │ │ │ │ │ │ └── main.scss │ │ │ │ │ │ ├── guestbook/ │ │ │ │ │ │ │ ├── edit_entry.jsp │ │ │ │ │ │ │ ├── entry_actions.jsp │ │ │ │ │ │ │ ├── view.jsp │ │ │ │ │ │ │ ├── view_entry.jsp │ │ │ │ │ │ │ └── view_search.jsp │ │ │ │ │ │ ├── guestbook_admin/ │ │ │ │ │ │ │ ├── edit_guestbook.jsp │ │ │ │ │ │ │ ├── guestbook_actions.jsp │ │ │ │ │ │ │ └── view.jsp │ │ │ │ │ │ └── init.jsp │ │ │ │ │ ├── content/ │ │ │ │ │ │ └── Language.properties │ │ │ │ │ └── portlet.properties │ │ │ │ └── settings.gradle │ │ │ └── lunar-resort-theme/ │ │ │ ├── lunar-resort-build/ │ │ │ │ └── assets/ │ │ │ │ └── Lunar_Resort_Pages-20191018194115187.lar │ │ │ └── lunar-resort-theme-complete/ │ │ │ ├── gulpfile.js │ │ │ ├── liferay-theme.json │ │ │ ├── package.json │ │ │ └── src/ │ │ │ ├── WEB-INF/ │ │ │ │ ├── liferay-look-and-feel.xml │ │ │ │ └── liferay-plugin-package.properties │ │ │ ├── css/ │ │ │ │ ├── _colors.scss │ │ │ │ ├── _custom.scss │ │ │ │ ├── _imports.scss │ │ │ │ ├── clay.scss │ │ │ │ └── color_schemes/ │ │ │ │ └── _eclipse.scss │ │ │ └── templates/ │ │ │ ├── footer.ftl │ │ │ ├── init_custom.ftl │ │ │ ├── navigation.ftl │ │ │ └── portal_normal.ftl │ │ └── drawings/ │ │ ├── README.markdown │ │ ├── application-layers.odg │ │ ├── guestbook-mvc-diagram.odg │ │ ├── guestbook.xmi │ │ ├── liferay-suites.odg │ │ ├── liferay-technologies.odg │ │ ├── model-service-persistence.psd │ │ ├── multi-client-application.odg │ │ ├── product-menu-parts.kra │ │ ├── service-builder.odg │ │ └── service-registry.odg │ ├── distribute/ │ │ ├── build.xml │ │ └── publish/ │ │ ├── articles/ │ │ │ └── 01-publishing-your-app/ │ │ │ ├── 00-publishing-your-app-intro.markdown │ │ │ ├── 01-planning-your-apps-distribution/ │ │ │ │ ├── 00-planning-your-apps-distribution-intro.markdown │ │ │ │ ├── 01-selling-your-app-or-making-it-free.markdown │ │ │ │ ├── 02-publishing-as-an-individual-or-on-behalf-of-a-company.markdown │ │ │ │ ├── 03-licensing-and-pricing-your-app.markdown │ │ │ │ └── 04-app-versioning-and-target-liferay-editions-and-versions.markdown │ │ │ ├── 02-preparing-your-app/ │ │ │ │ ├── 00-preparing-your-app-intro.markdown │ │ │ │ ├── 01-marketplace-app-metadata-guidelines.markdown │ │ │ │ └── 02-packaging-your-marketplace-app.markdown │ │ │ ├── 03-submitting-your-app/ │ │ │ │ ├── 00-submitting-your-app-intro.markdown │ │ │ │ ├── 01-your-apps-initial-details.markdown │ │ │ │ ├── 02-uploading-your-app.markdown │ │ │ │ ├── 03-creating-your-licensing-and-pricing-model.markdown │ │ │ │ └── 04-submitting-your-app-for-review.markdown │ │ │ ├── 04-understanding-the-app-review-process/ │ │ │ │ └── 00-understanding-the-app-review-process-intro.markdown │ │ │ └── 05-managing-your-published-app/ │ │ │ ├── 00-managing-your-app-intro.markdown │ │ │ ├── 01-tracking-app-performance.markdown │ │ │ └── 02-making-changes-to-published-apps.markdown │ │ └── build.xml │ └── user/ │ ├── articles/ │ │ ├── 01-the-liferay-distinction/ │ │ │ ├── 01-the-liferay-distinction-intro.markdown │ │ │ └── 02-whats-new-72.markdown │ │ ├── 05-setting-up/ │ │ │ ├── 00-intro.markdown │ │ │ ├── 01-system-settings/ │ │ │ │ ├── 00-intro.markdown │ │ │ │ ├── 01-system-settings.markdown │ │ │ │ ├── 02-config-files/ │ │ │ │ │ ├── 00-intro.markdown │ │ │ │ │ ├── 01-creating-config-files.markdown │ │ │ │ │ ├── 02-factory-configurations.html │ │ │ │ │ └── 02-factory-configurations.markdown │ │ │ │ └── 03-server-administration/ │ │ │ │ ├── 00-intro.html │ │ │ │ ├── 00-intro.markdown │ │ │ │ ├── 01-server-admin-resources.markdown │ │ │ │ └── 02-server-admin-external-services.markdown │ │ │ ├── 02-instance-settings/ │ │ │ │ ├── 00-intro.markdown │ │ │ │ ├── 01-virtual-instances.markdown │ │ │ │ └── 02-configuring-virtual-instance-settings/ │ │ │ │ ├── 01-configuring-virtual-instance-settings-intro.markdown │ │ │ │ ├── 02-email-instance-settings.markdown │ │ │ │ ├── 03-instance-configuration-settings.markdown │ │ │ │ ├── 04-user-authentication-settings.markdown │ │ │ │ ├── 05-users-instance-settings.markdown │ │ │ │ └── 06-analytics-infra-localization-third-party-settings.markdown │ │ │ ├── 03-script-console/ │ │ │ │ ├── 00-intro.markdown │ │ │ │ ├── 01-invoking-liferay-services-from-scripts.markdown │ │ │ │ ├── 02-running-scripts-from-script-console.markdown │ │ │ │ ├── 03-leveraging-the-script-engine-in-workflow.markdown │ │ │ │ └── 04-script-examples.markdown │ │ │ └── 08-custom-fields.markdown │ │ ├── 10-managing-users/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-users-and-organizations/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-managing-users.markdown │ │ │ │ ├── 03-advanced-user-management.markdown │ │ │ │ ├── 04-organizations.markdown │ │ │ │ └── 05-managing-organizations.markdown │ │ │ ├── 03-roles-and-permissions/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-managing-roles.markdown │ │ │ │ └── 03-roles-and-permissions.markdown │ │ │ ├── 04-managing-user-data/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-sanitizing-user-data.markdown │ │ │ │ └── 03-exporting-user-data.markdown │ │ │ ├── 05-user-groups/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-creating-user-group.markdown │ │ │ │ ├── 03-user-groups-site-membership.markdown │ │ │ │ ├── 04-user-group-sites.markdown │ │ │ │ ├── 05-user-group-permissions.markdown │ │ │ │ └── 06-editing-user-groups.markdown │ │ │ └── 10-password-policies.markdown │ │ ├── 100-web-experience-management/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-authoring-content.markdown │ │ │ ├── 03-building-a-site/ │ │ │ │ ├── 01-building-a-site-intro.markdown │ │ │ │ ├── 02-site-management/ │ │ │ │ │ ├── 01-site-management-intro.markdown │ │ │ │ │ ├── 02-understanding-site-management.markdown │ │ │ │ │ └── 03-adding-sites.markdown │ │ │ │ ├── 03-adding-pages-to-sites/ │ │ │ │ │ ├── 01-adding-pages-to-sites-intro.markdown │ │ │ │ │ ├── 02-creating-pages/ │ │ │ │ │ │ └── 01-creating-pages-intro.markdown │ │ │ │ │ ├── 03-content-pages/ │ │ │ │ │ │ ├── 01-creating-content-pages-intro.markdown │ │ │ │ │ │ ├── 02-content-page-elements.markdown │ │ │ │ │ │ ├── 03-content-page-interface.markdown │ │ │ │ │ │ ├── 04-building-content-pages-and-templates.markdown │ │ │ │ │ │ ├── 05-content-page-propagation.markdown │ │ │ │ │ │ ├── 06-creating-page-fragments.markdown │ │ │ │ │ │ └── 07-export-import-fragments.markdown │ │ │ │ │ ├── 04-widget-pages/ │ │ │ │ │ │ ├── 01-using-widget-pages intro.markdown │ │ │ │ │ │ ├── 02-creating-widget-pages.markdown │ │ │ │ │ │ └── 03-creating-widget-pages-from-templates.markdown │ │ │ │ │ ├── 05-building-a-responsive-site/ │ │ │ │ │ │ ├── 01-building-a-responsive-site-intro.markdown │ │ │ │ │ │ ├── 02-built-in-mobile-support.markdown │ │ │ │ │ │ └── 03-mobile-device-rules/ │ │ │ │ │ │ ├── 01-mobile-device-rules-intro.markdown │ │ │ │ │ │ ├── 02-creating-mobile-device-rules.markdown │ │ │ │ │ │ └── 03-mobile-device-actions.markdown │ │ │ │ │ └── 06-using-full-page-applications/ │ │ │ │ │ └── 01-using-full-page-applications-intro.markdown │ │ │ │ ├── 04-liferay-navigation/ │ │ │ │ │ ├── 01-liferay-navigation-intro.markdown │ │ │ │ │ ├── 02-page-hierarchy.markdown │ │ │ │ │ ├── 03-creating-menus.markdown │ │ │ │ │ └── 04-displaying-navigation-menus.markdown │ │ │ │ ├── 05-building-sites-with-templates/ │ │ │ │ │ ├── 01-building-sites-from-templates-intro.markdown │ │ │ │ │ ├── 02-creating-a-site-template.markdown │ │ │ │ │ ├── 03-managing-site-templates.markdown │ │ │ │ │ ├── 04-propagating-changes-from-site-templates.markdown │ │ │ │ │ └── 05-sharing-site-templates.markdown │ │ │ │ ├── 06-configuring-sites/ │ │ │ │ │ ├── 01-configuring-sites-and-pages-intro.markdown │ │ │ │ │ ├── 02-configuring-site-settings.markdown │ │ │ │ │ ├── 03-site-social-settings.markdown │ │ │ │ │ ├── 04-advanced-site-settings.markdown │ │ │ │ │ ├── 05-customizing-personal-sites.markdown │ │ │ │ │ ├── 06-importing-exporting-pages-and-content.markdown │ │ │ │ │ └── 07-styling-widgets-and-assets/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-styling-widgets-with-widget-templates.markdown │ │ │ │ │ ├── 03-widget-template-example.markdown │ │ │ │ │ └── 04-setting-a-default-widget-template.markdown │ │ │ │ ├── 07-configuring-pages/ │ │ │ │ │ ├── 01-configuring-pages-intro.markdown │ │ │ │ │ ├── 02-configuring-page-sets/ │ │ │ │ │ │ ├── 01-configuring-page-sets-intro.markdown │ │ │ │ │ │ ├── 02-configuring-page-sets.markdown │ │ │ │ │ │ └── 03-advanced-page-set-options.markdown │ │ │ │ │ └── 03-configuring-individual-pages/ │ │ │ │ │ ├── 01-configuring-individual-pages-intro.markdown │ │ │ │ │ ├── 02-customizing-pages.markdown │ │ │ │ │ ├── 03-personalizing-pages.markdown │ │ │ │ │ └── 04-page-permissions.markdown │ │ │ │ ├── 08-configuring-widgets/ │ │ │ │ │ ├── 01-configuring-widgets-intro.markdown │ │ │ │ │ ├── 02-look-and-feel-configuration.markdown │ │ │ │ │ ├── 03-importing-exporting-app-data.markdown │ │ │ │ │ ├── 04-communication-between-widgets.markdown │ │ │ │ │ ├── 05-sharing-applications-with-other-sites.markdown │ │ │ │ │ ├── 06-widget-permissions.markdown │ │ │ │ │ ├── 07-widget-scope.markdown │ │ │ │ │ └── 08-configuration-templates.markdown │ │ │ │ └── 09-managing-site-members/ │ │ │ │ ├── 01-managing-membership-intro.markdown │ │ │ │ ├── 02-adding-members-to-sites.markdown │ │ │ │ └── 03-creating-teams-to-empower-site-members.markdown │ │ │ ├── 04-managing-content/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-web-content/ │ │ │ │ │ ├── 01-managing-web-content-intro.markdown │ │ │ │ │ ├── 02-publishing-basic-web-content/ │ │ │ │ │ │ ├── 01-publishing-basic-web-content-intro.markdown │ │ │ │ │ │ ├── 02-creating-web-content.markdown │ │ │ │ │ │ ├── 03-using-the-web-content-editor.markdown │ │ │ │ │ │ ├── 04-displaying-web-content.markdown │ │ │ │ │ │ └── 05-other-content-options.markdown │ │ │ │ │ ├── 03-designing-uniform-content/ │ │ │ │ │ │ ├── 01-designing-structured-content-intro.markdown │ │ │ │ │ │ ├── 02-creating-structured-content/ │ │ │ │ │ │ │ ├── 01-creating-structured-web-content-intro.markdown │ │ │ │ │ │ │ ├── 02-editing-structures.markdown │ │ │ │ │ │ │ ├── 03-configuring-structure-fields.markdown │ │ │ │ │ │ │ └── 04-structure-settings.markdown │ │ │ │ │ │ └── 03-designing-web-content-with-templates/ │ │ │ │ │ │ ├── 01-designing-web-content-with-templates-intro.markdown │ │ │ │ │ │ ├── 02-adding-templates.markdown │ │ │ │ │ │ ├── 03-embedding-widgets-in-templates.markdown │ │ │ │ │ │ ├── 04-template-taglibs.markdown │ │ │ │ │ │ └── 05-template-permissions.markdown │ │ │ │ │ └── 04-display-pages-for-web-content/ │ │ │ │ │ ├── 01-display-pages-for-web-content-intro.markdown │ │ │ │ │ ├── 02-creating-display-pages.markdown │ │ │ │ │ └── 03-display-page-template-example.markdown │ │ │ │ └── 03-content-sets/ │ │ │ │ ├── 01-content-sets-intro.markdown │ │ │ │ ├── 02-creating-content-sets.markdown │ │ │ │ ├── 03-displaying-content-sets.markdown │ │ │ │ └── 04-converting-asset-publisher.markdown │ │ │ ├── 05-organizing-content-with-tags-and-categories/ │ │ │ │ ├── 01-organizing-content-with-tags-and-categories-intro.markdown │ │ │ │ ├── 02-tagging-content.markdown │ │ │ │ ├── 03-defining-categories-for-content.markdown │ │ │ │ ├── 04-targeted-vocabularies.markdown │ │ │ │ └── 05-geolocating-assets.markdown │ │ │ ├── 06-publishing-content-dynamically/ │ │ │ │ ├── 01-publishing-content-dynamically-intro.markdown │ │ │ │ ├── 02-defining-content-relationships.markdown │ │ │ │ ├── 03-using-the-asset-publisher/ │ │ │ │ │ ├── 01-publishing-assets-intro.markdown │ │ │ │ │ ├── 02-querying-for-content.markdown │ │ │ │ │ ├── 03-selecting-assets.markdown │ │ │ │ │ ├── 04-configuring-display-settings.markdown │ │ │ │ │ └── 05-configuring-asset-publisher-subscriptions.markdown │ │ │ │ └── 04-publishing-rss-feeds/ │ │ │ │ ├── 01-publishing-rss-feeds-intro.markdown │ │ │ │ ├── 02-configuring-rss-feeds.markdown │ │ │ │ └── 03-rss-publisher-widget.markdown │ │ │ └── 07-restoring-deleted-assets-with-recycle-bin/ │ │ │ ├── 01-restoring-deleted-assets-intro.markdown │ │ │ ├── 02-configuring-recycle-bin.markdown │ │ │ ├── 03-using-recycle-bin.markdown │ │ │ └── 04-recycle-bin-intelligence-and-support.markdown │ │ ├── 110-collaboration/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-managing-documents-and-media/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-publishing-files/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-adding-files.markdown │ │ │ │ │ ├── 03-creating-folders.markdown │ │ │ │ │ ├── 04-dm-management-bar.markdown │ │ │ │ │ ├── 05-file-previews.markdown │ │ │ │ │ ├── 06-editing-images.markdown │ │ │ │ │ ├── 07-publishing-files.markdown │ │ │ │ │ ├── 08-checkout-edit-files.markdown │ │ │ │ │ ├── 09-sharing-files.markdown │ │ │ │ │ ├── 10-configuring-sharing.markdown │ │ │ │ │ ├── 11-desktop-access.markdown │ │ │ │ │ └── 12-linking-to-google-drive.markdown │ │ │ │ ├── 03-metadata-sets/ │ │ │ │ │ └── 01-metadata-sets-intro.markdown │ │ │ │ ├── 04-document-types/ │ │ │ │ │ └── 01-document-types-intro.markdown │ │ │ │ ├── 05-online-editing-google-docs/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-configuring-gdocs-integration.markdown │ │ │ │ │ └── 03-creating-editing-files-gdocs.markdown │ │ │ │ ├── 06-online-editing-office-365/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-configuring-office365-integration.markdown │ │ │ │ │ └── 03-creating-editing-files-office365.markdown │ │ │ │ └── 07-store-types/ │ │ │ │ └── 01-store-types-intro.markdown │ │ │ ├── 03-sync/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-administering-sync/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-prerequisites.markdown │ │ │ │ │ ├── 03-configuring-sync.markdown │ │ │ │ │ ├── 04-file-deletion.markdown │ │ │ │ │ └── 05-sync-security.markdown │ │ │ │ ├── 03-sync-desktop/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-install-configure-sync-desktop.markdown │ │ │ │ │ ├── 03-using-sync-desktop.markdown │ │ │ │ │ └── 04-sync-folder.markdown │ │ │ │ └── 04-sync-mobile/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-connecting-sync-mobile.markdown │ │ │ │ └── 03-sync-mobile-files.markdown │ │ │ ├── 04-adaptive-media/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-installing-adaptive-media.markdown │ │ │ │ ├── 03-adding-image-resolutions.markdown │ │ │ │ ├── 04-managing-image-resolutions.markdown │ │ │ │ ├── 05-creating-content-adapted-images.markdown │ │ │ │ ├── 06-migrating-dm-thumbnails.markdown │ │ │ │ └── 07-advanced-configuration.markdown │ │ │ ├── 05-collaborating/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-blogs/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-adding-blog-entries.markdown │ │ │ │ │ ├── 03-blog-entry-editor.markdown │ │ │ │ │ ├── 04-managing-blog-entries.markdown │ │ │ │ │ ├── 05-configuring-blogs-app.markdown │ │ │ │ │ ├── 06-displaying-blogs.markdown │ │ │ │ │ ├── 07-aggregating-blogs.markdown │ │ │ │ │ └── 08-recent-bloggers.markdown │ │ │ │ ├── 03-message-boards/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-message-boards.markdown │ │ │ │ │ ├── 03-configuring-message-boards.markdown │ │ │ │ │ ├── 04-message-board-permissions.markdown │ │ │ │ │ ├── 05-message-board-categories.markdown │ │ │ │ │ ├── 06-mb-subscriptions-lists.markdown │ │ │ │ │ ├── 07-using-message-boards.markdown │ │ │ │ │ └── 08-managing-message-boards.markdown │ │ │ │ ├── 04-mentions/ │ │ │ │ │ └── 01-mentions-intro.markdown │ │ │ │ ├── 05-wiki/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-getting-started-wiki.markdown │ │ │ │ │ ├── 03-add-edit-wiki.markdown │ │ │ │ │ ├── 04-using-wiki-site-pages.markdown │ │ │ │ │ ├── 05-wiki-page-details.markdown │ │ │ │ │ └── 06-other-wiki-widgets.markdown │ │ │ │ ├── 06-alerts-announcements/ │ │ │ │ │ └── 01-alerts-announcements-intro.markdown │ │ │ │ ├── 07-notifications-requests/ │ │ │ │ │ └── 01-notifications-requests-intro.markdown │ │ │ │ ├── 08-knowledge-base/ │ │ │ │ │ ├── 01-intro.markdown │ │ │ │ │ ├── 02-creating-kb-articles.markdown │ │ │ │ │ ├── 03-managing-kb.markdown │ │ │ │ │ ├── 04-kb-templates.markdown │ │ │ │ │ ├── 05-kb-feedback.markdown │ │ │ │ │ ├── 06-kb-display.markdown │ │ │ │ │ ├── 07-other-kb-widgets.markdown │ │ │ │ │ ├── 08-kb-import.markdown │ │ │ │ │ ├── 09-kb-zip-requirements.markdown │ │ │ │ │ ├── 10-kb-importer-faq.markdown │ │ │ │ │ └── 11-kb-system-settings.markdown │ │ │ │ └── 11-invites/ │ │ │ │ └── 01-invites-intro.markdown │ │ │ ├── 06-assets/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-configuring-auto-tagging.markdown │ │ │ │ ├── 03-auto-tagging-images.markdown │ │ │ │ └── 04-text-auto-tagging.markdown │ │ │ └── 07-social-network/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-activities-widget.markdown │ │ │ ├── 03-connecting-users.markdown │ │ │ ├── 04-exporting-widgets.markdown │ │ │ ├── 05-facebook.markdown │ │ │ └── 06-social-bookmarks.markdown │ │ ├── 120-search/ │ │ │ ├── 01-search-intro.markdown │ │ │ ├── 02-what-new-search/ │ │ │ │ └── 01-whats-new-search-intro.markdown │ │ │ ├── 03-configuring-a-search-page/ │ │ │ │ └── 01-configuring-a-search-page-intro.markdown │ │ │ ├── 04-searching-for-assets/ │ │ │ │ └── 01-searching-for-assets-intro.markdown │ │ │ ├── 05-facets/ │ │ │ │ ├── 01-facets-intro.markdown │ │ │ │ ├── 02-site-facet.markdown │ │ │ │ ├── 03-type-facet.markdown │ │ │ │ ├── 04-tag-category-facets.markdown │ │ │ │ ├── 05-folder-facet.markdown │ │ │ │ ├── 06-user-facet.markdown │ │ │ │ ├── 07-modified-facet.markdown │ │ │ │ └── 08-custom-facet.markdown │ │ │ ├── 06-search-results/ │ │ │ │ ├── 01-search-results-intro.markdown │ │ │ │ ├── 02-display-settings.markdown │ │ │ │ ├── 03-custom-filters.markdown │ │ │ │ ├── 04-sort-widget.markdown │ │ │ │ └── 05-results-behavior.markdown │ │ │ ├── 07-search-insights/ │ │ │ │ └── 01-search-insights-intro.markdown │ │ │ ├── 08-multilanguage-search/ │ │ │ │ └── 01-multilanguage-search-intro.markdown │ │ │ ├── 09-search-configuration/ │ │ │ │ └── 01-search-configuration-intro.markdown │ │ │ └── 10-low-level-search-options.markdown │ │ ├── 130-forms/ │ │ │ ├── 01-forms-intro.markdown │ │ │ ├── 02-creating-forms.markdown │ │ │ ├── 03-managing-form-entries.markdown │ │ │ ├── 04-form-fields.markdown │ │ │ ├── 04-form-rules/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-show-hide-rules.markdown │ │ │ │ ├── 03-require-rules.markdown │ │ │ │ ├── 04-enable-rules.markdown │ │ │ │ ├── 05-jump-to-page-rules.markdown │ │ │ │ ├── 06-autofill-rules.markdown │ │ │ │ └── 07-calculate-rules.markdown │ │ │ ├── 05-element-sets.markdown │ │ │ ├── 06-data-providers.markdown │ │ │ ├── 07-autosave.markdown │ │ │ ├── 08-translating-forms.markdown │ │ │ ├── 09-autocomplete.markdown │ │ │ ├── 10-success-pages.markdown │ │ │ ├── 10-workflow-and-forms.markdown │ │ │ ├── 11-duplicating-forms-and-fields.markdown │ │ │ ├── 12-form-pages.markdown │ │ │ ├── 13-placeholders-predefined-values.markdown │ │ │ ├── 14-validation.markdown │ │ │ ├── 15-captcha.markdown │ │ │ ├── 16-form-notifications.markdown │ │ │ ├── 17-redirecting-users.markdown │ │ │ ├── 18-form-permissions.markdown │ │ │ ├── 19-styling-forms.markdown │ │ │ └── 99-ddl/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-creating-data-definitions.markdown │ │ │ ├── 03-managing-data-definitions.markdown │ │ │ ├── 04-creating-data-lists.markdown │ │ │ ├── 05-using-templates.markdown │ │ │ ├── 06-creating-form-templates.markdown │ │ │ └── 07-creating-display-templates.markdown │ │ ├── 140-workflow/ │ │ │ ├── 01-workflow-intro.markdown │ │ │ ├── 02-activating-workflow.markdown │ │ │ ├── 03-managing-workflows.markdown │ │ │ └── 04-reviewing-assets.markdown │ │ ├── 160-segmentation-and-personalization/ │ │ │ ├── 01-segmentation-intro.markdown │ │ │ ├── 02-segment-editor.markdown │ │ │ ├── 03-creating-segments.markdown │ │ │ ├── 04-creating-segments-with-custom-fields.markdown │ │ │ ├── 05-personalization-experience-managment.markdown │ │ │ ├── 06-content-page-personalization.markdown │ │ │ └── 07-content-set-personalization.markdown │ │ ├── 170-content-publication-management/ │ │ │ ├── 01-intro.markdown │ │ │ └── 02-staging/ │ │ │ ├── 01-intro.markdown │ │ │ ├── 02-enabling-staging/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-enabling-local-live-staging.markdown │ │ │ │ ├── 03-enabling-remote-live-staging.markdown │ │ │ │ ├── 04-configuring-servers-for-remote-live-staging.markdown │ │ │ │ └── 05-enabling-page-versioning-and-staged-content.markdown │ │ │ ├── 03-publishing-staged-content-efficiently/ │ │ │ │ └── 01-publishing-staged-content-efficiently-intro.markdown │ │ │ ├── 04-using-the-staging-environment/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-staging-content.markdown │ │ │ │ ├── 03-advanced-publication-with-staging.markdown │ │ │ │ ├── 04-managing-content-types-in-staging.markdown │ │ │ │ └── 05-staging-processes-and-templates.markdown │ │ │ ├── 05-disabling-staging/ │ │ │ │ └── 01-disabling-staging-intro.markdown │ │ │ ├── 06-publishing-single-assets-from-staged-site/ │ │ │ │ └── 01-publishing-single-assets-from-staged-site-intro.markdown │ │ │ ├── 07-organizing-pages-for-staging/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-using-multi-and-single-page-variations.markdown │ │ │ │ ├── 03-creating-multi-and-single-page-variations.markdown │ │ │ │ └── 04-merging-site-pages-variations.markdown │ │ │ ├── 08-managing-staging-permissions/ │ │ │ │ └── 01-intro.markdown │ │ │ └── 09-scheduling-web-content-publication/ │ │ │ └── 00-scheduling-web-content-publication-intro.markdown │ │ ├── 200-managing-apps/ │ │ │ ├── 01-managing-apps-intro.markdown │ │ │ ├── 02-managing-and-configuring-apps.markdown │ │ │ ├── 03-using-the-liferay-marketplace.markdown │ │ │ ├── 04-installing-apps-manually.markdown │ │ │ ├── 05-app-types.markdown │ │ │ └── 06-blacklisting-osgi-bundles-and-components.markdown │ │ ├── 220-polls.markdown │ │ └── 230-calendar/ │ │ ├── 01-calendar-intro.markdown │ │ ├── 02-using-the-calendar-widget.markdown │ │ └── 03-calendar-resources-and-porting.markdown │ ├── articles-dxp/ │ │ ├── 10-managing-users/ │ │ │ └── 11-auditing-users/ │ │ │ ├── 01-auditing-users-intro.markdown │ │ │ ├── 02-viewing-user-activity.markdown │ │ │ └── 03-configuring-user-audits.markdown │ │ ├── 120-search/ │ │ │ ├── 12-synonyms.markdown │ │ │ └── 13-result-rankings.markdown │ │ ├── 140-workflow/ │ │ │ ├── 05-workflow-metrics-sla.markdown │ │ │ ├── 06-workflow-metrics-reports.markdown │ │ │ ├── 07-workflow-designer/ │ │ │ │ ├── 01-intro.markdown │ │ │ │ ├── 02-managing-workflow-with-designer.markdown │ │ │ │ ├── 03-workflow-designer-nodes.markdown │ │ │ │ ├── 04-workflow-designer-workflow-processing.markdown │ │ │ │ └── 05-workflow-designer-tasks.markdown │ │ │ └── 08-using-kaleo-forms.markdown │ │ └── 160-segmentation-and-personalization/ │ │ ├── 05-analaytics-cloud-segmentation.markdown │ │ ├── 08-recommending-content-based-on-view-history.markdown │ │ └── 09-ab-testing/ │ │ ├── 01-ab-testing-intro.markdown │ │ ├── 02-enabling-ab-testing.markdown │ │ ├── 03-creating-ab-tests.markdown │ │ ├── 04-running-ab-tests.markdown │ │ ├── 05-monitoring-ab-test-results.markdown │ │ └── 06-publishing-ab-test-variants.markdown │ ├── build.xml │ └── drawings/ │ └── guestbook-messages.psd ├── guidelines/ │ ├── 01-creating-docs-for-liferay.markdown │ ├── 02-standards-and-customizations.markdown │ ├── 03-writers-guidelines.markdown │ └── 04-faq.markdown ├── ja/ │ └── build.xml ├── lib/ │ ├── com.liferay.knowledge.base.markdown.converter.cli-1.0.0.jar │ ├── com.liferay.portal.kernel-2.2.0.jar │ ├── commons-io-2.0.1.jar │ ├── commons-lang3-3.4.jar │ ├── freemarker.jar │ ├── htmlparser.jar │ ├── liferay-doc-utils.jar │ ├── org.eclipse.jgit-3.4.1.jar │ └── properties-converter.jar └── release-site.properties ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Set default behaviour, in case users don't have core.autocrlf set. * text=auto # Explicitly declare text files we want to always be normalized and converted # to native line endings on checkout. *.c text *.h text *.java text *.xml text *.gradle text *.fodp text diff=xml *.properties text *.css text *.js text *.sql text *.txt text *.xsl text *.tex text *.markdown text *.md text # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary *.gif binary ================================================ FILE: .gitignore ================================================ *~ *.bak *.class *.iml *.swp *-tmp.html .project .classpath .settings git-modified-list.txt nbbuild.xml properties/ .METADATA .DS_Store .directory .gradle .nb-gradle .vscode node_modules/ /*.ipr /*.iws /*.patch build.*.properties build/ classes/ dist/ **nbproject** .ivy/ .sass-cache/ .DS_Store/ *# /develop/tutorials/code/osgi/modules/form-nav-extension-portlet/.nb-gradle/ /develop/tutorials/code/osgi/modules/com.liferay.docs.greetingimpl/.nb-gradle/ /develop/tutorials/code/osgi/modules/com.liferay.docs.greetingapi/.nb-gradle/ /develop/tutorials/code/osgi/modules/com.liferay.docs.greetingcommand/.nb-gradle/ **/temp/** /develop/tutorials/code/com-liferay-docs-guestbook/.nb-gradle/ ================================================ FILE: README.markdown ================================================ # LIFERAY-DOCS Liferay Docs Icon [![Slack Status](https://community-chat.liferay.com/badge.svg)](https://liferay-community.slack.com/messages/C5XE4BH3Q/) Welcome to Liferay's official documentation project, the home of [Liferay Developer Network](https://dev.liferay.com) articles. All articles are written in Markdown, making them easy to write and read. Approved articles are uploaded to the Liferay Developer Network (LDN) and converted automatically to HTML. In this project, you can contribute new articles, improve existing articles, or fix documentation bugs. To produce documentation that is comprehensive and stylistically consistent, the liferay-docs project provides writing guidelines, standards & customizations, and a tutorial template. You'll learn how quickly to submit a new article and its images next. ## Quick Steps to Submit a New Article You can follow these steps to create a new article and contribute it from GitHub. 1. Sign in to GitHub. If you don't already have a GitHub account, you must [join](https://github.com/join) GitHub in order to contribute to liferay-docs. 2. Click on a `new-articles` link folder below. These match the LDN sections where you can add a articles: - `develop/new-articles` - `discover/new-articles` - `distribute/new-articles` 3. Select the liferay-docs branch that matches the Liferay Portal version you're writing about:
 Branch   Portal Version 
 master   7.2
 7.1.x   7.1
 7.0.x   7.0
 6.2.x  6.2
4. Click the **plus** sign after **new-articles/:** ![new articles folder](guidelines/images/new-articles-folder.png) 5. Write your article and click the ![Propose new file](guidelines/images/propose-new-file.png) button, to prepare a [pull request](https://help.github.com/articles/using-pull-requests/). 6. Send the pull request to the default user `liferay`. 7. Drag your article's images into the pull request's comments to associate the images with your article. Your new article is submitted! Liferay's Knowledge Management team will review your contribution. Approved changes are merged into the liferay-docs repo and published to the [Liferay Developer Network](https://dev.liferay.com). If you want to clone our repository and do serious documentation work on your own machine, [click here](guidelines/01-creating-docs-for-liferay.markdown) to go to our guidelines section. Thanks for helping us out with Liferay documentation! ## Redirect instructions ``` cd liferay-docs ./bin/update_liferay_learn_links.sh path/to/properties/file ``` For example, ```bash cd liferay-learn cat ./site/docs/redirects_keep.properties ./site/docs/redirects_new.properties > ~/redirects.properties cd ../liferay-docs ./bin/update_liferay_learn_links.sh ~/redirects.properties ``` ================================================ FILE: bin/convert-markdown.sh ================================================ #!/bin/sh scriptPath=$(readlink -f "$0") classpathDir=$(dirname $(dirname "$scriptPath"))/lib htmlFile=$1 htmlFile=$(echo "${htmlFile}" | sed 's/[^.]*$//') htmlFile=${htmlFile}html #echo "java -cp "${classpathDir}/*" com.liferay.documentation.util.MarkdownToHtml $1 ${htmlFile}" #java -cp "${classpathDir}/*" com.liferay.documentation.util.MarkdownToHtml $1 ${htmlFile} # echo "java -cp "${classpathDir}/*" com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI $1 > ${htmlFile}" java -cp "${classpathDir}/*" com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI /dev/stdin exit 0 ================================================ FILE: bin/convert.bat ================================================ @echo off set CLASSPATH=%~dp0\..\lib if "%1"=="" goto usage if "%2"=="" goto convert java -cp .;%CLASSPATH%\* com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI %1 > %2 goto end :usage echo Usage: convert.bat [Markdown file to convert] or convert.bat [Markdown file to convert] [HTML file to be written] echo. echo Run the convert.bat script from any directory. echo. echo The first argument is the path to the Markdown file to convert to HTML. echo. echo The second argument is optional. It specifies the path to the HTML file to be created. If this argument is omitted, the HTML file to be created will be created in the same directory as the Markdown file and will have the same filename as the Markdown file except that the .markdown file extension will be replaced by the .html file extension. echo. goto end :convert set htmlFile=%1 set htmlFile=%htmlFile:markdown=html% java -cp .;%CLASSPATH%\* com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI %1 > %htmlFile% :end ================================================ FILE: bin/convert.sh ================================================ #!/bin/sh scriptPath=$(readlink -f "$0") classpathDir=$(dirname $(dirname "$scriptPath"))/lib if [ $# -lt 1 ]; then echo echo Usage: ./convert.sh \[Markdown file to convert\] or ./convert.sh \[Markdown file to convert\] \[HTML file to be written\] echo echo Run the convert.sh script from the liferay-docs directory or any subdirectory. echo echo The first argument is the path to the Markdown file to convert to HTML. echo echo The second argument is optional. It specifies the path to the HTML file to be created. If this argument is omitted, the HTML file to be created is created in the same directory as the Markdown file and has the same filename as the Markdown file except that the .markdown file extension is replaced by the .html file extension. echo exit 1 elif [ $# -lt 2 ]; then htmlFile=$1 htmlFile=$(echo "${htmlFile}" | sed 's/[^.]*$//') htmlFile=${htmlFile}html #echo "java -cp "${classpathDir}/*" com.liferay.documentation.util.MarkdownToHtml $1 ${htmlFile}" #java -cp "${classpathDir}/*" com.liferay.documentation.util.MarkdownToHtml $1 ${htmlFile} echo "java -cp "${classpathDir}/*" com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI $1 > ${htmlFile}" java -cp "${classpathDir}/*" com.liferay.knowledge.base.markdown.converter.cli.MarkdownConverterCLI $1 > ${htmlFile} exit 0 else java -cp "${classpathDir}/*" com.liferay.documentation.util.MarkdownToHtml $1 > $2 exit 0 fi ================================================ FILE: bin/migrate.py ================================================ import os import re import shutil import sys def append_to_line(line, new_line): line = line.replace("\r","") line = line.replace("\n", "") line = line + " " + new_line.lstrip() return line def end_sidebar(sidebar_line, newFile): if sidebar_line not in "": sidebar_line = sidebar_line.replace("| ", "") newFile.write(sidebar_line) newFile.write("```\n") sidebar_line = "" return sidebar_line def write_and_reset_string(line, newFile): if line not in "": newFile.write(line) line = "" return line if __name__ == "__main__": if (len(sys.argv) < 2): print("Usage:\n\tpython migrate.py article [dest_folder]\n\nDescription:\n\tMigrates the article to an .md file and converts the content to liferay-learn Markdown/MyST syntax. The converted article destination folder [dest_folder] is the current folder by default but is typically set to a liferay-learn folder.\n\n\tIMPORTANT: Run this script in the liferay-docs article's folder so that the script can use the image file paths found in the article to copy the image files to the [dest_folder]/[article_name]/images/ folder.") sys.exit() article = sys.argv[1] dest_folder = "." if (len(sys.argv)) > 2: dest_folder = sys.argv[2] if not (os.path.isdir(dest_folder)): os.mkdir(dest_folder) article_name = article.split('.markdown')[0] new_article_path = dest_folder + "/" + article_name + ".md" # Copy referenced image files to [article destination folder]/[article_name]/images file = open(article) content = file.read() file.close() png_split = content.split('.png)') images = [] png_split_len = len(png_split) - 1 ii = 0; while (ii < png_split_len): paran_split = png_split[ii].split('(') if (len(paran_split) > 1): image = paran_split[len(paran_split) - 1] + '.png' images.append(image) ii = ii + 1 article_name_folder = dest_folder + "/" + article_name images_folder = article_name_folder + "/images" if (len(images) > 0): if not os.path.isdir(article_name_folder): os.mkdir(article_name_folder) if not os.path.isdir(images_folder): os.mkdir(images_folder) print("Writing images to folder: " + images_folder) for ff in images: shutil.copy(ff, images_folder) # Process the text file = open(article) lines = file.readlines() file.close() # Open the destination file for writing to newFile = open(new_article_path, 'w') print("Migrating to article: " + new_article_path) # Track the last significant line's first non-space character column index prev_index = -1 # Variables for storing text that belongs on the same line; hard returns are # removed list_item_line = "" para_line = "" sidebar_line = "" # Variables that allow you to skip more detailed condition checks done_header_id = False done_title = False done_toc = False prev_sidebar_line_empty = False # Variables for markup context in_code = False in_header_id = False images_folder_path = "](./" + article_name + "/images/" for line in lines: # Set all image file locations to the ./[article_name]/images/ folder line = re.sub("\]\((../)+images/", images_folder_path, line) # Replace legacy tokens line = re.sub("@product@", "Liferay DXP", line) line = re.sub("@commerce@", "Liferay Commerce", line) line = re.sub("@ide@", "Dev Studio DXP", line) line = re.sub("@app-ref@", "https://docs.liferay.com/dxp/apps", line) line = re.sub("@platform-ref@", "https://docs.liferay.com/dxp/portal", line) # Convenience variable for working with the current line free of leading # and trailing white space. trimmed_line = line.lstrip() if not done_header_id: # Skip over all of the legacy header-id stuff if trimmed_line.startswith("---"): if not in_header_id: in_header_id = True else: done_header_id = True in_header_id = False continue if not done_title and trimmed_line in "": # Skip empty lines until the title is written continue elif line.startswith("#"): # Handle heading newFile.write(line) if not done_title: done_title = True elif not done_toc and trimmed_line in "": # Skip empty lines until TOC is done continue elif trimmed_line.startswith("```"): # Toggle whether we're in code # Write any saved text para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Write the current line as is newFile.write(line) # Track whether code block has started or ended if (in_code): in_code = False else: in_code = True elif re.search("^\d.", trimmed_line): # Handle an ordered list item # Write any saved text para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Start all ordered list items with 1. list_item_line = re.sub("\d+.\s*", "1. ", line, 1) prev_index = re.search("\S", line).start() elif trimmed_line.startswith("-"): # Handle an unordered-list item starting with - # Write any saved text para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Start list items with '*' instead of '-', followed by a single space list_item_line = re.sub("-\s*", "* ", line, 1) prev_index = re.search("\S", line).start() elif trimmed_line.startswith("* "): # Handle an unordered-list item starting with * # Write any saved text para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Start list items with '*', followed by a single space list_item_line = re.sub("\*\s*", "* ", line, 1) prev_index = re.search("\S", line).start() elif re.search("^\|", line.strip()): if not re.search("^\|.*\|$", line.strip()): # Handle a sidebar line # Write any saved text para_line = write_and_reset_string(para_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) pipe_index = re.search("|", line).start() tmp_line = line.replace("| ", "", 1).strip() if tmp_line in "": prev_sidebar_line_empty = True sidebar_line += "\n" else: # Replace the leading pipe with a space stripped_line = line.replace("| ", "", 1) if sidebar_line in "": # Start the sidebar if "**Note:**" in stripped_line: newFile.write("```{note}\n") stripped_line = stripped_line.replace("**Note:**", "", 1) elif "**Tip:**" in stripped_line: newFile.write("```{tip}\n") stripped_line = stripped_line.replace("**Tip:**", "", 1) elif "**Warning:**" in stripped_line: newFile.write("```{warning}\n") stripped_line = stripped_line.replace("**Warning:**", "", 1) elif "**Important:**" in stripped_line: newFile.write("```{important}\n") stripped_line = stripped_line.replace("**Important:**", "", 1) elif "**Note**:" in stripped_line: newFile.write("```{note}\n") stripped_line = stripped_line.replace("**Note**:", "", 1) elif "**Tip**:" in stripped_line: newFile.write("```{tip}\n") stripped_line = stripped_line.replace("**Tip**:", "", 1) elif "**Warning**:" in stripped_line: newFile.write("```{warning}\n") stripped_line = stripped_line.replace("**Warning**:", "", 1) elif "**Important**:" in stripped_line: newFile.write("```{important}\n") stripped_line = stripped_line.replace("**Important**:", "", 1) else: newFile.write("```{note}\n") if sidebar_line in "": sidebar_line = stripped_line.lstrip() else: if prev_sidebar_line_empty: sidebar_line = sidebar_line + stripped_line prev_sidebar_line_empty = False else: sidebar_line = sidebar_line.rstrip() + " " + stripped_line.lstrip() prev_index = re.search("\S", line).start() else: # Handle table line para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Write the table line newFile.write(line) elif in_code: # Write code line newFile.write(line) elif trimmed_line.startswith("[TOC"): # Don't write anything for the legacy TOC line done_toc = True continue elif para_line != "": if re.search("^[\d\-]", trimmed_line): # Write the existing paragraph and start a list item list_item_line = line para_line = write_and_reset_string(para_line, newFile) prev_index = re.search("\S", line).start() elif re.search("^[\:]", trimmed_line): # Write definition term newFile.write(para_line) # Start the term definition para_line = line prev_index = re.search("\S", line).start() elif re.search("^[\w\*\@\!\(\&\.\[\`\s(\w\*\@\!\(\&\.\[\`)]", trimmed_line): para_line = append_to_line(para_line, trimmed_line) else: # Write the existing paragraph and the current line para_line = write_and_reset_string(para_line, newFile) newFile.write(line) if re.search("\S", line): prev_index = re.search("\S", line).start() elif re.search("^[\w\*\@\!\(\&\.\[\`\s\:(\w\*\@\!\(\&\.\[\`)]", trimmed_line): sidebar_line = end_sidebar(sidebar_line, newFile) if re.search("\S", line): index = re.search("\S", line).start() if index > prev_index: if list_item_line != "": list_item_line = append_to_line(list_item_line, line) elif (para_line != ""): para_line = append_to_line(para_line, line) else: para_line = line prev_index = index else: para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) para_line = line prev_index = index else: # Empty line para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Write the current line newFile.write(line) else: if not done_header_id and trimmed_line in "": # Skip writing empty lines before writing the title line continue else: # Set index to first non-space character if re.search("\S", line): prev_index = re.search("\S", line).start() para_line = write_and_reset_string(para_line, newFile) sidebar_line = end_sidebar(sidebar_line, newFile) list_item_line = write_and_reset_string(list_item_line, newFile) # Write the current line newFile.write(line) # Done looping through the lines # Finish writing the current paragraph, list item, or sidebar if para_line != "": newFile.write(para_line) elif list_item_line != "": newFile.write(list_item_line) elif sidebar_line != "": newFile.write(sidebar_line) newFile.close() ================================================ FILE: bin/update-liferay-learn-links.sh ================================================ #!/bin/bash # This script was inspired by the answers at # https://stackoverflow.com/questions/30724170/how-to-read-properties-file-using-shell-script redirects_file=~/redirects_all.properties if [ "${#}" -eq 0 ] then echo "Must input a properties file" echo "Usage: ./bin/update-liferay-learn_links.sh path/to/properties/file" exit 1 fi if [ "${#}" -ge 1 ] then if [ ! -f ${1} ] then echo "File ${1} does not exist." exit 1 else redirects_file=${1} fi fi echo "Redirects file ${redirects_file}" # Read configuration into an associative array declare -A REDIRECT # IFS is the 'internal field separator'. In this case, your file uses '=' IFS="=" while read -r key value do if [ -n $value ]; then REDIRECT[$key]=$value else REDIRECT[$key]=$value fi done < ${redirects_file} unset IFS for file in `find . -type f -name "*.markdown"` do if [ -n "$(grep learn.liferay.com $(echo ${file}))" ] then echo "${file}" for key in "${!REDIRECT[@]}" do sed -i s@$key@${REDIRECT[$key]}@g "${file}" done fi done ================================================ FILE: bin/update-markdown-sidebar-syntax-win.sh ================================================ #!/bin/sh developdir="../develop" distributedir="../distribute" usedir="../use" for file in $(find $developdir -name "*.markdown") do perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)+[ ]*!\[.*\]\(.*\)[\s]*/\r\n\+\$\$\$\r\n\r\n/g' $file perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)/\r\n\$\$\$\r\n/g' $file done for file in $(find $distributedir -name "*.markdown") do perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)+[ ]*!\[.*\]\(.*\)[\s]*/\r\n\+\$\$\$\r\n\r\n/g' $file perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)/\r\n\$\$\$\r\n/g' $file done for file in $(find $usedir -name "*.markdown") do perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)+[ ]*!\[.*\]\(.*\)[\s]*/\r\n\+\$\$\$\r\n\r\n/g' $file perl -p0777i.bak -e 's/\r\n[ \t]*---[ ]*(\r\n)/\r\n\$\$\$\r\n/g' $file done ================================================ FILE: bin/update-markdown-sidebar-syntax.sh ================================================ #!/bin/sh developdir="../develop" distributedir="../distribute" usedir="../use" for file in $(find $developdir -name "*.markdown") do perl -p0777i -e 's/\n[ \t]*---[ ]*\n+[ ]*!\[.*\]\(.*\)[\s]*/\n\+\$\$\$\n\n/g' $file perl -p0777i -e 's/\n[ \t]*---[ ]*\n/\n\$\$\$\n/g' $file done for file in $(find $distributedir -name "*.markdown") do perl -p0777i -e 's/\n[ \t]*---[ ]*\n+[ ]*!\[.*\]\(.*\)[\s]*/\n\+\$\$\$\n\n/g' $file perl -p0777i -e 's/\n[ \t]*---[ ]*\n/\n\$\$\$\n/g' $file done for file in $(find $usedir -name "*.markdown") do perl -p0777i -e 's/\n[ \t]*---[ ]*\n+[ ]*!\[.*\]\(.*\)[\s]*/\n\+\$\$\$\n\n/g' $file perl -p0777i -e 's/\n[ \t]*---[ ]*\n/\n\$\$\$\n/g' $file done ================================================ FILE: book/developer/appdev.aux ================================================ \relax \providecommand{\transparent@use}[1]{} \providecommand\hyper@newdestlabel[2]{} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {229}Application Development}{661}{chapter.229}\protected@file@percent } \newlabel{application-development}{{229}{661}{Application Development}{chapter.229}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.1}Getting Started with Liferay Development}{662}{section.229.1}\protected@file@percent } \newlabel{getting-started-with-liferay-development}{{229.1}{662}{Getting Started with Liferay Development}{section.229.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.2}Create Your Object Model and Database in One Shot}{662}{section.229.2}\protected@file@percent } \newlabel{create-your-object-model-and-database-in-one-shot}{{229.2}{662}{Create Your Object Model and Database in One Shot}{section.229.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.3}Create a REST Interface}{663}{section.229.3}\protected@file@percent } \newlabel{create-a-rest-interface}{{229.3}{663}{Create a REST Interface}{section.229.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.4}Create a Web Client}{663}{section.229.4}\protected@file@percent } \newlabel{create-a-web-client}{{229.4}{663}{Create a Web Client}{section.229.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.5}Use Liferay's Frameworks}{663}{section.229.5}\protected@file@percent } \newlabel{use-liferays-frameworks}{{229.5}{663}{Use Liferay's Frameworks}{section.229.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {229.6}Next Steps}{663}{section.229.6}\protected@file@percent } \newlabel{next-steps}{{229.6}{663}{Next Steps}{section.229.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {230}Developing Web Front-Ends}{665}{chapter.230}\protected@file@percent } \newlabel{developing-web-front-ends}{{230}{665}{Developing Web Front-Ends}{chapter.230}{}} \@writefile{toc}{\contentsline {section}{\numberline {230.1}Using Popular Frameworks}{665}{section.230.1}\protected@file@percent } \newlabel{using-popular-frameworks}{{230.1}{665}{Using Popular Frameworks}{section.230.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {230.2}Getting Started}{666}{section.230.2}\protected@file@percent } \newlabel{getting-started}{{230.2}{666}{Getting Started}{section.230.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {231}Developing an Angular Application}{669}{chapter.231}\protected@file@percent } \newlabel{developing-an-angular-application}{{231}{669}{Developing an Angular Application}{chapter.231}{}} \@writefile{lof}{\contentsline {figure}{\numberline {231.1}{\ignorespaces Apps like this Guestbook app are easy to migrate to Liferay DXP.}}{669}{figure.231.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {231.1}Related Topics}{673}{section.231.1}\protected@file@percent } \newlabel{related-topics}{{231.1}{673}{Related Topics}{section.231.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {232}Developing a React Application}{675}{chapter.232}\protected@file@percent } \newlabel{developing-a-react-application}{{232}{675}{Developing a React Application}{chapter.232}{}} \@writefile{lof}{\contentsline {figure}{\numberline {232.1}{\ignorespaces Apps like this Guestbook app are easy to migrate to Liferay DXP.}}{675}{figure.232.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {232.1}Related Topics}{678}{section.232.1}\protected@file@percent } \newlabel{related-topics-1}{{232.1}{678}{Related Topics}{section.232.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {233}Developing a Vue Application}{679}{chapter.233}\protected@file@percent } \newlabel{developing-a-vue-application}{{233}{679}{Developing a Vue Application}{chapter.233}{}} \@writefile{lof}{\contentsline {figure}{\numberline {233.1}{\ignorespaces Vue Apps like this Guestbook App are easy to deploy, and they look great in Liferay DXP.}}{680}{figure.233.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {233.1}Related Topics}{683}{section.233.1}\protected@file@percent } \newlabel{related-topics-2}{{233.1}{683}{Related Topics}{section.233.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {234}Liferay MVC Portlet}{685}{chapter.234}\protected@file@percent } \newlabel{liferay-mvc-portlet}{{234}{685}{Liferay MVC Portlet}{chapter.234}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.1}MVC Layers and Modularity}{685}{section.234.1}\protected@file@percent } \newlabel{mvc-layers-and-modularity}{{234.1}{685}{MVC Layers and Modularity}{section.234.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.2}Liferay MVC Command Classes}{686}{section.234.2}\protected@file@percent } \newlabel{liferay-mvc-command-classes}{{234.2}{686}{Liferay MVC Command Classes}{section.234.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.3}Liferay MVC Portlet Component}{686}{section.234.3}\protected@file@percent } \newlabel{liferay-mvc-portlet-component}{{234.3}{686}{Liferay MVC Portlet Component}{section.234.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.4}A Simpler MVC Portlet}{687}{section.234.4}\protected@file@percent } \newlabel{a-simpler-mvc-portlet}{{234.4}{687}{A Simpler MVC Portlet}{section.234.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {235}Creating an MVC Portlet}{689}{chapter.235}\protected@file@percent } \newlabel{creating-an-mvc-portlet}{{235}{689}{Creating an MVC Portlet}{chapter.235}{}} \@writefile{lof}{\contentsline {figure}{\numberline {235.1}{\ignorespaces The example portlet shows a message defined by the language property \texttt {yourmvc.caption=Hello\ from\ YourMVC!} in the Language.properties file.}}{691}{figure.235.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {235.1}Related Topics}{691}{section.235.1}\protected@file@percent } \newlabel{related-topics-3}{{235.1}{691}{Related Topics}{section.235.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {236}Writing MVC Portlet Controller Code}{693}{chapter.236}\protected@file@percent } \newlabel{writing-mvc-portlet-controller-code}{{236}{693}{Writing MVC Portlet Controller Code}{chapter.236}{}} \@writefile{toc}{\contentsline {section}{\numberline {236.1}Action Methods}{693}{section.236.1}\protected@file@percent } \newlabel{action-methods}{{236.1}{693}{Action Methods}{section.236.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {236.2}Render Logic}{694}{section.236.2}\protected@file@percent } \newlabel{render-logic}{{236.2}{694}{Render Logic}{section.236.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {236.3}Setting and Retrieving Request and Response Parameters and Attributes}{695}{section.236.3}\protected@file@percent } \newlabel{setting-and-retrieving-request-and-response-parameters-and-attributes}{{236.3}{695}{Setting and Retrieving Request and Response Parameters and Attributes}{section.236.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {236.4}Related Topics}{696}{section.236.4}\protected@file@percent } \newlabel{related-topics-4}{{236.4}{696}{Related Topics}{section.236.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {237}Configuring the View Layer}{697}{chapter.237}\protected@file@percent } \newlabel{configuring-the-view-layer}{{237}{697}{Configuring the View Layer}{chapter.237}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.1}Using the init.jsp}{697}{section.237.1}\protected@file@percent } \newlabel{using-the-init.jsp}{{237.1}{697}{Using the init.jsp}{section.237.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.2}Using Render URLs}{698}{section.237.2}\protected@file@percent } \newlabel{using-render-urls}{{237.2}{698}{Using Render URLs}{section.237.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.3}Using Action URLs}{699}{section.237.3}\protected@file@percent } \newlabel{using-action-urls}{{237.3}{699}{Using Action URLs}{section.237.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.4}Related Topics}{699}{section.237.4}\protected@file@percent } \newlabel{related-topics-5}{{237.4}{699}{Related Topics}{section.237.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {238}MVC Action Command}{701}{chapter.238}\protected@file@percent } \newlabel{mvc-action-command}{{238}{701}{MVC Action Command}{chapter.238}{}} \@writefile{toc}{\contentsline {section}{\numberline {238.1}Related Topics}{703}{section.238.1}\protected@file@percent } \newlabel{related-topics-6}{{238.1}{703}{Related Topics}{section.238.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {239}MVC Render Command}{705}{chapter.239}\protected@file@percent } \newlabel{mvc-render-command}{{239}{705}{MVC Render Command}{chapter.239}{}} \@writefile{toc}{\contentsline {section}{\numberline {239.1}Related Topics}{706}{section.239.1}\protected@file@percent } \newlabel{related-topics-7}{{239.1}{706}{Related Topics}{section.239.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {240}MVC Resource Command}{707}{chapter.240}\protected@file@percent } \newlabel{mvc-resource-command}{{240}{707}{MVC Resource Command}{chapter.240}{}} \@writefile{toc}{\contentsline {section}{\numberline {240.1}Related Topics}{709}{section.240.1}\protected@file@percent } \newlabel{related-topics-8}{{240.1}{709}{Related Topics}{section.240.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {241}PortletMVC4Spring}{711}{chapter.241}\protected@file@percent } \newlabel{portletmvc4spring}{{241}{711}{PortletMVC4Spring}{chapter.241}{}} \@writefile{lof}{\contentsline {figure}{\numberline {241.1}{\ignorespaces This PortletMVC4Spring portlet enables users to enter job applications. It uses the Spring features mentioned above and handles requests from multiple portlet phases.}}{712}{figure.241.1}\protected@file@percent } \gdef \LT@xii {\LT@entry {1}{234.8775pt}\LT@entry {1}{234.8775pt}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {242}Developing a Portlet Using PortletMVC4Spring}{715}{chapter.242}\protected@file@percent } \newlabel{developing-a-portlet-using-portletmvc4spring}{{242}{715}{Developing a Portlet Using PortletMVC4Spring}{chapter.242}{}} \@writefile{lof}{\contentsline {figure}{\numberline {242.1}{\ignorespaces The archetype's sample portlet prints a greeting (e.g., \emph {Hello, Joe Bloggs}) on submitting a first and last name.}}{715}{figure.242.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {242.1}Related Topics}{719}{section.242.1}\protected@file@percent } \newlabel{related-topics-9}{{242.1}{719}{Related Topics}{section.242.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {243}Migrating to PortletMVC4Spring}{721}{chapter.243}\protected@file@percent } \newlabel{migrating-to-portletmvc4spring}{{243}{721}{Migrating to PortletMVC4Spring}{chapter.243}{}} \@writefile{toc}{\contentsline {section}{\numberline {243.1}Related Topics}{722}{section.243.1}\protected@file@percent } \newlabel{related-topics-10}{{243.1}{722}{Related Topics}{section.243.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {244}JSF Portlet}{723}{chapter.244}\protected@file@percent } \newlabel{jsf-portlet}{{244}{723}{JSF Portlet}{chapter.244}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {245}Developing a JSF Portlet Application}{725}{chapter.245}\protected@file@percent } \newlabel{developing-a-jsf-portlet-application}{{245}{725}{Developing a JSF Portlet Application}{chapter.245}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {246}Bean Portlet}{729}{chapter.246}\protected@file@percent } \newlabel{bean-portlet}{{246}{729}{Bean Portlet}{chapter.246}{}} \@writefile{toc}{\contentsline {section}{\numberline {246.1}Portlet Configuration Annotations}{729}{section.246.1}\protected@file@percent } \newlabel{portlet-configuration-annotations}{{246.1}{729}{Portlet Configuration Annotations}{section.246.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {246.2}Dependency Injection}{730}{section.246.2}\protected@file@percent } \newlabel{dependency-injection}{{246.2}{730}{Dependency Injection}{section.246.2}{}} \gdef \LT@xiii {\LT@entry {1}{287.70345pt}\LT@entry {1}{182.05156pt}} \@writefile{toc}{\contentsline {section}{\numberline {246.3}Portlet Phase Methods}{731}{section.246.3}\protected@file@percent } \newlabel{portlet-phase-methods}{{246.3}{731}{Portlet Phase Methods}{section.246.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {247}Creating a Bean Portlet}{733}{chapter.247}\protected@file@percent } \newlabel{creating-a-bean-portlet}{{247}{733}{Creating a Bean Portlet}{chapter.247}{}} \@writefile{lof}{\contentsline {figure}{\numberline {247.1}{\ignorespaces The Foo portlet prints the message returned from \texttt {doView} method and shows the included JSP's contents.}}{735}{figure.247.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {247.1}Related Topics}{735}{section.247.1}\protected@file@percent } \newlabel{related-topics-11}{{247.1}{735}{Related Topics}{section.247.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {248}Service Builder}{737}{chapter.248}\protected@file@percent } \newlabel{service-builder}{{248}{737}{Service Builder}{chapter.248}{}} \@writefile{toc}{\contentsline {section}{\numberline {248.1}Customization via Implementation Classes}{738}{section.248.1}\protected@file@percent } \newlabel{customization-via-implementation-classes}{{248.1}{738}{Customization via Implementation Classes}{section.248.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {248.2}Hibernate Configurations}{738}{section.248.2}\protected@file@percent } \newlabel{hibernate-configurations}{{248.2}{738}{Hibernate Configurations}{section.248.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {248.3}Caching}{738}{section.248.3}\protected@file@percent } \newlabel{caching}{{248.3}{738}{Caching}{section.248.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {248.4}Dynamic Query and Custom SQL Query}{738}{section.248.4}\protected@file@percent } \newlabel{dynamic-query-and-custom-sql-query}{{248.4}{738}{Dynamic Query and Custom SQL Query}{section.248.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {249}Creating a Service Builder Project}{741}{chapter.249}\protected@file@percent } \newlabel{creating-a-service-builder-project}{{249}{741}{Creating a Service Builder Project}{chapter.249}{}} \@writefile{toc}{\contentsline {section}{\numberline {249.1}Related Topics}{742}{section.249.1}\protected@file@percent } \newlabel{related-topics-12}{{249.1}{742}{Related Topics}{section.249.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {250}Creating the service.xml File}{743}{chapter.250}\protected@file@percent } \newlabel{creating-the-service.xml-file}{{250}{743}{Creating the service.xml File}{chapter.250}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {251}Defining Global Service Information}{745}{chapter.251}\protected@file@percent } \newlabel{defining-global-service-information}{{251}{745}{Defining Global Service Information}{chapter.251}{}} \@writefile{toc}{\contentsline {section}{\numberline {251.1}Dependency Injector}{745}{section.251.1}\protected@file@percent } \newlabel{dependency-injector}{{251.1}{745}{Dependency Injector}{section.251.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {251.2}Package Path}{746}{section.251.2}\protected@file@percent } \newlabel{package-path}{{251.2}{746}{Package Path}{section.251.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {251.3}Multiversion concurrency control (MVCC)}{746}{section.251.3}\protected@file@percent } \newlabel{multiversion-concurrency-control-mvcc}{{251.3}{746}{Multiversion concurrency control (MVCC)}{section.251.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {251.4}Namespace Options}{747}{section.251.4}\protected@file@percent } \newlabel{namespace-options}{{251.4}{747}{Namespace Options}{section.251.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {251.5}Author}{747}{section.251.5}\protected@file@percent } \newlabel{author}{{251.5}{747}{Author}{section.251.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {252}Defining Service Entities}{749}{chapter.252}\protected@file@percent } \newlabel{defining-service-entities}{{252}{749}{Defining Service Entities}{chapter.252}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {253}Defining the Columns (Attributes) for Each Service Entity}{751}{chapter.253}\protected@file@percent } \newlabel{defining-the-columns-attributes-for-each-service-entity}{{253}{751}{Defining the Columns (Attributes) for Each Service Entity}{chapter.253}{}} \@writefile{toc}{\contentsline {section}{\numberline {253.1}Create Entity Columns}{751}{section.253.1}\protected@file@percent } \newlabel{create-entity-columns}{{253.1}{751}{Create Entity Columns}{section.253.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {253.2}Support Multi-tenancy}{752}{section.253.2}\protected@file@percent } \newlabel{support-multi-tenancy}{{253.2}{752}{Support Multi-tenancy}{section.253.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {253.3}Workflow Fields}{752}{section.253.3}\protected@file@percent } \newlabel{workflow-fields}{{253.3}{752}{Workflow Fields}{section.253.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {253.4}Audit Entities}{752}{section.253.4}\protected@file@percent } \newlabel{audit-entities}{{253.4}{752}{Audit Entities}{section.253.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {254}Defining Relationships Between Service Entities}{753}{chapter.254}\protected@file@percent } \newlabel{defining-relationships-between-service-entities}{{254}{753}{Defining Relationships Between Service Entities}{chapter.254}{}} \@writefile{lof}{\contentsline {figure}{\numberline {254.1}{\ignorespaces Relating entities is a snap in Liferay Dev Studio DXP's \emph {Diagram} mode for \texttt {service.xml}.}}{754}{figure.254.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {255}Defining Ordering of Service Entity Instances}{755}{chapter.255}\protected@file@percent } \newlabel{defining-ordering-of-service-entity-instances}{{255}{755}{Defining Ordering of Service Entity Instances}{chapter.255}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {256}Defining Service Entity Finder Methods}{757}{chapter.256}\protected@file@percent } \newlabel{defining-service-entity-finder-methods}{{256}{757}{Defining Service Entity Finder Methods}{chapter.256}{}} \@writefile{toc}{\contentsline {section}{\numberline {256.1}Creating Finders}{757}{section.256.1}\protected@file@percent } \newlabel{creating-finders}{{256.1}{757}{Creating Finders}{section.256.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {257}Running Service Builder}{759}{chapter.257}\protected@file@percent } \newlabel{running-service-builder}{{257}{759}{Running Service Builder}{chapter.257}{}} \@writefile{toc}{\contentsline {section}{\numberline {257.1}Gradle}{759}{section.257.1}\protected@file@percent } \newlabel{gradle}{{257.1}{759}{Gradle}{section.257.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {257.2}Maven}{759}{section.257.2}\protected@file@percent } \newlabel{maven}{{257.2}{759}{Maven}{section.257.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {258}Understanding the Code Generated by Service Builder}{761}{chapter.258}\protected@file@percent } \newlabel{understanding-the-code-generated-by-service-builder}{{258}{761}{Understanding the Code Generated by Service Builder}{chapter.258}{}} \@writefile{lof}{\contentsline {figure}{\numberline {258.1}{\ignorespaces Service Builder generates these persistence classes and interfaces for an example entity called \emph {Event}. You shouldn't (and you won't need to) customize any of these classes or interfaces.}}{762}{figure.258.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {258.2}{\ignorespaces Service Builder generates these service classes and interfaces. Only the {[}ENTITY\_NAME{]}LocalServiceImpl (e.g., EventLocalServiceImpl for the Event entity) allows custom methods to be added to the service layer.}}{763}{figure.258.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {258.3}{\ignorespaces Service Builder generates these model classes and interfaces. Only \texttt {{[}ENTITY\_NAME{]}Impl} (e.g., \texttt {EventImpl} for the Event entity) allows custom methods to be added to the service layer.}}{765}{figure.258.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {259}Iterative Development}{767}{chapter.259}\protected@file@percent } \newlabel{iterative-development}{{259}{767}{Iterative Development}{chapter.259}{}} \@writefile{toc}{\contentsline {section}{\numberline {259.1}Related Topics}{767}{section.259.1}\protected@file@percent } \newlabel{related-topics-13}{{259.1}{767}{Related Topics}{section.259.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {260}Customizing Model Entities With Model Hints}{769}{chapter.260}\protected@file@percent } \newlabel{customizing-model-entities-with-model-hints}{{260}{769}{Customizing Model Entities With Model Hints}{chapter.260}{}} \@writefile{toc}{\contentsline {section}{\numberline {260.1}Model Hint Types}{771}{section.260.1}\protected@file@percent } \newlabel{model-hint-types}{{260.1}{771}{Model Hint Types}{section.260.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {260.2}Default Hints}{771}{section.260.2}\protected@file@percent } \newlabel{default-hints}{{260.2}{771}{Default Hints}{section.260.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {260.3}Hint Collections}{772}{section.260.3}\protected@file@percent } \newlabel{hint-collections}{{260.3}{772}{Hint Collections}{section.260.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {261}Configuring service.properties}{775}{chapter.261}\protected@file@percent } \newlabel{configuring-service.properties}{{261}{775}{Configuring service.properties}{chapter.261}{}} \@writefile{toc}{\contentsline {section}{\numberline {261.1}Related Topics}{775}{section.261.1}\protected@file@percent } \newlabel{related-topics-14}{{261.1}{775}{Related Topics}{section.261.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {262}Connecting Service Builder to an External Database}{777}{chapter.262}\protected@file@percent } \newlabel{connecting-service-builder-to-an-external-database}{{262}{777}{Connecting Service Builder to an External Database}{chapter.262}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {263}Connecting the Data Source Using a DataSourceProvider}{779}{chapter.263}\protected@file@percent } \newlabel{connecting-the-data-source-using-a-datasourceprovider}{{263}{779}{Connecting the Data Source Using a DataSourceProvider}{chapter.263}{}} \@writefile{toc}{\contentsline {section}{\numberline {263.1}Related Topics}{781}{section.263.1}\protected@file@percent } \newlabel{related-topics-15}{{263.1}{781}{Related Topics}{section.263.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {264}Connecting the Data Source Using Spring Beans}{783}{chapter.264}\protected@file@percent } \newlabel{connecting-the-data-source-using-spring-beans}{{264}{783}{Connecting the Data Source Using Spring Beans}{chapter.264}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.1}Specify Your Database and a Data Source Name in Your \texttt {service.xml}}{784}{section.264.1}\protected@file@percent } \newlabel{specify-your-database-and-a-data-source-name-in-your-service.xml}{{264.1}{784}{\texorpdfstring {Specify Your Database and a Data Source Name in Your \texttt {service.xml}}{Specify Your Database and a Data Source Name in Your service.xml}}{section.264.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.2}Create the Database Manually}{784}{section.264.2}\protected@file@percent } \newlabel{create-the-database-manually}{{264.2}{784}{Create the Database Manually}{section.264.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.3}Define the Data Source}{784}{section.264.3}\protected@file@percent } \newlabel{define-the-data-source}{{264.3}{784}{Define the Data Source}{section.264.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.4}Connect Your Service Builder Module to the Data Source Via a Spring Bean}{784}{section.264.4}\protected@file@percent } \newlabel{connect-your-service-builder-module-to-the-data-source-via-a-spring-bean}{{264.4}{784}{Connect Your Service Builder Module to the Data Source Via a Spring Bean}{section.264.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.5}Run Service Builder}{786}{section.264.5}\protected@file@percent } \newlabel{run-service-builder}{{264.5}{786}{Run Service Builder}{section.264.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.6}Related Topics}{786}{section.264.6}\protected@file@percent } \newlabel{related-topics-16}{{264.6}{786}{Related Topics}{section.264.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {265}Migrating a Service Builder Module from Spring DI to OSGi DS}{787}{chapter.265}\protected@file@percent } \newlabel{migrating-a-service-builder-module-from-spring-di-to-osgi-ds}{{265}{787}{Migrating a Service Builder Module from Spring DI to OSGi DS}{chapter.265}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.1}Step 1: Prepare Your Project for DS}{787}{section.265.1}\protected@file@percent } \newlabel{step-1-prepare-your-project-for-ds}{{265.1}{787}{Step 1: Prepare Your Project for DS}{section.265.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.2}Step 2: Update Your Spring Bean Classes}{788}{section.265.2}\protected@file@percent } \newlabel{step-2-update-your-spring-bean-classes}{{265.2}{788}{Step 2: Update Your Spring Bean Classes}{section.265.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.3}Step 3: Resolve Any Circular Dependencies}{789}{section.265.3}\protected@file@percent } \newlabel{step-3-resolve-any-circular-dependencies}{{265.3}{789}{Step 3: Resolve Any Circular Dependencies}{section.265.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.4}Related Topics}{790}{section.265.4}\protected@file@percent } \newlabel{related-topics-17}{{265.4}{790}{Related Topics}{section.265.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {266}Business Logic with Service Builder}{791}{chapter.266}\protected@file@percent } \newlabel{business-logic-with-service-builder}{{266}{791}{Business Logic with Service Builder}{chapter.266}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {267}Implementing an Add Method}{793}{chapter.267}\protected@file@percent } \newlabel{implementing-an-add-method}{{267}{793}{Implementing an Add Method}{chapter.267}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.1}Step 1: Declare an add method with parameters for creating the entity}{794}{section.267.1}\protected@file@percent } \newlabel{step-1-declare-an-add-method-with-parameters-for-creating-the-entity}{{267.1}{794}{Step 1: Declare an add method with parameters for creating the entity}{section.267.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.2}Step 2: Validate the parameters}{795}{section.267.2}\protected@file@percent } \newlabel{step-2-validate-the-parameters}{{267.2}{795}{Step 2: Validate the parameters}{section.267.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.3}Step 3: Generate a primary key}{795}{section.267.3}\protected@file@percent } \newlabel{step-3-generate-a-primary-key}{{267.3}{795}{Step 3: Generate a primary key}{section.267.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.4}Step 4: Create an entity instance}{795}{section.267.4}\protected@file@percent } \newlabel{step-4-create-an-entity-instance}{{267.4}{795}{Step 4: Create an entity instance}{section.267.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.5}Step 5: Populate the entity attributes}{796}{section.267.5}\protected@file@percent } \newlabel{step-5-populate-the-entity-attributes}{{267.5}{796}{Step 5: Populate the entity attributes}{section.267.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.6}Step 6: Persist the entity}{796}{section.267.6}\protected@file@percent } \newlabel{step-6-persist-the-entity}{{267.6}{796}{Step 6: Persist the entity}{section.267.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.7}Step 7: Return the entity}{796}{section.267.7}\protected@file@percent } \newlabel{step-7-return-the-entity}{{267.7}{796}{Step 7: Return the entity}{section.267.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {268}Implementing Update and Delete Methods}{797}{chapter.268}\protected@file@percent } \newlabel{implementing-update-and-delete-methods}{{268}{797}{Implementing Update and Delete Methods}{chapter.268}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.1}Implementing an Update Method}{797}{section.268.1}\protected@file@percent } \newlabel{implementing-an-update-method}{{268.1}{797}{Implementing an Update Method}{section.268.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.2}Step 1: Declare an Update Method with Parameters for Updating the Entity}{798}{section.268.2}\protected@file@percent } \newlabel{step-1-declare-an-update-method-with-parameters-for-updating-the-entity}{{268.2}{798}{Step 1: Declare an Update Method with Parameters for Updating the Entity}{section.268.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.3}Step 2: Validate the Parameters}{798}{section.268.3}\protected@file@percent } \newlabel{step-2-validate-the-parameters-1}{{268.3}{798}{Step 2: Validate the Parameters}{section.268.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.4}Step 3: Retrieve the Entity Instance}{799}{section.268.4}\protected@file@percent } \newlabel{step-3-retrieve-the-entity-instance}{{268.4}{799}{Step 3: Retrieve the Entity Instance}{section.268.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.5}Step 4: Update the Entity Attributes}{799}{section.268.5}\protected@file@percent } \newlabel{step-4-update-the-entity-attributes}{{268.5}{799}{Step 4: Update the Entity Attributes}{section.268.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.6}Step 5: Persist and Return the Updated Entity Instance}{799}{section.268.6}\protected@file@percent } \newlabel{step-5-persist-and-return-the-updated-entity-instance}{{268.6}{799}{Step 5: Persist and Return the Updated Entity Instance}{section.268.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.7}Step 6: Run Service Builder}{799}{section.268.7}\protected@file@percent } \newlabel{step-6-run-service-builder}{{268.7}{799}{Step 6: Run Service Builder}{section.268.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.8}Implementing a Delete Method}{799}{section.268.8}\protected@file@percent } \newlabel{implementing-a-delete-method}{{268.8}{799}{Implementing a Delete Method}{section.268.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.9}Related Topics}{800}{section.268.9}\protected@file@percent } \newlabel{related-topics-18}{{268.9}{800}{Related Topics}{section.268.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {269}Implementing Methods to Get and Count Entities}{801}{chapter.269}\protected@file@percent } \newlabel{implementing-methods-to-get-and-count-entities}{{269}{801}{Implementing Methods to Get and Count Entities}{chapter.269}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.1}Getter Methods}{801}{section.269.1}\protected@file@percent } \newlabel{getter-methods}{{269.1}{801}{Getter Methods}{section.269.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.2}Counter Methods}{802}{section.269.2}\protected@file@percent } \newlabel{counter-methods}{{269.2}{802}{Counter Methods}{section.269.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.3}Service Method Prefixes and Transactional Aspects}{803}{section.269.3}\protected@file@percent } \newlabel{service-method-prefixes-and-transactional-aspects}{{269.3}{803}{Service Method Prefixes and Transactional Aspects}{section.269.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.4}Related Topics}{804}{section.269.4}\protected@file@percent } \newlabel{related-topics-19}{{269.4}{804}{Related Topics}{section.269.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {270}Implementing Any Other Business Logic}{805}{chapter.270}\protected@file@percent } \newlabel{implementing-any-other-business-logic}{{270}{805}{Implementing Any Other Business Logic}{chapter.270}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {271}Integrating with Liferay's Frameworks}{807}{chapter.271}\protected@file@percent } \newlabel{integrating-with-liferays-frameworks}{{271}{807}{Integrating with Liferay's Frameworks}{chapter.271}{}} \@writefile{toc}{\contentsline {section}{\numberline {271.1}Related Topics}{808}{section.271.1}\protected@file@percent } \newlabel{related-topics-20}{{271.1}{808}{Related Topics}{section.271.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {272}Invoking Local Services}{809}{chapter.272}\protected@file@percent } \newlabel{invoking-local-services}{{272}{809}{Invoking Local Services}{chapter.272}{}} \@writefile{toc}{\contentsline {section}{\numberline {272.1}Step 1: Reference the Local Service Component}{809}{section.272.1}\protected@file@percent } \newlabel{step-1-reference-the-local-service-component}{{272.1}{809}{Step 1: Reference the Local Service Component}{section.272.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {272.2}Step 2: Call the Component's Methods}{810}{section.272.2}\protected@file@percent } \newlabel{step-2-call-the-components-methods}{{272.2}{810}{Step 2: Call the Component's Methods}{section.272.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {272.3}Related Topics}{811}{section.272.3}\protected@file@percent } \newlabel{related-topics-21}{{272.3}{811}{Related Topics}{section.272.3}{}} \gdef \LT@xiv {\LT@entry {1}{194.50543pt}\LT@entry {1}{275.24957pt}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {273}Invoking Services from Spring Service Builder Code}{813}{chapter.273}\protected@file@percent } \newlabel{invoking-services-from-spring-service-builder-code}{{273}{813}{Invoking Services from Spring Service Builder Code}{chapter.273}{}} \@writefile{toc}{\contentsline {section}{\numberline {273.1}Referencing a Spring Bean that is in the Application Context}{814}{section.273.1}\protected@file@percent } \newlabel{referencing-a-spring-bean-that-is-in-the-application-context}{{273.1}{814}{Referencing a Spring Bean that is in the Application Context}{section.273.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {273.2}Referencing OSGi Services}{814}{section.273.2}\protected@file@percent } \newlabel{referencing-osgi-services}{{273.2}{814}{Referencing OSGi Services}{section.273.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {273.3}Related Topics}{815}{section.273.3}\protected@file@percent } \newlabel{related-topics-22}{{273.3}{815}{Related Topics}{section.273.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {274}Advanced Queries}{817}{chapter.274}\protected@file@percent } \newlabel{advanced-queries}{{274}{817}{Advanced Queries}{chapter.274}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {275}Custom SQL}{819}{chapter.275}\protected@file@percent } \newlabel{custom-sql}{{275}{819}{Custom SQL}{chapter.275}{}} \@writefile{toc}{\contentsline {section}{\numberline {275.1}Specify Your Custom SQL}{819}{section.275.1}\protected@file@percent } \newlabel{specify-your-custom-sql}{{275.1}{819}{Specify Your Custom SQL}{section.275.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {275.2}Related Topics}{820}{section.275.2}\protected@file@percent } \newlabel{related-topics-23}{{275.2}{820}{Related Topics}{section.275.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {276}Defining a Custom Finder Method}{821}{chapter.276}\protected@file@percent } \newlabel{defining-a-custom-finder-method}{{276}{821}{Defining a Custom Finder Method}{chapter.276}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {277}Dynamic Query}{823}{chapter.277}\protected@file@percent } \newlabel{dynamic-query}{{277}{823}{Dynamic Query}{chapter.277}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.1}Example Finder Method: findByGuestbookNameEntryName}{823}{section.277.1}\protected@file@percent } \newlabel{example-finder-method-findbyguestbooknameentryname}{{277.1}{823}{Example Finder Method: findByGuestbookNameEntryName}{section.277.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.2}Using a Hibernate Session}{825}{section.277.2}\protected@file@percent } \newlabel{using-a-hibernate-session}{{277.2}{825}{Using a Hibernate Session}{section.277.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.3}Creating Dynamic Queries}{826}{section.277.3}\protected@file@percent } \newlabel{creating-dynamic-queries}{{277.3}{826}{Creating Dynamic Queries}{section.277.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.4}Restriction Criteria}{826}{section.277.4}\protected@file@percent } \newlabel{restriction-criteria}{{277.4}{826}{Restriction Criteria}{section.277.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.5}Projection Criteria}{827}{section.277.5}\protected@file@percent } \newlabel{projection-criteria}{{277.5}{827}{Projection Criteria}{section.277.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.6}Order Criteria}{827}{section.277.6}\protected@file@percent } \newlabel{order-criteria}{{277.6}{827}{Order Criteria}{section.277.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {277.7}Executing the Dynamic Query}{828}{section.277.7}\protected@file@percent } \newlabel{executing-the-dynamic-query}{{277.7}{828}{Executing the Dynamic Query}{section.277.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {278}Accessing Your Custom Finder Method from the Service Layer}{829}{chapter.278}\protected@file@percent } \newlabel{accessing-your-custom-finder-method-from-the-service-layer}{{278}{829}{Accessing Your Custom Finder Method from the Service Layer}{chapter.278}{}} \@writefile{toc}{\contentsline {section}{\numberline {278.1}Related Topics}{830}{section.278.1}\protected@file@percent } \newlabel{related-topics-24}{{278.1}{830}{Related Topics}{section.278.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {279}Actionable Dynamic Queries}{831}{chapter.279}\protected@file@percent } \newlabel{actionable-dynamic-queries}{{279}{831}{Actionable Dynamic Queries}{chapter.279}{}} \@writefile{toc}{\contentsline {section}{\numberline {279.1}Related Topics}{833}{section.279.1}\protected@file@percent } \newlabel{related-topics-25}{{279.1}{833}{Related Topics}{section.279.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {280}REST Builder}{835}{chapter.280}\protected@file@percent } \newlabel{rest-builder}{{280}{835}{REST Builder}{chapter.280}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {281}Generating APIs with REST Builder}{837}{chapter.281}\protected@file@percent } \newlabel{generating-apis-with-rest-builder}{{281}{837}{Generating APIs with REST Builder}{chapter.281}{}} \@writefile{toc}{\contentsline {section}{\numberline {281.1}Related Topics}{838}{section.281.1}\protected@file@percent } \newlabel{related-topics-26}{{281.1}{838}{Related Topics}{section.281.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {282}Troubleshooting Application Development Issues}{839}{chapter.282}\protected@file@percent } \newlabel{troubleshooting-application-development-issues}{{282}{839}{Troubleshooting Application Development Issues}{chapter.282}{}} \@writefile{toc}{\contentsline {section}{\numberline {282.1}Modules}{839}{section.282.1}\protected@file@percent } \newlabel{modules}{{282.1}{839}{Modules}{section.282.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {282.2}Services and Components}{840}{section.282.2}\protected@file@percent } \newlabel{services-and-components}{{282.2}{840}{Services and Components}{section.282.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {283}Adjusting Module Logging}{841}{chapter.283}\protected@file@percent } \newlabel{adjusting-module-logging}{{283}{841}{Adjusting Module Logging}{chapter.283}{}} \@writefile{toc}{\contentsline {section}{\numberline {283.1}Related Topics}{842}{section.283.1}\protected@file@percent } \newlabel{related-topics-27}{{283.1}{842}{Related Topics}{section.283.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {284}Identifying Liferay Artifact Versions for Dependencies}{843}{chapter.284}\protected@file@percent } \newlabel{identifying-liferay-artifact-versions-for-dependencies}{{284}{843}{Identifying Liferay Artifact Versions for Dependencies}{chapter.284}{}} \@writefile{toc}{\contentsline {section}{\numberline {284.1}Related Topics}{843}{section.284.1}\protected@file@percent } \newlabel{related-topics-28}{{284.1}{843}{Related Topics}{section.284.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {285}Resolving Bundle-SymbolicName Syntax Issues}{845}{chapter.285}\protected@file@percent } \newlabel{resolving-bundle-symbolicname-syntax-issues}{{285}{845}{Resolving Bundle-SymbolicName Syntax Issues}{chapter.285}{}} \@writefile{toc}{\contentsline {section}{\numberline {285.1}Related Topics}{845}{section.285.1}\protected@file@percent } \newlabel{related-topics-29}{{285.1}{845}{Related Topics}{section.285.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {286}Calling Non-OSGi Code that Uses OSGi Services}{847}{chapter.286}\protected@file@percent } \newlabel{calling-non-osgi-code-that-uses-osgi-services}{{286}{847}{Calling Non-OSGi Code that Uses OSGi Services}{chapter.286}{}} \@writefile{toc}{\contentsline {section}{\numberline {286.1}Related Topics}{847}{section.286.1}\protected@file@percent } \newlabel{related-topics-30}{{286.1}{847}{Related Topics}{section.286.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {287}Connecting to JNDI Data Sources}{849}{chapter.287}\protected@file@percent } \newlabel{connecting-to-jndi-data-sources}{{287}{849}{Connecting to JNDI Data Sources}{chapter.287}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {288}Detecting Unresolved OSGi Components}{851}{chapter.288}\protected@file@percent } \newlabel{detecting-unresolved-osgi-components}{{288}{851}{Detecting Unresolved OSGi Components}{chapter.288}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.1}Declarative Services Components}{851}{section.288.1}\protected@file@percent } \newlabel{declarative-services-components}{{288.1}{851}{Declarative Services Components}{section.288.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.2}Declarative Services Unsatisfied Component Scanner}{852}{section.288.2}\protected@file@percent } \newlabel{declarative-services-unsatisfied-component-scanner}{{288.2}{852}{Declarative Services Unsatisfied Component Scanner}{section.288.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.3}ds:unsatisfied Command}{852}{section.288.3}\protected@file@percent } \newlabel{dsunsatisfied-command}{{288.3}{852}{ds:unsatisfied Command}{section.288.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.4}Service Builder Components}{853}{section.288.4}\protected@file@percent } \newlabel{service-builder-components}{{288.4}{853}{Service Builder Components}{section.288.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.5}Unavailable Component Scanner}{853}{section.288.5}\protected@file@percent } \newlabel{unavailable-component-scanner}{{288.5}{853}{Unavailable Component Scanner}{section.288.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.6}dm na Command}{854}{section.288.6}\protected@file@percent } \newlabel{dm-na-command}{{288.6}{854}{dm na Command}{section.288.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.7}ServiceProxyFactory}{854}{section.288.7}\protected@file@percent } \newlabel{serviceproxyfactory}{{288.7}{854}{ServiceProxyFactory}{section.288.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {288.8}Related Topics}{854}{section.288.8}\protected@file@percent } \newlabel{related-topics-31}{{288.8}{854}{Related Topics}{section.288.8}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {289}Disabling Cache for Table Mapper Tables}{855}{chapter.289}\protected@file@percent } \newlabel{disabling-cache-for-table-mapper-tables}{{289}{855}{Disabling Cache for Table Mapper Tables}{chapter.289}{}} \@writefile{toc}{\contentsline {section}{\numberline {289.1}Why would I want to disable cache on a table mapper?}{855}{section.289.1}\protected@file@percent } \newlabel{why-would-i-want-to-disable-cache-on-a-table-mapper}{{289.1}{855}{Why would I want to disable cache on a table mapper?}{section.289.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {289.2}Disabling a Table Mapper Cache}{856}{section.289.2}\protected@file@percent } \newlabel{disabling-a-table-mapper-cache}{{289.2}{856}{Disabling a Table Mapper Cache}{section.289.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {290}Implementing Logging}{857}{chapter.290}\protected@file@percent } \newlabel{implementing-logging}{{290}{857}{Implementing Logging}{chapter.290}{}} \@writefile{toc}{\contentsline {section}{\numberline {290.1}Related Topics}{858}{section.290.1}\protected@file@percent } \newlabel{related-topics-32}{{290.1}{858}{Related Topics}{section.290.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {291}Declaring Optional Import Package Requirements}{859}{chapter.291}\protected@file@percent } \newlabel{declaring-optional-import-package-requirements}{{291}{859}{Declaring Optional Import Package Requirements}{chapter.291}{}} \@writefile{toc}{\contentsline {section}{\numberline {291.1}Related Topics}{860}{section.291.1}\protected@file@percent } \newlabel{related-topics-33}{{291.1}{860}{Related Topics}{section.291.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {292}Resolving Bundle Requirements}{861}{chapter.292}\protected@file@percent } \newlabel{resolving-bundle-requirements}{{292}{861}{Resolving Bundle Requirements}{chapter.292}{}} \@writefile{toc}{\contentsline {section}{\numberline {292.1}Related Topics}{862}{section.292.1}\protected@file@percent } \newlabel{related-topics-34}{{292.1}{862}{Related Topics}{section.292.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {293}Resolving ClassNotFoundException and NoClassDefFoundError in OSGi Bundles}{863}{chapter.293}\protected@file@percent } \newlabel{resolving-classnotfoundexception-and-noclassdeffounderror-in-osgi-bundles}{{293}{863}{Resolving ClassNotFoundException and NoClassDefFoundError in OSGi Bundles}{chapter.293}{}} \@writefile{toc}{\contentsline {section}{\numberline {293.1}Case 1: The Missing Class Belongs to an OSGi Module}{863}{section.293.1}\protected@file@percent } \newlabel{case-1-the-missing-class-belongs-to-an-osgi-module}{{293.1}{863}{Case 1: The Missing Class Belongs to an OSGi Module}{section.293.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {293.2}Case 2: The Missing Class Doesn't Belong to an OSGi Module}{864}{section.293.2}\protected@file@percent } \newlabel{case-2-the-missing-class-doesnt-belong-to-an-osgi-module}{{293.2}{864}{Case 2: The Missing Class Doesn't Belong to an OSGi Module}{section.293.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {293.3}Case 3: The Missing Class Belongs to a Global Library}{864}{section.293.3}\protected@file@percent } \newlabel{case-3-the-missing-class-belongs-to-a-global-library}{{293.3}{864}{Case 3: The Missing Class Belongs to a Global Library}{section.293.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {293.4}Case 4: The Missing Class Belongs to a Java Runtime Package}{865}{section.293.4}\protected@file@percent } \newlabel{case-4-the-missing-class-belongs-to-a-java-runtime-package}{{293.4}{865}{Case 4: The Missing Class Belongs to a Java Runtime Package}{section.293.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {293.5}Related Topics}{865}{section.293.5}\protected@file@percent } \newlabel{related-topics-35}{{293.5}{865}{Related Topics}{section.293.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {294}System Check}{867}{chapter.294}\protected@file@percent } \newlabel{system-check}{{294}{867}{System Check}{chapter.294}{}} \@writefile{toc}{\contentsline {section}{\numberline {294.1}Related Topics}{867}{section.294.1}\protected@file@percent } \newlabel{related-topics-36}{{294.1}{867}{Related Topics}{section.294.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {295}Troubleshooting Front-End Development Issues}{869}{chapter.295}\protected@file@percent } \newlabel{troubleshooting-front-end-development-issues}{{295}{869}{Troubleshooting Front-End Development Issues}{chapter.295}{}} \@writefile{toc}{\contentsline {section}{\numberline {295.1}CSS}{869}{section.295.1}\protected@file@percent } \newlabel{css}{{295.1}{869}{CSS}{section.295.1}{}} \newlabel{broken-css-angular-app}{{295.1}{869}{CSS}{section*.7}{}} \newlabel{portal-css-broken-ie}{{295.1}{869}{CSS}{section*.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {295.2}Modules}{869}{section.295.2}\protected@file@percent } \newlabel{modules-1}{{295.2}{869}{Modules}{section.295.2}{}} \newlabel{jquery-anonymous-module-error}{{295.2}{869}{Modules}{section*.9}{}} \newlabel{source-maps-not-showing}{{295.2}{870}{Modules}{section*.10}{}} \newlabel{disable-bundler-analytics}{{295.2}{870}{Modules}{section*.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {295.3}Portlets}{870}{section.295.3}\protected@file@percent } \newlabel{portlets}{{295.3}{870}{Portlets}{section.295.3}{}} \newlabel{angular-react-vue-portlet-disable-spa}{{295.3}{870}{Portlets}{section*.12}{}} \@setckpt{developer/appdev}{ \setcounter{page}{871} \setcounter{equation}{0} \setcounter{enumi}{3} \setcounter{enumii}{1} \setcounter{enumiii}{3} \setcounter{enumiv}{0} \setcounter{footnote}{0} \setcounter{mpfootnote}{0} \setcounter{@memmarkcntra}{0} \setcounter{storedpagenumber}{1} \setcounter{book}{0} \setcounter{part}{3} \setcounter{chapter}{295} \setcounter{section}{3} \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}{943} \setcounter{lastsheet}{2851} \setcounter{lastpage}{2779} \setcounter{figure}{0} \setcounter{lofdepth}{1} \setcounter{table}{0} \setcounter{lotdepth}{1} \setcounter{Item}{1096} \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}{14} \setcounter{LT@chunks}{3} \setcounter{parentequation}{0} \setcounter{FancyVerbLine}{0} } ================================================ FILE: book/developer/appdev.tex ================================================ \chapter{Application Development}\label{application-development} Writing applications on Liferay's standards-based platform makes your life easier. Whether you create headless services for clients to access, full-blown web applications with beautiful UIs, or anything in between, Liferay DXP streamlines the process to help you get your job done faster. Liferay's framework embraces your existing tools and build environments like \href{https://maven.apache.org}{Maven} and \href{https://gradle.org}{Gradle}. You can work with the standard technologies you know and leverage Liferay's APIs for Documents, Permissions, Search, or Content when you need them. Here's a high level view of what you can do: \begin{itemize} \item \textbf{Deployment of existing standards-based apps:} If you have an existing app built outside of Liferay DXP, you can deploy it on Liferay DXP. The Liferay Bundler Generator and Liferay npm Bundler provide the project scaffolding and packaging to deploy \href{https://angular.io/}{Angular}, \href{https://reactjs.org/}{React}, and \href{https://vuejs.org/}{Vue} web front-ends as Widgets. Spring Portlet MVC app conversion to \href{https://github.com/liferay/portletmvc4spring}{PortletMVC4Spring} requires only a few steps. JSF applications work almost as-is. Portlet 3.0 or 2.0 compliant portlets deploy on Liferay DXP. \item \textbf{Back-end Java services, web services, and REST services:} Service Builder is an object-relational mapper where you describe your data model in a single \texttt{xml} file. From this, you can generate the tables, a Java API for accessing your data model, and web services. On top of these, REST Builder generates OpenAPI-based REST services your client applications can call. \item \textbf{Authentication and single-sign on (SSO):} OAuth 2.0, OpenID Connect, and SAML are built-in and ready to go. \item \textbf{Front-end web development using Java EE and/or JavaScript:} Use Java EE standard Portlet technology (JSR 168, JSR 286, JSR 362) with CDI and/or JSF. Prefer Spring? \href{https://github.com/liferay/portletmvc4spring}{PortletMVC4Spring} brings the Spring MVC Framework to Liferay. Rather have a client-side app? Write it in \href{https://angular.io/}{Angular}, \href{https://reactjs.org/}{React}, or \href{https://vuejs.org/}{Vue}. Been using Liferay DXP for a while? Liferay MVC Portlet is better than ever. \item \textbf{Frameworks and APIs for every need:} Be more productive by using Liferay's built-in and well-tested APIs that cover often-used features like file management(upload/download), permissions, comments, out-of-process messaging, or UI elements such as data tables and item selectors. Liferay DXP offers many APIs for every need, from an entire workflow framework to a streamlined way of getting request parameters. \item \textbf{Tool freedom:} Liferay provides Maven archetypes, \href{/docs/7-2/reference/-/knowledge_base/r/liferay-workspace}{Liferay Workspace}, \href{/docs/7-2/reference/-/knowledge_base/r/gradle-plugins}{Gradle} and \href{/docs/7-2/reference/-/knowledge_base/r/maven-plugins}{Maven} plugins, a \href{http://yeoman.io/}{Yeoman}-based \href{/docs/7-2/reference/-/knowledge_base/r/theme-generator}{theme generator}, and \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} to integrate with any development workflow. On top of that, you can use our \href{/docs/7-2/reference/-/knowledge_base/r/intellij}{IntelliJ plugin} or the Eclipse-based \href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay Developer Studio} if you need a full-blown development environment. \item \textbf{Developer community:} The \href{https://liferay.dev}{Liferay DXP community} is helpful and active. \end{itemize} \section{Getting Started with Liferay Development}\label{getting-started-with-liferay-development} Want to see what it's like to develop an app on Liferay DXP? Here's a quick tour. \section{Create Your Object Model and Database in One Shot}\label{create-your-object-model-and-database-in-one-shot} You don't need a database to work with Liferay, but if your app uses one, you can design it and your object model at the same time with Liferay's object-relational mapper, \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder}. You define your object model in a single \texttt{xml} file: \begin{verbatim} liferay GB \end{verbatim} Service Builder generates your object model, database, SOAP, and JSON web services automatically. Java classes are ready for you to implement your business logic around generated CRUD operations. The web services are mapped to your business logic. If you want a REST interface, you can create one. \section{Create a REST Interface}\label{create-a-rest-interface} \href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder} helps you define REST interfaces for your APIs, using \href{https://swagger.io/docs/specification/about/}{OpenAPI/Swagger}. Create your \href{https://swagger.io/docs/specification/basic-structure/}{YAML definition} file for your REST interface along with a configuration file defining where Java classes, a client, and tests should be generated, and you have REST endpoints ready to call your API. Next, you need a client. You can use Liferay DXP in headless mode and write your web and mobile clients any way you want. Or you can create your web clients on Liferay's platform and take advantage of its many tools and APIs that speed up development. \section{Create a Web Client}\label{create-a-web-client} Liferay DXP is an ideal platform upon which to build a web client. Its Java EE-based technology means you can pick from the best it has to offer: Spring MVC using \href{https://github.com/liferay/portletmvc4spring}{PortletMVC4Spring}, the new backwards-compatible Portlet 3, JSF using \href{https://liferayfaces.org}{Liferay Faces}, or the venerable OSGi-based \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay MVC Portlet}. If you're a front-end developer, deploy your Angular, React, or Vue-based front-end applications to run as widgets next to the rest of Liferay DXP's installed applications. \section{Use Liferay's Frameworks}\label{use-liferays-frameworks} Your apps need features. Liferay has implemented tons of common functionality you can use in your applications. The \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/liferay-ui/tld-summary.html}{Liferay-UI} tag library has tons of web components like Search Container (a sortable data table), panels, buttons, and more. Liferay's \href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset Framework} can publish data from your application in context wherever users need it---as a notification, a related asset, as tagged or categorized data, or as relevant data based on a \href{/docs/7-2/user/-/knowledge_base/u/creating-user-segments}{user segment}. Need to provide file upload/download? Use the \href{/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api}{Documents API}. Need a robust permissions system? Use \href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{Liferay permissions}. Want users to submit comments? Use Liferay's \href{/docs/7-2/frameworks/-/knowledge_base/f/adding-comments-to-your-app}{comments}. Need to process data outside the request/response? Use the Message Bus. Should users select items from a list? Use the \href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item Selector}. \section{Next Steps}\label{next-steps} So what's next? \href{/download}{Download} Liferay DXP and \href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{create your first project}! Have a look at our \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{back-end}, \href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder}, and \href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{front-end} docs, examine what Liferay's \href{/docs/7-2/frameworks/-/knowledge_base/f/frameworks}{frameworks} have to offer, and then go create the beautiful things that only you can make. \chapter{Developing Web Front-Ends}\label{developing-web-front-ends} Liferay's open development framework removes barriers so developers can write applications faster. If you already have an application, you can deploy it on Liferay DXP: \begin{itemize} \tightlist \item Java-based standards (CDI, JSF, Portlets, Spring) \item Front-end standards (Angular, React, Vue) \end{itemize} If you plan to write a new application and deploy it on Liferay DXP, you can use the frameworks you know along with the build tools (Gradle, Maven) you know. Liferay also offers its own development framework called MVC Portlet that it uses to develop applications. When you want to integrate with \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Liferay services} and frameworks such as permissions, assets, and indexers, you'll find that these easily and seamlessly blend with your application to provide a great user experience. Regardless of your development strategy for applications, you'll find Liferay DXP to be a flexible platform that supports anything you need to write. \section{Using Popular Frameworks}\label{using-popular-frameworks} Liferay gives you a head start on developing and deploying apps that use these popular Java and JavaScript-based technologies: \begin{itemize} \tightlist \item \href{/docs/7-2/appdev/-/knowledge_base/a/developing-an-angular-application}{Angular Widget} \item \href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-react-application}{React Widget} \item \href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-vue-application}{Vue Widget} \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 Portlet} \item \href{/docs/7-2/appdev/-/knowledge_base/a/jsf-portlet}{JSF Portlet} \end{itemize} \noindent\hrulefill \textbf{Note:} The Reference section describes \href{/docs/7-2/reference/-/knowledge_base/r/sample-projects}{sample projects} and \href{/docs/7-2/reference/-/knowledge_base/r/project-templates}{project templates} for creating UIs using other technologies. \noindent\hrulefill Angular, React, and Vue applications are written the same as you would outside of Liferay DXP---using \href{https://www.npmjs.com/}{npm} and the webpack dev server. The Liferay JS Generator creates a portlet bundle (project) for developing and deploying each type of app. The bundle project comes with npm commands for building, testing, and deploying the app. It packages the app's dependencies (including JavaScript packages), deploys the bundle as a JAR, and installs the bundle to Liferay DXP's run time environment, making your app available as a widget. You can also develop web front-ends using Java EE standards. Liferay DXP supports the \href{https://jcp.org/en/jsr/detail?id=362}{JSR 362} Portlet 3.0 standard which is backwards-compatible with the \href{http://jcp.org/en/jsr/detail?id=286}{JSR 286} Portlet 2.0 standard from the Java Community Process (JCP). Each portlet framework has benefits you may wish to consider. Bean Portlet is the only framework containing all of the Portlet 3 features: \begin{itemize} \tightlist \item Contexts and Dependency Injection (CDI) \item Extended method annotations \item Explicit render state \item Action, render, and resource parameters \item Asynchronous support \end{itemize} If you're a JavaServer Faces (JSF) developer, the \href{/docs/7-1/reference/-/knowledge_base/r/understanding-liferay-faces-bridge}{Liferay Faces Bridge} supports deploying 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. If Spring is your thing, Spring Portlet MVC portlets are easy to configure and deploy on Liferay DXP. You can continue using Spring features, including Spring beans and Spring dependency injection. Last but not least, Liferay MVC Portlet continues to be a favorite with experienced Liferay developers, and makes portlet development easy for Liferay newcomers. It leverages OSGi Declarative Services (DS) for injecting dependencies and defining configurable extension points. Since Liferay DXP core and Liferay-written apps use DS, gaining experience with DS helps you develop Liferay DXP extensions and customizations. Liferay MVC Portlet works seamlessly with many Liferay frameworks, such as MVC commands, Service Builder, and more. No matter which development framework you choose, you'll be able to get an app up and running fast. \section{Getting Started}\label{getting-started} If you have an existing app that uses one the frameworks described above, your first step is to deploy it to Liferay DXP. Most deployments involve configuration steps that you can complete in an hour or less. You can also build apps from scratch using the tools you like or leveraging Liferay's tool offering. Liferay provides templates for creating all kinds of apps and samples that you can examine and modify to fit your needs. Once your app is functional, you can improve your app by integrating it with Liferay frameworks: \begin{itemize} \tightlist \item Localization \item Permissions \item Search and indexing \item Asset publishing \item Workflow \item Staging \item Data export and import \end{itemize} Liferay provides frameworks that integrate these features fast. As you develop apps on Liferay DXP, you'll enjoy using what you know, discover frameworks and tools that boost your productivity, and have fun creating rich, full-featured applications. If you're experienced with developing one of the listed app types, feel free to jump ahead to it. Otherwise, Angular Widgets is next. \chapter{Developing an Angular Application}\label{developing-an-angular-application} Running an existing Angular app on Liferay DXP makes the app available as a widget for using on site pages. You can \href{/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product}{adapt your existing Angular app}, but this doesn't give you access to the bundler and its various loaders to develop your project further in Liferay DXP. To have access to all of Liferay DXP's features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, adapt your routes and CSS, and deploy your bundle. \begin{figure} \centering \includegraphics{./images/appdev-angular-app-migrated.png} \caption{Apps like this Guestbook app are easy to migrate to Liferay DXP.} \end{figure} Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Using \href{https://www.npmjs.com}{npm}, install the Liferay JS Generator: \begin{verbatim} npm install -g yo generator-liferay-js \end{verbatim} \item Generate an Angular-based portlet bundle project for deploying your app to your \href{/deployment/docs/installing-product}{Liferay DXP installation}. \begin{verbatim} yo liferay-js \end{verbatim} Select \texttt{Angular\ based\ portlet} and opt for generating sample code. Here's the bundle's structure: \begin{itemize} \tightlist \item \texttt{{[}my-angular-portlet-bundle{]}} \begin{itemize} \tightlist \item \texttt{assets/} → CSS, HTML templates, and resources \begin{itemize} \tightlist \item \texttt{css/} → CSS files \begin{itemize} \tightlist \item \texttt{styles.css} → Default CSS file \end{itemize} \item \texttt{app/} → HTML templates \begin{itemize} \tightlist \item \texttt{app.component.html} → Root component template \end{itemize} \end{itemize} \item \texttt{features/} → Liferay DXP bundle features \begin{itemize} \tightlist \item \texttt{localization/} → Resource bundles \begin{itemize} \tightlist \item \texttt{Language.properties} → Default language keys \end{itemize} \end{itemize} \item \texttt{src/} → JavaScript an TypeScript files \begin{itemize} \tightlist \item \texttt{app/} → Application modules and Components \begin{itemize} \tightlist \item \texttt{app.component.ts} → Main component \item \texttt{app.module.ts} → Root module \item \texttt{dynamic.loader.ts} → Loads an Angular component dynamically for the portlet to attach to \end{itemize} \item \texttt{types/} \begin{itemize} \tightlist \item \texttt{LiferayParams.ts} → Parameters passed by Liferay DXP to the JavaScript module \end{itemize} \item \texttt{index.ts} → Main module invoked by the ``bootstrap'' module to initialize the portlet \item \texttt{polyfills.ts} → Fills in browser JavaScript implementation gaps \end{itemize} \item \texttt{package.json} → npm bundle configuration \item \texttt{README.md} \item \texttt{.npmbuildrc} → Build configuration \item \texttt{.npmbundlerrc} → Bundler configuration \item \texttt{tsconfig.json} → TypeScript configuration \end{itemize} \end{itemize} \item Copy your app files, matching the types listed below, into your new project. \end{enumerate} \noindent\hrulefill \begin{verbatim} File type | Destination | Comments | --------- | ----------- | -------- | HTML | `assets/app/` | Merge your main component with the existing `app.component.html`. | CSS | `assets/css/` | Overwrite `styles.css`. | TypeScript and JavaScript | `src/app/` | Merge with all files **except** `app.module.ts`---the root module merge is explained in a later step. | \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Update your component class \texttt{templateUrl}s to use the \texttt{web-context} value declared in your project's \texttt{.npmbundlerrc} file. Here's the format: \begin{verbatim} templateUrl: `/o/[web-context]/app/[template]` \end{verbatim} Here's an example: \begin{verbatim} templateUrl: '/o/my-angular-guestbook/app/add-entry/add-entry.component.html' \end{verbatim} \item Update your bundle to use portlet-level styling. \begin{itemize} \item Import all component CSS files through the CSS file (default is \texttt{styles.css}) your bundle's \texttt{package.json} file sets for your portlet. Here's the default setting: \begin{verbatim} "portlet": { "com.liferay.portlet.header-portlet-css": "/css/styles.css", ... } \end{verbatim} \item Remove \texttt{selector} and \texttt{styleUrls} properties from your component classes. \end{itemize} \item In your routing module's \texttt{@NgModule} decorator, configure the router option \texttt{useHash:\ true}. This tells Angular to use client-side routing in the form of \texttt{.../\#/{[}route{]}}, which prevents client-side parameters (i.e., anything after \texttt{\#}) from being sent back to Liferay DXP. For example, your routing module class \texttt{@NgModule} decorator might look like this: \begin{verbatim} @NgModule({ imports: [RouterModule.forRoot(routes, {useHash: true})], exports: [RouterModule] }) export class AppRoutingModule { } \end{verbatim} \item Also in your routing module, export your view components for your root module (discussed next) to use. For example, \begin{verbatim} export const routingComponents = [ViewComponent1, ViewComponent2] \end{verbatim} \item Merge your root module with \texttt{src/app/app.module.ts}, configuring it to dynamically load components. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Components must be loaded dynamically to attach to the portlet's DOM. The DOM is determined at run time when the portlet's page is rendered. \end{verbatim} \noindent\hrulefill \begin{verbatim} - Import the `routingComponents` constant and the app routing module class from your app routing module. For example, ```javascript import { AppRoutingModule, routingComponents } from './app-routing.module'; ``` - Specify the base href for the router to use in the navigation URLs. ```javascript import { APP_BASE_HREF } from '@angular/common'; ... @NgModule({ ... providers: [{provide: APP_BASE_HREF, useValue: '/'}] }) ``` - Declare the `routingComponents` constant in your `@NgModule` decorator. ```javascript @NgModule({ declarations: [ routingComponents, ... ], ... }) ``` - Make sure your `@NgModule` `bootstrap` property has no components. All components are loaded dynamically using the `entryComponents` array property. The empty `ngDoBootstrap()` method nullifies the default bootstrap implementation. ```javascript @NgModule({ ... entryComponents: [AppComponent], bootstrap: [], ... }) export class AppModule { ngDoBootstrap() {} ... } ``` Your root module `app.module.ts` should look like this: ```javascript import { APP_BASE_HREF } from '@angular/common'; import { AppRoutingModule, routingComponents } from './app-routing.module'; // more imports ... @NgModule({ declarations: [ AppComponent, routingComponents, // more declarations ... ], imports: [ AppRoutingModule, // more imports ... ], entryComponents: [AppComponent], providers: [{provide: APP_BASE_HREF, useValue: '/'}], bootstrap: [], // more properties ... }) export class AppModule { ngDoBootstrap() {} // ... } ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{8} \tightlist \item Merge your app \texttt{package.json} file's \texttt{dependencies} and \texttt{devDependencies} into the bundle's \texttt{package.json}. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** To work around build errors caused by the `rxjs` dependency, set the dependency to version `"6.0.0"`. See [LPS-92848](https://issues.liferay.com/browse/LPS-92848) for details. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{9} \item Finally, deploy your bundle: \begin{verbatim} npm run deploy \end{verbatim} \end{enumerate} Congratulations! Your Angular app is deployed and now available as a widget that you can add to site pages. The Liferay npm Bundler confirms the deployment: \begin{verbatim} Report written to liferay-npm-bundler-report.html Deployed my-angular-guestbook-1.0.0.jar to c:\git\bundles \end{verbatim} The Liferay DXP console confirms your bundle started: \begin{verbatim} 2019-03-22 20:17:53.181 INFO [fileinstall-C:/git/bundles/osgi/modules][BundleStartStopLogger:39] STARTED my-angular-guestbook_1.0.0 [1695] \end{verbatim} To find your widget, select the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}), navigate to \emph{Widgets} and then the category you specified to the Liferay Bundle Generator (\emph{Sample} is the default category). \section{Related Topics}\label{related-topics} \href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{Web Services} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization} \chapter{Developing a React Application}\label{developing-a-react-application} Running an existing React app on Liferay DXP makes the app available as a widget for using on site pages. You can \href{/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product}{adapt your existing React app}, but this doesn't give you access to the bundler and its various loaders to develop your project further in Liferay DXP. To have access to all of Liferay DXP's features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, update your static resource paths, and deploy your bundle. \begin{figure} \centering \includegraphics{./images/appdev-react-app-migrated.png} \caption{Apps like this Guestbook app are easy to migrate to Liferay DXP.} \end{figure} Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Using \href{https://www.npmjs.com/}{npm}, install the Liferay JS Generator: \begin{verbatim} npm install -g yo generator-liferay-js \end{verbatim} \item Generate a React based portlet bundle project for deploying your app to your \href{/deployment/docs/installing-product}{Liferay DXP installation}. \begin{verbatim} yo liferay-js \end{verbatim} Select \texttt{React\ based\ portlet} and opt for generating sample code. Here's the bundle's structure: \begin{itemize} \tightlist \item \texttt{my-react-portlet-bundle} \begin{itemize} \tightlist \item \texttt{assets/} → CSS and resources \begin{itemize} \tightlist \item \texttt{css/} → CSS files \begin{itemize} \tightlist \item \texttt{styles.css} → Default CSS file \end{itemize} \end{itemize} \item \texttt{features/} → Liferay DXP bundle features \begin{itemize} \tightlist \item \texttt{localization} → Resource bundles \begin{itemize} \tightlist \item \texttt{Language.properties} → Default language keys \end{itemize} \end{itemize} \item \texttt{src/} → JavaScript and React component files \begin{itemize} \tightlist \item \texttt{AppComponent.js} → Sample React component that you can remove \item \texttt{index.js} → Main module used to initialize the portlet \end{itemize} \item \texttt{.babelrc} → Babel configuration \item \texttt{.npmbuildrc} → Build configuration \item \texttt{.npmbundlerrc} → Bundler configuration \item \texttt{package.json} → npm bundle configuration \item \texttt{README.md} \end{itemize} \end{itemize} \item Copy your app files, matching the types listed below, into your new project. \end{enumerate} \noindent\hrulefill \begin{verbatim} File type | Destination | Comments | --------- | ----------- | -------- | CSS | `assets/css/` | Overwrite `styles.css`. | JavaScript | `src/` | Merge with all files **except** `index.js`---the main module merge is explained in a later step. | Static resources | `assets/` | Include resources such as image files here | \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Update your bundle to use portlet-level styling. \begin{itemize} \tightlist \item Import all component CSS files through the CSS file (default is \texttt{styles.css}) your bundle's \texttt{package.json} file sets for your portlet. Here's the default setting: \end{itemize} \begin{verbatim} "portlet": { "com.liferay.portlet.header-portlet-css": "/css/styles.css", ... } \end{verbatim} \begin{itemize} \tightlist \item Remove any CSS imports you have in your JS files \end{itemize} \item Update any static resource references to use the \texttt{web-context} value declared in your project's \texttt{.npmbundlerrc} file, and remove any imports for the resource. For example, if you have an image file called \texttt{logo.png} in your \texttt{assets} folder, you would use the format below. Note that the \texttt{assets} folder is not included in the path. Here is the format: \begin{verbatim} /o/[web-context]/[resource] \end{verbatim} Here's an example image resource: \begin{verbatim} React logo \end{verbatim} \item Merge your entry module with \texttt{src/index.js}, configuring it to dynamically load components. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Components must be loaded dynamically to attach to the portlet's DOM. The DOM is determined at run time when the portlet's page is rendered. \end{verbatim} \noindent\hrulefill \begin{verbatim} - Use the `HashRouter` for routing between component views, as Liferay DXP requires hash routing for proper portal navigation: ```javascript import { HashRouter as Router } from 'react-router-dom'; ``` - Place your code inside the `main()` function. - Render your app inside the `portletElementId` element that is passed in the `main()` function. This is required to render the React app inside the portlet. Your entry module `index.js` should look like this. ```javascript import React from 'react'; import ReactDOM from 'react-dom'; //import './index.css';//removed for Portal Migration import App from './App'; import { HashRouter as Router } from 'react-router-dom'; export default function main({portletNamespace, contextPath, portletElementId}) { ReactDOM.render(( ), document.getElementById(portletElementId)); } ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{6} \item Merge your app \texttt{package.json} file's \texttt{dependencies} and \texttt{devDependencies} into the bundle's \texttt{package.json}. \item Finally, deploy your bundle: \begin{verbatim} npm run deploy \end{verbatim} \end{enumerate} Congratulations! Your React app is deployed and now available as a widget that you can add to site pages. The Liferay npm Bundler confirms the deployment: \begin{verbatim} Report written to liferay-npm-bundler-report.html Deployed my-react-guestbook-1.0.0.jar to c:\git\bundles \end{verbatim} The Liferay DXP console confirms your bundle started: \begin{verbatim} 2019-03-22 20:17:53.181 INFO [fileinstall-C:/git/bundles/osgi/modules][BundleStartStopLogger:39] STARTED my-react-guestbook_1.0.0 [1695] \end{verbatim} To Find your widget, click the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}), navigate to \emph{Widgets} and then the category you specified to the Liferay Bundle Generator (\emph{Sample} is the default category). \section{Related Topics}\label{related-topics-1} \href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{Web Services} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization} \chapter{Developing a Vue Application}\label{developing-a-vue-application} Running an existing Vue app on Liferay DXP makes the app available as a widget for using on site pages. You can \href{/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product}{adapt your existing Vue app}, but this doesn't give you access to the bundler and its various loaders to develop your project further in Liferay DXP. To have access to all of Liferay DXP's features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, update your static resource paths, and deploy your bundle. The steps below demonstrate how to prepare a Vue app that uses single file components (\texttt{.vue} files) with multiple views. \begin{figure} \centering \includegraphics{./images/appdev-vue-migrated.png} \caption{Vue Apps like this Guestbook App are easy to deploy, and they look great in Liferay DXP.} \end{figure} \noindent\hrulefill \textbf{Note:} if you have a tree of components expressed as \texttt{.vue} templates, only the root one will be available as a true AMD module. \noindent\hrulefill Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Using \href{https://www.npmjs.com/}{npm}, install the Liferay JS Generator: \begin{verbatim} npm install -g yo generator-liferay-js \end{verbatim} \item Generate a Vue based portlet bundle project: \begin{verbatim} yo liferay-js \end{verbatim} Select \texttt{Vue\ based\ portlet} and opt for generating sample code. Here's the bundle's structure: \begin{itemize} \tightlist \item \texttt{my-vue-portlet-bundle} \begin{itemize} \tightlist \item \texttt{assets/} → CSS and resources \begin{itemize} \tightlist \item \texttt{css/} → CSS not included in \texttt{.vue} files. \end{itemize} \item \texttt{features/} → Liferay DXP bundle features \begin{itemize} \tightlist \item \texttt{localization/} → Resource bundles \begin{itemize} \tightlist \item \texttt{Language.properties} → Default language keys \end{itemize} \item \texttt{settings.json} → Placeholder System Settings \end{itemize} \item \texttt{src/} → JavaScript and Vue files \begin{itemize} \tightlist \item \texttt{index.js} → Main module used to initialize the portlet \end{itemize} \item \texttt{.babelrc} → Babel configuration \item \texttt{.npmbuildrc} → Build configuration \item \texttt{.npmbundlerrc} → Bundler configuration \item \texttt{package.json} → npm bundle configuration \item \texttt{README.md} \end{itemize} \end{itemize} \item Copy your app files, matching the types listed below, into your new project. \end{enumerate} \noindent\hrulefill \begin{verbatim} File type | Destination | Comments | --------- | ----------- | -------- | CSS | `assets/css/` | Overwrite `styles.css`. | Static resources | `assets` | Include resources such as image files here | VUE and JS| `src` | Merge your main component with the existing `index.js`. More info on that below. | \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Update your bundle to use portlet-level styling. \begin{itemize} \item If you have internal CSS included with \texttt{\textless{}style\textgreater{}} tags in your \texttt{.vue} files, import \texttt{.index.css} in \texttt{/assets/styles.css}. This is generated by the modified build script further down: \begin{verbatim} @import '../index.css'; \end{verbatim} \item Import all custom CSS files (i.e.~CSS not included in \texttt{.vue} files) through the CSS file (default is \texttt{styles.css}) your bundle's \texttt{package.json} file sets for your portlet. Here's the default setting: \begin{verbatim} "portlet": { "com.liferay.portlet.header-portlet-css": "/css/styles.css", ... } \end{verbatim} \end{itemize} \item Update any static resource references to use the \texttt{web-context} value declared in your project's \texttt{.npmbundlerrc} file. Here's the format: \begin{verbatim} /o/[web-context]/[resource] \end{verbatim} Here's an example image resource: \begin{verbatim} Vue logo \end{verbatim} \item Merge your entry module with \texttt{src/index.js}, following these steps to dynamically load components. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Components must be loaded dynamically to attach to the portlet's DOM. The DOM is determined at runtime when the portlet's page is rendered. \end{verbatim} \noindent\hrulefill \begin{verbatim} - Use Vue's runtime + compiler module (`import Vue from 'vue/dist/vue.common';`) so you don't have to process templates during build time. This is imported by default at the top of the file. - Remove the sample content from the `main()` function (i.e. the `node` constant and its use), and replace it with your router code. - Make these updates to the `new Vue` instance: - Remove the default data properties (the ones you just removed in the sample content), and set the render element to `portletElementId`. This is required and ensures that your app is rendered inside the portlet. - Add the router. - Add a render function that mounts your component wrapper to the Vue instance and displays it. Your updated configuration should look like this: ```javascript new Vue({ el: `#${portletElementId}`, render: h => h(App), router }) ``` Your entry module `index.js` should look like this. ```javascript import Vue from 'vue/dist/vue.common'; import App from './App.vue' import VueRouter from 'vue-router' //Component imports export default function main({portletNamespace, contextPath, portletElementId}) { Vue.config.productionTip = false Vue.use(VueRouter) const router = new VueRouter({ routes: [ { ... } ] }) new Vue({ el: `#${portletElementId}`, render: h => h(App), router }) } ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{6} \item Merge your app \texttt{package.json} file's \texttt{dependencies} and \texttt{devDependencies} into the project's \texttt{package.json}, and replace the \texttt{babel-cli} and \texttt{babel-preset-env} dev dependencies with the newer \texttt{"@babel/cli":\ "\^{}7.0.0"} and \texttt{"@babel/preset-env":\ "\^{}7.4.2"} packages instead. Also include the \texttt{"vueify":\ "9.4.1"} dev dependency. \item Update the \texttt{.babelrc} file to use \texttt{@babel/preset-env} instead of \texttt{env}: \begin{verbatim} "presets": ["@babel/preset-env"] \end{verbatim} \item If you're using \texttt{.vue} files, replace the build script in the \texttt{package.json} with the one below to use \texttt{vue-cli-service}. The updated build script uses vue-cli to access the main entrypoint for the app (\texttt{index.js} in the example below) and combines all the Vue templates and JS files into one single file named \texttt{index.common.js} and generates an \texttt{index.css} file for any internal CSS included with \texttt{\textless{}style\textgreater{}} tags in \texttt{.vue} files: \begin{verbatim} "scripts": { "build": "babel --source-maps -d build src && vue-cli-service build --dest build/ --formats commonjs --target lib --name index ./src/index.js && npm run copy-assets && liferay-npm-bundler", "copy-assets": "lnbs-copy-assets", "deploy": "npm run build && lnbs-deploy", "start": "lnbs-start" } \end{verbatim} \item Update the \texttt{main} entry of the \texttt{package.json} to match the new \href{http://www.commonjs.org/}{CommonJS} file name specified in the previous step: \begin{verbatim} "main": "index.common" \end{verbatim} \item Finally, deploy your portlet bundle: \begin{verbatim} npm run deploy \end{verbatim} \end{enumerate} Congratulations! Your Vue app is deployed and now available as a widget that you can add to site pages. The liferay-npm-bundler confirms the deployment: \begin{verbatim} Report written to liferay-npm-bundler-report.html Deployed my-vue-guestbook-1.0.0.jar to c:\git\bundles \end{verbatim} The Liferay DXP console confirms your bundle started: \begin{verbatim} 2019-03-22 20:17:53.181 INFO [fileinstall-C:/git/bundles/osgi/modules][BundleStartStopLogger:39] STARTED my-vue-guestbook_1.0.0 [1695] \end{verbatim} Find your widget by selecting the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}) and navigating to \emph{Widgets} and the category you specified to the Liferay Bundle Generator (\emph{Sample} is the default category). \section{Related Topics}\label{related-topics-2} \href{/docs/7-2/frameworks/-/knowledge_base/f/web-services}{Web Services} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{Localization} \chapter{Liferay MVC Portlet}\label{liferay-mvc-portlet} If you're an experienced developer, this is not the first time you've heard about Model View Controller. If there are so many implementations of MVC frameworks in Java, why did Liferay create yet another one? Stay with us and you'll see that Liferay MVC Portlet provides these benefits: \begin{itemize} \tightlist \item It's lightweight, as opposed to many other Java MVC frameworks. \item There are no special configuration files that need to be kept in sync with your code. \item It's a simple extension of \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/GenericPortlet.html}{\texttt{GenericPortlet}}. \item You avoid writing a bunch of boilerplate code, since Liferay's MVC Portlet framework only looks for some pre-defined parameters when the \texttt{init()} method is called. \item The controller can be broken down into MVC command classes, each of which handles the controller code for a particular \href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{portlet phase} (render, action, and resource serving phases). \item An MVC command class can serve multiple portlets. \item Liferay's portlets use it. That means there are plenty of robust implementations to reference when you need to design or troubleshoot your Liferay applications. \end{itemize} The Liferay MVC Portlet framework is light and easy to use. The default \href{/docs/7-2/reference/-/knowledge_base/r/using-the-mvc-portlet-template}{\texttt{MVCPortlet} project} template generates a fully configured and working project. Here, you'll learn how MVCPortlet works by covering these topics: \begin{itemize} \tightlist \item \hyperref[mvc-layers-and-modularity]{MVC layers and modularity} \item \hyperref[liferay-mvc-command-classes]{Liferay MVC command classes} \item \hyperref[liferay-mvc-portlet-component]{Liferay MVC portlet component} \item \hyperref[a-simpler-mvc-portlet]{Simple MVC portlets} \end{itemize} Review how each layer of the Liferay MVC portlet framework helps you separate the concerns of your application. \section{MVC Layers and Modularity}\label{mvc-layers-and-modularity} In MVC, there are three layers, and you can probably guess what they are. \textbf{Model:} The model layer holds the application data and logic for manipulating it. \textbf{View:} The view layer contains logic for displaying data. \textbf{Controller:} The middle man in the MVC pattern, the Controller contains logic for passing the data back and forth between the view and the model layers. Liferay DXP's applications are divided into multiple discrete modules. With \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder}, the model layer is generated into a \texttt{service} and an \texttt{api} module. That accounts for the model in the MVC pattern. The view and the controller layers share a module, the \texttt{web} module. Generating the skeleton for a \href{/docs/7-2/reference/-/knowledge_base/r/using-the-service-builder-template}{multi-module Service Builder-driven MVC application} saves you lots of time and gets you started on the more important (and interesting, if we're being honest) development work. \section{Liferay MVC Command Classes}\label{liferay-mvc-command-classes} In a larger application, your \texttt{-Portlet} class can become monstrous and unwieldy if it holds all of the controller logic. Liferay provides MVC command classes to break up your controller functionality. \begin{itemize} \tightlist \item \textbf{\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCActionCommand.html}{\texttt{MVCActionCommand}}:} Use \texttt{-ActionCommand} classes to hold each of your portlet actions, which are invoked by action URLs. \item \textbf{\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html}{\texttt{MVCRenderCommand}}:} Use \texttt{-RenderCommand} classes to hold a \texttt{render} method that dispatches to the appropriate JSP, by responding to render URLs. \item \textbf{\href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCResourceCommand.html}{\texttt{MVCResourceCommand}}:} Use \texttt{-ResourceCommand} classes to serve resources based on resource URLs. \end{itemize} There must be some confusing configuration files to keep everything wired together and working properly, right? Wrong: it's all easily managed in the \texttt{-Portlet} class's \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation. \section{Liferay MVC Portlet Component}\label{liferay-mvc-portlet-component} Whether or not you plan to split up the controller into MVC command classes, the portlet \texttt{@Component} annotation configures the portlet. Here's a simple portlet component as an example: \begin{verbatim} @Component( property = { "com.liferay.portlet.css-class-wrapper=portlet-hello-world", "com.liferay.portlet.display-category=category.sample", "com.liferay.portlet.icon=/icons/hello_world.png", "com.liferay.portlet.preferences-owned-by-group=true", "com.liferay.portlet.private-request-attributes=false", "com.liferay.portlet.private-session-attributes=false", "com.liferay.portlet.remoteable=true", "com.liferay.portlet.render-weight=50", "com.liferay.portlet.use-default-template=true", "javax.portlet.display-name=Hello World", "javax.portlet.expiration-cache=0", "javax.portlet.init-param.always-display-default-configuration-icons=true", "javax.portlet.name=" + HelloWorldPortletKeys.HELLO_WORLD, "javax.portlet.resource-bundle=content.Language", "javax.portlet.security-role-ref=guest,power-user,user", "javax.portlet.supports.mime-type=text/html" }, service = Portlet.class ) public class HelloWorldPortlet extends MVCPortlet { } \end{verbatim} The \texttt{javax.portlet.name} property is required. When using MVC commands, the \texttt{javax.portlet.name} property value links particular portlet URL/command combinations to the correct portlet. \noindent\hrulefill \textbf{Important:} Make your portlet name unique, considering how \href{/docs/7-2/reference/-/knowledge_base/r/portlet-descriptor-to-osgi-service-property-map\#ten}{Liferay DXP uses the name to create the portlet's ID}. \noindent\hrulefill There can be some confusion over exactly what kind of \texttt{Portlet.class} implementation you're publishing with a component. The service registry expects this to be the \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/Portlet.html}{\texttt{javax.portlet.Portlet}} interface. Import that, and not, for example, \texttt{com.liferay.portal.kernel.model.Portlet}. \noindent\hrulefill \textbf{Note:} The DTD \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} defines all the Liferay-specific attributes you can specify as properties in your portlet components. Consider the \texttt{\textless{}css-class-wrapper\textgreater{}} element from the above link as an example. To specify that property in your component, use this syntax in your property list: \texttt{"com.liferay.portlet.css-class-wrapper=portlet-hello-world",} The properties namespaced with \texttt{javax.portlet.} are elements of the \href{https://docs.liferay.com/portlet-api/3.0/portlet-app_3_0.xsd}{\texttt{portlet.xml} descriptor}. \noindent\hrulefill \section{A Simpler MVC Portlet}\label{a-simpler-mvc-portlet} In simpler applications, you don't use MVC commands. Your portlet render URLs specify JSP paths in \texttt{mvcPath} parameters. \begin{verbatim} \end{verbatim} As you've seen, Liferay's MVC Portlet framework gives you a well-structured controller layer that takes very little time to implement. With all your free time, you could \begin{itemize} \tightlist \item Learn a new language \item Take pottery classes \item Lift weights \item Work on your application's business logic \end{itemize} It's entirely up to you. To get into the details of creating an MVC Portlet application, continue with \href{/docs/7-2/appdev/-/knowledge_base/a/creating-an-mvc-portlet}{Creating an MVC Portlet}. \chapter{Creating an MVC Portlet}\label{creating-an-mvc-portlet} Generating MVC portlet projects is a snap using Liferay's project templates. Here you'll generate an MVC Portlet project and deploy the portlet to Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Generate an \href{/docs/7-2/reference/-/knowledge_base/r/using-the-mvc-portlet-template}{MVC Portlet project} using a Gradle or Maven. Here's the resulting folder structure for an MVC Portlet class named \texttt{MyMvcPortlet} in a base package \texttt{com.liferay.docs.mvcportlet}: \begin{itemize} \tightlist \item \texttt{my-mvc-portlet-project} → Arbitrary project name. \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} → Declares portlet constants. \end{itemize} \item \texttt{portlet} \begin{itemize} \tightlist \item \texttt{MyMvcPortlet.java} → MVC Portlet class. \end{itemize} \end{itemize} \end{itemize} \item \texttt{resources} \begin{itemize} \tightlist \item \texttt{content} \begin{itemize} \tightlist \item \texttt{Language.properties} → Resource bundle \end{itemize} \item \texttt{META-INF} \begin{itemize} \tightlist \item \texttt{resources} \begin{itemize} \tightlist \item \texttt{init.jsp} → Imports classes and taglibs and defines commonly used objects from the theme and the portlet. \item \texttt{view.jsp} → Default view template. \end{itemize} \end{itemize} \end{itemize} \end{itemize} \end{itemize} \item \texttt{bnd.bnd} → OSGi bundle metadata. \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 is exactly the same. Here's the resulting MVC Portlet class: \end{enumerate} \begin{verbatim} package com.liferay.docs.mvcportlet.portlet; import com.liferay.docs.mvcportlet.constants.MyMvcPortletKeys; import com.liferay.portal.kernel.portlet.bridges.mvc.MVCPortlet; import javax.portlet.Portlet; import org.osgi.service.component.annotations.Component; @Component( immediate = true, property = { "com.liferay.portlet.display-category=category.sample", "com.liferay.portlet.instanceable=true", "javax.portlet.display-name=my-mvc-portlet-project Portlet", "javax.portlet.init-param.template-path=/", "javax.portlet.init-param.view-template=/view.jsp", "javax.portlet.name=" + MyMvcPortletKeys.MyMvc, "javax.portlet.resource-bundle=content.Language", "javax.portlet.security-role-ref=power-user,user" }, service = Portlet.class ) public class MyMvcPortlet extends MVCPortlet { } \end{verbatim} The class extends \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCPortlet.html}{\texttt{MVCPortlet}}. The \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation and \texttt{service\ =\ Portlet.class} attribute makes the class an OSGi Declarative Services component that provides the \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/Portlet.html}{\texttt{javax.portlet.Portlet}} service type. The \texttt{immediate\ =\ true} attribute activates the service immediately on the portlet's deployment. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Set any portlet configuration or Liferay portlet configuration values using \texttt{javax.portlet.*} and \texttt{com.liferay.portlet.*} \texttt{@Component} annotation properties \href{/docs/7-2/reference/-/knowledge_base/r/portlet-descriptor-to-osgi-service-property-map}{\texttt{javax.portlet.*} and \texttt{com.liferay.portlet.*} \texttt{@Component} annotation properties} respectively. Here are the example component's properties: \begin{itemize} \item \texttt{"com.liferay.portlet.display-category=category.sample"}: Sets the Widget's category to ``Sample''. \item \texttt{"com.liferay.portlet.instanceable=true"}: Activates the component immediately when its bundle installs. \item \texttt{"javax.portlet.display-name=my-mvc-portlet-project\ Portlet"}: Sets the portlet's Widget name. \item \texttt{"javax.portlet.init-param.template-path=/"}: The path under \texttt{src/main/resources/META-INF/resources/} where the templates reside. \item \texttt{"javax.portlet.init-param.view-template=/view.jsp"}: Default view template. \item \texttt{"javax.portlet.name="\ +\ MyMvcPortletKeys.MyMvc}: The portlet's unique identity. \item \texttt{"javax.portlet.resource-bundle=content.Language"}: Sets the portlet's \href{/docs/7-2/frameworks/-/knowledge_base/f/localization}{resource bundle} to the \texttt{content/Language*.properties} file(s) in the \texttt{src/main/resources/} folder. \item \texttt{"javax.portlet.security-role-ref=power-user,user"}: Makes the Liferay DXP virtual instance's power user and user Roles available for defining the portlet's permissions. \end{itemize} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** To opt-in to Portlet 3.0 features, set the component property `"javax.portlet.version=3.0"`. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item The portlet renders content via the view template \texttt{src/main/resources/META-INF/resources/view.jsp} by default. \item Build your project. \emph{Gradle:} \begin{verbatim} gradlew jar \end{verbatim} \emph{Maven:} \begin{verbatim} mvn clean package \end{verbatim} \item Deploy the project \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{using your build environment} or by building the project JAR and copying it to the \texttt{deploy/} folder in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}. \end{enumerate} The MVC Portlet is now available in the Liferay DXP UI, in the Widget category you assigned it. \begin{figure} \centering \includegraphics{./images/default-mvc-portlet-on-page.png} \caption{The example portlet shows a message defined by the language property \texttt{yourmvc.caption=Hello\ from\ YourMVC!} in the Language.properties file.} \end{figure} Congratulations on creating and deploying an MVC Portlet! \section{Related Topics}\label{related-topics-3} \href{/docs/7-2/appdev/-/knowledge_base/a/writing-mvc-portlet-controller-code}{Writing MVC Portlet Controller Code} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configuring the View Layer} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command} \chapter{Writing MVC Portlet Controller Code}\label{writing-mvc-portlet-controller-code} In MVC, your controller is a traffic director: it provides data to the right front-end view for display to the user, and it takes data the user entered in the front-end and passes it to the right back-end service. For this reason, the controller must process requests from the front-end, and it must determine the right front-end view to pass data back to the user. If you have a small application that's not heavy on controller logic, you can put all your controller code in the \texttt{-Portlet} class. If you have more complex needs (lots of actions, complex render logic to implement, or maybe even some resource serving code), consider breaking the controller into \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command classes}, \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command classes}, and \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command classes}. Here you'll implement controller logic for small applications, where all the controller code is in the \texttt{-Portlet} class. It involves these things: \begin{itemize} \tightlist \item Action methods \item Render logic \item Setting and retrieving request parameters and attributes \end{itemize} Start with creating action methods. \section{Action Methods}\label{action-methods} Your portlet class can act as your controller by itself and process requests using action methods. Here's a sample action method: \begin{verbatim} public void addGuestbook(ActionRequest request, ActionResponse response) throws PortalException, SystemException { ServiceContext serviceContext = ServiceContextFactory.getInstance( Guestbook.class.getName(), request); String name = ParamUtil.getString(request, "name"); try { _guestbookService.addGuestbook(serviceContext.getUserId(), name, serviceContext); SessionMessages.add(request, "guestbookAdded"); } catch (Exception e) { SessionErrors.add(request, e.getClass().getName()); response.setRenderParameter("mvcPath", "/html/guestbook/edit_guestbook.jsp"); } } \end{verbatim} This action method has one job: call a service to add a guestbook. If the call succeeds, the message \texttt{"guestbookAdded"} is associated with the request and added to the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/servlet/SessionMessages.html}{\texttt{SessionMessages} object}. If an exception is thrown, it's caught, and the class name is associated with the request and added to the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/servlet/SessionErrors.html}{\texttt{SessionErrors} object}, and the response is set to render \texttt{edit\_guestbook.jsp}. Setting the \texttt{mvcPath} render parameter is a Liferay \texttt{MVCPortlet} framework convention that denotes the next view to render to the user. While action methods respond to user actions, render logic determines the view to display to the user. Render logic is next. \section{Render Logic}\label{render-logic} Here's how MVC Portlet determines which view to render. Note the \texttt{init-param} properties you set in your component: \begin{verbatim} "javax.portlet.init-param.template-path=/", "javax.portlet.init-param.view-template=/view.jsp", \end{verbatim} The \texttt{template-path} property tells the MVC framework where your JSP files live. In the above example, \texttt{/} means that the JSP files are in your project's root \texttt{resources} folder. That's why it's important to follow Liferay's standard folder structure. The \texttt{view-template} property directs the default rendering to \texttt{view.jsp}. Here's the path of a hypothetical Web module's resource folder: \begin{verbatim} docs.liferaymvc.web/src/main/resources/META-INF/resources \end{verbatim} Based on that resource folder, the \texttt{view.jsp} file is found at \begin{verbatim} docs.liferaymvc.web/src/main/resources/META-INF/resources/view.jsp \end{verbatim} and that's the application's default view. When the portlet's \texttt{init} method (e.g., your portlet's override of \texttt{MVCPortlet.init()}) is called, Liferay reads the initialization parameters you specify and directs rendering to the default JSP. Throughout the controller, you can render different views (JSP files) by setting the render parameter \texttt{mvcPath} like this: \begin{verbatim} actionResponse.setRenderParameter("mvcPath", "/error.jsp"); \end{verbatim} You can avoid render logic by using initialization parameters and render parameters, but most of the time you'll override the portlet's \texttt{render} method. Here's an example: \begin{verbatim} @Override public void render(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, IOException { try { ServiceContext serviceContext = ServiceContextFactory.getInstance( Guestbook.class.getName(), renderRequest); long groupId = serviceContext.getScopeGroupId(); long guestbookId = ParamUtil.getLong(renderRequest, "guestbookId"); List guestbooks = _guestbookService .getGuestbooks(groupId); if (guestbooks.size() == 0) { Guestbook guestbook = _guestbookService.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 render logic provides the view layer with data to display to the user. The \texttt{render} method above sets the render request attribute \texttt{guestbookId} with the ID of a guestbook to display. If guestbooks exist, it chooses the first. Otherwise, it creates a guestbook and sets it to display. Lastly the method passes the render request and render response objects to the base class via its \texttt{render} method. \noindent\hrulefill \textbf{Note:} Are you wondering how to call \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder services} in 7.0? In short, obtain a reference to the service by annotating one of your fields of that service type with the \texttt{@Reference} \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative Services} annotation. \texttt{@Reference\ \ private\ GuestbookService\ \_guestbookService;} Once done, you can call the service's methods. \texttt{\_guestbookService.addGuestbook(serviceContext.getUserId(),\ "Main",\ \ \ \ \ \ \ \ \ \ serviceContext);} \noindent\hrulefill Before venturing into the view layer, the next section demonstrates ways to pass information between the controller and view layers. \section{Setting and Retrieving Request and Response Parameters and Attributes}\label{setting-and-retrieving-request-and-response-parameters-and-attributes} A handy utility class called \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html}{\texttt{ParamUtil}} facilitates retrieving parameters from an \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/ActionRequest.html}{\texttt{ActionRequest}} or a \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/RenderRequest.html}{\texttt{RenderRequest}}. For example, this JSP passes a parameter named \texttt{guestbookId} in an action URL. \begin{verbatim} \end{verbatim} The \texttt{\textless{}portlet:actionURL\textgreater{}} tag's \texttt{name} attribute maps the action URL to a controller action method named \texttt{doSomething}. Triggering an action URL invokes the corresponding method in the controller. The controller's \texttt{doSomething} method referenced in this example gets the \texttt{guestbookId} parameter value from the \texttt{ActionRequest}. \begin{verbatim} long guestbookId = ParamUtil.getLong(actionRequest, "guestbookId"); \end{verbatim} To pass information back to the view layer, the controller code can set render parameters on response objects. \begin{verbatim} actionResponse.setRenderParameter("mvcPath", "/error.jsp"); \end{verbatim} The code above sets a parameter called \texttt{mvcPath} to JSP path \texttt{/error.jsp}. This causes the controller's render method to redirect the user to that JSP. Your controller class can also set attributes into response objects using the \texttt{setAttribute} method. \begin{verbatim} renderResponse.setAttribute("guestbookId", guestbookId); \end{verbatim} JSPs can use Java code in scriptlets to interact with the request object. \begin{verbatim} <% long guestbookId = Long.valueOf((Long) renderRequest .getAttribute("guestbookId")); %> \end{verbatim} Passing information back and forth from your view and controller is important, but there's more to the view layer than that. The view layer is up next. \section{Related Topics}\label{related-topics-4} \href{/docs/7-2/appdev/-/knowledge_base/a/creating-an-mvc-portlet}{Creating an MVC Portlet} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configuring the View Layer} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command} \chapter{Configuring the View Layer}\label{configuring-the-view-layer} This section briefly covers how to get your view layer working, from organizing your imports in one JSP file, to creating URLs that direct processing to methods in your portlet class. \noindent\hrulefill Note: As you create JSPs, you can \href{/docs/7-1/tutorials/-/knowledge_base/t/applying-clay-styles-to-your-app}{apply Clay styles to your app to match Liferay's apps}. \noindent\hrulefill \section{Using the init.jsp}\label{using-the-init.jsp} Liferay's practice puts all Java imports, tag library declarations, and variable initializations into a JSP called \texttt{init.jsp}. If you use \href{/docs/7-1/tutorials/-/knowledge_base/t/blade-cli}{Blade CLI} or \href{/docs/7-1/tutorials/-/knowledge_base/t/liferay-ide}{Liferay Dev Studio DXP} to create a module based on the \texttt{mvc-portlet} project template, these taglib declarations and initializations are added automatically to your \texttt{init.jsp}: \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} Here are the tag libraries it gives you: \begin{itemize} \tightlist \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/c/tld-frame.html}{\texttt{c}}: JSTL core tags. \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/portlet/tld-frame.html}{\texttt{portlet}}: Standard portlet component tags. \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/aui/tld-summary.html}{\texttt{aui}}: \href{https://alloyui.com/}{AlloyUI} component tags. \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/liferay-portlet/tld-frame.html}{\texttt{liferay-portlet}}: Liferay portlet component tags. \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/liferay-theme/tld-frame.html}{\texttt{liferay-theme}}: Liferay theme component tags. \item \href{https://docs.liferay.com/ce/portal/7.2-latest/taglibs/util-taglib/liferay-ui/tld-frame.html}{\texttt{liferay-ui}}: Liferay UI component tags. \end{itemize} These tags make portlet and Liferay objects available: \begin{itemize} \item \texttt{\textless{}portlet:defineObjects\ /\textgreater{}}: Implicit Java variables that reference Portlet API objects. The objects available are limited to those available in the current portlet request. For details, see the \texttt{defineObjects} tag in \href{https://jcp.org/en/jsr/detail?id=286}{JSR-286}. \item \texttt{\textless{}liferay-theme:defineObjects\ /\textgreater{}}: Implicit Java variables that reference Liferay objects. \end{itemize} To use all that the \texttt{init.jsp} has, include it in your other JSPs: \begin{verbatim} <%@include file="/html/init.jsp"%> \end{verbatim} A JSP uses render URLs to display other pages and action URLs to invoke controller methods. \section{Using Render URLs}\label{using-render-urls} A render URL attached to a UI component action displays another page. For example, this render URL displays the JSP \texttt{/path/to/foo.jsp}. \begin{verbatim} \end{verbatim} Here's how to use a render URL: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/renderURL.html}{\texttt{\textless{}portlet:renderURL\textgreater{}}} to your JSP. \item Name the render URL via a \texttt{var} attribute in the \texttt{\textless{}portlet:renderURL\textgreater{}} tag. The \texttt{\textless{}portlet:renderURL\textgreater{}} tag constructs the URL and assigns it to the variable. For example, this render URL is assigned to the variable named \texttt{adminURL}: \begin{verbatim} ... \end{verbatim} \item As sub-element to the \texttt{\textless{}portlet:renderURL\textgreater{}} tag, add a \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/param.html}{\texttt{\textless{}portlet:param\textgreater{}}} tag with the following attributes: \texttt{name="mvcPath"}: Your controller's \texttt{render} method forwards processing to the JSP at the path specified in the \texttt{value}. \texttt{value="/path/to/foo.jsp"}: The path to the JSP to render. Replace the value \texttt{/path/to/foo.jsp} with your JSP path. \begin{verbatim} \end{verbatim} \item To invoke the render URL, assign its variable (\texttt{var}) to a UI component action, such as a button or navigation bar item action. \end{enumerate} Invoking the UI component causes the controller's render method to display the \texttt{mvcPath} parameter's JSP. \section{Using Action URLs}\label{using-action-urls} Action methods are different because they invoke an action (i.e., code), rather than link to another page. For example, this action URL invokes a controller method called \texttt{doSomething} and passes a parameter called \texttt{redirect}. The \texttt{redirect} parameter contains the path of the JSP to render after invoking the action: \begin{verbatim} \end{verbatim} Here's how to use an action URL: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/actionURL.html}{\texttt{\textless{}portlet:actionURL\textgreater{}}} to your JSP. \item Add a \texttt{name} and \texttt{var} attribute to the \texttt{\textless{}portlet:actionURL\textgreater{}}. The \texttt{\textless{}portlet:actionURL\textgreater{}} tag constructs the URL and assigns it to the \texttt{var} variable. \texttt{name}: Controller action to invoke. \texttt{var}: Variable to assign the action URL to. \begin{verbatim} ... \end{verbatim} \item As sub-element to the \texttt{\textless{}portlet:actionURL\textgreater{}} tag, add a \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/param.html}{\texttt{\textless{}portlet:param\textgreater{}}} tag that has the following attributes: \texttt{name="redirect"}: Tells the portlet to redirect to the JSP associated with this parameter. \texttt{value="/path/to/foo.jsp"}: Redirects the user to this JSP path after invoking the action. Replace the value \texttt{/path/to/bar.jsp} with your JSP path. \begin{verbatim} \end{verbatim} \item To invoke the action URL, assign its variable (\texttt{var}) to a UI component action, such as a button or navigation bar item action. \end{enumerate} Congratulations! Your portlet is ready for action. These simple examples demonstrate how Liferay MVC Portlet facilitates communication between a smaller application's view layer and controller. \section{Related Topics}\label{related-topics-5} \href{/docs/7-2/appdev/-/knowledge_base/a/writing-mvc-portlet-controller-code}{Writing MVC Portlet Controller Code} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command} \href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{Front-end Taglibs} \href{https://portal.liferay.dev/docs/7-2/reference/-/knowledge_base/r/liferay-javascript-apis}{Liferay JavaScript APIs} \chapter{MVC Action Command}\label{mvc-action-command} Liferay's MVC Portlet framework enables you to handle \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{MVCPortlet} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer\#using-action-urls}{actions} in separate classes. This facilitates managing action logic in portlets that have many actions. Each action URL in your portlet's JSPs invokes an appropriate action command class. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configure your JSPs} to use action URLs via \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/actionURL.html}{\texttt{\textless{}portlet:actionURL\textgreater{}}} tags. For example, the \href{https://github.com/liferay/liferay-blade-samples/blob/7.1/gradle/apps/action-command-portlet/src/main/resources/META-INF/resources/view.jsp}{action-command-portlet} sample uses this action URL: \begin{verbatim} \end{verbatim} Name the action URL via its \texttt{name} attribute. Your \texttt{*MVCActionCommand} class maps to this name. Assign the \texttt{var} attribute a variable name. \item Assign the action URL variable (\texttt{var}) to a UI component. Acting on the UI component invokes the action. For example, the sample's \texttt{greetURL} action URL variable triggers on submitting this form: \begin{verbatim} \end{verbatim} \item Create a class that implements the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCActionCommand.html}{\texttt{MVCActionCommand}} interface, or that extends 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. The latter may save you time, since it already implements \texttt{MVCActionCommand}. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Tip:** Naming your `*MVCActionCommand` class after the action it performs makes the action mappings more obvious for maintaining the code. For example, if your action class edits some kind of entry, you could name its class `EditEntryMVCActionCommand`. If your application has several MVC command classes, naming them this way helps differentiate them. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Annotate your class with an \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation, like this one: \begin{verbatim} @Component( property = { "javax.portlet.name=your_portlet_name_YourPortlet", "mvc.command.name=/your/jsp/action/url" }, service = MVCActionCommand.class ) \end{verbatim} \item Set a \texttt{javax.portlet.name} property to your portlet's internal ID. Note, you can apply MVC Command classes to multiple portlets by setting a \texttt{javax.portlet.name} property for each portlet. For example, the \texttt{javax.portlet.name} properties in this component apply it to three specific portlets. \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 = MVCActionCommand.class ) public class EditEntryMVCActionCommand extends BaseMVCActionCommand { ... } \end{verbatim} \item Set the \texttt{mvc.command.name} property to your \texttt{\textless{}portlet:actionURL\textgreater{}} tag's \texttt{name}. This maps your class to the action URL of the same name. \item Register your class as an \texttt{MVCActionCommand} service by setting the \texttt{service} attribute to \texttt{MVCActionCommand.class}. \item Implement your action logic by overriding the appropriate method of the class you're implementing or extending. \begin{itemize} \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}} implementations override the \texttt{processAction} method. \item \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/BaseMVCActionCommand.html}{\texttt{BaseMVCActionCommand}} extensions override the \texttt{doProcessAction} method. \end{itemize} Here's an example of overriding \texttt{MVCActionCommand}'s \texttt{processAction} method. This action logic gets the name parameter from the \texttt{ActionRequest} and adds it to the session messages and to an \texttt{ActionRequest} attribute. \begin{verbatim} @Override public boolean processAction( ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException { _handleActionCommand(actionRequest); return true; } private void _handleActionCommand(ActionRequest actionRequest) { String name = ParamUtil.get(actionRequest, "name", StringPool.BLANK); if (_log.isInfoEnabled()) { _log.info("Hello " + name); } String greetingMessage = "Hello " + name + "! Welcome to OSGi"; actionRequest.setAttribute("GREETER_MESSAGE", greetingMessage); SessionMessages.add(actionRequest, "greetingMessage", greetingMessage); } private static final Log _log = LogFactoryUtil.getLog( GreeterActionCommand.class); \end{verbatim} \end{enumerate} Congratulations! You've created an \texttt{MVCActionCommand} that handles your portlet actions. \section{Related Topics}\label{related-topics-6} \href{/docs/7-2/appdev/-/knowledge_base/a/creating-an-mvc-portlet}{Creating an MVC Portlet} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configuring the View Layer} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands}{MVC Command Overrides} \chapter{MVC Render Command}\label{mvc-render-command} \texttt{MVCRenderCommand}s are classes that respond to \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{MVCPortlet} \href{/docs/7-2/appdev/-/knowledge_base/a/writing-mvc-portlet-controller-code\#render-logic}{render URLs}. If your render logic is simple and you want to implement all of your render logic in your portlet class, see \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Writing MVC Portlet Controller Code}. If your render logic is complex or you want clean separation between render paths, use \texttt{MVCRenderCommand}s. Each render URL in your portlet's JSPs invokes an appropriate render command class. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configure your JSPs} to generate render URLs via \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/renderURL.html}{\texttt{\textless{}portlet:renderURL\textgreater{}}} tags. For example, this \href{https://github.com/liferay/liferay-blade-samples/tree/7.1/gradle/apps/render-command-portlet}{render-command-portlet} sample render URL invokes an MVC render command named \texttt{/blade/render}. \begin{verbatim} \end{verbatim} \item Name the render URL via its \texttt{\textless{}portlet:param\textgreater{}} named \texttt{mvcRenderCommandName}. The render URL and \texttt{*MVCRenderCommand} class (demonstrated later) map to the \texttt{mvcRenderCommandName} value. \item Assign the \texttt{\textless{}portlet:renderURL\textgreater{}}'s \texttt{var} attribute a variable name to pass to a UI component. \item Assign the render URL variable (\texttt{var}) to a UI component. When the user triggers the UI component, the \texttt{*MVCRenderCommand} class that matches the render URL handles the render request. For example, the render URL with the variable \texttt{bladeRender} triggers on users clicking this button. \begin{verbatim} \end{verbatim} \item Create a class that implements 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. \item Annotate the class with an \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation, like this one: \begin{verbatim} @Component( property = { "javax.portlet.name=com_liferay_blade_samples_portlet_rendercommand_BladeRenderPortlet", "mvc.command.name=/blade/render" }, service = MVCRenderCommand.class ) \end{verbatim} \item Set a \texttt{javax.portlet.name} property to your portlet's internal ID. \item Set a \texttt{mvc.command.name} property to your \texttt{\textless{}portlet:renderURL\textgreater{}} tag \texttt{mvcRenderCommandName} portlet parameter value. This maps your class to the render URL. \item Register your class as an \texttt{MVCRenderCommand} service by setting the \texttt{service} attribute to \texttt{MVCRenderCommand.class}. Note, you can apply MVC Command classes to multiple portlets by setting a \texttt{javax.portlet.name} property for each portlet and apply MVC Command classes to multiple command names by setting an \texttt{mvc.command.name} property for each command name. For example, this component's \texttt{javax.portlet.name} properties and \texttt{mvc.command.name} properties apply it to two specific portlets and two specific command names. \begin{verbatim} @Component( immediate = true, property = { "javax.portlet.name=" + HelloWorldPortletKeys.HELLO_MY_WORLD, "javax.portlet.name=" + HelloWorldPortletKeys.HELLO_WORLD, "mvc.command.name=/hello/edit_super_entry", "mvc.command.name=/hello/edit_entry" }, service = MVCRenderCommand.class ) \end{verbatim} \item Implement your render logic in a method that overrides \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html}{\texttt{MVCRenderCommand}}'s \texttt{render} method. Some \texttt{*MVCRenderCommand}s, such as the one below, always render the same JSP. \begin{verbatim} public class BlogsViewMVCRenderCommand implements MVCRenderCommand { @Override public String render( RenderRequest renderRequest, RenderResponse renderResponse) { return "/blogs/view.jsp"; } } \end{verbatim} \end{enumerate} As you can see, MVC render commands are easy to implement and can respond to multiple command names for multiple portlets. \section{Related Topics}\label{related-topics-7} \href{/docs/7-2/appdev/-/knowledge_base/a/creating-an-mvc-portlet}{Creating an MVC Portlet} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configuring the View Layer} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-resource-command}{MVC Resource Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands}{MVC Command Overrides} \chapter{MVC Resource Command}\label{mvc-resource-command} When using Liferay's \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{MVCPortlet framework}, you can create resource URLs in your JSPs to retrieve images, XML, or any other kind of resource from a Liferay DXP instance. The resource URL then invokes the corresponding MVC resource command class (\texttt{*MVCResourceCommand}) that processes the resource request and response. Here how to create your own MVC Resource Command: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configure your JSPs} to generate resource URLs via \href{https://docs.liferay.com/dxp/portal/7.2-latest/taglibs/util-taglib/portlet/resourceURL.html}{\texttt{\textless{}portlet:resourceURL\textgreater{}}} tags. For example, this \href{https://github.com/liferay/liferay-blade-samples/tree/7.1/gradle/apps/resource-command-portlet}{resource-command-portlet} sample resource URL invokes an MVC resource command named \texttt{/blade/captcha}. \begin{verbatim} \end{verbatim} \item Name the resource URL via its \texttt{id} attribute. \item Assign the resource URL's \texttt{var} attribute a variable name to pass to a UI component. \item Assign the resource URL variable (\texttt{var}) to a UI component, such as a button or icon. When the user triggers the UI component, the \texttt{*MVCResourceCommand} class that matches the resource URL handles the resource request. For example, the sample's resource URL is triggered when the user clicks on this \href{https://docs.liferay.com/dxp/apps/foundation/latest/taglibdocs/liferay-captcha/captcha.html}{\texttt{liferay-captcha}} component: \begin{verbatim} \end{verbatim} \item Create a class that implements 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, or that extends the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/BaseMVCResourceCommand.html}{\texttt{BaseMVCResourceCommand}} class. The latter may save you time, since it already implements \texttt{MVCResourceCommand}. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Tip:** Naming your `*MVCResourceCommand` class after the resource it provides makes the resource mappings more obvious for maintaining the code. For example, if your resource URL serves a captcha, you could name its class `CaptchaMVCResourceCommand`. If your application has several MVC command classes, naming them this way helps differentiate them. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{5} \item Annotate your class with an \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation, like this one: \begin{verbatim} @Component( property = { "javax.portlet.name=your_portlet_name_YourPortlet", "mvc.command.name=/your/jsp/resource/url" }, service = MVCResourceCommand.class ) public class YourMVCResourceCommand extends BaseMVCResourceCommand { // your resource handling code } \end{verbatim} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item Set a \texttt{javax.portlet.name} property to your portlet's internal ID. \item Set the \texttt{mvc.command.name} property to your \texttt{\textless{}portlet:resourceURL\textgreater{}} tag's \texttt{id}. This maps your class to the resource URL of the same name. \item Register your class as an \texttt{MVCResourceCommand} service by setting the \texttt{service} attribute to \texttt{MVCResourceCommand.class}. \end{enumerate} \item Implement your resource logic by overriding the appropriate method of the class you're implementing or extending. \begin{itemize} \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}} implementations override the \texttt{serveResource} method. \item \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/BaseMVCResourceCommand.html}{\texttt{BaseMVCResourceCommand}} extensions override the \texttt{doServeResource} method. \end{itemize} \end{enumerate} For example, the \href{https://github.com/liferay/liferay-blade-samples/tree/7.1/gradle/apps/resource-command-portlet}{resource-command-portlet}'s \texttt{CaptchaMVCResourceCommand} class implements the \texttt{MVCResourceCommand} interface with only a single method: \texttt{serveResource}. \begin{verbatim} @Component( immediate = true, property = { "javax.portlet.name=com_liferay_blade_samples_portlet_resourcecommand_CaptchaPortlet", "mvc.command.name=/blade/captcha" }, service = MVCResourceCommand.class ) public class CaptchaMVCResourceCommand implements MVCResourceCommand { @Override public boolean serveResource( ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws PortletException { if (_log.isInfoEnabled()) { _log.info("get captcha resource "); } try { CaptchaUtil.serveImage(resourceRequest, resourceResponse); return false; } catch (Exception e) { _log.error(e.getMessage(), e); return true; } } private static final Log _log = LogFactoryUtil.getLog( CaptchaMVCResourceCommand.class); } \end{verbatim} This \texttt{serveResource} method processes the resource request and response via the \texttt{javax.portlet.ResourceRequest} and \texttt{javax.portlet.ResourceResponse} parameters, respectively. Note that the \texttt{try} block uses the helper class \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/captcha/CaptchaUtil.html}{\texttt{CaptchaUtil}} to serve the CAPTCHA image. Though you don't have to create such a helper class, doing so often simplifies your code. Great! Now you know how to use \texttt{MVCResourceCommand} to process resources in your Liferay MVC Portlets. \section{Related Topics}\label{related-topics-8} \href{/docs/7-2/appdev/-/knowledge_base/a/creating-an-mvc-portlet}{Creating an MVC Portlet} \href{/docs/7-2/appdev/-/knowledge_base/a/configuring-the-view-layer}{Configuring the View Layer} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-render-command}{MVC Render Command} \href{/docs/7-2/appdev/-/knowledge_base/a/mvc-action-command}{MVC Action Command} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands}{MVC Command Overrides} \chapter{PortletMVC4Spring}\label{portletmvc4spring} PortletMVC4Spring is a way to develop portlets using the Spring Framework and the Model View Controller (MVC) pattern. While the Spring Framework supports developing \emph{servlet-based} web applications using \href{https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html}{Spring Web MVC}, PortletMVC4Spring supports developing \emph{portlet-based} applications using MVC. You can build \href{https://spring.io/projects/spring-framework}{Spring Framework} Liferay DXP portlets with features like these: \begin{itemize} \tightlist \item Inversion of control (IoC) / dependency injection (DI) \item Annotations \item Security \item Binding and validation \item Multi-part file upload \item \ldots{} and more \end{itemize} You'll learn these things about PortletMVC4Spring: \begin{itemize} \item \textbf{Developing a Portlet Using PortletMVC4Spring:} Demonstrates creating and deploying a portlet using PortletMVC4Spring. \item \textbf{Annotation-based Controller Development:} Shows how to implement controllers using plain old Java objects (POJOs) and annotations. \end{itemize} \noindent\hrulefill \textbf{Background:} The PortletMVC4Spring project began as Spring Portlet MVC and was part of the \href{https://spring.io/projects/spring-framework}{Spring Framework}. When the project was pruned from version 5.0.x of the Spring Framework under \href{https://github.com/spring-projects/spring-framework/issues/18701}{SPR-14129}, it became necessary to fork and rename the project. This made it possible to improve and maintain the project for compatibility with the latest versions of the Spring Framework and the Portlet API. \href{http://www.liferay.com}{Liferay} adopted Spring Portlet MVC in March of 2019 and the project was renamed to \textbf{PortletMVC4Spring}. \noindent\hrulefill If you're familiar with Spring Web MVC, it's helpful to compare it with PortletMVC4Spring. Portlet workflow differs from servlet workflow because a request to the portlet can have two distinct phases: the \texttt{ACTION\_PHASE} and the \texttt{RENDER\_PHASE}. The \texttt{ACTION\_PHASE} is executed only once and is where any back-end changes or actions occur, such as making changes in a database. The \texttt{RENDER\_PHASE} presents the portlet's content to the user each time the display is refreshed. Thus for a single request, the \texttt{ACTION\_PHASE} is executed only once, but the \texttt{RENDER\_PHASE} may be executed multiple times. This provides (and requires) a clean separation between the activities that modify the system's persistent state and the activities that generate content. The Portlet 2.0 Specification added two more phases: The event phase and the resource phase, both of which are supported by annotation-driven dispatching. PortletMVC4Spring provides annotations that support requests from the render, action, event, and resource serving portlet phases; Spring Web MVC provides only a \texttt{@RequestMapping} annotation. Where a Spring Web MVC controller might have a single handler method annotated with \texttt{@RequestMapping}, an equivalent PortletMVC4Spring controller might have multiple handler methods, each using one of the phase annotations: \texttt{@ActionMapping}, \texttt{@EventMapping}, \texttt{@RenderMapping}, or \texttt{@ResourceMapping}. The PortletMVC4Spring framework uses a \texttt{DispatcherPortlet} that dispatches requests to handlers, with configurable handler mappings and view resolution, just as the \texttt{DispatcherServlet} in the web framework does. \noindent\hrulefill \textbf{Note:} For more information on portlets, portlet specifications, and how portlets differ from servlets, see \href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets}. \noindent\hrulefill Liferay also provides full-featured sample portlets that demonstrate using JSP and \href{https://www.thymeleaf.org}{Thymeleaf} view templates. They exercise many features that form-based portlet applications typically require. \begin{figure} \centering \includegraphics{./images/portletmvc4spring-applicant-jsp-app.png} \caption{This PortletMVC4Spring portlet enables users to enter job applications. It uses the Spring features mentioned above and handles requests from multiple portlet phases.} \end{figure} The samples are available here: \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 Source Code \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Maven Central \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot \href{https://github.com/liferay/portletmvc4spring/tree/master/demo/applicant-jsp-portlet}{applicant-jsp-portlet} & \href{https://search.maven.org/search?q=a:com.liferay.portletmvc4spring.demo.applicant.jsp.portlet}{com.liferay.portletmvc4spring.demo.applicant.jsp.portlet.war} \\ \href{https://github.com/liferay/portletmvc4spring/tree/master/demo/applicant-thymeleaf-portlet}{applicant-thymeleaf-portlet} & \href{https://search.maven.org/search?q=a:com.liferay.portletmvc4spring.demo.applicant.thymeleaf.portlet}{com.liferay.portletmvc4spring.demo.applicant.thymeleaf.portlet.war} \\ \end{longtable} \noindent\hrulefill Now that you have a basic understanding of PortletMVC4Spring portlets and how they compare to Spring Web MVC applications, it's time to develop a PortletMVC4Spring portlet. \chapter{Developing a Portlet Using PortletMVC4Spring}\label{developing-a-portlet-using-portletmvc4spring} PortletMVC4Spring compliments the Spring Web framework and MVC design pattern by providing annotations that map portlet requests to Controller classes and methods. Here you'll develop and deploy a portlet application that uses PortletMVC4Spring, Spring, and JSP/JSPX templates. \begin{figure} \centering \includegraphics{./images/portletmvc4Spring-developing.png} \caption{The archetype's sample portlet prints a greeting (e.g., \emph{Hello, Joe Bloggs}) on submitting a first and last name.} \end{figure} Follow these steps to create and deploy your portlet application: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a PortletMVC4Spring project. See \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-project-anatomy}{PortletMVC4Spring Project Anatomy} for the project structure and commands for generating PortletMVC4Spring projects. Here's the Maven command for generating the JSP/JSPX project. This article lists sample code from the generated project. \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} \item In your project's Gradle build file or Maven POM, add your app's dependencies. Here are the PortletMVC4Spring development dependencies: \textbf{Gradle:} \begin{verbatim} compile group: 'com.liferay.portletmvc4spring', name: 'com.liferay.portletmvc4spring.framework', version: '5.1.0' compile group: 'com.liferay.portletmvc4spring', name: 'com.liferay.portletmvc4spring.security', version: '5.1.0' providedCompile group: 'javax.portlet', name: 'portlet-api', version: '3.0.0' \end{verbatim} \textbf{Maven:} \begin{verbatim} com.liferay.portletmvc4spring com.liferay.portletmvc4spring.framework 5.1.0 com.liferay.portletmvc4spring com.liferay.portletmvc4spring.security 5.1.0 javax.portlet portlet-api 3.0.0 provided \end{verbatim} At this point you can develop your Model, View, or Controller components, in any order. \item Create your Model class(es) in a package for models (e.g., \texttt{java/{[}my-package-path{]}/dto}). The sample Model class is \texttt{User}. \begin{verbatim} public class User implements Serializable { private static final long serialVersionUID = 1234273427623725552L; @NotBlank private String firstName; @NotBlank private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } \end{verbatim} \item Create your View using a Spring Web-supported template type. If you didn't generate your project using the archetype mentioned above, specify a View resolver for template type in your \texttt{spring-context/portlet-application-context.xml} portlet application context. (See \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-configuration-files}{PortletMVC4Spring Configuration Files} for details). The sample \texttt{user.jspx} template renders a form for submitting a user's first and last name. \begin{verbatim}


\end{verbatim} To invoke actions in your Controller, associate action URLs with your templates. The sample template associates the action URL variable \texttt{mainFormActionURL} with its form element. \begin{verbatim} ... \end{verbatim} A \texttt{\textless{}form:form/\textgreater{}} element's \texttt{modelAttribute} attribute targets an application Model. The sample template targets the application's \texttt{user} Model. \item Style your portlet by adding CSS to a stylesheet (e.g., \texttt{webapp/resources/css/main.css}) and linking your template to it. \begin{verbatim} \end{verbatim} \item Define your portlet's messages in a properties file (e.g., \texttt{src/main/resources/content/{[}portlet{]}.properties}). The sample \texttt{user.jspx} template references some of these properties: \begin{verbatim} first-name=First Name greetings=Greetings, {0} {1}! javax.portlet.display-name=com.mycompany.my.form.jsp.portlet javax.portlet.keywords=com.mycompany.my.form.jsp.portlet javax.portlet.short-title=com.mycompany.my.form.jsp.portlet javax.portlet.title=com.mycompany.my.form.jsp.portlet last-name=Last Name personal-information=Personal Information submit=Submit todays-date-is=Today''s date is {0} \end{verbatim} \item Create a Controller class to handle portlet requests. Here's an example: \begin{verbatim} @Controller @RequestMapping("VIEW") public class MyController { ... } \end{verbatim} The \href{https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/stereotype/Controller.html}{\texttt{@Controller}} annotation applies the Spring Controller component stereotype. The Spring Framework scans Controller classes for Controller annotations. The \href{https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html}{\texttt{@RequestMapping("VIEW")}} annotation marks the class's public methods as request handler methods for the portlet's \href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{VIEW mode}. \item In your Controller, apply \texttt{@RenderMapping} annotations to methods for handling portlet render requests. Import the annotation \texttt{com.liferay.portletmvc4spring.bind.annotation.RenderMapping} and make sure each handler method returns a string that matches the name of a template to render. Here are the sample's render request handler methods: \begin{verbatim} @RenderMapping public String prepareView() { return "user"; } @RenderMapping(params = "javax.portlet.action=success") public String showGreeting(ModelMap modelMap) { DateFormat dateFormat = new SimpleDateFormat("EEEE, MMMM d, yyyy G"); Calendar todayCalendar = Calendar.getInstance(); modelMap.put("todaysDate", dateFormat.format(todayCalendar.getTime())); return "greeting"; } \end{verbatim} The \texttt{@RenderMapping} annotation causes the \texttt{prepareView} method above to be invoked if no other handler methods match the request. \texttt{prepareView} renders the \texttt{user} template (i.e., \texttt{user.jspx}). The \texttt{@RenderMapping(params\ =\ "javax.portlet.action=success")} annotation causes the \texttt{showGreeting} method to be invoked if the render request has the parameter setting \texttt{javax.portlet.action=success}. \texttt{showGreeting} renders the \texttt{greeting} template (i.e., \texttt{greeting.jspx}). \item In your Controller, apply \texttt{@ActionMapping} annotations to your portlet action request handling methods. Import the annotation \texttt{com.liferay.portletmvc4spring.bind.annotation.ActionMapping}. The sample Controller's action handler method below is annotated with \texttt{@ActionMapping}, making it the default action handler if no other action handlers match the request. Since this portlet only has one action handler, the \texttt{submitApplicant} method handles all of the portlet's action requests. \begin{verbatim} @ActionMapping public void submitApplicant(@ModelAttribute("user") User user, BindingResult bindingResult, ModelMap modelMap, Locale locale, ActionResponse actionResponse, SessionStatus sessionStatus) { localValidatorFactoryBean.validate(user, bindingResult); if (!bindingResult.hasErrors()) { if (logger.isDebugEnabled()) { logger.debug("firstName=" + user.getFirstName()); logger.debug("lastName=" + user.getLastName()); } MutableRenderParameters mutableRenderParameters = actionResponse.getRenderParameters(); mutableRenderParameters.setValue("javax.portlet.action", "success"); sessionStatus.setComplete(); } } \end{verbatim} The \href{https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html}{\texttt{@ModelAttribute}} annotation in method parameter \texttt{@ModelAttribute("user")\ User\ user} associates the View's \texttt{user} Model (comprising a first name and last name) to the \texttt{User} object passed to this method. Note, the \texttt{submitApplicant} method sets the \texttt{javax.portlet.action} render parameter to \texttt{success}---the previous render handler method \texttt{showGreeting} matches this request parameter. \item Configure any additional resources and beans in the project's descriptors and Spring context files respectively. (See \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-configuration-files}{PortletMVC4Spring Configuration Files} for details). \item Build the project WAR using Gradle or Maven. \item Deploy the WAR by copying it to your \texttt{{[}Liferay-Home{]}/deploy} folder. \end{enumerate} Liferay DXP logs the deployment and the portlet is now available in the Liferay DXP UI. Find your portlet by selecting the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}) and navigating to \emph{Widgets} and the category you specified to the Liferay Bundle Generator (\emph{Sample} is the default category). Congratulations! You created and deployed a PortletMVC4Spring Portlet. \section{Related Topics}\label{related-topics-9} \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-project-anatomy}{PortletMVC4Spring Project Anatomy} \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-annotations}{PortletMVC4Spring Annotations} \href{/docs/7-2/reference/-/knowledge_base/r/portletmvc4spring-configuration-files}{PortletMVC4Spring Configuration Files} \href{/docs/7-2/appdev/-/knowledge_base/a/migrating-to-portletmvc4spring}{Migrating to PortletMVC4Spring} \chapter{Migrating to PortletMVC4Spring}\label{migrating-to-portletmvc4spring} To continue developing a portlet to use Spring Framework version 5.0 onward, migrate it from Spring Portlet MVC to \href{/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring}{PortletMVC4Spring}. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your \texttt{pom.xml} or \texttt{build.gradle} descriptor, use the Spring Framework version 5.1.x artifacts by replacing dependencies on the \texttt{spring-webmvc-portlet} artifact with the \texttt{com.liferay.portletmvc4spring.framework} artifact. Maven: \begin{verbatim} com.liferay.portletmvc4spring com.liferay.portletmvc4spring.framework 5.1.0 com.liferay.portletmvc4spring com.liferay.portletmvc4spring.security 5.1.0 \end{verbatim} Gradle: \begin{verbatim} compile group: 'com.liferay.portletmvc4spring', name: 'com.liferay.portletmvc4spring.framework', version: '5.1.0' compile group: 'com.liferay.portletmvc4spring', name: 'com.liferay.portletmvc4spring.security', version: '5.1.0' \end{verbatim} \item In your \texttt{WEB-INF/portlet.xml} descriptor, replace uses of \texttt{org.springframework.web.portlet.DispatcherPortlet} with \href{https://liferay.github.io/portletmvc4spring/apidocs/com/liferay/portletmvc4spring/DispatcherPortlet.html}{\texttt{com.liferay.portletmvc4spring.DispatcherPortlet}}. \item Replace uses of the Spring Portlet MVC \href{https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.html}{\texttt{AnnotationMethodHandlerAdapter}} class with the PortletMVC4Spring \href{https://liferay.github.io/portletmvc4spring/apidocs/com/liferay/portletmvc4spring/mvc/method/annotation/PortletRequestMappingHandlerAdapter.html}{\texttt{PortletRequestMappingHandlerAdapter}} class. \texttt{PortletRequestMappingHandlerAdapter} uses the \texttt{HandlerMethod} infrastructure that \href{https://docs.spring.io/spring/docs/5.1.x/spring-framework-reference/web.html\#spring-web}{Spring Web MVC 5.1.x} is based on. \item If you specified \texttt{AnnotationMethodHandlerAdapter} as a \texttt{\textless{}bean\textgreater{}} in a Spring configuration descriptor, replace its fully-qualified class name \texttt{org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter} with \texttt{com.liferay.portletmvc4spring.mvc.method.annotation.PortletRequestMappingHandlerAdapter}. Also address these bean property changes: \begin{itemize} \item \href{https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.html\#setCustomModelAndViewResolver-org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver-}{\texttt{customModelAndViewResolver}} (no longer available) \item \href{https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.html\#setCustomArgumentResolver-org.springframework.web.bind.support.WebArgumentResolver-}{\texttt{customArgumentResolver}} (no longer available) \item \href{https://liferay.github.io/portletmvc4spring/apidocs/com/liferay/portletmvc4spring/mvc/method/annotation/PortletRequestMappingHandlerAdapter.html\#setCustomArgumentResolvers-java.util.List-}{\texttt{customArgumentResolvers}} (specify a list of \href{https://docs.spring.io/spring/docs/5.1.4.RELEASE/javadoc-api/org/springframework/web/method/support/HandlerMethodArgumentResolver.html}{\texttt{HandlerMethodArgumentResolver}} instead of a list of \href{https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/bind/support/WebArgumentResolver.html}{\texttt{WebArgumentResolver}}) \end{itemize} \item If you're using \href{https://commons.apache.org/proper/commons-fileupload/}{Apache Commons Fileupload}, update your Spring configuration descriptor: \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item Replace this legacy bean: \begin{verbatim} \end{verbatim} With this new one from PortletMVC4Spring: \begin{verbatim} \end{verbatim} \end{enumerate} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Alternatively, you can use the native Portlet 3.0 file upload support that PortletMVC4Spring provides by setting the `portletMultipartResolver` `` element's `class` to `com.liferay.portletmvc4spring.multipart.StandardPortletMultipartResolver` \end{verbatim} \noindent\hrulefill \begin{verbatim} 2. Remove these dependencies from your `pom.xml` or `build.gradle` descriptor: ```xml commons-fileupload commons-fileupload commons-io commons-io ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{4} \item Throughout your project, replace all uses of the \texttt{org.springframework.web.portlet} package path with \texttt{com.liferay.portletmvc4spring}. \item Continue \href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-portlet-using-portletmvc4spring}{developing your portlet using PortletMVC4Spring}. \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Build and deploy your project}. \end{enumerate} Congratulations! You migrated your project from Spring Portlet MVC to PortletMVC4Spring. \section{Related Topics}\label{related-topics-10} \href{/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring}{PortletMVC4Spring} \href{/docs/7-2/appdev/-/knowledge_base/a/developing-a-portlet-using-portletmvc4spring}{Developing a Portlet Using PortletMVC4Spring} \href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{Configuring Dependencies} \chapter{JSF Portlet}\label{jsf-portlet} Do you want to develop MVC-based portlets using the Java EE standard? Do you want to use a portlet development framework with a UI component model that makes it easy to develop sophisticated, rich UIs? Or have you been writing web apps using JSF that you'd like to use in Liferay DXP? If you answered \emph{yes} to any of these questions, you're in luck! You can use the JSF portlet technology in Liferay DXP by leveraging the Liferay Faces project, which provides all these capabilities and more. Liferay Faces is an umbrella project that provides support for the JavaServer™ Faces (JSF) standard in 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} lets you 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/378 Portlet Bridge Standard. \item \href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-alloy}{Liferay Faces Alloy} lets you 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} lets you leverage Liferay-specific utilities and UI components in JSF portlets. \end{itemize} For a comprehensive demo for the JSF component suite, visit the \href{https://faces.liferay.dev}{Liferay Faces Developer site}. If you're new to JSF, you may want to know its strengths, its weaknesses, and how it stacks up to developing portlets with CSS/JavaScript. Here are some good reasons to use JSF and Liferay Faces: \begin{itemize} \tightlist \item JSF is the Java EE standard for developing web applications that use the Model/View/Controller (MVC) design pattern. As a standard, the specification is actively maintained by the Java Community Process (JCP), and the Oracle reference implementation (Mojarra) has frequent releases. Software Architects often choose standards like JSF because they are supported by Java EE application server vendors and have a guaranteed service life according to Service Level Agreements (SLAs). \item JSF was first introduced in 2003 and is a mature technology for developing web applications that are (arguably) easy to maintain. \item JSF Portlet Bridges (like Liferay Faces Bridge) are also standardized by the JCP and make it possible to deploy JSF web applications as portlets without writing portlet-specific Java code. \item Support for JSF (via Liferay Faces) is included with Liferay DXP support. \item JSF is a unique framework in that it provides a UI component model that makes it easy to develop sophisticated, rich user interfaces. \item JSF has built-in Ajax functionality that provides automatic updates to the browser by replacing elements in the DOM. \item JSF is designed with many extension points that make a variety of integrations possible. \item There are several JSF component suites available including Liferay Faces Alloy, Primefaces, ICEfaces, and RichFaces. Each of these component suites fortify JSF with a variety of UI components and complimentary technologies. \item JSF is a good choice for server-side developers that need to build web user interfaces. This enables server-side developers to focus on their core competencies rather than being experts in HTML/CSS/JavaScript. \item JSF provides the Facelets templating engine which makes it possible to create reusable UI components that are encapsulated as markup. \item JSF provides good integration with HTML5 markup \item JSF provides the Faces Flows feature which makes it easy for developers to create wizard-like applications that flow from view-to-view. \item JSF has good integration with dependency injection frameworks such as CDI and Spring that make it easy for developers to create beans that are placed within a scope managed by a container: \texttt{@RequestScoped}, \texttt{@ViewScoped}, \texttt{@SessionScoped}, \texttt{@FlowScoped} \item Since JSF is a stateful technology, the framework encapsulates the complexities of managing application state so the developer doesn't have to write state management code. It is also possible to use JSF in a stateless manner, but some of the features of application state management become effectively disabled. \end{itemize} There are some reasons not to use JSF. For example, if you are a front-end developer who makes heavy use of HTML/CSS/JavaScript, you might find that JSF UI components render HTML in a manner that gives you less control over the overall HTML document. Sticking with a JavaScript framework may be better for you. Or, perhaps standards aren't a major consideration for you or you may simply prefer developing portlets using your current framework. Whether you develop your next portlet application with JSF and Liferay Faces or with HTML/CSS/JavaScript is entirely up to you. But you probably want to learn more about Liferay Faces and try it out for yourself. \chapter{Developing a JSF Portlet Application}\label{developing-a-jsf-portlet-application} To run an existing JSF web app on Liferay DXP, you must leverage the Liferay Faces project. The \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 code. You must also provide portlet-specific descriptor files to make it compatible with the Liferay DXP platform. The easiest way to do this is by generating a new Liferay JSF Portlet project and migrating your code to it. Then you can deploy your new JSF portlet project to Liferay DXP. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a new JSF portlet project. The following Maven archetypes are available: \begin{itemize} \tightlist \item \texttt{com.liferay.faces.archetype.alloy.portlet} (Liferay Faces Alloy portlet) \item \texttt{com.liferay.faces.archetype.bootsfaces.portlet} (Liferay BootsFaces portlet) \item \texttt{com.liferay.faces.archetype.butterfaces.portlet} (Liferay ButterFaces portlet) \item \texttt{com.liferay.faces.archetype.icefaces.portlet} (Liferay ICEFaces portlet) \item \texttt{com.liferay.faces.archetype.jsf.portlet} (Liferay JSF portlet) \item \texttt{com.liferay.faces.archetype.primefaces.portlet} (Liferay PrimeFaces portlet) \item \texttt{com.liferay.faces.archetype.richfaces.portlet} (Liferay RichFaces portlet) \end{itemize} Choose the archetype that matches your web app's JSF component suite. For example, \begin{verbatim} mvn archetype:generate \ -DarchetypeGroupId=com.liferay.faces.archetype \ -DarchetypeArtifactId=com.liferay.faces.archetype.jsf.portlet \ -DarchetypeVersion=5.0.6 \ -DgroupId=com.mycompany \ -DartifactId=com.mycompany.my.jsf.portlet \end{verbatim} The above archetypes support both Gradle and Maven development by providing a \texttt{build.gradle} and \texttt{pom.xml}, respectively. For more information, visit \href{https://faces.liferay.dev}{faces.liferay.dev}. Here's the resulting project structure for a JSF Standard portlet: \begin{itemize} \tightlist \item {[}liferay-jsf-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{bean/} → Sub-package for managed Java beans (optional) \item \texttt{dto/} → Sub-package for model (data transfer object) classes (optional) \end{itemize} \item \texttt{resources/} → Resources to include in the class path \begin{itemize} \tightlist \item \texttt{i18n.properties} → Internationalization configuration \item \texttt{log4j.properties} → Log4J logging configuration \end{itemize} \item \texttt{webapp/} \begin{itemize} \tightlist \item \texttt{resources/} \begin{itemize} \tightlist \item \texttt{images/} → Images \end{itemize} \item \texttt{WEB-INF/} \begin{itemize} \tightlist \item \texttt{resources/} Frontend files (e.g., CSS, JS, XHTML, etc.) that shouldn't be accessed directly by the browser \begin{itemize} \tightlist \item \texttt{css/} → Stylesheets \end{itemize} \item \texttt{views/} → View templates \item \texttt{faces-config.xml} → JSF application configuration file \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} \end{itemize} \item Update your dependencies as desired. The generated portlet already includes the required artifacts required to deploy a simple JSF portlet to Liferay DXP. For example, the \href{/docs/7-2/reference/-/knowledge_base/r/understanding-liferay-faces-bridge}{Liferay Faces Bridge} artifacts look like this: \textbf{Maven:} \begin{verbatim} com.liferay.faces com.liferay.faces.bridge.ext 5.0.4 runtime com.liferay.faces com.liferay.faces.bridge.impl 4.1.3 runtime \end{verbatim} \textbf{Gradle:} \begin{verbatim} dependencies { runtime group: 'com.liferay.faces', name: 'com.liferay.faces.bridge.ext', version: '5.0.4' runtime group: 'com.liferay.faces', name: 'com.liferay.faces.bridge.impl', version: '4.1.3' } \end{verbatim} \item Copy your Java classes to the new \texttt{java/{[}my-package-path{]}/} folder. \item Copy your view templates to the new \texttt{src/main/webapp/WEBINF/views} folder. \item Add your frontend files (e.g., CSS, JS, etc.) that shouldn't be accessed directly by the browser to the \texttt{webapp/WEB-INF/resources/} folder. For example, your web app's CSS files would reside in the \texttt{webapp/WEB-INF/resources/css} folder. \item Add your image files to the \texttt{webapp/resources/images} folder. \item Add localized messages to the \texttt{resources/i18n.properties} file. The messages in the \texttt{i18n.properties} file can be accessed via the Expression Language using the implicit \texttt{i18n} object provided by Liferay Faces Util. The \texttt{i18n} object can access messages both from a resource bundle defined in the portlet's \texttt{portlet.xml} file, and from Liferay DXP's \texttt{Language.properties} file. \item Configure your portlet's logging configuration as desired. The \texttt{log4j.properties} file in the \texttt{src/main/resources} folder sets properties for the Log4j logging utility defined in your JSF portlet (i.e., \texttt{faces-config.xml}). \item Replace your new JSF portlet's \texttt{webapp/WEB-INF/faces-config.xml} with your web app's \texttt{faces-config.xml} file. The \texttt{faces-config.xml} file is a JSF portlet's application configuration file, which is used to register and configure objects and navigation rules. \item Replace your new JSF portlet's \texttt{webapp/WEB-INF/web.xml} with your web app's \texttt{web.xml} file. The \texttt{web.xml} file serves as a deployment descriptor that provides necessary configurations for your JSF portlet to deploy and function in Liferay DXP. Make sure the Faces Servlet is configured in your \texttt{web.xml}: \begin{verbatim} Faces Servlet javax.faces.webapp.FacesServlet 1 \end{verbatim} This is required to initialize JSF and should be defined in all JSF portlets deployed to Liferay DXP. \item Modify your \texttt{webapp/WEB-INF/portlet.xml} as desired. The \texttt{portlet.xml} descriptor describes the portlet to the portlet container. For example, it describes portlet info, security settings, etc. Also, the \texttt{javax.portlet.faces.GenericFacesPortlet} is defined here, which handles invocations to your JSF portlet and makes your portlet, since it relies on Liferay Faces Bridge, easy to develop by acting as a turnkey implementation. The \texttt{init-param} is also defined here, which ensures your portlet is visible when deployed to Liferay DXP by pointing to your default view template: \begin{verbatim} javax.portlet.faces.defaultViewId.view /WEB-INF/views/view.xhtml \end{verbatim} \item Modify your \texttt{webapp/WEB-INF/liferay-portlet.xml} as desired. It specifies additional information Liferay DXP uses to enhance your portlet: supported security roles, portlet icon, CSS and JavaScript locations, and more. 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} elements. \item Modify your \texttt{webapp/WEB-INF/liferay-display.xml} as desired. It configures characteristics for displaying your portlet. For example, this \texttt{liferay-display.xml} snippet specifies the Widget category in the Add Widget menu: \begin{verbatim} \end{verbatim} \item Modify your \texttt{webapp/WEB-INF/liferay-plugin-package.properties} as desired. It describes the portlet application's packaging and version information and specifies any required OSGi metadata. For example, this \texttt{liferay-plugin-package.properties} snippet tells the OSGi container not to scan for CDI annotations in Liferay DXP. \begin{verbatim} -cdiannotations: \end{verbatim} This is required for JSF portlets leveraging CDI deployed to Liferay DXP. They must reference their own included CDI implementation. On deploying the 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 DXP'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. \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Build and deploy your project}. \end{enumerate} Liferay DXP logs the deployment. \begin{verbatim} 2019-05-30 14:10:59.405 INFO [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:261] Processing guestbook-jsf-portlet.war ... 2019-05-30 14:11:11.401 INFO [fileinstall-C:/liferay-ce-portal-7.2.0-ga1/osgi/war][BaseDeployer:877] Deploying guestbook-jsf-portlet.war ... 2019-05-30 14:11:26.379 INFO [fileinstall-C:/liferay-ce-portal-7.2.0-ga1/osgi/war][BundleStartStopLogger:39] STARTED guestbook-jsf-portlet_7.2.0.1 [2155] ... 2019-05-30 14:11:67.569 INFO [fileinstall-C:/liferay-ce-portal-7.2.0-ga1/osgi/war][PortletHotDeployListener:288] 1 portlet for guestbook-jsf-portlet is available for use \end{verbatim} The portlet is now available in the Liferay DXP UI. Find your portlet by selecting the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}) and navigating to \emph{Widgets} and the category you specified (\emph{Sample} is the default category). Great! You've successfully developed a Liferay JSF portlet and migrated your web app logic to it. \chapter{Bean Portlet}\label{bean-portlet} \noindent\hrulefill \textbf{Important:} Bean Portlet is in development and is not yet available. \noindent\hrulefill Portlet 3.0, the \href{https://jcp.org/en/jsr/detail?id=362}{JSR 362} standard, features a new style of portlet development called Bean Portlets that use Contexts and Dependency Injection (CDI). Bean Portlets fully leverage \href{https://portals.apache.org/pluto/v301/v3Features.html}{all the new Portlet 3.0 features} in compliant portals, and are fully supported in Liferay DXP. Bean Portlets are plain old Java objects (POJOs): they don't need to extend anything. Portlet descriptors declare them to be portlets. Configuration annotations, phase method annotations, and CDI are some of the features you'll use in Portlet 3.0. \section{Portlet Configuration Annotations}\label{portlet-configuration-annotations} The \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/PortletConfiguration.html}{\texttt{@PortletConfiguration}} annotation describes your portlet to the portlet container. You can use the annotation instead of or in addition to the traditional \texttt{portlet.xml} descriptor file. The \texttt{@PortletConfiguration} annotation describes your portlet in the portlet code instead of a separate file. \noindent\hrulefill \textbf{Note:} You can configure Bean Portlets using configuration annotations, descriptors, or both. If using annotations and descriptors, the descriptors take precedence. \noindent\hrulefill This example portlet was generated using the \texttt{com.liferay.project.templates.cdi.bean.portlet} archetype, and it uses \texttt{@PortletConfiguration} and \texttt{@LiferayPortletConfiguration} annotations: \begin{verbatim} import com.mycompany.constants.FooPortletKeys; import com.liferay.bean.portlet.LiferayPortletConfiguration; import javax.portlet.annotations.LocaleString; import javax.portlet.annotations.PortletConfiguration; @PortletConfiguration( portletName = FooPortletKeys.Foo, title = @LocaleString(value = FooPortletKeys.Foo)) @LiferayPortletConfiguration( portletName = FooPortletKeys.Foo, properties = { "com.liferay.portlet.display-category=category.sample", "com.liferay.portlet.instanceable=true" } ) public class FooPortlet { ... } \end{verbatim} \texttt{@PortletConfiguration}'s \texttt{portletName} attribute names the portlet. It's the only required attribute. The \texttt{title} attribute typically uses a nicer looking name (e.g., uses spaces and capitalization). The \texttt{title} above is assigned the key constant \texttt{FooPortletKeys.Foo}. You can also localize a title to one or more languages using an array of \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/LocaleString.html}{\texttt{@LocaleString}} annotations, each specifying a different value for the \texttt{locale} element. The \texttt{@LiferayPortletConfiguration} annotation specifies additional Liferay-specific configuration properties. For example, the \texttt{com.liferay.portlet.display-category} property lets you assign the Widget category where users will find your portlet. Setting the \texttt{com.liferay.portlet.instanceable=true} enables adding multiple instances of the portlet to a page. \noindent\hrulefill \textbf{Note:} The \texttt{@PortletConfiguration} and \texttt{@LiferayPortletConfiguration} annotations are respectively synonymous with the \texttt{javax.portlet.*} and \texttt{com.liferay.portlet.*} properties in the OSGi \texttt{@Component} annotation (used in \href{/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet}{Liferay MVC Portlets}, for example). If you're familiar with the \texttt{portlet.xml} and \texttt{liferay-portlet.xml} descriptors, the \href{/docs/7-2/reference/-/knowledge_base/r/portlet-descriptor-to-osgi-service-property-map}{Portlet Descriptor to OSGi Service Property Map} shows you the OSGi \texttt{@Component} property equivalent. There's an \texttt{@PortletConfiguration} or \texttt{@LiferayPortletConfiguration} equivalent setting for each \texttt{@Component} property. \noindent\hrulefill To opt-in to Portlet 3.0 features, add the following \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/PortletApplication.html}{\texttt{@PortletApplication}} annotation to the class. \begin{verbatim} @PortletApplication(version="3.0") \end{verbatim} Once you've configured your portlet, you should declare the objects it uses (depends on). \section{Dependency Injection}\label{dependency-injection} Bean Portlets use the \texttt{@Inject} CDI annotation (by default) to inject dependencies. Apply the annotation to a field you want injected with an object of the specified type. This example portlet injects the portlet's \texttt{PortletConfig} object. \begin{verbatim} import javax.inject.Inject; import javax.portlet.PortletConfig; public class FooPortlet { @Inject PortletConfig portletConfig; // Invoke methods on portletConfig ... } \end{verbatim} \noindent\hrulefill \textbf{Note:} \href{/docs/7-2/frameworks/-/knowledge_base/f/osgi-cdi-integration}{OSGi Integration} allows you to use OSGi services (e.g., Liferay's \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/UserLocalService.html}{\texttt{UserLocalService}}) in your Bean Portlets. \noindent\hrulefill Portlet 3.0 defines annotations for declaring methods that handle portlet phases. \section{Portlet Phase Methods}\label{portlet-phase-methods} Phase method annotations apply methods for handling a portlet's phases. You can add them to methods in any class anywhere in the portlet WAR. There's no mandatory method naming convention: assign a phase annotation to the methods you want to invoke to process the phase. Here are the annotations: \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.6154}} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.3846}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright Phase \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Annotation \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Header (new) & \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/HeaderMethod.html}{\texttt{@HeaderMethod}} \\ Render & \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/RenderMethod.html}{\texttt{@RenderMethod}} \\ Action & \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/ActionMethod.html}{\texttt{@ActionMethod}} \\ Event & \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/EventMethod.html}{\texttt{@EventMethod}} \\ Resource-serving & \href{https://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/annotations/ServeResourceMethod.html}{\texttt{@ServeResourceMethod}} \\ \end{longtable} \noindent\hrulefill You can specify resource dependencies, such as CSS, in the Header phase prior to the Render phase. It helps you avoid loading the same resources multiple times. You'll definitely want to define a Render method. For example, here's a method invoked during the Render phase: \begin{verbatim} import javax.portlet.annotations.RenderMethod; @RenderMethod( include = "/WEB-INF/jsp/view.jsp", portletNames = {FooPortletKeys.Foo}) public String doView() { return "Hello from " + portletConfig.getPortletName(); } \end{verbatim} The \texttt{@RenderMethod} annotation sets the method to be invoked during the Render phase of the WAR's portlets matching any of the names listed for the \texttt{portletNames} attribute. The example Render method produces this content: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A string greeting \texttt{"Hello\ from\ "\ +\ portletConfig.getPortletName()} \item The JSP template \texttt{/WEB-INF/jsp/view.jsp}---the \texttt{@RenderMethod} annotation's \texttt{include} attribute references it. \end{enumerate} These are just a few of the Portlet 3.0 features that facilitate developing applications. This section covers more Portlet 3.0 features and Bean Portlet demonstrations. Creating and deploying your own Bean Portlet is next. \chapter{Creating a Bean Portlet}\label{creating-a-bean-portlet} \noindent\hrulefill \textbf{Important:} Bean Portlet is in development and is not yet available. \noindent\hrulefill Your first step in developing a Bean Portlet is to create one. Here you'll generate a Bean Portlet project and deploy your Bean Portlet to Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Generate a Bean Portlet project using a Maven command like this: \begin{verbatim} mvn archetype:generate \ -DarchetypeGroupId=com.liferay \ -DarchetypeArtifactId=com.liferay.project.templates.cdi.bean.portlet \ -DarchetypeVersion=1.0.0 \ -DgroupId=com.mycompany \ -DartifactId=com.mycompany.demo.bean.portlet \end{verbatim} Here's the resulting folder structure for a Bean Portlet class named \texttt{Foo}: \begin{itemize} \tightlist \item \texttt{com.mycompany.demo.bean.portlet} → Arbitrary project name. \begin{itemize} \tightlist \item \texttt{src/main/java/} \begin{itemize} \tightlist \item \texttt{com.mycompany.constants.FooPortletKeys} → Declares portlet constants. \item \texttt{com.mycompany.portlet.FooPortlet} → Bean Portlet class. \end{itemize} \item \texttt{src/main/webapp/WEB-INF/} \begin{itemize} \tightlist \item \texttt{jsp/view.jsp} → Default view template. \item \texttt{beans.xml} → Signals CDI to scan the portlet for annotations. \end{itemize} \item \texttt{pom.xml} → Specifies the project's dependencies and packaging. \end{itemize} \end{itemize} Here's the example Bean Portlet class: \begin{verbatim} package com.mycompany.portlet; import com.mycompany.constants.FooPortletKeys; import com.liferay.bean.portlet.LiferayPortletConfiguration; import javax.inject.Inject; import javax.portlet.PortletConfig; import javax.portlet.annotations.LocaleString; import javax.portlet.annotations.PortletConfiguration; import javax.portlet.annotations.RenderMethod; @PortletConfiguration( portletName = FooPortletKeys.Foo, title = @LocaleString(value = FooPortletKeys.Foo)) @LiferayPortletConfiguration( portletName = FooPortletKeys.Foo, properties = { "com.liferay.portlet.display-category=category.sample", "com.liferay.portlet.instanceable=true" } ) public class FooPortlet { @Inject PortletConfig portletConfig; @RenderMethod( include = "/WEB-INF/jsp/view.jsp", portletNames = {FooPortletKeys.Foo}) public String doView() { return "Hello from " + portletConfig.getPortletName(); } } \end{verbatim} \item Set any portlet configuration or Liferay portlet configuration values using \href{/docs/7-2/reference/-/knowledge_base/r/portlet-descriptor-to-osgi-service-property-map}{\texttt{@PortletConfiguration} and \texttt{Liferay@PortletConfiguration} attributes}. \item Inject any CDI beans using the \texttt{@Inject} annotation. \item Update your render method \texttt{doView} (it's annotated with \texttt{@RenderMethod}). It displays the template \texttt{WEB-INF/jsp/view.jsp} by default. \item Add any other logic you like to your portlet class. \item Build your portlet: \begin{verbatim} mvn clean package \end{verbatim} \item Deploy your portlet by copying the portlet WAR to your \texttt{{[}Liferay\ \ \ \ \ Home{]}/deploy} folder. The \href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{WAB Generator} converts the WAR to an OSGi Web Application Bundle (WAB) and installs it to Liferay's OSGi container. \end{enumerate} Liferay DXP logs the deployment. \begin{verbatim} INFO [main][HotDeployImpl:226] Deploying com.mycompany.demo.bean.portlet from queue INFO [main][PluginPackageUtil:1001] Reading plugin package for com.mycompany.demo.bean.portlet ... INFO [main][PortletHotDeployListener:181] 1 bean portlets for com.mycompany.demo.bean.portlet are available for use \end{verbatim} The Bean Portlet is now available in the Liferay DXP UI. The example portlet is in the Widget category you assigned it. \begin{figure} \centering \includegraphics{./images/portlet-3-portlet.png} \caption{The Foo portlet prints the message returned from \texttt{doView} method and shows the included JSP's contents.} \end{figure} Congratulations on creating and deploying a Bean Portlet! \section{Related Topics}\label{related-topics-11} \href{/docs/7-2/frameworks/-/knowledge_base/f/osgi-cdi-integration}{OSGi CDI Integration} \chapter{Service Builder}\label{service-builder} An application without reliable business logic or persistence isn't much of an application at all. Unfortunately, writing your own persistence code often takes a great deal of time. Service Builder is an object-relational mapping tool that can generate your model, persistence, and service layers from a single \texttt{xml} file. Once generated, the code is completely customizable: you can write your own persistence code along with custom SQL if necessary. Regardless of how you produce your persistence code, you can then use Service Builder to implement your app's business logic. This section demonstrates using Service Builder to \begin{itemize} \item Generate and customize your persistence framework \item Implement your business logic \end{itemize} When you configure your model and its relationships in your \texttt{service.xml} file and run Service Builder, it generates these layers of code: \begin{itemize} \item \textbf{Model layer:} defines objects to represent your project's entities. \item \textbf{Persistence layer:} saves entities to and retrieves entities from the database and updates entities. \item \textbf{Service layer:} a blank layer ready for you to create your API and business logic. . \end{itemize} Here are some key features these layers contain: \begin{itemize} \item Stubbed-out classes for implementing custom business logic \item Hibernate configurations \item Configurable caching support \item Flexibility and support for adding custom SQL queries and dynamic queries \end{itemize} \noindent\hrulefill \textbf{Note:} You don't have to use Service Builder for your back-end services on @product. It's entirely possible to use your persistence framework of choice, such as JPA or Hibernate. Note that ``under the hood,'' Service Builder uses Hibernate. \noindent\hrulefill \section{Customization via Implementation Classes}\label{customization-via-implementation-classes} Each entity Service Builder generates contains these \emph{implementation} classes: \begin{itemize} \item \textbf{Entity implementation} (\texttt{*Impl.java}): Is responsible for customizing the entity. \item \textbf{Local service implementation} (\texttt{*LocalServiceImpl.java}): Is responsible for calling the persistence layer to retrieve and store data entities. Local services contain the business logic and access the persistence layer. They can be invoked by client code running in the same Java Virtual Machine. \item \textbf{Remote service implementation} (\texttt{*ServiceImpl.java}): Generated if the \texttt{service.xml} is configured for remote services. Remote services usually contain permission checking code and are meant to be accessible from outside the JVM. Service Builder automatically generates code that makes the remote services available via JSON or SOAP, and you can also create your own remote APIs through \href{/docs/7-2/appdev/-/knowledge_base/a/rest-builder}{REST Builder} or \href{/docs/7-2/frameworks/-/knowledge_base/f/jax-rs}{JAX-RS}. \end{itemize} These classes are where you implement custom business logic. They're the only classes generated by Service Builder intended for customization. \section{Hibernate Configurations}\label{hibernate-configurations} Service Builder uses the Hibernate persistence framework for object-relational mapping. Service Builder hides the complexities of Hibernate, while still giving you access to technology like dynamic queries and custom SQL. You can take advantage of Object-Relational Mapping (ORM) in your projects without having to manually set up a Hibernate environment or make any configurations. \section{Caching}\label{caching} Service Builder caches objects at three levels: \emph{entity}, \emph{finder}, and \emph{Hibernate}. By default, Liferay uses Ehcache as an underlying cache provider for each of these cache levels. However, this is configurable via \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties}. All you must do to enable entity and finder caching in your project is to set the \texttt{cache-enabled=true} attribute of your entity's \texttt{\textless{}entity\textgreater{}} element in your \texttt{service.xml} file. \href{/docs/7-2/deploy/-/knowledge_base/d/enabling-cluster-link}{Liferay Clustering} describes Liferay caching in a cluster. \section{Dynamic Query and Custom SQL Query}\label{dynamic-query-and-custom-sql-query} Service Builder automates many of the common tasks associated with creating database persistence code but it doesn't prevent you from creating \href{/docs/7-2/appdev/-/knowledge_base/a/custom-sql}{custom SQL queries}. You can define custom SQL queries in an XML file and implement finder methods to run the queries. If you have some crazy join to do, Service Builder gets out of your way. You can also use \href{/docs/7-2/appdev/-/knowledge_base/a/dynamic-query}{dynamic query} to access Hibernate's criteria API. Service Builder is used exclusively throughout Liferay DXP and its applications, so it's well-tested and robust. It saves lots of development time, both initial development time and time that would have to be spent maintaining, extending, or customizing a project. Now \href{/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project}{create your own Service Builder project}. \chapter{Creating a Service Builder Project}\label{creating-a-service-builder-project} To use \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder}, you must generate the projects where you'll configure your object-relational map. There's an API project and an implementation project. \begin{itemize} \item \texttt{{[}project{]}/{[}project{]}-api/} → Service interfaces. \item \texttt{{[}project{]}/{[}project{]}-service/} → Service implementations and supporting files. \end{itemize} Here's how to create a Service Builder project. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Decide on a project name. If the project is part of an application, name the project after the application. \item \href{/docs/7-2/reference/-/knowledge_base/r/creating-a-project}{Create a project} using \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Blade CLI} and the \href{/docs/7-2/reference/-/knowledge_base/r/using-the-service-builder-template}{\texttt{service-builder} project template}, passing your project name as a parameter. For example, here are Gradle and Maven commands for creating a Service Builder project called \texttt{guestbook}. Gradle: \begin{verbatim} blade create -t service-builder -p com.liferay.docs.guestbook guestbook \end{verbatim} Maven: \begin{verbatim} mvn archetype:generate \ -DarchetypeGroupId=com.liferay \ -DarchetypeArtifactId=com.liferay.project.templates.service.builder \ -DgroupId=com.liferay \ -DartifactId=guestbook \ -Dpackage=com.liferay.docs.guestbook \ -Dversion=1.0 \ -DapiPath=com.liferay.api.path \ -DliferayVersion=7.2 \end{verbatim} \end{enumerate} \noindent\hrulefill \textbf{Note:} To use the Spring dependency injector instead of the \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative Services} dependency injector, use the \texttt{-\/-dependency-injector\ spring} option (Blade CLI) or \texttt{-DdependencyInjector=spring} (Maven). \noindent\hrulefill A message like this one reports project creation success: \begin{verbatim} Successfully created project bookmarks in C:\workspaces_liferay\72-ws\modules \end{verbatim} Blade CLI generates the parent project folder and sub-folders for the \texttt{*-api} and \texttt{*-service} module projects. \begin{itemize} \tightlist \item \texttt{guestbook/} \begin{itemize} \tightlist \item \texttt{guestbook-api/} \begin{itemize} \tightlist \item \texttt{bnd.bnd} \item \texttt{build.gradle} \end{itemize} \item \texttt{guestbook-service/} \begin{itemize} \tightlist \item \texttt{bnd.bnd} \item \texttt{build.gradle} \item \texttt{service.xml} → Service definition file. \end{itemize} \item \texttt{build.gradle} \end{itemize} \end{itemize} Congratulations! You've created your Service Builder project. The \texttt{service.xml} file is where you'll define your model objects (entities) and services. \section{Related Topics}\label{related-topics-12} \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{Service Builder Samples} \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-gradle-plugin}{Service Builder Gradle Plugin} \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-plugin}{Service Builder Maven Plugin} \chapter{Creating the service.xml File}\label{creating-the-service.xml-file} To define a service for your portlet project, you must create a \texttt{service.xml} file. The DTD (Document Type Declaration) file \href{https://docs.liferay.com/ce/portal/7.2-latest/definitions/liferay-service-builder_7_2_0.dtd.html}{liferay-service-builder\_7\_2\_0.dtd} specifies the format and requirements of the XML to use. A \texttt{service.xml} was created for you when you \href{/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project}{created your Service Builder project}. It's in your \texttt{*-service} module's root folder with an \texttt{entity} element named \emph{Foo}. This is (obviously) an example entity, but you can use it as a pattern for creating your own. \href{/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio}{Liferay Dev Studio DXP} provides a Diagram mode and a Source mode to give you different perspectives of the service information in your \texttt{service.xml} file. \begin{itemize} \tightlist \item \textbf{Diagram mode} facilitates creating and visualizing relationships between service entities. \item \textbf{Source mode} brings up the \texttt{service.xml} file's raw XML content in the editor. \end{itemize} If you use Liferay Dev Studio DXP, you can switch between these modes. Of course, you don't have to use Liferay Dev Studio DXP to work on Liferay projects. Next, you'll specify your service's global information. \chapter{Defining Global Service Information}\label{defining-global-service-information} A service's global information applies to all its entities. It contains the \begin{itemize} \tightlist \item \hyperref[dependency-injector]{Dependency Injector} \item \hyperref[package-path]{Package path} \item \hyperref[namespace-options]{Namespace options} \item \hyperref[multiversion-concurrency-control-mvcc]{Multiversion concurrency control} \item \hyperref[author]{Author} \end{itemize} \section{Dependency Injector}\label{dependency-injector} The default dependency injector is \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi Declarative Services}. This makes Service Builder work consistently the way other modules do. Prior versions of Liferay used Spring. The only difference is how you inject the services when you \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{go to use them later}. Declarative Services Dependency Injector: \begin{verbatim} \end{verbatim} Spring Dependency Injector: \begin{verbatim} \end{verbatim} \noindent\hrulefill \textbf{Important:} When a project is created using the \href{/docs/7-2/reference/-/knowledge_base/r/using-the-service-builder-template}{Service Builder template}, the Declarative Services dependency injector and its dependencies are configured for the project by default. To use the Spring dependency injector instead, create the project using the Service Builder template and the \texttt{-\/-dependency-injector\ spring} option (Blade CLI) or \texttt{-DdependencyInjector=spring} (Maven). \noindent\hrulefill \noindent\hrulefill \textbf{Note:} Prior to Liferay DXP 7.2, Spring was the sole dependency injector. The services were Spring beans. Liferay's Spring bean framework accommodates Spring beans referencing each other: for example, Spring bean A has a Spring bean B field and vice versa. When Spring is the dependency injector, the base services Service Builder generates include local service and persistence fields of all the \texttt{service.xml}'s entities. This causes circular references. Since OSGi Declarative Services doesn't accommodate circular references, Service Builder does not create these fields in the base classes when DS is the dependency injector. For more details, see \href{understanding-the-code-generated-by-service-builder}{Understanding the Code}. \noindent\hrulefill \section{Package Path}\label{package-path} The package path specifies the package where the service and persistence classes are generated. The package path for Guestbook ensures that the \texttt{*-api} module's service classes are generated in the \texttt{com.liferay.docs.guestbook} package. The persistence classes are generated in a package of the same name in the \texttt{*-service} module. A later article \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{describes the package content}. \section{Multiversion concurrency control (MVCC)}\label{multiversion-concurrency-control-mvcc} The \texttt{service-builder} element's \texttt{mvcc-enabled} attribute is \texttt{false} by default. Setting \texttt{mvcc-enabled="true"} (hint: edit \texttt{service.xml} in \emph{Source} view) enables \href{https://en.wikipedia.org/wiki/Multiversion_concurrency_control}{multiversion concurrency control} (MVCC) for all of the service's entities. In systems, concurrent updates are common. Without MVCC people may read or overwrite data from an invalid state unknowingly. With MVCC, each modification is made upon a given base version number. When Hibernate receives the update, it generates an \texttt{update} SQL statement that uses a \texttt{where} clause to make sure the current data version is the version you expect. If the current data version \begin{itemize} \item \textbf{matches the expected version}, your data operation is based on up-to-date data and is accepted. \item \textbf{doesn't match the expected version}, the data you're operating on is outdated. Liferay DXP rejects your data operation and throws an exception, which you can catch to help the user handle the exception (e.g., suggest retrying the operation). \end{itemize} \textbf{Important:} Enable MVCC for all your services by setting \texttt{mvcc-enabled="true"} in your \texttt{\textless{}service-builder/\textgreater{}} element. When invoking service entity updates (e.g., \texttt{fooService.update(object)}), make sure to do so in transactions. Propagate rejected transactions to the UI for the user to handle. \begin{verbatim} \end{verbatim} \section{Namespace Options}\label{namespace-options} Service Builder names the database tables using the service namespace. For example, \emph{GB} could serve as the namespace for a Guestbook application service. \begin{verbatim} GB \end{verbatim} Service Builder uses the namespace in the following SQL scripts it generates in your \texttt{src/main/resources/sql} folder: \begin{itemize} \tightlist \item \texttt{indexes.sql} \item \texttt{sequences.sql} \item \texttt{tables.sql} \end{itemize} \noindent\hrulefill \textbf{Note:} The generated SQL script folder location is configurable. For example, if you're using Gradle, you can define the \texttt{sqlDir} setting in the project's Gradle \texttt{build.gradle} file or Maven \texttt{pom.xml} file, the same way the \texttt{databaseNameMaxLength} setting is applied in the examples below. \noindent\hrulefill Service Builder uses the SQL scripts to create database tables for all the entities the \texttt{service.xml} defines. The database table names have the namespace prepended when they are created. Since the example namespace value is \texttt{GB}, the database table names created for the entities start with \texttt{GB\_\_} as their prefix. Each Service Builder project's namespace must be unique. Separate plugins should use separate namespaces and should not use a namespace already used by Liferay entities (such as \texttt{Users} or \texttt{Groups}). Check the table names in Liferay's database to see the namespaces already in use. \textbf{Warning:} Use caution when assigning namespace values. Some databases have strong restrictions on database table and column name lengths. The Service Builder \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-gradle-plugin\#task-properties}{Gradle} and \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-plugin\#available-parameters}{Maven} plugin parameter \texttt{databaseNameMaxLength} sets the maximum length you can use for your table and column names. Here are paraphrased examples of setting \texttt{databaseNameMaxLength} in build files: \textbf{Gradle \texttt{build.gradle}} \begin{verbatim} buildService { ... databaseNameMaxLength = 64 ... } \end{verbatim} \textbf{Maven \texttt{pom.xml}} \begin{verbatim} ... 64 ... \end{verbatim} \section{Author}\label{author} As the last piece of global information, enter your name as the service's \emph{author} in your \texttt{service.xml} file. Service Builder adds \texttt{@author} annotations with the specified name to all the Java classes and interfaces it generates. Save your \texttt{service.xml} file. Next, you'll add entities for your services. \begin{verbatim} Liferay \end{verbatim} \chapter{Defining Service Entities}\label{defining-service-entities} Entities are the heart and soul of a service. They represent the map between the model objects in Java and your database fields and tables. Service Builder maps your Java model to the entities you define automatically, giving you a facility for taking Java objects and persisting them. For the Guestbook application, two entities are created according to its \texttt{service.xml}: one for Guestbooks and one for Guestbook Entries. Here's a summary of the \texttt{Guestbook} entity information: \begin{itemize} \tightlist \item \textbf{Name:} \texttt{Guestbook} \item \textbf{Local service:} \emph{yes} \item \textbf{Remote service:} \emph{yes} \end{itemize} And here's what is used for the \texttt{GuestbookEntry} entity: \begin{itemize} \tightlist \item \textbf{Name:} \texttt{GuestbookEntry} \item \textbf{Local service:} \emph{yes} \item \textbf{Remote service:} \emph{yes} \end{itemize} Here's how you define entities: \begin{verbatim} \end{verbatim} The entity's database table name includes the entity name prefixed with the namespace. The Guestbook example creates one database table named \texttt{GW\_Guestbook} and another named \texttt{GB\_GuestbookEntry}. Setting \emph{Local Service} (the \texttt{local-service} attribute) to \texttt{true} instructs Service Builder to generate local interfaces for the entity's services. Local services can only be invoked from the Liferay server on which they're deployed. Setting \emph{Remote Service} (the \texttt{remote-service} attribute) to \texttt{true} instructs Service Builder to generate remote interfaces for the service. You can build a fully-functional application without generating remote services. In that case, you could set your entity local services to \texttt{true} and remote services to \texttt{false}. If, however, you want to enable remote access to your application's services, set both local service and remote service to \texttt{true}. \noindent\hrulefill \textbf{Tip:} Suppose you have an existing Data Access Object (DAO) service for an entity built using some other framework such as JPA. You can set local service to \texttt{false} and remote service to \texttt{true} so that the methods of your remote \texttt{-Impl} class can call the methods of your existing DAO. This enables your entity to integrate with Liferay's permission-checking system and provides access to the web service APIs generated by Service Builder. This is a very handy, quite powerful, and often used feature of Liferay. \noindent\hrulefill Now that you've seen how to create your application's entities, you'll learn how to describe their attributes using entity \emph{columns}. \chapter{Defining the Columns (Attributes) for Each Service Entity}\label{defining-the-columns-attributes-for-each-service-entity} An entity's columns represent its attributes. These attributes map table fields to Java object fields. To add attributes for your entity, add \texttt{\textless{}column\ /\textgreater{}} tags to your entity definition: \begin{verbatim} \end{verbatim} Service Builder creates a database field for each column you add to the \texttt{service.xml} file. It maps a database field type appropriate to the Java type specified for each column, and it does this across all the databases Liferay supports. Once Service Builder runs, it generates a Hibernate configuration that handles the object-relational mapping. Service Builder automatically generates getter/setter methods in the model class for these attributes. The column's name specifies the name used in the getters and setters that are created for the entity's Java field. The column's type indicates the Java type of this field for the entity. If a column's \texttt{primary} (i.e., primary key) attribute is set to \texttt{true}, the column becomes part of the primary key for the entity. If only one column has \texttt{primary} set to \texttt{true}, that column represents the entire primary key for the entity. This is the case in the Guestbook application. If you define multiple columns with the \texttt{primary} attribute set to true, the combination of columns makes up a compound primary key for the entity. \noindent\hrulefill \textbf{Note:} The \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method\#step-3-generate-a-primary-key}{Implementing an Add Method} article demonstrates how to generate unique primary keys for entity instances. \noindent\hrulefill \section{Create Entity Columns}\label{create-entity-columns} Define the columns you need for your first entity. The Guestbook entity is simple: it has only two attributes; a primary key and a name: \begin{verbatim} \end{verbatim} \textbf{Note}: On deploying a \texttt{*service} module, Service Builder automatically generates indexes for all entity primary keys. Create a column for each attribute of your entity or entities, using the Java type you'll use in your application. Service Builder handles mapping it to SQL for you. \section{Support Multi-tenancy}\label{support-multi-tenancy} In addition to columns for your entity's primary key and attributes, add portal instance ID and site ID columns. Then you can support Liferay's multi-tenancy features, so that each portal instance and each Site in a portal instance can have independent sets of your application's data. To hold the site's ID, add a column called \texttt{groupId} of type \texttt{long}. To hold the portal instance's ID, add a column called \texttt{companyId} of type \texttt{long}: \begin{verbatim} \end{verbatim} \section{Workflow Fields}\label{workflow-fields} You can support Liferay's \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow system} by adding the fields it needs to track an entity's progress: \begin{verbatim} \end{verbatim} \section{Audit Entities}\label{audit-entities} Finally, you can add columns to help audit your entities. To track each entity instance's owner, add a column called \texttt{userId} of type \texttt{long}. Create a column named \texttt{createDate} of type \texttt{Date} to note an entity instance's creation date. Add a column named \texttt{modifiedDate} of type \texttt{Date} to track the last time an entity instance was modified. \begin{verbatim} \end{verbatim} Great! Your entities have columns that not only represent their attributes, but also support multi-tenancy, workflow, and auditing. Next, you'll learn how to specify the relationship service entities. \chapter{Defining Relationships Between Service Entities}\label{defining-relationships-between-service-entities} Relationships between database entities or Java objects are necessary for most applications. The Guestbook application, therefore, defines a relationship between a Guestbook and its entries. As mentioned earlier, each entry must belong to a particular Guestbook. Therefore, each \texttt{GuestbookEntry} entity must relate to a \texttt{Guestbook} entity. Create the \texttt{GuestbookEntry} entity's fields: \begin{verbatim} \end{verbatim} Note the last field in the list is the \texttt{guestbookId} field. Since it's the same name as the \texttt{Guestbook} object's primary key, a relationship is created between the two objects. If you're using Liferay Dev Studio DXP, you can see this relationship in its diagram mode. \begin{figure} \centering \includegraphics{./images/service-builder-relate-entities.png} \caption{Relating entities is a snap in Liferay Dev Studio DXP's \emph{Diagram} mode for \texttt{service.xml}.} \end{figure} Congratulations! You've related two entities. Next, add the instance, audit, and status fields mentioned from the previous step to enable Liferay's multi-tenancy, audit, and workflow features. Now that your entity columns are in place and entity relationships are established, you can specify the default order in which the entity instances are retrieved from the database. \chapter{Defining Ordering of Service Entity Instances}\label{defining-ordering-of-service-entity-instances} Often, you want to retrieve multiple instances of a given entity and list them in a particular order. The \texttt{service.xml} file lets you specify the default order of your entities. Suppose you want to return \texttt{GuestbookEntry} entities by their creation date. It's easy to specify these default orderings: \begin{verbatim} \end{verbatim} You can enter \texttt{asc} or \texttt{desc} for ascending or descending order. Now that you know how to order your service entities, the last thing to do is to define the finder methods for retrieving entity instances from the database. \chapter{Defining Service Entity Finder Methods}\label{defining-service-entity-finder-methods} Finder methods retrieve entity objects from the database based on specified parameters. For each finder defined, Service Builder generates several methods to fetch, find, remove, and count entity instances based on the finder's parameters. When supporting Liferay's multi-tenancy, it's important to be able to find its entities per Site. \section{Creating Finders}\label{creating-finders} Finders are easy to create: \begin{verbatim} \end{verbatim} The example above is among the simplest of finders, and is one you should always add if you're supporting multi-tenancy. This finder returns a collection of objects that belong to the Site on which your application has been placed. Service Builder generates finder-related methods (e.g., \texttt{fetchByGroupId}, \texttt{findByGroupId}, \texttt{removeByGroupId}, \texttt{countByGroupId}) for the your entities in the \texttt{*Persistence} and \texttt{*PersistenceImpl} classes. The first of these classes is the interface; the second is its implementation. For example, the Guestbook application generates its entity finder methods in the \texttt{-Persistence} classes found in the \texttt{/guestbook-api/src/main/java/com/liferay/docs/guestbook/service/persistence} folder and the \texttt{-PersistenceImpl} classes in the \texttt{/guestbook/src/main/java/com/liferay/docs/service/persistence/impl} folder. You're not limited to finding by one column, however; you can create multi-column finders: \begin{verbatim} \end{verbatim} \noindent\hrulefill \textbf{Important}: DO NOT create finders that use entity primary key as parameters. They're unnecessary as Service Builder automatically generates \texttt{findByPrimaryKey} and \texttt{fetchByPrimaryKey} methods for all entity primary keys. On deploying a \texttt{*service} module, Service Builder creates indexes for all entity primary key columns and finder columns. Adding finders that use entity primary keys results in attempts to create multiple indexes for the same columns---Oracle DB, for example, reports these attempts as errors. \noindent\hrulefill Now you know to configure Service Builder to create finder methods for your entity. Terrific! Now that you've specified the service for your project, you're ready to \emph{build} the service by running Service Builder. It's time to \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{run Service Builder and examine the code it generates}. \chapter{Running Service Builder}\label{running-service-builder} Here you'll learn how to run Service Builder. If want to use Service Builder in your application but haven't yet \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{created a \texttt{service.xml} file that defines an object-relational map for you application}, make sure to do so before proceeding. Open a command line and navigate to your application folder (the folder that contains your \texttt{*-api} and \texttt{*-service} modules). \section{Gradle}\label{gradle} To build your services using Gradle, enter the following command: \begin{verbatim} blade gw buildService \end{verbatim} or \begin{verbatim} gradlew buildService \end{verbatim} Blade's \texttt{gw} command works in any project that has a Gradle Wrapper available to it. Projects generated using Liferay project templates have a Gradle Wrapper. \noindent\hrulefill \textbf{Note:} Liferay Workspace's Gradle Wrapper script is in the workspace root folder. If your application project folder is located at \texttt{{[}workspace{]}/modules/{[}application{]}}, for example, the Gradle Wrapper is available at \texttt{../../gradlew}. \noindent\hrulefill \section{Maven}\label{maven} If you're using Maven, build the services by running the following command: \begin{verbatim} mvn service-builder:build \end{verbatim} \textbf{Important:} 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. More information is available on \href{/docs/7-2/reference/-/knowledge_base/r/using-service-builder-in-a-maven-project}{using Maven to run Service Builder}. On successfully building the services, Service Builder prints the message \texttt{BUILD\ SUCCESSFUL}. Many generated files appear in your project. They represent a model layer, service layer, and persistence layer for your entities. Don't worry about the number of generated files---they're explained in the next article, where you can review the code Service Builder generates for your entities. \chapter{Understanding the Code Generated by Service Builder}\label{understanding-the-code-generated-by-service-builder} \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Service Builder generates code} to support your entities. The files listed under Local Service and Remote Service below are only generated for an entity that has both \texttt{local-service} and \texttt{remote-service} attributes set to \texttt{true}. Service Builder generates services for these entities in your application's \texttt{*-api} and \texttt{*-service} modules in the packages you specified in \texttt{service.xml}. For example, here are the package paths for Liferay's Bookmarks application: \begin{itemize} \tightlist \item \texttt{/guestbook-api/src/main/java/com/liferay/docs/guestbook} \item \texttt{/guestbook-service/src/main/java/com/liferay/docs/guestbook} \end{itemize} The \texttt{guestbook-api} module's interfaces define the Guestbook application API. The \texttt{*-api} module interfaces define the application's persistence layer, service layer, and model layer. Whenever you compile and deploy the \texttt{*-api} module, all its classes and interfaces are packaged in a \texttt{.jar} file called \texttt{PROJECT\_NAME-api.jar} in the module's \texttt{build/libs} folder. Deploying this JAR to Liferay \emph{defines} the API as OSGi services. The \texttt{guestbook-service} module classes implement the \texttt{guestbook-api} module interfaces. The \texttt{*-service} module provides the OSGi service implementations to deploy to Liferay's OSGi framework. Next, examine the classes and interfaces generated for the entities you specified. Similar classes are generated for each entity, depending on how each entity is specified in the \texttt{service.xml}. Here are the three types of customizable classes: \begin{itemize} \tightlist \item \texttt{*LocalServiceImpl} \item \texttt{*ServiceImpl} \item \texttt{*Impl} \end{itemize} The \texttt{*} represents the entity name in the classes listed above. Here are the persistence, service, and model classes: \begin{itemize} \tightlist \item Persistence \begin{itemize} \tightlist \item \texttt{{[}ENTITY\_NAME{]}Persistence}: Persistence interface that defines CRUD methods for the entity such as \texttt{create}, \texttt{remove}, \texttt{countAll}, \texttt{find}, \texttt{findAll}, etc. \item \texttt{{[}ENTITY\_NAME{]}PersistenceImpl}: Persistence implementation class that implements \texttt{{[}ENTITY\_NAME{]}Persistence}. \item \texttt{{[}ENTITY\_NAME{]}Util}: Persistence utility class that wraps \texttt{{[}ENTITY\_NAME{]}PersistenceImpl} and provides direct access to the database for CRUD operations. This utility should only be used by the service layer; in your portlet classes, use the \texttt{{[}ENTITY\_NAME{]}} class by referencing it with the \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference} annotation}. \end{itemize} \begin{figure} \centering \includegraphics{./images/service-builder-persistence-diagram.png} \caption{Service Builder generates these persistence classes and interfaces for an example entity called \emph{Event}. You shouldn't (and you won't need to) customize any of these classes or interfaces.} \end{figure} \item Local Service (generated for an entity only if the entity's \texttt{local-service} attribute is set to \texttt{true} in \texttt{service.xml}) \begin{itemize} \tightlist \item \texttt{{[}ENTITY\_NAME{]}LocalService}: Local service interface. \item \texttt{{[}ENTITY\_NAME{]}LocalServiceImpl} (\textbf{LOCAL SERVICE IMPLEMENTATION}): Local service implementation. This is the only class in the local service that you should modify: it's where you add your business logic. For any methods added here, Service Builder adds corresponding methods to the \texttt{{[}ENTITY\_NAME{]}LocalService} interface the next time you run it. \item \texttt{{[}ENTITY\_NAME{]}LocalServiceBaseImpl}: Local service base implementation. This is an abstract class. Service Builder injects a number of instances of various service and persistence classes into this class. \texttt{@abstract} \item \texttt{{[}ENTITY\_NAME{]}LocalServiceUtil}: Local service utility class which wraps \texttt{{[}ENTITY\_NAME{]}LocalServiceImpl}. This class is generated for backwards compatibility purposes only. Use the \texttt{*LocalService} class by referencing it with the \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference} annotation}. \item \texttt{{[}ENTITY\_NAME{]}LocalServiceWrapper}: Local service wrapper which implements \texttt{{[}ENTITY\_NAME{]}LocalService}. This class is designed to be extended and it lets you \href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{customize the entity's local services}. \end{itemize} \end{itemize} \begin{figure} \centering \includegraphics{./images/service-builder-service-diagram.png} \caption{Service Builder generates these service classes and interfaces. Only the {[}ENTITY\_NAME{]}LocalServiceImpl (e.g., EventLocalServiceImpl for the Event entity) allows custom methods to be added to the service layer.} \end{figure} \begin{itemize} \tightlist \item Remote Service (generated for an entity only if an entity's \texttt{remote-service} attribute is \emph{not} set to \texttt{false} in \texttt{service.xml}) \begin{itemize} \tightlist \item \texttt{{[}ENTITY\_NAME{]}Service}: Remote service interface. \item \texttt{{[}ENTITY\_NAME{]}ServiceImpl} (\textbf{REMOTE SERVICE IMPLEMENTATION}): Remote service implementation. This is the only class in the remote service that you should modify manually. Here, you can write code that adds additional security checks and invokes the local services. For any custom methods added here, Service Builder adds corresponding methods to the \texttt{{[}ENTITY\_NAME{]}Service} interface the next time you run it. \item \texttt{{[}ENTITY\_NAME{]}ServiceBaseImpl}: Remote service base implementation. This is an abstract class. \texttt{@abstract} \item \texttt{{[}ENTITY\_NAME{]}ServiceUtil}: Remote service utility class which wraps \texttt{{[}ENTITY\_NAME{]}ServiceImpl}. This class is generated for backwards compatibility purposes only. Use the \texttt{*Service} class by referencing it with the \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference} annotation}. \item \texttt{{[}ENTITY\_NAME{]}ServiceWrapper}: Remote service wrapper which implements \texttt{{[}ENTITY\_NAME{]}Service}. This class is designed to be extended and it lets you \href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{customize the entity's remote services}. \item \texttt{{[}ENTITY\_NAME{]}ServiceSoap}: SOAP utility which the remote \texttt{{[}ENTITY\_NAME{]}ServiceUtil} remote service utility can access. \item \texttt{{[}ENTITY\_NAME{]}Soap}: SOAP model, similar to \texttt{{[}ENTITY\_NAME{]}ModelImpl}. \texttt{{[}ENTITY\_NAME{]}Soap} is serializable; it does not implement \texttt{{[}ENTITY\_NAME{]}}. \end{itemize} \item Model \begin{itemize} \tightlist \item \texttt{{[}ENTITY\_NAME{]}Model}: Base model interface. This interface and its \texttt{{[}ENTITY\_NAME{]}ModelImpl} implementation serve only as a container for the default property accessors Service Builder generates. Any helper methods and all application logic should be added to \texttt{{[}ENTITY\_NAME{]}Impl}. \item \texttt{{[}ENTITY\_NAME{]}ModelImpl}: Base model implementation. \item \texttt{{[}ENTITY\_NAME{]}}: \texttt{{[}ENTITY\_NAME{]}} model interface which extends \texttt{{[}ENTITY\_NAME{]}Model}. \item \texttt{{[}ENTITY\_NAME{]}Impl}: (\textbf{MODEL IMPLEMENTATION}) Model implementation. You can use this class to add helper methods and application logic to your model. If you don't add any helper methods or application logic, only the auto-generated field getters and setters are available. Whenever you add custom methods to this class, Service Builder adds corresponding methods to the \texttt{{[}ENTITY\_NAME{]}} interface the next time you run it. \item \texttt{{[}ENTITY\_NAME{]}Wrapper}: Wrapper, wraps \texttt{{[}ENTITY\_NAME{]}}. This class is designed to be extended and it lets you \href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{customize the entity}. \end{itemize} \end{itemize} \begin{figure} \centering \includegraphics{./images/service-builder-model-diagram.png} \caption{Service Builder generates these model classes and interfaces. Only \texttt{{[}ENTITY\_NAME{]}Impl} (e.g., \texttt{EventImpl} for the Event entity) allows custom methods to be added to the service layer.} \end{figure} \noindent\hrulefill \textbf{Note:} \texttt{*Util} classes are generated for backwards compatibility purposes only. Your module applications should avoid calling the util classes. Use the non-util classes instead--you can reference them using the \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{\texttt{@Reference} annotation}. \noindent\hrulefill Each file that Service Builder generates is assembled from an associated FreeMarker template. The FreeMarker templates are in the \href{https://github.com/liferay/liferay-portal/tree/master/modules/util/portal-tools-service-builder}{portal-tools-service-builder module's} \texttt{src/main/resources/com/liferay/portal/tools/service/builder/dependencies/} folder. For example, Service Builder uses the \texttt{service\_impl.ftl} template to generate the \texttt{*ServiceImpl.java} classes. You can modify any \texttt{*Impl} class Service Builder generates. The most common are \texttt{*LocalServiceImpl}, \texttt{*ServiceImpl} and \texttt{*Impl}. If you modify the other classes, Service Builder overwrites the changes the next time you run it. Whenever you add methods to, remove methods from, or change a method signature of a \texttt{*LocalServiceImpl} class, \texttt{*ServiceImpl} class, or \texttt{*Impl} class, you should run Service Builder again to regenerate the affected interfaces and the service JAR. \noindent\hrulefill \textbf{Note:} Service Builder may generate code that requires adding dependencies to your \texttt{*-service} module build file. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} When \texttt{spring} is the dependency injector (see \emph{Dependency Injector} in \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information}{Defining Global Service Information}), the \texttt{-LocalServiceBaseImpl} classes Service Builder generates include \texttt{-LocalService} and \texttt{-Persistence} member fields of all the \texttt{service.xml}'s entities. \texttt{-LocalServiceImpl} classes inherit these fields and are Spring beans. The Spring beans can reference each other. For example, Spring bean A can have a Spring bean B field and vice versa. Liferay's \texttt{spring} dependency injector accommodates Spring bean circular references. The \texttt{ds} dependency injector does not accommodate circular references. When using \texttt{ds} as the dependency injector, \texttt{-LocalServiceImpl} classes are OSGi Declarative Services. Such services start only after all the other services they reference have started. If declarative service A has a declarative service B member field and vice versa, neither service can start. For this reason, the \texttt{-LocalServiceBaseImpl} classes Service Builder generates don't include \texttt{-LocalService} member fields of the \texttt{service.xml}'s other entities. When using the \texttt{ds} dependency injector, you must make sure member fields you add to service classes don't create circular dependencies. \noindent\hrulefill Congratulations! You've generated your application's initial model, persistence, and service layers and you understand the generated code. \textbf{Related Topics} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Running Service Builder} \href{/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext}{Understanding Service Context} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating Local Services} \chapter{Iterative Development}\label{iterative-development} As you develop an application, you might need to add fields to your database. This is a normal process of iterative development: you get an idea for a new feature, or it's suggested to you, and that feature requires additional data in the database. \textbf{New fields added to \texttt{service.xml} are not automatically added to the database.} To add the fields, you must do one of two things: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Write an \href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes}{upgrade process} to modify the tables and preserve the data, or \item Run the \texttt{cleanServiceBuilder} \href{/docs/7-2/reference/-/knowledge_base/r/db-support-gradle-plugin}{Gradle task} (also supported on Maven and Ant), which drops your tables so they get re-created the next time your app is deployed. The \href{/docs/7-2/reference/-/knowledge_base/r/db-support-plugin}{Maven DB Support Plugin} reference article explains how to run this command from a Maven project. \end{enumerate} Use the first option if you have a released application and you must preserve user data. Use the second option if you're adding new columns during development. \section{Related Topics}\label{related-topics-13} \href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes}{Upgrade Processes} \href{/docs/7-2/reference/-/knowledge_base/r/db-support-gradle-plugin}{Gradle DB Support Plugin} \href{/docs/7-2/reference/-/knowledge_base/r/db-support-plugin}{Maven DB Support Plugin} \chapter{Customizing Model Entities With Model Hints}\label{customizing-model-entities-with-model-hints} Once you've used Service Builder to define model entities, you may want to further refine how users enter that data. For example, model hints can define a calendar field with selectable dates only in the future. Model hints specify entity data restrictions and other formatting. You define model hints in a file called \texttt{portlet-model-hints.xml}. The \texttt{portlet-model-hints.xml} file goes in the service module's \texttt{src/main/resources/META-INF} folder. Model hints define two things: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item How entities are presented to users \item The size of database columns \end{enumerate} As Liferay renders your form fields, it customizes the form's input fields based on your configuration. \noindent\hrulefill \textbf{Note:} If you chose Spring as the dependency injector, Service Builder generates a number of XML configuration files in your service module's \texttt{src/main/resources/META-INF} folder. Service Builder uses most of these files to manage Spring and Hibernate configurations. Don't modify the Spring or Hibernate configuration files; changes to them are overwritten when Service Builder runs. You can, however, safely edit the \texttt{portlet-model-hints.xml} file. \noindent\hrulefill Since the Guestbook doesn't have much of a model hints file, as an example, consider the \href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.bookmarks.service/}{Bookmarks app service module's} model hints file: \begin{verbatim} 4000 255 20 ... \end{verbatim} The root-level element is \texttt{model-hints}. Model entities are represented by \texttt{model} sub-elements of the \texttt{model-hints} element. Each \texttt{model} element must have a \texttt{name} attribute specifying the fully-qualified class name. Models have \texttt{field} elements representing their entity's columns. Lastly, \texttt{field} elements must have a name and a type. Each \texttt{field} element's name and type maps to the name and type specified for the entity's column in the service module's \texttt{service.xml} file. Service Builder generates all these elements for you, based on the \texttt{service.xml}. To add hints to a field, add a \texttt{hint} child element. For example, you can add a \texttt{display-width\ hint} to specify the pixel width to use in displaying the field. The default pixel width is \texttt{350}. To show a \texttt{String} field with 50 pixels, you could nest a \texttt{hint} element named \texttt{display-width} and give it a value of \texttt{50}. To see the effect of a hint on a field, \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{run Service Builder} again and \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{redeploy your module}. Note that changing \texttt{display-width} doesn't limit the number of characters a user can enter into the \texttt{name} field; it only controls the field's width in the AlloyUI input form. To configure the maximum size of a model field's database column (i.e., the maximum number of characters that can be saved for the field), use the \texttt{max-length} hint. The default \texttt{max-length} value is \texttt{75} characters. If you want the \texttt{name} field to persist up to 100 characters, add a \texttt{max-length} hint to that field: \begin{verbatim} 50 100 \end{verbatim} Remember to run Service Builder and redeploy your project after updating the \texttt{portlet-model-hints.xml} file. \section{Model Hint Types}\label{model-hint-types} So far, you've seen a few different hints. The following table describes the portlet model hints available for use. \textbf{Model Hint Values and Descriptions} \noindent\hrulefill \begin{verbatim} Name | Value Type | Description | Default | \end{verbatim} \texttt{auto-escape} \textbar{} boolean \textbar{} sets whether text values should be escaped via \texttt{HtmlUtil.escape} \textbar{} true \textbar{} \texttt{autoSize} \textbar{} boolean \textbar{} displays the field in a for scrollable text area \textbar{} false \textbar{} \texttt{day-nullable} \textbar{} boolean \textbar{} allows the day to be null in a date field \textbar{} false \textbar{} \texttt{default-value} \textbar{} String \textbar{} sets the default value of the form field rendered using the aui taglib \textbar{} (empty String) \textbar{} \texttt{display-height} \textbar{} integer \textbar{} sets the display height of the form field rendered using the aui taglib \textbar{} 15 \textbar{} \texttt{display-width} \textbar{} integer \textbar{} sets the display width of the form field rendered using the aui taglib \textbar{} 350 \textbar{} \texttt{editor} \textbar{} boolean \textbar{} sets whether to provide an editor for the input \textbar{} false \textbar{} \texttt{max-length} \textbar{} integer \textbar{} sets the maximum column size for SQL file generation \textbar{} 75 \textbar{} \texttt{month-nullable} \textbar{} boolean \textbar{} allows the month to be null in a date field \textbar{} false \textbar{} \texttt{secret} \textbar{} boolean \textbar{} sets whether to hide the characters input by the user \textbar{} false \textbar{} \texttt{show-time} \textbar{} boolean \textbar{} sets whether to show the time along with the date \textbar{} true \textbar{} \texttt{upper-case} \textbar{} boolean \textbar{} converts all characters to upper case \textbar{} false \textbar{} \texttt{year-nullable} \textbar{} boolean \textbar{} allows a date field's year to be null \textbar{} false \textbar{} \texttt{year-range-delta} \textbar{} integer \textbar{} specifies the number of years to display from today's date in a date field rendered with the aui taglib \textbar{} 5 \textbar{} \texttt{year-range-future} \textbar{} boolean \textbar{} sets whether to include future dates \textbar{} true \textbar{} \texttt{year-range-past} \textbar{} boolean \textbar{} sets whether to include past dates \textbar{} true \textbar{} \noindent\hrulefill \noindent\hrulefill \textbf{Note}: The aui taglib is fully supported and not related to AlloyUI (the JavaScript library) that's deprecated. \noindent\hrulefill \noindent\hrulefill \textbf{Note}: You can use a mix of Clay and aui tags in a form. Model hints, however, affect aui tags only. \noindent\hrulefill Note that Liferay has its own model hints file (\texttt{portal-model-hints.xml}). It's in \texttt{portal-impl.jar}'s \texttt{META-INF} folder. This file contains many hint examples, so you can reference it when creating \texttt{portlet-model-hints.xml} files. \section{Default Hints}\label{default-hints} You can use the \texttt{default-hints} element to define a list of hints to apply to every field of a model. For example, adding the following element inside a model element applies a \texttt{display-width} of 300 pixels to each field: \begin{verbatim} 300 \end{verbatim} \section{Hint Collections}\label{hint-collections} You can define \texttt{hint-collection} elements inside the \texttt{model-hints} root-level element to define a list of hints to apply together. A hint collection must have a name. For example, Liferay's \texttt{portal-model-hints.xml} defines the following hint collections: \begin{verbatim} 2000000 true 2000000 254 200 true true true false 105 500 4000 4000 \end{verbatim} You can apply a hint collection to a model field by referencing the hint collection's name. For example, if you define a \texttt{SEARCHABLE-DATE} collection like the one above in your \texttt{model-hints} element, you can apply it to your model's date field by using a \texttt{hint-collection} element that references the collection by its name: \begin{verbatim} \end{verbatim} Suppose you want to use a couple of model hints in your project. Start by providing users with an editor for filling in their comment fields. To apply the same hint to multiple entities, define it as a hint collection. Then reference the hint collection in each entity. To define a hint collection, add a \texttt{hint-collection} element inside the \texttt{model-hints} root element in your \texttt{portlet-model-hints.xml} file. For example: \begin{verbatim} 105 500 4000 \end{verbatim} To reference a hint collection for a specific field, add the \texttt{hint-collection} element inside the field's \texttt{field} element: \begin{verbatim} \end{verbatim} After defining hint collections and adding hint collection references, rebuild your services, redeploy your project, and check that the hints defined in your hint collection have taken effect. Nice work! Now you can not only influence how your model's input fields are displayed, but you can also can set its database table column sizes. You can organize hints, insert individual hints directly into your fields, apply a set of default hints to all of a model's fields, or define collections of hints to apply at either of those scopes. \chapter{Configuring service.properties}\label{configuring-service.properties} Service Builder generates a \texttt{service.properties} file in your \texttt{*-service} module's \texttt{src/main/resources} folder. Liferay DXP uses this file's properties to alter your service's database schema. You should not modify this file directly, but rather make any necessary property overrides in a \texttt{service-ext.properties} file in that same folder. Here are some of the properties the \texttt{service.properties} file includes: \begin{itemize} \tightlist \item \texttt{build.namespace}: This is the \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{namespace you defined in your \texttt{service.xml}}. Liferay distinguishes different modules from each other using their namespaces. \item \texttt{build.number}: Liferay distinguishes your module's different Service Builder builds. Each time you deploy a distinct Service Builder build to Liferay, Liferay increments this number. \item \texttt{build.date}: This is the time of your module's latest Service Builder build. \item \texttt{include-and-override}: The default value of this property defines \texttt{service-ext.properties} as an override file for \texttt{service.properties}. \end{itemize} \noindent\hrulefill \textbf{Note}: In Liferay Portal 6.x Service Builder portlets, the \texttt{build.auto.upgrade} property in \texttt{service.properties} applies Liferay Service schema changes upon rebuilding services and redeploying the portlets. This property was deprecated in Liferay 7.0. The Build Auto Upgrade feature is now different and is set in a global property \texttt{schema.module.build.auto.upgrade} in the file \texttt{{[}Liferay\_Home{]}/portal-developer.properties}. \noindent\hrulefill Awesome! You now have all the tools necessary to set up your own \texttt{service-ext.properties} file. \section{Related Topics}\label{related-topics-14} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder?} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating Local Services} \chapter{Connecting Service Builder to an External Database}\label{connecting-service-builder-to-an-external-database} If you want to use a database separate from Liferay DXP's, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Specify your database and a data source name in your \texttt{service.xml}. \item Create the database manually. \item Define the data source. \item Connect your Service Builder module to the data source. \item Run Service Builder. \end{enumerate} There are two different ways to create the connection: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{\texttt{DataSourceProvider}:} This approach involves implementing a \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/jdbc/DataSourceProvider.html}{\texttt{DataSourceProvider}} \href{https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html}{\texttt{ServiceProviderInterface}} (SPI). This way requires the fewest files and steps and works regardless of whether your Service Builder module uses the \texttt{ds} or \texttt{spring} \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#dependency-injector}{dependency injector}. \item \textbf{Spring Beans:} Configure the connection using Spring XML files. This approach only works with Service Builder modules that use the \texttt{spring} \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#dependency-injector}{dependency injection option}. \end{enumerate} \noindent\hrulefill \textbf{Note:} All entities defined in a Service Builder module's \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{\texttt{service.xml}} file are bound to the same data source. Binding different entities to different data sources requires defining the entities in separate Service Builder modules and configuring each of the modules to use a different data source. \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} If your Service Builder services require nested transactions, using an external data source may not be appropriate. Transactions between separate data sources cannot be fully nested. Rollbacks may not propagate between services that use an external data source and Liferay DXP services (or another app's services) that use a different data source. \noindent\hrulefill Since \texttt{DataSourceProvider} is the easiest, most versatile approach, it's explained first. \chapter{Connecting the Data Source Using a DataSourceProvider}\label{connecting-the-data-source-using-a-datasourceprovider} Connecting to an external database by creating and registering a \texttt{DataSourceProvider} as a JDK \href{https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html}{\texttt{ServiceProviderInterface}} (SPI) is the easiest way. This approach works regardless of whether your Service Builder module uses the \texttt{ds} or \texttt{spring} \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#dependency-injector}{dependency injection option} and it requires the fewest files and steps. \noindent\hrulefill \textbf{Note:} All entities defined in a Service Builder module's \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{\texttt{service.xml}} file are bound to the same data source. Binding different entities to different data sources requires defining the entities in separate Service Builder modules and configuring each of the modules to use a different data source. \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} If your Service Builder services require nested transactions, using an external data source may not be appropriate for you. Transactions between separate data sources cannot be fully nested. Rollbacks may not propagate between a module that uses an external data source and Liferay DXP services (or another app's services) that use a different data source. \noindent\hrulefill Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your \texttt{service.xml} file, specify the same arbitrary data source name for all of the entities, a unique table name for each entity, and a database column name for each column. Here's an example: \begin{verbatim} TestDB \end{verbatim} Note the example's \texttt{\textless{}entity\textgreater{}} tag attributes: \emph{\texttt{data-source}}: The \texttt{liferayDataSource} alias \texttt{ext-spring.xml} specifies. \emph{\texttt{table}}: Your entity's database table. Also note that your entity's \texttt{\textless{}column\textgreater{}}s must have a \emph{\texttt{db-name}} attribute set to the column name. \item \href{https://learn.liferay.com/dxp/latest/en/installation-and-upgrades/installing-liferay/configuring-a-database.html}{Manually create the database} you defined in your \texttt{service.xml}. \item Define the data source. One way is to use \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties} in a \texttt{portal-ext.properties} file. Distinguish your data source from Liferay's default data source by giving it a prefix other than \texttt{jdbc.default.}. This example uses prefix \texttt{jdbc.ext.}. \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} \item Restart your server if you defined your data source using portal properties. \item Connect your Service Builder module to the data source by implementing the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/jdbc/DataSourceProvider.html}{\texttt{DataSourceProvider}} interface. Since the \texttt{DataSourceProvider} must be visible to your \texttt{*-service} module class loader, it's common to put the \texttt{DataSourceProvider} in the \texttt{*-service} module. This example uses \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/jdbc/DataSourceFactoryUtil.html}{\texttt{DataSourceFactoryUtil}} to create a data source from \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties} that have the prefix \texttt{jdbc.ext.}. \begin{verbatim} package com.liferay.external.data.source.test.internal; import com.liferay.portal.kernel.dao.jdbc.DataSourceFactoryUtil; import com.liferay.portal.kernel.dao.jdbc.DataSourceProvider; import com.liferay.portal.kernel.util.PropsUtil; import javax.sql.DataSource; public class DataSourceProviderImpl implements DataSourceProvider { @Override public DataSource getDataSource() { try { return DataSourceFactoryUtil.initDataSource( PropsUtil.getProperties("jdbc.ext.", true)); } catch (Exception e) { throw new RuntimeException(e); } } } \end{verbatim} \item Register the implementation as a JDK \href{https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html}{\texttt{ServiceProviderInterface}} (SPI) in a \texttt{/META-INF/services/com.liferay.portal.kernel.dao.jdbc.DataSourceProvider} file in your \texttt{*-service} module. For example, this file registers the \texttt{DataSourceProvider} implementation from the previous step. \begin{verbatim} com.liferay.external.data.source.test.internal.DataSourceProviderImpl \end{verbatim} \item \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Run Service Builder}. \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy} your \texttt{-service} module. If your \texttt{DataSourceProvider} is in a different project, deploy it too. \end{enumerate} Congratulations! Your module's Service Builder services are persisting data to your external data source. \section{Related Topics}\label{related-topics-15} \href{/docs/7-2/appdev/-/knowledge_base/a/connecting-to-data-sources-using-jndi}{Connecting to JNDI Data Sources} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Business Logic with Service Builder} \chapter{Connecting the Data Source Using Spring Beans}\label{connecting-the-data-source-using-spring-beans} Sometimes you want to use a database other than Liferay DXP's. To do this, its data source must be defined in \texttt{portal-ext.properties} or configured as a JNDI data source on the app server. Here you'll connect \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} to a data source using Spring XML files. This approach only works with Service Builder modules that use the \texttt{spring} \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#dependency-injector}{dependency injection option}. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Specify your database and a data source name in your \texttt{service.xml}. \item Create the database manually. \item Define the data source. \item Create a Spring bean that points to the data source. \item Run Service Builder. \end{enumerate} \noindent\hrulefill \textbf{Note:} All entities defined in a Service Builder module's \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{\texttt{service.xml}} file are bound to the same data source. Binding different entities to different data sources requires defining the entities in separate Service Builder modules and configuring each of the modules to use a different data source. \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} If your Service Builder services require nested transactions, using an external data source may not be appropriate for you. Transactions between separate data sources cannot be fully nested. Rollbacks may not propagate between a module that uses an external data source and Liferay DXP services (or another app's services) that use a different data source. \noindent\hrulefill \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 \section{\texorpdfstring{Specify Your Database and a Data Source Name in Your \texttt{service.xml}}{Specify Your Database and a Data Source Name in Your service.xml}}\label{specify-your-database-and-a-data-source-name-in-your-service.xml} In your \texttt{service.xml} file, specify the same arbitrary data source name for all of the entities, a unique table name for each entity, and a database column name for each column. Here's an example: \begin{verbatim} TestDB \end{verbatim} Note the example's \texttt{\textless{}entity\textgreater{}} tag attributes: \emph{\texttt{data-source}}: The \texttt{liferayDataSource} alias \texttt{ext-spring.xml} specifies. \emph{\texttt{table}}: Your entity's database table. Also note that your entity's \texttt{\textless{}column\textgreater{}}s must have a \emph{\texttt{db-name}} attribute set to the column name. \section{Create the Database Manually}\label{create-the-database-manually} \href{https://learn.liferay.com/dxp/latest/en/installation-and-upgrades/installing-liferay/configuring-a-database.html}{Create the database} per the database specification in your \texttt{service.xml}. Next, use portal properties to set your data source. \section{Define the Data Source}\label{define-the-data-source} If the application server defines the data source using JNDI, skip this step. Otherwise, specify the data source in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}. Distinguish it from Liferay's default data source by giving it a prefix other than \texttt{jdbc.default.}. This example uses prefix \texttt{jdbc.ext.}: \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} Restart your server if you defined your data source using portal properties. \section{Connect Your Service Builder Module to the Data Source Via a Spring Bean}\label{connect-your-service-builder-module-to-the-data-source-via-a-spring-bean} To do this, create a parent context extension (e.g.,\texttt{ext-spring.xml}) in your \texttt{*-service} module's \texttt{src/main/resources/META-INF/spring} folder or in your traditional portlet's \texttt{WEB-INF/src/META-INF} folder. Create this folder if it doesn't exist already. Define the following elements: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A data source factory Spring bean for the data source. It's different based on the type. \begin{itemize} \tightlist \item \textbf{JNDI}: Specify an arbitrary property prefix and prepend the prefix to a JNDI name property key. Here's an example: \end{itemize} \begin{verbatim} jdbc/externalDataSource \end{verbatim} \begin{itemize} \tightlist \item \textbf{Portal Properties}: Specify a property prefix that matches the prefix (e.g., \texttt{jdbc.ext.}) you used in \texttt{portal-ext.properties}. \end{itemize} \begin{verbatim} \end{verbatim} \item A Liferay data source bean that refers to the data source factory Spring bean. \item An alias for the Liferay data source bean. Name the alias after the data source name you specified in the \texttt{service.xml}. Here's an example \texttt{ext-spring.xml} that points to a JNDI data source: \begin{verbatim} jdbc/externalDataSource \end{verbatim} \end{enumerate} The \texttt{liferayDataSourceFactory} above refers to a JNDI data source named \texttt{jdbc/externalDataSource}. If the data source is in a \texttt{portal-ext.properties} file, the bean requires only a \texttt{propertyPrefix} property that matches the data source property prefix. The data source bean \texttt{liferayDataSource} is overridden with one that refers to the \texttt{liferayDataSourceFactory} bean. The override affects this bundle (module or \href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{Web Application Bundle}) only. The alias \texttt{extDataSource} refers to the \texttt{liferayDataSource} data source bean. \noindent\hrulefill \textbf{Important:} The \texttt{alias} element's \texttt{alias} attribute value must match the data source name specified in the \texttt{service.xml}. For example, the alias attribute value above is \texttt{extDataSource}. \noindent\hrulefill \noindent\hrulefill \textbf{Note}: To use an external data source in multiple Service Builder bundles, you must override the \texttt{liferayDataSource} bean in each bundle. \noindent\hrulefill \section{Run Service Builder}\label{run-service-builder} \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Run Service Builder} and \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy} your \texttt{-service} module. Now your Service Builder services use the data source. You can \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{use the services in your business logic} as you always have regardless of the underlying data source. Congratulations! You've connected Service Builder to your external data source. \section{Related Topics}\label{related-topics-16} \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-using-external-database-via-jndi}{Sample Service Builder Application Using External Database via JNDI} \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-application-using-external-database-via-jdbc}{Sample Service Builder Application Using External Database via JDBC} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Business Logic with Service Builder} \chapter{Migrating a Service Builder Module from Spring DI to OSGi DS}\label{migrating-a-service-builder-module-from-spring-di-to-osgi-ds} Prior to Liferay DXP 7.2, Service Builder modules could only use Spring for dependency injection (DI). Now \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi Declarative Services} (DS) is the default dependency injection mechanism for new Service Builder modules. It's easier to learn and fosters loose coupling between services. If you have an existing Service Builder module that uses Spring DI, you can modify it to use DS. Here are the conversion steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Prepare your project for DS \item Update your Spring bean classes \item Resolve any circular dependencies \end{enumerate} Now prepare your project. \section{Step 1: Prepare Your Project for DS}\label{step-1-prepare-your-project-for-ds} Prepare your project's metadata, dependencies, and \texttt{service.xml} for DS. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enable the DS annotation option for your inherited dependencies by adding this line to your \texttt{bnd.bnd} file: \begin{verbatim} -dsannotations-options: inherit \end{verbatim} \item Since DS Service Builder modules use the AOP API, add it as a compile dependency in \texttt{build.gradle}: \begin{verbatim} compileOnly group: "com.liferay:com.liferay.portal.aop.api", version: "1.0.0" \end{verbatim} \item Add the \texttt{dependency-injector="ds"} attribute to your \texttt{service.xml} file's \texttt{\textless{}service-builder\textgreater{}} element: \begin{verbatim} \end{verbatim} \end{enumerate} \section{Step 2: Update Your Spring Bean Classes}\label{step-2-update-your-spring-bean-classes} Some of your \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{non-generated Spring bean classes} must be updated to use DS. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}} annotation to your \texttt{*LocalServiceImpl}, \texttt{*ServiceImpl}, and \texttt{*FinderImpl} classes. \item If the class implements a \texttt{*Finder} interface, declare the component as that service type. Example: \begin{verbatim} @Component(service = MyFinder.class) \end{verbatim} \item If the class implements a remote or local service, declare the component as the \texttt{com.liferay.portal.aop.AopService} service type. Example: \begin{verbatim} @Component(service = AopService.class) \end{verbatim} \item If it's a remote service (i.e., \texttt{-ServiceImpl} instead of \texttt{-LocalServiceImpl}), enable JSON web services by setting these properties in your \texttt{@Component} annotation: \begin{itemize} \tightlist \item \texttt{json.web.service.context.name} \item \texttt{json.web.service.context.path} \end{itemize} Set them to the same values as the properties in your remote service interface's \href{https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/spring/osgi/OSGiBeanProperties.html}{\texttt{@OSGiBeanProperties}} annotation. \item If it's a local service, enable \href{https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/PersistedModelLocalService.html}{\texttt{PersistedModelLocalService}} service tracking by setting the \texttt{@Component} property \texttt{model.class.name} to the service entity's fully qualified class name. \item Replace all the \texttt{@ServiceReference} and \texttt{@BeanReference} field annotations with the DS \href{https://osgi.org/javadoc/r6/cmpn/org/osgi/service/component/annotations/Reference.html}{\texttt{@Reference}} annotation. \item Use the \texttt{@Reference} field annotation to access any other services you need. \item \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Run Service Builder} to regenerate the interfaces based on your implementation changes. \item Replace the following methods: \begin{itemize} \item \texttt{afterPropertiesSet()\ \{...\}} → \texttt{activate()\ \{...\}} and annotate with \href{https://osgi.org/javadoc/r6/cmpn/org/osgi/service/component/annotations/Activate.html}{\texttt{@Activate}}. \item \texttt{destroy()\ \{...\}} → \texttt{deactivate()\ \{...\}} and annotate with \href{https://osgi.org/javadoc/r6/cmpn/org/osgi/service/component/annotations/Deactivate.html}{\texttt{@Deactivate}}. \end{itemize} \end{enumerate} Next, you'll work out any remaining references you need. \section{Step 3: Resolve Any Circular Dependencies}\label{step-3-resolve-any-circular-dependencies} Circular dependencies occur in a module if two or more of its DS services refer to each another (either directly or indirectly). A direct reference occurs, for example, when service \texttt{A} references service \texttt{B}, and \texttt{B} references \texttt{A}. Here's what the service components might look like: \texttt{AImpl.java}: \begin{verbatim} @Component(service = A.class) public class AImpl implements A { @Reference private B _b; } \end{verbatim} \texttt{BImpl.java}: \begin{verbatim} @Component(service = B.class) public class BImpl implements B { @Reference private A _a; } \end{verbatim} \texttt{AImpl} and \texttt{BImpl} directly depend on each other. This circular dependency prevents each service component from resolving. DS service activation requires that all of a service's dependencies (references) be satisfied. \textbf{Note:} Service resolution is independent and separate from module (OSGi bundle) resolution: \begin{itemize} \tightlist \item Module resolution is determined by the module's manifest. \item Modules resolve before any of their services become active. \item Services inside a module cannot activate if the module cannot resolve. \item A module can resolve even if none of its services activate. \end{itemize} The example above demonstrates a very small circle, composed of only two classes, but a circle can compose more classes. For example, \texttt{A} references \texttt{B}, \texttt{B} references \texttt{C}, \texttt{C} references \texttt{A}. Detecting and resolving such a dependency can be complicated. There is no general, correct way to detect and resolve circular dependencies; cases vary. However, Liferay provides tools that facilitate detecting circular dependencies and examining the DS service components involved. \begin{itemize} \item \texttt{system:check}: This \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo shell} command provides several checks, including one that detects inactive service components whose required references are unresolved. \item \texttt{scr:info\ {[}component{]}}: Execute this \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo shell} command on an unresolved component to report its unresolved references. \end{itemize} \noindent\hrulefill \textbf{Note:} Service resolution in DS dependency injection (DI) is different than in services that use Liferay's Spring DI. In the latter case, all Spring beans in the same module act as a single bundle of services that activate together and can bind together before activation. DS doesn't have this feature. With DS, each component in a module is its own service and must resolve on its own. \noindent\hrulefill Congratulations on converting your service module to use Declarative Services. \section{Related Topics}\label{related-topics-17} \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{Understanding the Code Service Builder Generates} \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative Services} \chapter{Business Logic with Service Builder}\label{business-logic-with-service-builder} Once you've \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{defined your application's entities} and \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{run Service Builder} to generate your service and persistence layers, you can begin adding business logic. Each entity generated by Service Builder contains a model implementation, local service implementation, and optionally a remote service implementation class. Your application's business logic can be implemented in these classes. The generated service layer contains default methods that call CRUD operations from the persistence layer. Once you've added your business logic, running Service Builder again regenerates your application's interfaces and makes your new logic available for invocation. The heart of your service is its \texttt{*LocalServiceImpl} class. This class is your entity's local service extension point. Local services are invoked from your application or by other applications running on the same instance as your application. Creating services takes these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Deciding to Create Local and Remote Services. \item Implementing the \texttt{add} Method. \item Implementing the \texttt{update} and \texttt{delete} Methods. \item Implementing \texttt{get} and \texttt{get*Count} Methods \item Implementing Other Business Logic \item Integrating with Liferay's Services. \end{enumerate} Start with deciding the service types you need. \href{/docs/7-2/appdev/-/knowledge_base/a/creating-the-service-xml-file}{Defining your object model} involves choosing whether to generate local and or remote service interfaces. Local services can only be invoked from the Liferay server on which they're deployed. Remote services are accessible to clients outside of the Liferay server. Before implementing local or remote services, consider the best practices described here: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you plan to have remote services, enable local services too. \item Implement your business logic in \texttt{*LocalServiceImpl}. \item Create corresponding remote services methods in your \texttt{*ServiceImpl}. \item Use the remote service methods to call the local service, wrapping the calls in permission checks. \item In your application, call only the remote services. This ensures that your service methods are secured and that you don't have to duplicate permissions code. \end{enumerate} If you are turning on local or remote services in your \texttt{service.xml} file just now, make sure to \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{run Service Builder} again to generate the service interfaces. Now you're ready to implement your business logic. \chapter{Implementing an Add Method}\label{implementing-an-add-method} Your \texttt{*LocalServiceImpl} represents your service layer, where you create the business logic that operates on your application's data and then calls the persistence layer to persist, retrieve, or delete your data, using the object model defined in \texttt{service.xml}. One of the first methods you'll likely implement is one that creates entities. Liferay's convention is to implement this in an \texttt{add*} method, where the part after \texttt{add} is the entity name (or a shortened version of it). Here are the steps for implementing an \texttt{add*} method: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Declare an \texttt{add*} method with parameters for creating the entity. \item Validate the parameters. \item Generate a primary key. \item Create an entity instance. \item Populate the entity attributes. \item Persist the entity. \item Return the entity instance. \end{enumerate} This article refers to the Guestbook application's \texttt{addGuestbookEntry} method from \texttt{GuestbookEntryLocalServiceImpl}. To keep things simple, we have excluded the code that integrates with Liferay services, such as assets, social bookmarks, and more. Here's the Guestbook application's \texttt{addGuestbookEntry} method: \begin{verbatim} public GuestbookEntry addEntry(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} This method uses the parameters to create \texttt{GuestbookEntry}. It validates the parameters, creates an entry with a generated entry ID (primary key), populates the entry, persists the entry, and returns it. You can refer to this method as you create your own \texttt{add*} method. Note that there's no real business logic here; it's a simple application that takes data the user entered, validates it, and then persists it to the database. \section{Step 1: Declare an add method with parameters for creating the entity}\label{step-1-declare-an-add-method-with-parameters-for-creating-the-entity} Create a public method for \emph{adding} (creating) your application's entity. Make it a public method that returns the entity it creates. \begin{verbatim} public [ENTITY] add[ENTITY](...) { } \end{verbatim} For example, here's the \texttt{addEntry} method signature: \begin{verbatim} public GuestbookEntry addEntry(long userId, long guestbookId, String name, String email, String message, ServiceContext serviceContext) throws PortalException { ... } \end{verbatim} This method specifies all the parameters needed to create and populate a \texttt{GuestbookEntry} as you specified them in your \texttt{service.xml} file. It throws a \texttt{PortalException} in case the parameters are invalid or a processing exception occurs (more on this in a later step). Make sure to account for primary keys of other related entities. For example, the \texttt{addEntry} method above includes a parameter \texttt{long\ guestbookId} to associate the new \texttt{GuestbookEntry} to a \texttt{Guestbook}. \section{Step 2: Validate the parameters}\label{step-2-validate-the-parameters} Validate the parameters as needed. You might need to make sure a parameter is not empty or \texttt{null}, or that a parameter value is within a valid range. Throw a \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/exception/PortalException.html}{\texttt{PortalException}} or an extension of \texttt{PortalException} for any invalid parameters. For example, the \texttt{addEntry} method invokes the following \texttt{validate} method to check if the URL parameter is \texttt{null}. \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} Next, generate a primary key for the entity instance you're creating. \section{Step 3: Generate a primary key}\label{step-3-generate-a-primary-key} Entities must each have a unique primary key. Liferay's \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/counter/kernel/service/CounterLocalService.html}{\texttt{CounterLocalService}} generates them per entity. Every \texttt{*BaseLocalServiceImpl} has a \texttt{counterLocalService} field that references a \texttt{CounterLocalService} object for the entity. Invoke the counter service's \texttt{increment} method to generate a primary key for your entity instance. \begin{verbatim} long id = counterLocalService.increment(); \end{verbatim} Now you have a unique ID for your entity instance. Always generate primary keys in this way, as it ensures your code is compatible with all the databases Liferay supports. \section{Step 4: Create an entity instance}\label{step-4-create-an-entity-instance} The \texttt{*Peristence} instance associated with your entity has a \texttt{create(long\ id)} method that constructs an entity instance with the given ID. Every \texttt{*BaseLocalServiceImpl} has a \texttt{*Persistence} field that references a \texttt{*Persistence} object for the entity. For example, \texttt{GuestbookEntryLocalServiceImpl} as a child of \texttt{GuestbookEntryLocalServiceBaseImpl} has a field \texttt{guestbookEntryPersistence}, which is a reference to a \texttt{GuestbookEntryPersistence} instance. \begin{verbatim} @Reference protected GuestbookEntryPersistence guestbookEntryPersistence; \end{verbatim} \texttt{GuestbookEntryLocalServiceImpl}'s \texttt{addEntry} method creates a \texttt{GuestbookEntry} instance using this call: \begin{verbatim} GuestbookEntry entry = guestbookEntryPersistence.create(entryId); \end{verbatim} To create an instance of your entity, invoke the \texttt{create} method on the \texttt{*Persistence} field associated with the entity, making sure to pass in the entity primary key you generated in the previous step. \begin{verbatim} [ENTITY_NAME] entity = [ENTITY_NAME]Persistence.create(id); \end{verbatim} It's time to populate the new entity instance. \section{Step 5: Populate the entity attributes}\label{step-5-populate-the-entity-attributes} Use the \texttt{add*} method parameter values and the entity's setter methods to populate your entity's attributes. For example, here are the \texttt{GuestbookEntry} attribute assignments: \begin{verbatim} 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); \end{verbatim} Note that the \texttt{ServiceContext} is commonly used to carry an entity's UUID and the \texttt{User} is associated to a company. \section{Step 6: Persist the entity}\label{step-6-persist-the-entity} It's time to store the entity. Invoke the \texttt{*Persistence} field's \texttt{update} method, passing in the entity object. For example, here's how the new \texttt{GuestbookEntry} is persisted: \begin{verbatim} guestbookEntryPersistence.update(entry); \end{verbatim} Your entity is persisted for the application. \section{Step 7: Return the entity}\label{step-7-return-the-entity} Finally, return the entity you just created so the caller can use it. \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{Run Service Builder} to propagate your new service method to the \texttt{*LocalService} interface. You've implemented your local service's \texttt{add*} method to create and persist your application's entities. \chapter{Implementing Update and Delete Methods}\label{implementing-update-and-delete-methods} After you've implementing an \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method}{\texttt{add*} method} for creating service entities, you'll want to create \hyperref[implementing-an-update-method]{\texttt{update*}} and \hyperref[implementing-a-delete-method]{\texttt{delete*}} methods for updating and deleting them. The main difference between these and the \texttt{add*} method is they must know which entity they're updating or deleting. \section{Implementing an Update Method}\label{implementing-an-update-method} An \texttt{update*} method for a local service resembles an \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method}{\texttt{add*} method} most because it has parameters for setting entity attribute values. Create an \texttt{update*} method this way: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Declare an \texttt{update*} method with parameters for updating the entity. \item Validate the parameters. \item Retrieve the entity instance, if necessary. \item Update the entity attributes. \item Persist the updated entity. \item Run Service Builder. \end{enumerate} The following code snippets from \texttt{GuestbookEntryLocalServiceImpl}'s \texttt{updateEntry} method are helpful to examine. \begin{verbatim} public GuestbookEntry updateEntry(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 = getGuestbookEntry(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 has all the makings of a good \texttt{update*} method: \begin{itemize} \tightlist \item parameter for looking up the entity instance \item parameters for updating the entity attributes \item parameter validation \item entity attribute updates \item entity persistence \item returns the entity instance \end{itemize} Refer to the example method above as you follow the steps to create your own \texttt{update*} method. \section{Step 1: Declare an Update Method with Parameters for Updating the Entity}\label{step-1-declare-an-update-method-with-parameters-for-updating-the-entity} Create a public method for updating your application's entity. \begin{verbatim} public [ENTITY] update[ENTITY](...) throws PortalException { } \end{verbatim} Replace \texttt{{[}ENTITY{]}} with your entity's name or nickname. Create a parameter list that satisfies the entity attributes you're updating. Include an entity instance parameter or an ID parameter for fetching the entity instance. For example, the \texttt{GuestbookEntryLocalServiceImpl}'s \texttt{updateEntry} method signature has an ID parameter (\texttt{entryId}) for fetching the \texttt{GuestbookEntry} entity instance. Also it has parameters \texttt{folderId}, \texttt{name}, \texttt{url}, and \texttt{description} for updating the \texttt{GuestbookEntry}'s respective attributes. \begin{verbatim} public GuestbookEntry updateEntry(long userId, long guestbookId, long entryId, String name, String email, String message, ServiceContext serviceContext) throws PortalException, SystemException { \end{verbatim} Note, user ID, group ID, and service context parameters are useful for integrating with Liferay's services. More on that later. \section{Step 2: Validate the Parameters}\label{step-2-validate-the-parameters-1} Similar to validating the \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method}{\texttt{add*} method} parameters, validate your \texttt{update*} parameters. Your \texttt{add*} and \texttt{update*} methods might be able to use the same validation code. Throw a \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/exception/PortalException.html}{\texttt{PortalException}} or an extension of \texttt{PortalException} for any invalid parameters. \section{Step 3: Retrieve the Entity Instance}\label{step-3-retrieve-the-entity-instance} If you're passing in an entity instance, you can update it directly. Otherwise, pass in the entity ID (the primary key). The \texttt{*Persistence} class Service Builder injects into \texttt{*BaseLocalServiceImpl} classes has a \texttt{findByPrimaryKey(long)} method that retrieves instances by ID. For example, the \texttt{GuestbookEntryLocalServiceImpl} retrieves the \texttt{GuestbookEntry} with the primary key \texttt{entryId}. \begin{verbatim} GuestbookEntry entry = guestbookEntryPersistence.findByPrimaryKey( entryId); \end{verbatim} Invoke the \texttt{findByPrimaryKey(long\ id)} method of your \texttt{*Persistence} class to retrieve the entity instance that matches your primary key parameter. \begin{verbatim} [ENTITY] entity = [ENTITY]Persistence.findByPrimaryKey(id); \end{verbatim} It's time to update the entity attributes. \section{Step 4: Update the Entity Attributes}\label{step-4-update-the-entity-attributes} Invoke the entity's setter methods to replace its attribute values. \section{Step 5: Persist and Return the Updated Entity Instance}\label{step-5-persist-and-return-the-updated-entity-instance} Persist the updated entity to the database and return the instance to the caller. \begin{verbatim} [ENTITY]Persistence.update(entity); ... return entity; \end{verbatim} \section{Step 6: Run Service Builder}\label{step-6-run-service-builder} Finally, run Service Builder to propagate your new service method to the \texttt{*LocalService} interface. You've created a service method to update your entity. If you thought that was easy, implementing a \texttt{delete*} method is even easier. \section{Implementing a Delete Method}\label{implementing-a-delete-method} The \texttt{remove} method of an entity's \texttt{*Persistence} class deletes an entity instance from the database. Use it in your local service's \texttt{delete*} method. Here's what a \texttt{delete*} method looks like: \begin{verbatim} public [ENTITY] delete[ENTITY](ENTITY entity) throws PortalException { [ENTITY]Persistence.remove(entity); // Clean up related to additional Liferay services goes here ... return entity; } \end{verbatim} Make sure to replace \texttt{{[}ENTITY{]}} with your entity's name or nickname. For example, here's paraphrased code from \texttt{GuestbookEntryLocalServiceImpl}'s \texttt{deleteEntry} method: \begin{verbatim} public GuestbookEntry deleteEntry(GuestbookEntry entry) throws PortalException { guestbookEntryPersistence.remove(entry); // Clean up related to additional Liferay services goes here ... return entry; } \end{verbatim} After implementing your \texttt{delete*} method, run Service Builder to propagate your new service method to the \texttt{*LocalService} interface. \section{Related Topics}\label{related-topics-18} \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method}{Implementing an add method} \chapter{Implementing Methods to Get and Count Entities}\label{implementing-methods-to-get-and-count-entities} Service Builder generates \texttt{findBy*} methods and \texttt{countBy*} methods in your \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{\texttt{*Persistence} classes} based on your \texttt{service.xml} file's \href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{finders}. You can leverage finder methods in your local services to get and count entities. \begin{itemize} \tightlist \item \hyperref[getter-methods]{Getters}: \texttt{get*} methods return entity instances matching criteria. \item \hyperref[counter-methods]{Counters}: \texttt{get*Count} methods return the number of instances matching criteria \end{itemize} Start with getting entities that match criteria. \section{Getter Methods}\label{getter-methods} The \texttt{findByPrimaryKey} methods and \texttt{findBy*} methods search for and return entity instances based on criteria. Your local service implementation must only wrap calls to the finder methods that get what you want. Here's how to create a method that gets an entity based on an ID (primary key): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a method using this format: \begin{verbatim} public [ENTITY] get[ENTITY_NAME](long id) { return [ENTITY]Persistence.findByPrimaryKey(id); } \end{verbatim} \item Replace \texttt{{[}ENTITY{]}} and \texttt{{[}ENTITY\_NAME{]}} with the respective entity type and entity name (or nickname). \item Run Service Builder to propagate the method to your local service interface. \end{enumerate} Here's how to get entities based on criteria: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Identify the criteria for finding the entity instance(s). \item If there is no \href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{\texttt{finder} element} for the criteria, create one for it and run Service Builder. \item Determine the \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{\texttt{*Persistence} class} \texttt{findBy*} method you want to call. Depending on your \texttt{finder} element columns, Service Builder might overload the method to include these parameters: \begin{itemize} \tightlist \item \texttt{int\ start} and \texttt{int\ end} parameters for specifying a range of entities. \item \texttt{com.liferay.portal.kernel.util.OrderByComparator\ orderByComparator} parameter for arranging the matching entities. \end{itemize} \item Specify your \texttt{get*} method signature, making sure to account for the \texttt{*Persistence} class \texttt{findBy*} method parameters you must satisfy. Use this method format: \begin{verbatim} public List<[ENTITY]> get[DESCRIBE_THE_ENTITIES](...) { } \end{verbatim} Replace \texttt{{[}ENTITY{]}} with the entity type. Replace \texttt{{[}DESCRIBE\_THE\_ENTITIES{]}} with a descriptive name for the entities you're getting. \item Call the \texttt{*Persistence} class \texttt{findBy*} method and return the list of matching entities. \item Run Service Builder. \end{enumerate} For example, \texttt{getGuestbookEntries} from \texttt{GuestbookEntryLocalServiceImpl} returns a range of \texttt{GuestbookEntry}s associated with a \texttt{Group} primary key: \begin{verbatim} public List getGuestbookEntries(long groupId, long guestbookId) { return guestbookEntryPersistence.findByG_G(groupId, guestbookId); } \end{verbatim} Now you know how to leverage finder methods to get entities. Methods that count entities are next. \section{Counter Methods}\label{counter-methods} Counting entities is just as easy as getting them. Your \texttt{*Persistence} class \texttt{countBy*} methods do all the work. Service Builder generates \texttt{countBy*} methods based on each finder and its columns. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Identify the criteria for entity instances you're counting and determine the \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{\texttt{*Persistence} class} \texttt{countBy*} method that satisfies the criteria. \item Create a \texttt{get*Count} method signature following this format: \begin{verbatim} public int get[DESCRIBE_THE_ENTITIES]Count(...) { } \end{verbatim} Replace \texttt{{[}DESCRIBE\_THE\_ENTITIES{]}} with a descriptive name for the entities you're counting. \item Call the \texttt{*Persistence} class' \texttt{countBy} method and return the value. For example, the method \texttt{getEntriesCount} from \texttt{GuestbookEntryLocalServiceImpl} returns the number of \texttt{GuestbookEntry}s that are associated with a group (matching \texttt{groupId}) and a guestbook (matching \texttt{guestbookId}). \end{enumerate} \begin{verbatim} public int getGuestbookEntriesCount(long groupId, long guestbookId) { return guestbookEntryPersistence.countByG_G(groupId, guestbookId); } \end{verbatim} Now your local service can get entities matching your criteria and return quick entity counts. \section{Service Method Prefixes and Transactional Aspects}\label{service-method-prefixes-and-transactional-aspects} Service Builder applies transactions to services by adding \texttt{@Transactional} annotations to the \texttt{*LocalService} and \texttt{*Service} interfaces and their methods. By default, Service Builder applies read-only transactions (e.g., \texttt{@Transactional\ (readOnly\ =\ true\ ...)}) to service methods prefixed with any of these words: \begin{itemize} \tightlist \item \texttt{dynamicQuery} \item \texttt{fetch} \item \texttt{get} \item \texttt{has} \item \texttt{is} \item \texttt{load} \item \texttt{reindex} \item \texttt{search} \end{itemize} Since these methods operate in read-only transactions, Liferay DXP optimizes their performance. Transactional service methods that don't have the read-only setting operate in regular transactions. \noindent\hrulefill \textbf{Note:} A method implementation can override its interface's \texttt{@Transactional} annotation attributes. For example, applying \texttt{@Transactional\ (readOnly\ =\ false\ ...)} to a method implementation makes it operate in a transaction that is not read only. \noindent\hrulefill \textbf{Important:} In methods that operate in read-only transactions, invoking a service method that persists data (adds, updates, or deletes data) must be done via the service object. Using the service object ensures that the defined transactional behavior is applied. \begin{verbatim} someService.addSomething(); \end{verbatim} For example, this \texttt{*LocalServiceImpl}'s getter method adds (\emph{persists}) a \texttt{ClassName} object if no object with that value exists. \begin{verbatim} public ClassName getClassName(String value) { if (Validator.isNull(value)) { return _nullClassName; } ClassName className = _classNames.get(value); if (className == null) { try { className = classNameLocalService.addClassName(value); ... } ... } ... } \end{verbatim} Using the service object \texttt{classNameLocalService} to invoke its \texttt{addClassName} method applies the service method's transaction (the regular transaction specified for the method in the \texttt{*Service} interface). If the \texttt{addClassName} method was invoked WITHOUT using the service object, the \texttt{ClassName} object would not persist because the method's regular transaction would not be applied. \section{Related Topics}\label{related-topics-19} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating Local Services} \href{/docs/7-2/appdev/-/knowledge_base/a/implementing-an-add-method}{Implementing an Add Method} \href{/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods}{Defining Service Entity Finder Methods} \href{/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder}{Understanding the Code Generated by Service Builder} \chapter{Implementing Any Other Business Logic}\label{implementing-any-other-business-logic} This section's earlier local service articles focus on CRUD methods: methods that \textbf{c}reate (add), \textbf{r}ead (get), \textbf{u}pdate, and \textbf{d}elete entities. But you might also need methods that provide business logic. Since the Guestbook application doesn't have any business logic, the Bookmarks application, which is extremely similar, (Bookmark Folders and Bookmark entries instead of Guestbooks and Guestbook entries) is used here to illustrate simple business logic. Bookmarks application users \emph{open} bookmarks (navigate to a URLs) by clicking on them. \href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/bookmarks/bookmarks-service/src/main/java/com/liferay/bookmarks/service/impl/BookmarksEntryLocalServiceImpl.java}{\texttt{BookmarksEntryLocalServiceImpl}}'s \texttt{openEntry} method supports this functionality: \begin{verbatim} public BookmarksEntry openEntry(long userId, BookmarksEntry entry) { entry.setVisits(entry.getVisits() + 1); bookmarksEntryPersistence.update(entry); assetEntryLocalService.incrementViewCounter( userId, BookmarksEntry.class.getName(), entry.getEntryId(), 1); return entry; } \end{verbatim} The \texttt{openEntry} method tracks and persists the number of visits to the \texttt{BookmarksEntry}'s URL, increments the number of views for the \texttt{BookmarksEntry} as an asset, and returns the \texttt{BookmarksEntry}. This method implements required business logic that compliments the CRUD methods. \emph{Convenience methods} might also be appropriate for your app. They're easier to use because they typically have these characteristics: \begin{itemize} \tightlist \item Shorter parameter list \item Intuitive name \end{itemize} Short parameter lists are easier to satisfy, and methods that have intuitive names are easier to find in Javadoc. For example, the Bookmarks application lets users move bookmarks to different folders. Moving a bookmark can be done using the service's \texttt{updateEntry(...)} method, but its long parameter list is overkill since all the operation requires is the bookmarks entry and the folder where it's going. Compare the following \texttt{update*} method call to a convenience method call. \textbf{Update method}: \begin{verbatim} bookmarksEntryLocalService.updateEntry(userId, entryId, groupId, folderId, name, url, description, serviceContext); \end{verbatim} \textbf{Convenience method}: \begin{verbatim} bookmarksEntryLocalService.moveEntry(entryId, folderId); \end{verbatim} Here's the \href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/bookmarks/bookmarks-service/src/main/java/com/liferay/bookmarks/service/impl/BookmarksEntryLocalServiceImpl.java}{\texttt{moveEntry} method}: \begin{verbatim} public BookmarksEntry moveEntry(long entryId, long parentFolderId) throws PortalException { BookmarksEntry entry = getBookmarksEntry(entryId); entry.setFolderId(parentFolderId); entry.setTreePath(entry.buildTreePath()); bookmarksEntryPersistence.update(entry); return entry; } \end{verbatim} The \texttt{moveEntry} method retrieves the \texttt{BookmarksEntry} entity by its ID, assigns it a new parent folder, updates its tree path, persists all the entity's changes, and returns the entity. Convenience methods like this one facilitate updating a subset of the entity's attributes. After implementing your custom business methods, \href{/docs/7-2/appdev/-/knowledge_base/a/running-service-builder}{run Service Builder} to propagate them to the interface. In your local services, you can implement business logic methods that suit your application. \textbf{Related Topics} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating Local Services} \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{Invoking Local Services} \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-services-from-spring-service-builder-code}{Invoking Services from Spring Service Builder Code} \chapter{Integrating with Liferay's Frameworks}\label{integrating-with-liferays-frameworks} New car buyers expect certain standard features: power windows, cruise control, floor mats (at least the cheap ones), and so on. Similarly, users expect applications to have certain features, and those features should behave consistently across applications. For example, a user might expect the app's content can be shared on social networks, tagged and rated, and discussed in comments. Liferay's frameworks implement these features users expect to see. Integrating with the frameworks is easy, and the frameworks provide intuitive, consistent user experiences. Here are some of Liferay's most popular frameworks: \href{/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions}{Permissions}: Defines resources and permissions for entities and actions that can be performed on them. \href{/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications}{Configurable Applications}: Provide configuration screens in the Control Panel. \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Workflow}: Equips entities for reviewing in workflows before publishing. \href{/docs/7-2/frameworks/-/knowledge_base/f/item-selector}{Item Selector}: Provides a consistent experience for browsing and selecting entities. \href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset Framework}: Provides a way to make entity data generic, so common tasks can be performed on them. This enables users to tag, categorize, rate, prioritize, and comment on anything that has been asset-enabled. Users can relate entities to each other as assets, and entities can be published in the Asset Publisher. \begin{itemize} \tightlist \item \href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-asset-categorization-and-tagging}{Tags and Categories}: Enables users to tag entities and categorize them into logical hierarchies. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/implementing-asset-priority}{Priority}: Users can ascribe numerical priorities to entities. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/relating-assets}{Related Assets}: Users can associate one entity with another as an asset. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset Renderer}: Enables entities appearing in Asset Publisher queries. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/adding-comments-to-your-app}{Comments}: Lets users comment on entities. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/rating-assets}{Ratings}: Enables rating systems, such as five stars or thumbs up/down, on entities. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/flagging-inappropriate-asset-content}{Flags}: Users can flag entity content as inappropriate. \item \href{/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks}{Social Bookmarks}: Users can share entity content on Twitter, Facebook, and more. \end{itemize} \href{/docs/7-2/frameworks/-/knowledge_base/f/export-import}{Export/Import}: Export entity data to and import entity data from files (\texttt{.lpkg} files). Exported data can be imported to another portal instance or saved for later use. \href{/docs/7-2/frameworks/-/knowledge_base/f/staging}{Staging}: Modify content behind the scenes without affecting the live site, and then publish to the live site when the content is ready. \href{/docs/7-2/frameworks/-/knowledge_base/f/search}{Search}: Index entities for searching. \href{/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin}{Recycle Bin}: Instead of deleting entities, put them into the Recycle Bin. Entities can be restored from the Recycle Bin or deleted permanently (manually or per a schedule). \section{Related Topics}\label{related-topics-20} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{Internationalization} \href{/docs/7-2/frameworks/-/knowledge_base/f/javascript-module-loaders}{JavaScript Module Loaders} \href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{Java Taglibs} \href{/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes}{Upgrade Processes} \chapter{Invoking Local Services}\label{invoking-local-services} Once you deploy your services module, those services are available in the container. Service Builder generates local and remote service classes as OSGi Declarative Services (DS) components. These components are accessible to other DS components, so you can invoke them from other components, such as your web application. Here's how: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a reference to the local service component. \item Call the component's methods. \end{enumerate} There's a Blade sample called \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-samples}{Basic Service Builder}. Its \texttt{basic-web} module has a \texttt{Portlet} service component that demonstrates referencing a local service component. This module also has JSPs that invoke the component's methods. Your first step is to add a reference to the local service component object. \section{Step 1: Reference the Local Service Component}\label{step-1-reference-the-local-service-component} Your application's Service Builder-generated local services are DS components that you can inject into your application's other DS components (classes annotated with \texttt{@Component}) \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{using the \texttt{@Reference} annotation}. The \texttt{basic-web} module's \texttt{JSPPortlet} class is a \texttt{Portlet} service component that references the \texttt{FooLocalService} local service as a DS component. \begin{verbatim} @Reference private volatile FooLocalService _fooLocalService; \end{verbatim} The OSGi service registry wires the service implementation object to your class that references it. The \texttt{JSPPortlet} sample class declares the \texttt{\_fooLocalService} field to be volatile, but making a field volatile is completely optional. \noindent\hrulefill \textbf{Note}: If you chose Spring as the dependency injector, Service Builder generates \texttt{*LocalServiceImpl}, \texttt{*ServiceImpl}, \texttt{*PersistenceImpl}, and \texttt{{[}ENTITY\_NAME{]}Impl} classes for your entities as Service Builder Spring Beans---not OSGi Declarative Services. \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-services-from-spring-service-builder-code}{Service Builder Spring Beans must use means other than the \texttt{@Reference} annotation to reference Liferay services and OSGi services}. \noindent\hrulefill \textbf{Important:} You should never invoke \texttt{*LocalServiceImpl} objects directly. You should only invoke them indirectly through their \texttt{*LocalService} service interface. The OSGi service registry wires the service implementation object to your class. You can make a service object available to JSPs by associating it with a \texttt{RenderRequest} attribute. For example, the \texttt{JSPPortlet}'s \texttt{render} method associates the \texttt{FooLocalService} object with an attribute called \texttt{fooLocalService}. \begin{verbatim} @Override public void render(RenderRequest request, RenderResponse response) throws IOException, PortletException { //set service bean request.setAttribute("fooLocalService", getFooLocalService()); super.render(request, response); } public FooLocalService getFooLocalService() { return _fooLocalService; } \end{verbatim} If your JSP declares the \texttt{\textless{}portlet:defineObjects\ /\textgreater{}} tag, it can retrieve the service object from the \texttt{RenderRequest} attribute. For example, the \texttt{JSPPortlet}'s \texttt{init.jsp} file retrieves the \texttt{FooLocalService} object from the \texttt{"fooLocalService"} attribute. \begin{verbatim} ... <%@ page import="com.liferay.blade.samples.servicebuilder.service.FooLocalService" %> ... <% ... //get service bean FooLocalService fooLocalService = (FooLocalService)request.getAttribute("fooLocalService"); %> \end{verbatim} All JSPs that include the above \texttt{init.jsp} can use the \texttt{fooLocalService} variable to invoke the local service component's methods. \section{Step 2: Call the Component's Methods}\label{step-2-call-the-components-methods} Now that you have the service component object, you can invoke its methods as you would any Java object's methods. The \texttt{basic-web} sample module's \texttt{view.jsp} and \texttt{edit\_foo.jsp} files include the \texttt{init.jsp} shown in the previous section. Therefore, they can access the \texttt{fooLocalService} variable which references the service component object. The \texttt{view.jsp} file uses the component's \texttt{getFoosCount} method and \texttt{getFoos} method in a Liferay Search Container that lists \texttt{Foo} instances. \begin{verbatim} ... \end{verbatim} The \texttt{edit\_foo.jsp} file calls \texttt{getFoo(long\ id)} to retrieve a \texttt{Foo} entity based on the entity instance's ID. \begin{verbatim} long fooId = ParamUtil.getLong(request, "fooId"); Foo foo = null; if (fooId > 0) { foo = fooLocalService.getFoo(fooId); } \end{verbatim} \noindent\hrulefill \textbf{Important:} When invoking service entity updates (e.g., \texttt{fooService.update(object)}) for services that have MVCC enabled, make sure to do so in transactions. Propagate rejected transactions to the UI for the user to handle. For details, see \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#multiversion-concurrency-control-mvcc}{Multiversion concurrency control (MVCC)}. \noindent\hrulefill Using the \texttt{@Reference} annotation, you can inject your application's OSGi DS components (such as a portlet DS component) with instances of your application's Service Builder-generated local service components. Also you can provide your JSPs access to the component instances via \texttt{RenderRequest} attributes. \section{Related Topics}\label{related-topics-21} \href{/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder}{Creating Local Services} \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{Invoking Local Services} \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-services-from-spring-service-builder-code}{Invoking Local Services from Spring Service Builder Code} \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{OSGi Services and Dependency Injection with Declarative Services} \chapter{Invoking Services from Spring Service Builder Code}\label{invoking-services-from-spring-service-builder-code} When using Spring as the dependency injector, all the services created within a Service Builder application are wired using an internal Spring Application Context. This uses AOP proxies to adapt the services for transactions, indexing, and security. In a module's \texttt{module-spring.xml} Spring Application Context file, Service Builder defines each entity's \texttt{*LocalServiceImpl}, \texttt{*ServiceImpl}, and \texttt{*PersistenceImpl} classes as Spring Beans. For example, Service Builder defines Spring Beans for the \texttt{Foo} entity in the \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-samples}{Liferay Blade Service Builder \texttt{basic-service} sample module's} \texttt{src/main/resources/META-INF/spring/module-spring.xml} file: \begin{verbatim} \end{verbatim} Here's a summary of the beans the example context defines: \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4118}} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5882}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright \textbf{Interface ID} \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright \textbf{Implementation Class} \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot \texttt{com.liferay.blade.samples.servicebuilder.service.FooLocalService} & \texttt{com.liferay.blade.samples.servicebuilder.service.impl.FooLocalServiceImpl} \\ \texttt{com.liferay.blade.samples.servicebuilder.service.FooService} & \texttt{com.liferay.blade.samples.servicebuilder.service.impl.FooServiceImpl} \\ \texttt{com.liferay.blade.samples.servicebuilder.service.persistence.FooPersistence} & \texttt{com.liferay.blade.samples.servicebuilder.service.persistence.impl.FooPersistenceImpl} \\ \end{longtable} Since these classes are Spring Beans and NOT OSGi Declarative Services components, they don't use the \texttt{@Reference} Declarative Services annotation to inject themselves. Here are the recommended Liferay annotations a Service Builder Spring Bean can use. \begin{itemize} \tightlist \item Use \texttt{@BeanReference} to reference a Spring Bean that is in the Application Context. \item Use \texttt{@ServiceReference} to reference an OSGi service. \end{itemize} \noindent\hrulefill \textbf{Important:} When invoking service entity updates (e.g., \texttt{fooService.update(object)}) for services that have MVCC enabled, make sure to do so in transactions. Propagate rejected transactions to the UI for the user to handle. For details, see \href{/docs/7-2/appdev/-/knowledge_base/a/defining-global-service-information\#multiversion-concurrency-control-mvcc}{Multiversion concurrency control (MVCC)}. \noindent\hrulefill The \texttt{@BeanReference} annotation is explained first. \section{Referencing a Spring Bean that is in the Application Context}\label{referencing-a-spring-bean-that-is-in-the-application-context} A Service Builder Spring Bean class, such as a \texttt{*LocalServiceImpl} class, should use Liferay's \texttt{@BeanReference} annotation to access other Spring Beans the module's Spring Application Context defines. For example, if your service module's \texttt{service.xml} file defines local services for entities named \texttt{Foo} and \texttt{Bar}, Service Builder generates a \texttt{module-spring.xml} file that defines local service Spring Beans for both entities. To inject the \texttt{BarLocalService} Spring Bean into the \texttt{FooLocalServiceImpl} class, for example, the \texttt{FooLocalServiceImpl} class must declare a \texttt{BarLocalService} field and apply an \texttt{@BeanReference} annotation to it. \begin{verbatim} @BeanReference private BarLocalService _barLocalService; \end{verbatim} The \texttt{@BeanReference} tells Liferay's AOP to treat the bean reference for use in transactions, search indexing, or security, if needed. The referencing class can invoke the Spring Bean class's methods. Besides the services Service Builder makes available for your application, Service Builder Spring Bean classes can also access any service published in the OSGi Registry. This means the following services are available: \begin{itemize} \tightlist \item Beans defined in Liferay's core \item Beans created in other module app contexts \item Services declared using OSGi Declarative Services \item Services registered using the OSGi low level API \end{itemize} These are all OSGi services. The next section demonstrates a Service Builder Spring Bean referencing OSGi services. \section{Referencing OSGi Services}\label{referencing-osgi-services} In many cases, your Service Builder code (Spring Beans) must use external services. Liferay's \texttt{@ServiceReference} annotation lets Liferay Spring Beans reference OSGi services. Suppose you're building an application with a simple entity your service module defines in its \texttt{service.xml} file. The application must send an SMS every time a new entity is created, and the \texttt{SMSService} is provided by a module installed in the system. Your \texttt{*LocalServiceImpl} (Spring Bean) could use an \texttt{@ServiceReference} annotation to reference the \emph{external} service. \begin{verbatim} @ServiceReference private SMSService _smsService; \end{verbatim} This annotation retrieves a reference to the OSGi service and provides some nice benefits. None of the Spring context is created until the \texttt{SMSService} service is available. Likewise, if the \texttt{SMSService} suddenly disappears, the whole Spring Application Context is destroyed. This makes Liferay Spring apps robust and versatile. Fortunately, Service Builder generates this kind of code for every entity your \texttt{service.xml} file references. For example, the \href{/docs/7-2/reference/-/knowledge_base/r/service-builder-samples}{Liferay Blade Service Builder sample project} \texttt{basic-service} module's \texttt{service.xml} file defines a \texttt{Foo} entity that references an \texttt{AssetEntry} entity: \begin{verbatim} \end{verbatim} Service Builder generated the \texttt{FooLocalServiceBaseImpl} class (the base class is part of the \texttt{FooLocalServiceImpl} class's hierarchy), which references the \texttt{AssetEntry} entity's local service \texttt{AssetEntryLocalService} using a field annotated with \texttt{@ServiceReference}: \begin{verbatim} @ServiceReference(type = com.liferay.asset.kernel.service.AssetEntryLocalService.class) protected com.liferay.asset.kernel.service.AssetEntryLocalService assetEntryLocalService; \end{verbatim} Great! You now know how to add a reference to any OSGi service to a Service Builder Spring Bean. You also know how to add a reference to any other Spring Bean in the Application Context of your Service Builder Spring Bean. \section{Related Topics}\label{related-topics-22} \href{/docs/7-2/appdev/-/knowledge_base/a/invoking-local-services}{Invoking Local Services} \href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{Service Trackers} \chapter{Advanced Queries}\label{advanced-queries} Service Builder doesn't limit you to what you can cook up with \texttt{\textless{}finder\ /\textgreater{}} elements in \texttt{service.xml}. If simple finders aren't sufficient for getting data out of your application, you can use Liferay's Dynamic Query API, which wraps Hibernate's Criteria API, or your own SQL to make exactly the queries you need. Though you can use custom SQL queries with Service Builder to retrieve data from the database, sometimes it's more convenient to build queries dynamically at runtime. You can do this with Liferay's Dynamic Query API, which wraps Hibernate's Criteria API. The Dynamic Query API lets you build queries without writing any SQL. It helps you think in terms of objects and member variables instead of tables and columns. Complex queries can be significantly easier to understand and maintain than the equivalent custom SQL (or HQL) queries. While you technically don't need to know SQL to construct Dynamic Queries, you still must take care to construct efficient queries. For information on Hibernate's Criteria API, please see \href{http://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/chapters/query/criteria/Criteria.html}{Hibernate's manual}. Whichever way you decide to implement your custom queries, this guide shows you how. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If using SQL, \href{/docs/7-2/appdev/-/knowledge_base/a/custom-sql}{create your SQL query}. \item \href{/docs/7-2/appdev/-/knowledge_base/a/defining-a-custom-finder-method}{Define a custom finder method}. \item \href{/docs/7-2/appdev/-/knowledge_base/a/dynamic-query}{Implement your finder using Dynamic Query API or SQL}. \item \href{/docs/7-2/appdev/-/knowledge_base/a/accessing-your-custom-finder-method-from-the-service-layer}{Add a method to your \texttt{*LocalServiceImpl} class that invokes your finder}. \end{enumerate} Once you've taken these steps, you can access your custom finder as a service method. Note: You can create multiple or overloaded \texttt{findBy*} finder methods in your \texttt{*FinderImpl} class. Next, you'll examine these steps in more detail. \chapter{Custom SQL}\label{custom-sql} Service Builder creates finder methods that retrieve entities by their attributes: their column values. When you add a column as a parameter for the finder in your \texttt{service.xml} file and run Service Builder, it generates the finder method in your persistence layer and adds methods to your service layer that invoke the finder. If your queries are simple enough, consider using \href{/docs/7-2/appdev/-/knowledge_base/a/dynamic-query}{Dynamic Query} to access Liferay's database. If you want to do something more complicated like JOINs, you can write your own custom SQL queries. Here, you'll learn how. The Guestbook application has two tables, one for guestbooks and one for guestbook entries. The entry entity's foreign key to its guestbook is the guestbook's ID. That is, the entry entity table, \texttt{GB\_GuestbookEntry}, tracks an entry's guestbook by its long integer ID in the table's \texttt{guestbookId} column. If you want to find a guestbook entry based on its name, message, and guestbook name, you must access the \emph{name} of the entry's guestbook. Of course, with SQL you can join the entry and guestbook tables to include the guestbook name. Service Builder lets you do this by specifying the SQL as \emph{Liferay custom SQL} and invoking it in your service via a \emph{custom finder method}. Using Custom SQL in Service Builder is the same as using dynamic queries; it just takes an additional first step to place the SQL you want to run in an XML file. If you plan to use dynamic queries instead, skip the rest of this tutorial and move on to the next one. \section{Specify Your Custom SQL}\label{specify-your-custom-sql} After you've tested your SQL, you must specify it in a particular file for Liferay to access it. \texttt{CustomSQL} class (from module \href{https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.portal.dao.orm.custom.sql.api/}{\texttt{com.liferay.portal.dao.orm.custom.sql.api}}) retrieves SQL from a file called \texttt{default.xml} in your service module's \texttt{src/main/resources/META-INF/custom-sql/} folder. You must create the \texttt{custom-sql} folder and create the \texttt{default.xml} file in that \texttt{custom-sql} folder. The \texttt{default.xml} file must adhere to the following format: \begin{verbatim} SQL query wrapped in No terminating semi-colon \end{verbatim} Create a \texttt{custom-sql} element for every SQL query you want in your application, and give each query a unique ID. The recommended convention to use for the ID value is the fully-qualified class name of the finder followed by a dot (\texttt{.}) character and the name of the finder method. More detail on the finder class and finder methods is provided in the next step. For example, in the Guestbook application, you could use the following ID value to specify a query: \begin{verbatim} com.liferay.docs.guestbook.service.persistence.EntryFinder.findByEntryNameEntryMessageGuestbookName \end{verbatim} Custom SQL must be wrapped in character data (\texttt{CDATA}) for the \texttt{sql} element. Importantly, do not terminate the SQL with a semi-colon. Following these rules, the \texttt{default.xml} file of the Guestbook application specifies an SQL query that joins the \texttt{GB\_GuestbookEntry} and \texttt{GB\_Guestbook} tables: \begin{verbatim} \end{verbatim} Now that you've specified some custom SQL, the next step is to implement a finder method to invoke it. The method name for the finder should match the ID you just specified for the \texttt{sql} element. Congratulations on developing a custom SQL query and custom finder for your application! \section{Related Topics}\label{related-topics-23} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers}{Customizing Liferay Services} \chapter{Defining a Custom Finder Method}\label{defining-a-custom-finder-method} Dynamic queries and custom SQL belong in finder methods. You implement them and then make them available through an interface. This article demonstrates defining the finder method in an implementation class, generating its interface and tying the implementation to the interface. An example of this is a Guestbook application with two entities: guestbook and entry. Each entry belongs to a guestbook so the entry entity has a \texttt{guestbookId} field as a foreign key. If you need a finder to search for guestbook entries by entry name and guestbook name, you'd add a finder method to \texttt{GuestbookFinderImpl} and name it \texttt{findByEntryNameGuestbookName}. The full method signature would be \texttt{findByEntryNameGuestbookName(String\ entryName,\ String\ guestbookName)}. The steps are below. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \texttt{{[}Entity{]}FinderImpl} class in the \texttt{{[}package\ \ \ \ \ \ path{]}.service.persistence.impl} package of your service module's \texttt{src/main/java} folder. Recall that you specify the \texttt{{[}package\ path{]}} in your \texttt{service.xml} file. Here's an example: \begin{verbatim} ... \end{verbatim} \item Define a \texttt{findBy*} finder method in the class you created. Make sure to add any required arguments to your finder method signature. \item Run Service Builder to generate the appropriate interface in the \texttt{{[}package\ \ \ \ \ \ path{]}.service.persistence} package in the \texttt{service} folder of your API and service modules. For example, after adding \texttt{findByEntryNameGuestbookName(String\ entryName,\ String\ guestbookName)} to \texttt{GuestbookFinderImpl} and running Service Builder, the interface \texttt{com.liferay.docs.guestbook.service.persistence.GuestbookFinder} is generated. \item Make the finder class a component (annotated with \href{https://docs.osgi.org/javadoc/osgi.cmpn/7.0.0/org/osgi/service/component/annotations/Component.html}{\texttt{@Component}}) that implements the \texttt{GuestbookFinder} interface. For example, the class declaration should look like this: \begin{verbatim} @Component(service = GuestbookFinder.class) public class GuestbookFinderImpl extends BasePersistenceImpl implements GuestbookFinder \end{verbatim} \end{enumerate} Your next step is to implement the query in your finder. You can do this via the Dynamic Query API or Custom SQL. The next tutorial covers Dynamic Query. To simply call custom SQL you have written, create a finder method to run your SQL: \begin{verbatim} public List findByEntryNameEntryMessageGuestbookName( String entryName, String entryMessage, String guestbookName, int begin, int end) { Session session = null; try { session = openSession(); String sql = _customSQL.get( getClass(), FIND_BY_ENTRYNAME_ENTRYMESSAGE_GUESTBOOKNAME); SQLQuery q = session.createSQLQuery(sql); q.setCacheable(false); q.addEntity("GB_Entry", EntryImpl.class); QueryPos qPos = QueryPos.getInstance(q); qPos.add(entryName); qPos.add(entryMessage); qPos.add(guestbookName); return (List) QueryUtil.list(q, getDialect(), begin, end); } catch (Exception e) { try { throw new SystemException(e); } catch (SystemException se) { se.printStackTrace(); } } finally { closeSession(session); } return null; } public static final String FIND_BY_ENTRYNAME_ENTRYMESSAGE_GUESTBOOKNAME = EntryFinder.class.getName() + ".findByEntryNameEntryMessageGuestbookName"; @Reference private CustomSQL _customSQL; \end{verbatim} The custom finder method opens a new Hibernate session and uses Liferay's \texttt{CustomSQL.get(Class\textless{}?\textgreater{}\ clazz,\ String\ id)} method to get the custom SQL to use for the database query. The \texttt{FIND\_BY\_ENTRYNAME\_ENTRYMESSAGE\_GUESTBOOKNAME} static field contains the custom SQL query's ID. The \texttt{FIND\_BY\_EVENTNAME\_EVENTDESCRIPTON\_LOCATIONNAME} string is based on the fully-qualified class name of the \texttt{*Finder} interface (\texttt{EventFinder}) and the name of the finder method (\texttt{findByEntryNameEntryMessageGuestbookName}). Awesome! You've implemented your finder class, and if you're using custom SQL, you've even implemented a method to call your finder. If you're using Dynamic Query, the next tutorial shows you how to implement a dynamic query finder method. \chapter{Dynamic Query}\label{dynamic-query} Once you've \href{/docs/7-2/appdev/-/knowledge_base/a/defining-a-custom-finder-method}{defined your custom finder method}, you can use the Dynamic Query API to implement your query in it. Here's what you must do in your finder method: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \hyperref[using-a-hibernate-session]{Open a Hibernate Session} \item \hyperref[creating-dynamic-queries]{Create a dynamic query using these Hibernate features}: \begin{itemize} \tightlist \item \emph{Restrictions}: Similar to \texttt{where} clauses of an SQL query, restrictions limit results based on criteria. \item \emph{Projections}: Modify the kind of results the query returns. \item \emph{Orders}: Organize results. \end{itemize} \item \hyperref[executing-the-dynamic-query]{Execute the Dynamic Query and return the results} \end{enumerate} Before implementing a dynamic query in your own finder method, it can be helpful to examine an example. The following example method uses multiple dynamic queries and all the Hibernate features. Instructions for implementing your own finder method follow the example. \section{Example Finder Method: findByGuestbookNameEntryName}\label{example-finder-method-findbyguestbooknameentryname} This finder method for the Guestbook application retrieves a list of Guestbook entries that have a specific name and that also belong to a Guestbook of a specific name: \begin{verbatim} public List findByEntryNameGuestbookName(String entryName, String guestbookName) { Session session = null; try { session = openSession(); ClassLoader classLoader = getClass().getClassLoader(); DynamicQuery guestbookQuery = DynamicQueryFactoryUtil.forClass(Guestbook.class, classLoader) .add(RestrictionsFactoryUtil.eq("name", guestbookName)) .setProjection(ProjectionFactoryUtil.property("guestbookId")); Order order = OrderFactoryUtil.desc("modifiedDate"); DynamicQuery entryQuery = DynamicQueryFactoryUtil.forClass(Entry.class, classLoader)) .add(RestrictionsFactoryUtil.eq("name", entryName)) .add(PropertyFactoryUtil.forName("guestbookId").in(guestbookQuery)) .addOrder(order); List entries = _entryLocalService.dynamicQuery(entryQuery); return entries; } catch (Exception e) { try { throw new SystemException(e); } catch (SystemException se) { se.printStackTrace(); } } finally { closeSession(session); } } \end{verbatim} The method first opens a Hibernate session. While the session is open in the \texttt{try} block, it creates and executes a dynamic query, which returns results (a list of guestbook \texttt{Entry} objects) if all goes well. The finder method has two distinct dynamic queries. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The first query retrieves a list of guestbook IDs corresponding to guestbook names that match the \texttt{guestbookName} parameter of the finder method. \item The second query retrieves a list of guestbook entries with entry names that match the \texttt{entryName} parameter and have \texttt{guestbookId} foreign keys belonging to the list returned by the first query. \end{enumerate} Here's the first query: \begin{verbatim} DynamicQuery guestbookQuery = DynamicQueryFactoryUtil.forClass(Guestbook.class, classLoader)) .add(RestrictionsFactoryUtil.eq("name", guestbookName)) .setProjection(ProjectionFactoryUtil.property("guestbookId")); \end{verbatim} By default, \texttt{DynamicQueryFactoryUtil.forClass(Guestbook.class,\ classLoader))} returns a query that retrieves a list of all guestbook entities. Adding the \texttt{.add(RestrictionsFactoryUtil.eq("name",\ guestbookName))} restriction limits the results to only those guestbooks whose guestbook names match the \texttt{guestbookName} parameter. The \texttt{.setProjection(ProjectionFactoryUtil.property("guestbookId"))} projection changes the result set from a list of guestbook entries to a list of guestbook IDs. This is useful since guestbook IDs are much less expensive to retrieve than full guestbook entities, and the entry query only needs the guestbook IDs. Next appears an order: \begin{verbatim} Order order = OrderFactoryUtil.desc("modifiedDate"); \end{verbatim} This arranges the results list in descending order of the query entity's \texttt{modifiedDate} attribute. Thus the most recently modified entities (guestbook entries, in our example) appear first and the least recently modified entities appear last. Here's the second query: \begin{verbatim} DynamicQuery entryQuery = DynamicQueryFactoryUtil.forClass(Entry.class, classLoader)) .add(RestrictionsFactoryUtil.eq("name", entryName)) .add(PropertyFactoryUtil.forName("guestbookId").in(guestbookQuery)) .addOrder(order); \end{verbatim} By default, \texttt{DynamicQueryFactoryUtil.forClass(Entry.class,\ classLoader))} returns a list of all guestbook entry entities. The \texttt{.add(RestrictionsFactoryUtil.eq("name",\ entryName))} restriction limits the results to only those guestbook entries whose names match the finder method's \texttt{entryName} parameter. \href{https://docs.liferay.com/dxp/portal/7.0-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/PropertyFactoryUtil.html}{\texttt{PropertyFactoryUtil}} is a Liferay utility class whose method \texttt{forName(String\ propertyName)} returns the specified property. This property can be passed to another Liferay dynamic query. This is exactly what happens in the following line of our example: \begin{verbatim} .add(PropertyFactoryUtil.forName("guestbookId").in(guestbookQuery)) \end{verbatim} Here, the code makes sure that the guestbook IDs (foreign keys) of the entry entities in the \texttt{entityQuery} belong to the list of guestbook IDs returned by the \texttt{guestbookQuery}. Declaring that an entity property in one query must belong to the result list of another query is a way to use the dynamic query API to create complex queries, similar to SQL joins. Lastly, the order defined earlier is applied to the entries returned by the \texttt{findByEntryNameGuestbookName} finder method: \begin{verbatim} .addOrder(order); \end{verbatim} This orders the list of guestbook entities by the \texttt{modifiedDate} attribute, from most recent to least recent. Lastly, the dynamic query is invoked on the \texttt{EntryLocalService} instance. It returns a list of \texttt{Entry} objects which are then returned by the finder method. \begin{verbatim} List entries = _entryLocalService.dynamicQuery(entryQuery); return entries; \end{verbatim} It's time to implement your finder method to use Dynamic Query. Start with opening and managing a Hibernate session. \section{Using a Hibernate Session}\label{using-a-hibernate-session} Your first step in implementing your custom finder method in your \texttt{*FinderImpl} class is to open a new Hibernate session. Since your \texttt{*FinderImpl} class extends \texttt{BasePersistenceImpl\textless{}Entity\textgreater{}}, and \texttt{BasePersistenceImpl\textless{}Entity\textgreater{}} contains a session factory object and an \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/persistence/impl/BasePersistenceImpl.html\#openSession--}{\texttt{openSession}} method, you can invoke the \texttt{openSession} method of your \texttt{*FinderImpl}'s parent class to open a new Hibernate session. The structure of your finder method should look like this: \begin{verbatim} public List findBy-(...) { Session session = null; try { session = openSession(); /* create a dynamic query to retrieve and return the desired list of entity objects */ } catch (Exception e) { // Exception handling } finally { closeSession(session); } return null; /* Return null only if there was an error returning the desired list of entity objects in the try block */ } \end{verbatim} Next, in the try block, create your dynamic query objects. \section{Creating Dynamic Queries}\label{creating-dynamic-queries} In Liferay, you don't create criteria objects directly from the Hibernate session. Instead, you create dynamic query objects using Liferay's \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/DynamicQueryFactoryUtil.html}{\texttt{DynamicQueryFactoryUtil}} service. Thus, instead of \begin{verbatim} Criteria entryCriteria = session.createCriteria(Entry.class); \end{verbatim} you use \begin{verbatim} DynamicQuery entryQuery = DynamicQueryFactoryUtil.forClass(Entry.class, classLoader)); \end{verbatim} In your finder method, initialize your dynamic query for your entity class. Most features of Hibernate's Criteria API, including restrictions, projections, and orders, can be used on Liferay dynamic query objects. Each criteria can be applied to your query. The restriction criteria type is described first. \section{Restriction Criteria}\label{restriction-criteria} Restrictions in Hibernate's Criteria API roughly correspond to the \texttt{where} clause of an SQL query: they offer a variety of ways to limit the results returned by the query. You can use restrictions, for example, to cause a query to return only results where a certain field has a particular value, or a value in a certain range, or a non-null value, etc. When you need to add restrictions to a dynamic query, don't call Hibernate's \texttt{Restrictions} class directly. Instead, use the \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/RestrictionsFactoryUtil.html}{\texttt{RestrictionsFactoryUtil}} service. \texttt{RestrictionsFactoryUtil} has the same methods that you're used to from Hibernate's \texttt{Restrictions} class: \texttt{in}, \texttt{between}, \texttt{like}, \texttt{eq}, \texttt{ne}, \texttt{gt}, \texttt{ge}, \texttt{lt}, \texttt{le}, etc. Thus, instead of using this call to specify that a guestbook must have a certain name, \begin{verbatim} entryCriteria.add(Restrictions.eq("name", guestbookName)); \end{verbatim} you use \begin{verbatim} entryQuery.add(RestrictionsFactoryUtil.eq("name", guestbookName)); \end{verbatim} The restriction above limits the results to guestbook entries whose \texttt{name} attribute matches the value of the variable \texttt{guestbookName}. Add the restrictions you need to get the results you want. Projections are the next criteria type. They let you transform the query results to return the field type you desire. \section{Projection Criteria}\label{projection-criteria} Projections in Hibernate's Criteria API let you modify the kind of results returned by a query. For example, if you don't want your query to return a list of entity objects (the default), you can set a projection on a query to return only a list of the values of a certain entity field, or fields. You can also use projections on a query to return the maximum or minimum value of an entity field, or the sum of all the values of a field, or the average, etc. For more information on restrictions and projections, please refer to Hibernate's \href{http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/querycriteria.html}{documentation}. Similarly, to set projections, create properties via Liferay's \href{https://docs.liferay.com/dxp/portal/7.0-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/PropertyFactoryUtil.html}{PropertyFactoryUtil} service instead of through Hibernate's \texttt{Property} class. Thus, instead of \begin{verbatim} entryCriteria.setProjection(Property.forName("guestbookId")); \end{verbatim} you use \begin{verbatim} entryQuery.setProjection(PropertyFactoryUtil.forName("guestbookId")); \end{verbatim} The projection above specifies the \texttt{guestbookId} entity field to changes the result set to a list of those field values. If you want to return a specific field type from your entities, add a projection for it. The last criteria type lets you organize results your way. \section{Order Criteria}\label{order-criteria} Orders in Hibernate's Criteria API let you control the order of the elements in the list a query returns. You can choose the property or properties to which an order applies as well as whether they're in ascending or descending order. This code creates an order by the entity's \texttt{modifiedDate} attribute: \begin{verbatim} Order order = OrderFactoryUtil.desc("modifiedDate"); \end{verbatim} When you apply this order, the results are arranged in descending order of the query entity's \texttt{modifiedDate} attribute. Thus the most recently modified entities (guestbook entries, in our example) appear first and the least recently modified entities appear last. Like Hibernate criteria, Liferay's dynamic queries are \emph{chain-able}: you can add criteria to, set projections on, and add orders to Liferay's dynamic query objects just by appending the appropriate method calls to the query object. For example, the following snippet demonstrates chaining a restriction criterion and a projection to a dynamic query object declaration: \begin{verbatim} DynamicQuery guestbookQuery = DynamicQueryFactoryUtil.forClass(Guestbook.class) .add(RestrictionsFactoryUtil.eq("name", guestbookName)) .setProjection(ProjectionFactoryUtil.property("guestbookId")); \end{verbatim} It's time to execute your dynamic query. \section{Executing the Dynamic Query}\label{executing-the-dynamic-query} In the previous article, you ran Service Builder after \href{/docs/7-2/appdev/-/knowledge_base/a/defining-a-custom-finder-method}{defining your custom finder}. Service Builder generated a \texttt{dynamicQuery(DynamicQuery\ dynamicQuery)} method in your \texttt{*LocalServiceBaseImpl} class. Using a \texttt{*LocalService} instance, invoke \texttt{dynamicQuery} method, passing it your dynamic query. Here's an example dynamic query execution. \begin{verbatim} List entities = _someLocalService.dynamicQuery(entityQuery); return entities; \end{verbatim} The dynamic query execution returns a list of entities and the finder method returns that list. \noindent\hrulefill \textbf{Note:} Service Builder not only generates a \texttt{public\ List\ \ dynamicQuery(DynamicQuery\ dynamicQuery)} method in \texttt{*LocalServiceBaseImpl} but it also generates \texttt{public\ List\ dynamicQuery(DynamicQuery\ dynamicQuery,\ int\ \ start,\ int\ end)} and \texttt{public\ List\ dynamicQuery(DynamicQuery\ dynamicQuery,\ int\ \ start,\ int\ end,\ OrderByComparator\ orderByComparator)} methods. You can go back to \href{/docs/7-2/appdev/-/knowledge_base/a/defining-a-custom-finder-method}{defining custom finder methods} and either modify your finder method or create overloaded versions of it to take advantage of these extra methods and their parameters. The \texttt{int\ start} and \texttt{int\ \ end} parameters are useful when paginating a result list. \texttt{start} is the lower bound of the range of model entity instances and \texttt{end} is the upper bound. The \texttt{OrderByComparator\ orderByComparator} is the comparator by which to order the results. \noindent\hrulefill To use the overloaded \texttt{dynamicQuery} methods of your \texttt{*LocalServiceBaseImpl} class in the (optionally overloaded) custom finders of your \texttt{*FinderImpl} class, just choose the appropriate methods for running the dynamic queries: \texttt{dynamicQuery(entryQuery)}, or \texttt{dynamicQuery(entryQuery,\ start,\ end)} or \texttt{dynamicQuery(entryQuery,\ start,\ end,\ orderByComparator)}. Great! You've now created a finder method using Liferay's Dynamic Query API. Your last step is to add a service method that calls your finder. \chapter{Accessing Your Custom Finder Method from the Service Layer}\label{accessing-your-custom-finder-method-from-the-service-layer} So far, you've created a \texttt{*FinderImpl} class, defined a \texttt{findBy*} finder method in that class, and implemented the finder method using Dynamic Query or custom SQL. Now how do you call your finder method from the service layer? When you ran Service Builder (if you haven't run it yet; run it now), the \texttt{*Finder} interface was generated (e.g., \texttt{GuestbookFinder}). For proper separation of concerns, only a local or remote service implementation (i.e., \texttt{*LocalServiceImpl} or \texttt{*ServiceImpl}) in your service module should invoke the \texttt{*Finder} class. The portlet classes in your application's web module invoke the business logic of the services published from your application's service module. The services, in turn, access the data model using the persistence layer's finder classes. \noindent\hrulefill \textbf{Note:} In previous versions of Liferay DXP, your finder methods were accessible via \texttt{*FinderUtil} utility classes. Finder methods are now injected into your app's local services, removing the need to call finder utilities. \noindent\hrulefill You'll add a method in the \texttt{*LocalServiceImpl} class that invokes the finder method implementation via the \texttt{*Finder} class. Then you'll rebuild your application's service layer so that the portlet classes and JSPs in your web module can access the services. For example, for the Guestbook application, you'd add the following method to the \texttt{GuestbookEntryLocalServiceImpl} class: \begin{verbatim} public List findByEntryNameGuestbookName(String entryName, String guestbookName) throws SystemException { return entryFinder.findByEntryNameGuestbookName(String entryName, String guestbookName); } \end{verbatim} After you've added your \texttt{findBy*} method to your \texttt{*LocalServiceImpl} class, run Service Builder to generate the interface and make the finder method available in the \texttt{EntryLocalService} class. Now you can indirectly call the finder method from your portlet class or from a JSP by calling \texttt{\_entryLocalService.findByEntryNameGuestbookName(...)}! Congratulations on following the process of developing custom queries in a custom finder and exposing it as a service for your portlet! \section{Related Topics}\label{related-topics-24} \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{Actionable Dynamic Queries}\label{actionable-dynamic-queries} Suppose you have over a million users, and you want to perform some kind of mass update to some of them. One approach might be to use a dynamic query to retrieve the list of users in question. Once loaded into memory, you could loop through the list and update each user. However, with over a million users, the memory cost of such an operation would be too great. In general, retrieving large numbers of Service Builder entities using dynamic queries requires too much memory and time. Liferay actionable dynamic queries solve this problem. Actionable dynamic queries use a pagination strategy to load only small numbers of entities into memory at a time and perform processing (i.e., perform an \emph{action}) on each entity. So instead of trying to use a dynamic query to load a million users into memory and then perform some processing on each of them, a much better strategy is to use an actionable dynamic query. This way, you can still process your million users, but only small numbers are loaded into memory at a time. Here's how to use actionable dynamic query: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Get an \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/ActionableDynamicQuery.html}{\texttt{ActionableDynamicQuery}} from your \texttt{*LocalService} by invoking its \texttt{getActionableDynamicQuery} method. \item Add query criteria and constraints, using the query's \texttt{setAddCriteriaMethod} and \texttt{setAddOrderCriteriaMethod} methods. \item Set an action to perform on the matching entities, using \texttt{setPerformActionMethod}. \item Execute the action on each matching entity, by invoking the query's \texttt{performActions} method. \end{enumerate} This method from a sample portlet creates an actionable dynamic query, adds a query restriction and an action, and executes the query: \begin{verbatim} protected void massUpdate() { ActionableDynamicQuery adq = _barLocalService.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); _barLocalService.updateBar(bar); } }); try { adq.performActions(); } catch (Exception e) { e.printStackTrace(); } } \end{verbatim} The example method demonstrates executing an actionable dynamic query on \texttt{Bar} entities that match certain criteria. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Retrieve an \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/ActionableDynamicQuery.html}{\texttt{ActionableDynamicQuery}} from local service \texttt{BarLocalService}. \begin{verbatim} ActionableDynamicQuery adq = _barLocalService.getActionableDynamicQuery(); \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Service Builder generates method `getActionableDynamicQuery()` in each entity's `*LocalService` interface and implements it in each entity's `*BaseLocalServiceImpl` class. @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) public ActionableDynamicQuery getActionableDynamicQuery(); \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Set query criteria to match \texttt{field3} values less than \texttt{100}. \begin{verbatim} adq.setAddCriteriaMethod(new ActionableDynamicQuery.AddCriteriaMethod() { @Override public void addCriteria(DynamicQuery dynamicQuery) { dynamicQuery.add(RestrictionsFactoryUtil.lt("field3", 100)); } }); \end{verbatim} \item Set an action to perform. The action increments the matching entity's \texttt{field3} value. \begin{verbatim} adq.setPerformActionMethod(new ActionableDynamicQuery.PerformActionMethod() { @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:

  • Use the --no-tracking flag in your package.json's build script to disable reporting:

    liferay-npm-bundler --no-tracking

  • 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}/navigation.ftl" />
${portlets}
<#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> \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 SettingsTemplate 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 = "\n

Added 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 = "\n

Added 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} \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) /> <#macro displayPages depth pages > <#if pages?has_content && ((depth < displayDepth?number) || (displayDepth?number == 0))> \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 = " ... \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); %>
<% columns++; } %>
\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
]]> \end{verbatim} Updated: \begin{verbatim}
Out of This World
Come to the Lunar Resort...
]]> \end{verbatim} \end{enumerate} The web content is updated! Next, you must update the theme's sitemap file. \chapter{Updating the 6.2 Sitemap}\label{updating-the-6.2-sitemap} \begin{verbatim}

Updating 6.2 Resources Importer

Step 3 of 3

\end{verbatim} In Liferay DXP 6.2, portlet IDs were incremental numbers. In 7.0, they're explicit class names. Update the \texttt{sitemap.json} file with the new portlet IDs. Follow these steps to update the sitemap: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Replace the portlet IDs with the updated class names. The \href{/docs/7-2/reference/-/knowledge_base/r/fully-qualified-portlet-ids}{Portlet ID Quick Reference Guide} list the default portlet IDs. Check \texttt{liferay-portlet.xml} for the portlet ID number in 6.2 and replace it with the updated ID in the quick reference Guide. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** you can also retrieve a portlet's ID from the UI. Open the portlet's *Options* menu, select *Look and Feel Configuration*. ![ You can find the portlet ID in the *Look and Feel Configuration* menu.](./images/upgrading-themes-look-and-feel-menu.png) Select the *Advanced Styling* tab. The `Portlet ID` value appears in the blue box. ![ The portlet ID appears within the blue box in the *Advanced Styling* tab.](./images/upgrading-themes-portlet-id.png) \end{verbatim} \noindent\hrulefill \begin{verbatim} The original and updated versions of the Lunar Resort theme's `sitemap.json` are shown below: Original: ```json { "name": "Collaboration", "title": "Collaboration", "friendlyURL": "/collaboration", "layoutTemplateId": "2_columns", "columns": [ [ { "portletId": "36" } ], [ { "portletId": "115" } ] ] } ``` Updated: ```json { "name": "Collaboration", "title": "Collaboration", "friendlyURL": "/collaboration", "layoutTemplateId": "2_columns", "columns": [ [ { "portletId": "com_liferay_wiki_web_portlet_WikiPortlet" } ], [ { "portletId": "com_liferay_blogs_web_portlet_BlogsAgreggatorPortlet" } ] ] }, ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item Update references to the web content articles in the \texttt{sitemap.json} to use the XML file extensions. \end{enumerate} Great! The Resources Importer updates are complete. Next you'll apply Clay markup patterns to the theme's custom UI. \chapter{Applying Clay Design Patterns}\label{applying-clay-design-patterns} 7.0 uses \href{https://clayui.com/}{Clay}, a web implementation of Liferay's \href{https://lexicondesign.io/}{Lexicon Experience Language}. The Lexicon Experience Language provides styling guidelines and best practices for application UIs. Clay's CSS, HTML, and JavaScript components enable developers to build fully-realized UIs quickly and effectively. Liferay DXP's \href{/docs/7-2/tutorials/-/knowledge_base/t/using-the-bootstrap-3-lexicon-css-compatibility-layer}{compatibility layer} let's you use \href{https://lexiconcss.wedeploy.io/}{Lexicon CSS} markup alongside \href{https://clayui.com/}{Clay CSS}. \noindent\hrulefill \textbf{Note:} The compatibility layer is meant as a short-term solution to ensure that your Bootstrap 3 and Lexicon CSS components aren't broken while you update your theme to use \href{https://getbootstrap.com/docs/4.3/migration/}{Bootstrap 4} and \href{https://clayui.com/docs/css-framework/scss.html}{Clay CSS}. It will be disabled in a future release. Migrate your theme to use Bootstrap 4 and Clay CSS as soon as you're able to. \noindent\hrulefill This section demonstrates how to apply Clay to the Lunar Resort's form. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Replace the \texttt{control-group} classes with \texttt{form-group} classes: \item Remove the \texttt{control-label} classes from the \texttt{label} elements: \item Remove \texttt{\textless{}div\ class="controls"\textgreater{}} elements. \item Add the \texttt{form-control} class to each \texttt{input} element. \item Add the \texttt{btn-primary} class to your submit buttons to emphasize them. \end{enumerate} The Lunar Resort's original form and updated form are shown below: Original form markup: \begin{verbatim}
Reservation Form
\end{verbatim} Updated form markup: \begin{verbatim}
Reservation Form
\end{verbatim} The Lunar Resort theme is updated for 7.0! \chapter{Upgrading Your Theme from Liferay Portal 7.0 to 7.2}\label{upgrading-your-theme-from-liferay-portal-7.0-to-7.2} In this tutorial, you'll learn how to use the \href{https://github.com/liferay/liferay-js-themes-toolkit/tree/master/packages}{Liferay JS Theme Toolkit} to upgrade a Liferay DXP 7.0 theme to 7.0. As you upgrade this theme, you'll learn how to update metadata, theme templates, UI (including support for Bootstrap 4 and Lexicon 2.0.), and more using all the best practices and standards. Completing this tutorial prepares you for upgrading your own theme. Theme upgrades involve these steps: \begin{itemize} \tightlist \item Updating project metadata \item Updating CSS \item Updating theme templates \end{itemize} Let's Go!{} \chapter{Running the Upgrade Task for 7.0 Themes}\label{running-the-upgrade-task-for-7.0-themes} You can upgrade a Liferay DXP 7.0 theme to 7.0, regardless of the development environment you use. This tutorial uses the Liferay JS Theme Toolkit's Gulp \texttt{upgrade} task to automate much of the steps. The Gulp \texttt{upgrade} task must be run twice to bring a Liferay DXP 7.0 theme up to 7.0. The Liferay Theme Generator is available in a few different versions. To update the Liferay DXP 7.0 theme to Liferay DXP 7.1, you must install v8.x.x of the \texttt{liferay-theme-tasks} dependency. After the theme is updated to 7.1, you must then install v9.x.x of the \texttt{liferay-theme-tasks} dependency to complete the upgrade process. Here's what the Upgrade Task does: \begin{itemize} \tightlist \item Updates the theme's Liferay version \item Updates the theme's Bootstrap version \item Updates the theme's Lexicon version \item Suggests specific code updates \end{itemize} Follow these steps to take the theme through the upgrade process: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the theme's root directory and run the command below to update the theme's \texttt{liferay-theme-tasks} dependency to version \texttt{8.x.x}: \begin{verbatim} npm install --save-dev liferay-theme-tasks@8.x.x \end{verbatim} \item Run the \texttt{gulp\ upgrade} command to upgrade the Liferay DXP 7.0 theme to 7.1. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note**: The Upgrade task overwrites the theme's files. We recommend that you backup your files before proceeding with the upgrade process. \end{verbatim} \noindent\hrulefill \begin{verbatim} Here's what it does: - Creates core code for generating theme base files - Collects removed Bootstrap and Lexicon variables - Updates Bootstrap version references - Updates Lexicon version references - Updates Liferay version references \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item You must update the theme's \texttt{liferay-theme-tasks} dependency to version \texttt{9.x.x} to complete the upgrade process. Install the latest version of the Liferay Theme Generator as well while you're at it, so future uses of the tool will be compatible with the 7.0 theme. Both commands are shown below. Run them separately: \begin{verbatim} npm install --save-dev liferay-theme-tasks@9.x.x npm install -g generator-liferay-theme@9.x.x \end{verbatim} \item With the \texttt{9.x.x} versions of the \texttt{liferay-theme-tasks} and Liferay Theme Generator installed, run the \texttt{gulp\ upgrade} command for the final time to upgrade the 7.1 theme to 7.2: Here's what it does: \begin{itemize} \tightlist \item Updates Liferay version references \item Updates theme dependencies \end{itemize} \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 during the Upgrade task 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 Run \texttt{gulp\ init} from your theme's root directory to update the path of your Liferay DXP server to point to your 7.2 Liferay DXP server. \end{enumerate} The Gulp \texttt{upgrade} task lists any deprecated or removed variables. For other areas of the code it suspects might need updates, it logs suggestions. The task also reports changes that may affect theme templates. This jump-starts the upgrade process, but it doesn't complete it. Manual updates are required. The remaining portion of this tutorial covers these manual steps. \chapter{Updating 7.0 CSS Code}\label{updating-7.0-css-code} 7.0's UI improvements requires these CSS-related changes: \begin{itemize} \tightlist \item Renaming CSS files \item Class variable changes \item Updating core imports \end{itemize} The theme upgrade process involves conforming to these changes. Now you'll update the theme's CSS files to reflect these changes. Start with updating CSS file names. Let's Go{} \chapter{Updating 7.0 CSS File Names for Clay}\label{updating-7.0-css-file-names-for-clay} \begin{verbatim}

Updating 7.0 CSS Code

Step 1 of 3

\end{verbatim} Some of the CSS filenames have changed to reflect the introduction of Clay (previously Lexicon CSS). The file name changes for the Unstyled theme are listed below. Refer to the \href{/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide}{Theme Reference Guide} for a complete list of expected theme CSS files. Orignal AUI file names: \begin{itemize} \tightlist \item \texttt{css/} \begin{itemize} \tightlist \item \texttt{\_aui\_custom.scss} \item \texttt{\_aui\_variables.scss} \item \texttt{aui.scss} \end{itemize} \end{itemize} Updated Clay file names: \begin{itemize} \tightlist \item \texttt{css/} \begin{itemize} \tightlist \item \texttt{\_clay\_custom.scss} \item \texttt{\_clay\_variables.scss} \item \texttt{clay.scss} \end{itemize} \end{itemize} Next, you can update the theme's CSS variables. \chapter{Updating 7.0 Class Variables}\label{updating-7.0-class-variables} \begin{verbatim}

Updating 7.0 CSS Code

Step 2 of 3

\end{verbatim} 7.0 uses Bootstrap 4's CSS rule syntax. The new syntax lets developers leverage Bootstrap 4 features and improvements. The \href{https://getbootstrap.com/docs/4.0/migration/}{Migrating to v4 guide} provides complete instructions for updating CSS rules to Bootstrap 4. Follow these steps to upgrade the theme's CSS variables: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Consult the upgrade log produced by the \texttt{gulp\ upgrade} task. It suggests the manual Lexicon updates required for the theme. \item Make the required changes in the log. The log lists removed and/or deprecated variables and suggests possible changes. For each update performed or suggested, the task reports a file name. For example, here is part of the task log for the 7.0 Westeros Bank theme: \end{enumerate} \begin{verbatim} Lexicon Upgrade (1.0 to 2.0) File: _variables_custom.scss $brand-default was deprecated in Lexicon CSS 1.x.x and has been removed in the new Clay 2.x.x version \end{verbatim} \noindent\hrulefill \textbf{Note:} If the \texttt{gulp\ upgrade} task detects any variables in the theme that are removed in Clay from the previous LexiconCSS version, it adds a \texttt{\_variables\_deprecated.scss} file to the theme containing the removed variables, to make sure the theme compiles and to decouple it from future upgrades. \noindent\hrulefill After updating your theme's CSS variables and mixins, you should update the imports next. \chapter{Updating 7.0 Imports}\label{updating-7.0-imports} \begin{verbatim}

Updating 7.0 CSS Code

Step 3 of 3

\end{verbatim} Font Awesome imports and core imports have changed. Follow these steps to update the theme: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Originally in Liferay Portal CE 7.0 and Liferay DXP, Font Awesome icons were imported in \texttt{\_aui\_variables.scss} (now renamed \texttt{\_clay\_variables.scss}). Font Awesome icons are now included as a package dependency if you answer yes (y) to include Font Awesome during the upgrade task. If a 7.0 theme was made prior to this move and \texttt{\_aui\_variables.scss} was modified, the Font Awesome imports shown below must be removed from \texttt{\_clay\_variables.scss}: \begin{verbatim} // Icon paths $FontAwesomePath: "aui/lexicon/fonts/alloy-font-awesome/font"; $font-awesome-path: "aui/lexicon/fonts/alloy-font-awesome/font"; $icon-font-path: "aui/lexicon/fonts/"; \end{verbatim} \item Update the old AUI lexicon paths to use the new Clay paths instead, as shown in the table below: \end{enumerate} \noindent\hrulefill \begin{verbatim} Pattern|Replacement| ---|---| `@import "/aui/lexicon/bootstrap/mixins/";`|removed| `@import "/aui/lexicon/lexicon-base/mixins/";`|removed| `@import "/aui/lexicon/atlas-theme/mixins/";`|removed| `@import "aui/lexicon/atlas-variables";`|`@import "clay/atlas-variables";`| `@import "aui/lexicon/atlas";`|`@import "clay/atlas";`| \end{verbatim} \noindent\hrulefill Great! Your imports are updated, and your CSS upgrade is complete. Next you can upgrade the theme templates. \chapter{Updating 7.0 Theme Templates to 7.2}\label{updating-7.0-theme-templates-to-7.2} Liferay DXP 7.0 theme templates and 7.0 theme templates are essentially the same. Here are the main changes: \begin{itemize} \tightlist \item Velocity templates were deprecated in Liferay Portal CE 7.0 and are now removed in favor of FreeMarker templates in 7.0. \end{itemize} Key reasons for using FreeMarker templates and removing Velocity templates are these: \begin{itemize} \item FreeMarker is developed and maintained regularly, while Velocity is no longer actively being developed. \item FreeMarker is faster and supports more sophisticated macros. \item FreeMarker supports using taglibs directly rather than requiring a method to represent them. You can pass body content to them, parameters, etc. \end{itemize} If you haven't converted your Velocity theme templates to FreeMarker, \textbf{you must convert your Velocity theme templates to FreeMarker now}. The \texttt{gulp\ upgrade} command reports the required theme template changes in the log. For example, here is the \texttt{gulp\ upgrade} log for the Westeros Bank theme: \begin{verbatim} Liferay Upgrade (7.0 to 7.1) Renamed aui.scss to clay.scss File: footer.ftl Warning: .container-fluid-1280 has been deprecated. Please use .container-fluid.container-fluid-max-xl instead. File: portal_normal.ftl Warning: .navbar-header has been removed. This container should be removed in most cases. Please, use your own container if necessary. \end{verbatim} The log warns about removed and deprecated code and suggests replacements when applicable. For reference, the main changes between Liferay DXP 7.0 themes and 7.0 themes appear below: \begin{itemize} \item List items inside a container with the \texttt{list-inline} class \href{https://getbootstrap.com/docs/4.3/migration/\#typography}{now require} the \texttt{list-inline-item} class. \item The \texttt{container-fluid-1280} class has been deprecated. Please use \texttt{container-fluid\ container-fluid-max-xl} instead. \item Responsive navbar behaviors \href{https://getbootstrap.com/docs/4.3/migration/\#navbar}{are now applied} to the \texttt{navbar} class via the required \texttt{navbar-expand-\{breakpoint\}} class. \item The \texttt{navbar-toggle} class is now \texttt{navbar-toggler} and \href{https://getbootstrap.com/docs/4.3/migration/\#navbar}{has different inner markup}. \item The \texttt{navbar-header} class has been removed. This container should be removed in most cases. Please, use your own container if necessary. \end{itemize} In this section you'll learn how to update various theme templates to 7.0. Let's Go{} \chapter{Updating 7.0 Theme Templates}\label{updating-7.0-theme-templates} Follow these steps to update the theme's templates. Note these changes are only required if the templates are modified in the theme: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open \texttt{portal\_normal.ftl} and remove the breadcrumbs: \begin{verbatim} \end{verbatim} \item Still inside \texttt{portal\_normal.ftl}, remove \texttt{id="main-surface"} from the \texttt{body} tag so it looks like the one below. This is not needed for SPA to work properly: \begin{verbatim} \end{verbatim} \item Open \texttt{navigation.ftl} and remove the \texttt{nav\_item\_attr\_selected} variable declaration at the top. Don't forget to remove all uses of the \texttt{nav\_item\_attr\_selected} throughout the rest of the template. \item Also inside \texttt{navigation.ftl}, remove the \texttt{nav\_child\_attr\_selected} variable from the bottom of the template, including all uses throughout the rest of the template. \item Open \texttt{portlet.ftl} and find the code snippet below: \begin{verbatim} " > \end{verbatim} Add the \texttt{list-unstyled} class to it: \begin{verbatim} " > \end{verbatim} \item Still inside \texttt{portlet.ftl}, find the \texttt{\textless{}div\ class="autofit-float\ autofit-row"\textgreater{}} element and add the \texttt{portlet-header} class to it: \begin{verbatim}
\end{verbatim} \end{enumerate} The theme templates are updated! If you modified any other FreeMarker theme templates, you can compare them with templates in the \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/templates}{\texttt{\_unstyled} theme}. Next you can learn how to use Liferay DXP's compatibility layer to help ease the transition to Bootstrap 4 and Clay CSS. \chapter{Using the Bootstrap 3 Lexicon CSS Compatibility Layer}\label{using-the-bootstrap-3-lexicon-css-compatibility-layer} By default, 7.0 includes Bootstrap 4 out-of-the-box. Bootstrap 4 has been completely rewritten and therefore includes some \href{https://getbootstrap.com/docs/4.3/migration/}{notable changes} and \href{https://getbootstrap.com/docs/4.3/getting-started/introduction/}{compatibility updates} that may be cause for concern if your theme uses Bootstrap 3 or Lexicon CSS. Not to worry though. To ensure that your upgrade runs smoothly, Liferay DXP includes a compatibility layer so you can use Bootstrap 3 markup and Lexicon CSS markup alongside the new Bootstrap 4 and Clay CSS. If your theme extends the \href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-styled}{Styled base theme}, this compatibility layer is included by default. \noindent\hrulefill \textbf{Note:} The compatibility layer is meant as a short-term solution to ensure that your Bootstrap 3 and Lexicon CSS components aren't broken while you update your theme to use \href{https://getbootstrap.com/docs/4.3/migration/}{Bootstrap 4} and \href{https://clayui.com/docs/css-framework/scss.html}{Clay CSS}. It will be disabled in a future release. Migrate your theme to use Bootstrap 4 and Clay CSS as soon as you're able to. \noindent\hrulefill Follow these guidelines to update your markup: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Inspect your themes UI with the compatibility layer enabled (it's enabled by default), and note any issues. \item Individually disable the component(s) in the compatibility layer that you don't need. These are listed in the \href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/frontend-theme/frontend-theme-styled/src/main/resources/META-INF/resources/_styled/css/compat/_variables.scss}{\texttt{css/compat/\_variables.scss}} file. For convenience, these components are listed below: \begin{verbatim} // Compatibility layer components config $compat-alerts: true !default; $compat-basic_search: true !default; $compat-breadcrumbs: true !default; $compat-button_groups: true !default; $compat-buttons: true !default; $compat-cards: true !default; $compat-component_animations: true !default; $compat-dropdowns: true !default; $compat-figures: true !default; $compat-form_validation: true !default; $compat-forms: true !default; $compat-grid: true !default; $compat-icons: true !default; $compat-labels: true !default; $compat-liferay: true !default; $compat-list_groups: true !default; $compat-management_bar: true !default; $compat-modals: true !default; $compat-nav_tabs: true !default; $compat-navbar: true !default; $compat-navs: true !default; $compat-pager: true !default; $compat-pagination: true !default; $compat-panels: true !default; $compat-progress_bars: true !default; $compat-responsive_utilities: true !default; $compat\noindent\hrulefill: true !default; $compat-simple_flexbox_grid: true !default; $compat-stickers: true !default; $compat-tables: true !default; $compat-toggle_card: true !default; $compat-toggle_switch: true !default; $compat-toolbar: true !default; $compat-user_icons: true !default; $compat-utilities: true !default; \end{verbatim} To disable a component, add the component you want to remove compatibility for to \texttt{/src/css/\_clay\_custom.scss} (create this file if it doesn't exist) and set its value to \texttt{false}. The example below removes compatibility for alerts and cards: \begin{verbatim} $compat-alerts: false !default; $compat-cards: false !default; \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Some Liferay DXP components haven't been migrated to Bootstrap 4. Disabling certain components might cause portions of the UI to break. Therefore, after upgrading your markup, we recommend that you re-enable any components you disable. Proceed with caution. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item Update your markup to Bootstrap 4 and Clay CSS until you're satisfied with the result. \item Re-enable any components you disabled in the compatibility layer by removing any components you set to false in \texttt{/src/css/\_clay\_custom.scss}. This prevents Liferay DXP's UI from breaking. \end{enumerate} Now you know how to use the Bootstrap 3 and Lexicon CSS compatibility layer to provide a smooth transition during your theme upgrade. \chapter{Upgrading 7.1 Themes to 7.2}\label{upgrading-7.1-themes-to-7.2} You can upgrade a Liferay Portal 7.1 theme to 7.0, regardless of the development environment you use. This tutorial uses the Liferay JS Theme Toolkit's Gulp \texttt{upgrade} task to automate much of the steps. This requires v9.x.x of the Liferay Theme Generator and liferay theme tasks. Here's what the Upgrade Task does: \begin{itemize} \tightlist \item Updates Liferay version references \item Updates theme dependencies \end{itemize} Follow these steps to upgrade the theme: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Install the Liferay Theme Generator v9.x.x with the command below: \begin{verbatim} npm install -g generator-liferay-theme@9.x.x \end{verbatim} \item You must update the theme's \texttt{liferay-theme-tasks} dependency to version \texttt{9.x.x} as well to run the upgrade process: \begin{verbatim} npm install --save-dev liferay-theme-tasks@9.x.x \end{verbatim} \item With the \texttt{9.x.x} versions of the \texttt{liferay-theme-tasks} and Liferay Theme Generator installed, run the \texttt{gulp\ upgrade} command to upgrade the 7.1 theme to 7.2. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note**: The Upgrade task overwrites the theme's files. We recommend that you backup your files before proceeding with the upgrade process. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item In 7.1, Font Awesome \& Glyphicons were included in the \href{/docs/7-1/tutorials/-/knowledge_base/t/using-the-bootstrap-3-lexicon-css-compatibility-layer}{compatibility layer}. 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 during the Upgrade task to include the Font Awesome dependency in your theme. This ensures that your icons won't break if a Site Administrator disables the global setting. \item Run \texttt{gulp\ init} from the theme's root directory to update the path of the app server to point to the new 7.2 app server. \end{enumerate} There you have it! The theme is ready to run on 7.0. \chapter{Upgrading a Layout Template to 7.2}\label{upgrading-a-layout-template-to-7.2} In these tutorials, you'll learn how to upgrade your layout templates from earlier versions of Liferay DXP to 7.0. By the end of the tutorial, you'll have a layout template that runs on 7.0. Select the tutorial below that corresponds to the current version of your layout template: Let's Go 6.2!{} Let's Go 7.0 and 7.1!{} \chapter{Upgrading 6.2 Layout Templates to 7.2}\label{upgrading-6.2-layout-templates-to-7.2} Upgrading your Liferay DXP 6.2 layout template to 7.0 requires a few updates: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open your layout template's \texttt{liferay-plugin-package.properties} file and update the \texttt{liferay-versions} property to \texttt{7.2.0+}: \begin{verbatim} liferay-versions=7.2.0+ \end{verbatim} \item Velocity layout templates are supported, but deprecated as of Liferay DXP 7.1. We recommend that you convert your Velocity layout templates to FreeMarker now. Wrap the \texttt{processor.processColumn("column-1",\ "portlet-column-content\ portlet-column-content-first")} methods with braces (\texttt{\{...\}}) and change the template's file extension to \texttt{.ftl}. \item Update the Bootstrap \texttt{span{[}number{]}} classes to use the newer \texttt{col-{[}size{]}-{[}number{]}} classes. See \href{/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro}{Layout Templates} for more information on the updated syntax. \item Save the changes. Below is an example configuration: Original: \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} Updated: \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} \end{enumerate} Awesome! Your layout template is upgraded. \chapter{Upgrading 7.0 and 7.1 Layout Templates to 7.2}\label{upgrading-7.0-and-7.1-layout-templates-to-7.2} If you're upgrading your Liferay DXP 7.0 or Liferay DXP 7.1 layout template to 7.0, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open your layout template's \texttt{liferay-plugin-package.properties} file and update the \texttt{liferay-versions} property to \texttt{7.2.0+}: \begin{verbatim} liferay-versions=7.2.0+ \end{verbatim} \item Velocity layout templates are supported, but deprecated as of Liferay DXP 7.1. We recommend that you convert your Velocity layout templates to FreeMarker now. Wrap the \texttt{processor.processColumn("column-1",\ "portlet-column-content\ portlet-column-content-first")} methods with braces (\texttt{\{...\}}) and change the template's file extension to \texttt{.ftl}. \item Save the changes. Below is an example configuration: Original (\texttt{my\_layout\_template.tpl}): \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} Updated (\texttt{my\_layout\_template.ftl}): \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} \end{enumerate} Awesome! Your layout template is upgraded. \chapter{Upgrading Frameworks and Features}\label{upgrading-frameworks-and-features} Your upgrade process not only relies on portlet technology, themes, and customization plugins, but also the frameworks your project leverages. The following frameworks and their upgrade processes are discussed in this section: \begin{itemize} \tightlist \item JNDI data source usage \item Service Builder service invocation \item Service Builder \item Velocity templates \end{itemize} Continue on to learn more about upgrading these frameworks. Let's Go!{} \chapter{Upgrading JNDI Data Source Usage}\label{upgrading-jndi-data-source-usage} \begin{verbatim}

Upgrading Frameworks and Features

Step 1 of 4

\end{verbatim} In Liferay DXP's OSGi environment, you must use the portal's class loader to load the application server's JNDI classes. An OSGi bundle's attempt to connect to a JNDI data source without using Liferay DXP's class loader results in a \texttt{java.lang.ClassNotFoundException}. For more information on how to do this, see the \href{/docs/7-2/appdev/-/knowledge_base/a/connecting-to-data-sources-using-jndi}{Connecting to JNDI Data Sources} article. \chapter{Upgrading Service Builder Service Invocation}\label{upgrading-service-builder-service-invocation} \begin{verbatim}

Upgrading Frameworks and Features

Step 2 of 4

\end{verbatim} When upgrading a portlet leveraging \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder}, you must first decide if you're building your Service Builder logic as a WAR or modularizing it. \noindent\hrulefill \textbf{Note:} Service Builder portlets automatically migrated to Liferay Workspace using the Upgrade Planner or Blade CLI's \texttt{convert} command automatically have its Service Builder logic converted to API and implementation modules. This is a best practice for 7.0. \noindent\hrulefill If you prefer keeping your Service Builder logic as a WAR, you must implement a service tracker to call services. See the \href{/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker}{Service Trackers} article for more information. \chapter{Upgrading Service Builder}\label{upgrading-service-builder} \begin{verbatim}

Upgrading Frameworks and Features

Step 3 of 4

\end{verbatim} 7.0 continues to use \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder}, so you can focus on your application's business logic instead of its persistence details. It still generates model classes, local and remote services, and persistence. Upgrading most Service Builder portlets involves these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item \hyperref[step-1-adapt-the-code-to-product-vers-api]{Adapt the code to 7.0's API} \item \hyperref[step-2-resolve-dependencies]{Resolve dependencies} \item \hyperref[step-3-build-the-services]{Build the services} \end{enumerate} Start by adapting the code. \section{Step 1: Adapt the Code to 7.0's API}\label{step-1-adapt-the-code-to-7.0s-api} Adapt the portlet to 7.0's API using the Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. For example, consider an example portlet with the following compilation error: \begin{verbatim} /html/guestbook/view.jsp(58,1) PWC6131: Attribute total invalid for tag search-container-results according to TLD \end{verbatim} The \texttt{view.jsp} file specifies a tag library attribute \texttt{total} that doesn't exist in 7.0's \texttt{liferay-ui} tag library. Notice the second attribute \texttt{total}. \begin{verbatim} \end{verbatim} Remove the \texttt{total} attribute assignment to make the tag like this: \begin{verbatim} \end{verbatim} Resolve these error types and others until your code is adapted to the new API. \section{Step 2: Resolve Dependencies}\label{step-2-resolve-dependencies} To adapt your app's dependencies, refer to the \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolving a Project's Dependencies} tutorial. Once your dependencies are upgraded, rebuild your services! \section{Step 3: Build the Services}\label{step-3-build-the-services} An example change where upgrading legacy Service Builder code can produce differing results is explained below. A Liferay Portal 6.2 portlet's \texttt{service.xml} file specifies exception class names in \texttt{exception} elements like this: \begin{verbatim} ... GuestbookName EntryName EntryMessage EntryEmail \end{verbatim} In Liferay Portal 6.2, Service Builder generates exception classes to the path attribute \texttt{package-path} specifies. In 7.0, Service Builder generates them to \texttt{{[}package-path{]}/exception}. Old path: \begin{verbatim} [package-path] \end{verbatim} New path: \begin{verbatim} [package-path]/exception \end{verbatim} For example, the example portlet's package path is \texttt{com.liferay.docs.guestbook}. Its exception class for \texttt{exception} element \texttt{GuestbookName} is generated to \texttt{docroot/WEB-INF/service/com/liferay/docs/guestbook/exception}. Classes that use the exception must import \texttt{com.liferay.docs.guestbook.exception.GuestbookNameException}. If this upgrade is required in your Service Builder project, you must update the references to your portlet's exception classes. Once your Service Builder portlet is upgraded, \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploy it}. \noindent\hrulefill \textbf{Note:} Service Builder portlets automatically migrated to Liferay Workspace using the Upgrade Planner or Blade CLI's \texttt{convert} command automatically has its Service Builder logic converted to API and implementation modules. This is a best practice for 7.0. \noindent\hrulefill The portlet is now available on Liferay DXP. Congratulations on upgrading a portlet that uses Service Builder! \chapter{Migrating Off of Velocity Templates}\label{migrating-off-of-velocity-templates} \begin{verbatim}

Upgrading Frameworks and Features

Step 4 of 4

\end{verbatim} Velocity templates were deprecated in Liferay Portal 7.0 and are now removed in favor of FreeMarker templates in 7.0. Below are the key reasons for this move: \begin{itemize} \item FreeMarker is developed and maintained regularly, while Velocity is no longer actively being developed. \item FreeMarker is faster and supports more sophisticated macros. \item FreeMarker supports using taglibs directly rather than requiring a method to represent them. You can pass body content to them, parameters, etc. \end{itemize} Although Velocity templates still work in 7.0, we highly recommend migrating to FreeMarker templates. For more information on this topic, see the \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-layout-template-to-7-2}{Upgrading Layout Templates} section. \chapter{Upgrading Portlets}\label{upgrading-portlets} All portlet types developed for Liferay Portal 6.x, 7.0, and 7.1 can be upgraded and deployed to 7.0. Upgrading most portlets involves these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Adapt the code to 7.0's API \item Resolve dependencies \end{enumerate} Liferay's Upgrade Planner helps you adapt your code to 7.0's API. This makes resolving a portlet's dependencies straightforward. In most cases, after you finish the above steps, you can deploy your portlet to Liferay DXP. The portlet upgrade tutorials show you how to upgrade the following common portlets: \begin{itemize} \tightlist \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-genericportlet}{GenericPortlet} \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-liferay-mvc-portlet}{Liferay MVC Portlet} \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-liferay-jsf-portlet}{Liferay JSF Portlet} \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-servlet-based-portlet}{Servlet-based portlet} \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-spring-portlet-mvc-portlet}{Spring Portlet MVC} \item \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-a-struts-1-portlet}{Struts Portlet} \end{itemize} Let's get your portlet running on 7.0! Let's Go!{} \chapter{Upgrading a GenericPortlet}\label{upgrading-a-genericportlet} \begin{verbatim}

Upgrading Portlets

Step 1 of 6

\end{verbatim} It's common to create portlets that extend \texttt{javax.portlet.GenericPortlet}. After all, \texttt{GenericPortlet} provides a default \texttt{javax.portlet.Portlet} interface implementation. Upgrading a \texttt{GenericPortlet} is straightforward and takes only two steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the portlet to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy it} \end{enumerate} When the portlet WAR file is deployed, Liferay DXP's Plugin Compatibility Layer converts the WAR to a Web Application Bundle (WAB) and installs the portlet as a WAB to Liferay DXP's OSGi runtime. On deploying an upgraded portlet, the server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} Deploying a portlet produces messages like these: \begin{verbatim} 2018-03-21 17:44:59.179 INFO [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:262] Processing sample-dao-portlet-7.1.0.1.war ... 2018-03-21 17:45:09.959 INFO [Refresh Thread: Equinox Container: 0012cbb0-7e2c-0018-146e-95a4d71cdf95][PortletHotDeployListener:298] 1 portlet for sample-dao-portlet is available for use ... 2018-03-21 17:45:10.151 INFO [Refresh Thread: Equinox Container: 0012cbb0-7e2c-0018-146e-95a4d71cdf95][BundleStartStopLogger:35] STARTED sample-dao-portlet_7.1.0.1 [655] \end{verbatim} The portlet is now available on Liferay DXP. You've learned how to upgrade and deploy a portlet that extends \texttt{GenericPortlet}. You adapt the code, resolve dependencies, and deploy the portlet as you always have. It's just that easy! \chapter{Upgrading a Liferay MVC Portlet}\label{upgrading-a-liferay-mvc-portlet} \begin{verbatim}

Upgrading Portlets

Step 2 of 6

\end{verbatim} Liferay's MVC Portlet framework is used extensively in Liferay DXP's portlets and is a popular choice for portlet developers. The \href{https://docs.liferay.com/dxp/portal/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCPortlet.html}{\texttt{MVCPortlet}} class is a lightweight extension of \texttt{javax.portlet.GenericPortlet}. Its \texttt{init} method saves you from writing a lot of boilerplate code. MVC portlets can be upgraded to 7.0 without a hitch. Upgrading a Liferay MVC Portlet involves these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the portlet to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy it} \end{enumerate} After deploying the upgraded portlet, the server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} You've upgraded and deployed your Liferay MVC Portlet on your 7.0 instance. Have fun showing off your upgraded portlet! \chapter{Upgrading a Liferay JSF Portlet}\label{upgrading-a-liferay-jsf-portlet} \begin{verbatim}

Upgrading Portlets

Step 3 of 6

\end{verbatim} Liferay JSF portlets are easy to upgrade and require few changes. They interface with the \href{/docs/7-2/reference/-/knowledge_base/r/liferay-faces}{Liferay Faces} project, which encapsulates Liferay DXP's Java API and JavaScript code. Because of this, upgrading JSF portlets to 7.0 requires only updating dependencies. There are two ways to find a JSF portlet's dependencies for 7.0: \begin{itemize} \tightlist \item The \url{http://liferayfaces.org/} home page lets you look up the dependencies (Gradle or Maven) by Liferay DXP version, JSF version, and component suites. \item The \href{/docs/7-2/reference/-/knowledge_base/r/liferay-faces-version-scheme}{Liferay Faces Version Scheme} article's tables list artifacts by Liferay DXP version, JSF version, portlet version, and AlloyUI and Metal component suite version. \end{itemize} In this article, you'll upgrade a Liferay DXP JSF portlet's (JSF 2.2) dependencies to 7.0. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open your Liferay JSF portlet's build file (e.g., \texttt{pom.xml}, \texttt{build.gradle}) to where the dependencies are configured. \item Navigate to the \url{http://liferayfaces.org/} site and generate a dependency list by choosing the environment to which you want to upgrade your portlet. \begin{figure} \centering \includegraphics{./images/jsf-dependency-generation.png} \caption{The Liferay Faces site gives you options to generate dependencies for many environments.} \end{figure} \item Compare the generated dependencies with your portlet's dependencies and make any necessary updates. For example, in the sample dependencies listed below, the Mojarra dependency and two Liferay Faces dependencies require updating: \begin{verbatim} org.glassfish javax.faces 2.2.13 runtime com.liferay.faces com.liferay.faces.bridge.ext 3.0.0 com.liferay.faces com.liferay.faces.bridge.impl 4.0.0 \end{verbatim} Using the \url{http://liferayfaces.org/} dependency list as a guide, these dependencies would be updated to \begin{verbatim} org.glassfish javax.faces 2.2.19 runtime com.liferay.faces com.liferay.faces.bridge.ext 5.0.4 com.liferay.faces com.liferay.faces.bridge.impl 4.1.3 \end{verbatim} \end{enumerate} Once your Liferay JSF portlet's dependencies are updated, it's deployable to 7.0! Follow the \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploying a Project} article for deployment help. When the portlet WAR is deployed, Liferay DXP's Plugin Compatibility Layer converts the WAR to a Web Application Bundle (WAB) and installs the portlet as a WAB to Liferay DXP's OSGi runtime. The server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} Deploying a Liferay JSF portlet produces messages like these: \begin{verbatim} 13:41:43,690 INFO ... [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:252] Processing com.liferay.faces.demo.jsf.applicant.portlet-1.0.war ... 13:42:03,522 INFO [fileinstall-C:/liferay-ce-portal-7.2-ga1/osgi/war][BundleStartStopLogger:35] STARTED com.liferay.faces.demo.jsf.applicant.portlet-1.0_4.1.0 [503] ... 13:42:05,169 INFO [fileinstall-C:/liferay-ce-portal-7.2-ga1/osgi/war][PortletHotDeployListener:293] 1 portlet for com.liferay.faces.demo.jsf.applicant.portlet-1.0 is available for use \end{verbatim} After the portlet deployment is complete, it's available on Liferay DXP. You've learned how to upgrade and deploy a Liferay JSF portlet. You resolved dependencies and deployed the portlet as you always have. It's just that easy! \chapter{Upgrading a Servlet-based Portlet}\label{upgrading-a-servlet-based-portlet} \begin{verbatim}

Upgrading Portlets

Step 4 of 6

\end{verbatim} This tutorial shows you how to upgrade servlet-based portlets. It refers to code from before and after upgrading a sample servlet-based portlet called \emph{Sample JSON} (project \texttt{sample-json-portlet}). The portlet shows a \emph{Click me} link. When users click the link, the Liferay logo appears. Follow these steps to upgrade a servlet-based portlet: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the portlet to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy it} \end{enumerate} For an example upgrade scenario, consider this: Some servlet-based portlets relied on Liferay Portal to provide several dependency JAR files. Here's the \texttt{portal-dependency-jars} property from a sample portlet's \texttt{liferay-plugin-package.properties} file: \begin{verbatim} portal-dependency-jars=\ dom4j.jar,\ jabsorb.jar,\ json-java.jar \end{verbatim} This property is deprecated in 7.0 because importing and exporting Java packages has replaced wholesale use of JARs. This means modules and WABs can import packages without concerning themselves with JARs. Liferay DXP exports many third party packages for plugins to use. Best practices for using packages that Liferay DXP exports are found \href{/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies}{here}. Once you've deployed your portlet, the server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} The portlet is installed to Liferay's OSGi runtime and is available to users. Congratulations! You've upgraded and deployed your servlet-based portlet to 7.0. \chapter{Upgrading a Spring Portlet MVC Portlet}\label{upgrading-a-spring-portlet-mvc-portlet} \begin{verbatim}

Upgrading Portlets
Step 5 of 6

\end{verbatim} Upgraded portlets that use Spring Portlet MVC should be migrated to use PortletMVC4Spring. The main reason is that PortletMVC4Spring is maintained for compatibility with the latest versions of the Spring Framework. \noindent\hrulefill \textbf{Note:} The PortletMVC4Spring project began as Spring Portlet MVC and was part of the \href{https://spring.io/projects/spring-framework}{Spring Framework}. When the project was pruned from version 5.0.x of the Spring Framework under \href{https://github.com/spring-projects/spring-framework/issues/18701}{SPR-14129}, it became necessary to fork and rename the project. This made it possible to improve and maintain the project for compatibility with the latest versions of the Spring Framework and the Portlet API. \noindent\hrulefill \noindent\hrulefill \href{http://www.liferay.com/}{Liferay} adopted Spring Portlet MVC in March of 2019 and the project was renamed to PortletMVC4Spring. \noindent\hrulefill For more information on PortletMVC4Spring, see its dedicated \href{/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring}{section of articles}. For specific information on migrating a portlet using Spring Portlet MVC to PortletMVC4Spring, see the \href{/docs/7-2/appdev/-/knowledge_base/a/migrating-to-portletmvc4spring}{Migrating to PortletMVC4Spring} article. Once you've migrated your portlet to leverage the PortletMVC4Spring framework, you must also adapt your Liferay-specific APIs and dependencies. To do this, complete the following steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the portlet to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy it} \end{enumerate} After deploying the upgraded portlet, the server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} You've migrated your Spring Portlet MVC portlet to the updated PortletMVC4Spring framework, updated any additional APIs and dependencies, and deployed it to your 7.0 instance. Your portlet's upgrade process is complete! \chapter{Upgrading a Struts 1 Portlet}\label{upgrading-a-struts-1-portlet} \begin{verbatim}

Upgrading Portlets

Step 6 of 6

\end{verbatim} Struts is a stable, widely adopted framework that implements the Model View Controller (MVC) design pattern. If you have a Struts portlet for previous versions of Liferay Portal, you can upgrade it to 7.0. Upgrading Struts portlets to 7.0 is easier than you might think. Liferay DXP lets you continue working with Struts portlets as Java EE web applications. This tutorial demonstrates how to upgrade a portlet that uses the Struts 1 Framework. Here's a sample Struts portlet's folder structure with file/folder descriptions: \begin{itemize} \tightlist \item \texttt{sample-struts-portlet} \begin{itemize} \tightlist \item \texttt{docroot/} \begin{itemize} \tightlist \item \texttt{html/portlet/sample\_struts\_portlet/} → JSPs \item \texttt{WEB-INF/} \begin{itemize} \tightlist \item \texttt{lib/} → Required third-party libraries unavailable in the Liferay DXP system \item \texttt{src/} \begin{itemize} \tightlist \item \texttt{com/liferay/samplestruts/model/} → Model classes \item \texttt{com/liferay/samplestruts/servlet/} → Test servlet and servlet context listener \item \texttt{com/liferay/samplestruts/struts/} \begin{itemize} \tightlist \item \texttt{action/} → \texttt{Action} classes that return View pages to the client \item \texttt{form/} → \texttt{ActionForm} classes for model interaction \item \texttt{render/} → \texttt{Action} classes that present additional pages and handle input \item \texttt{SampleException.java} → Exception class \end{itemize} \item \texttt{content/test/} → Resource bundles \item \texttt{META-INF/} → Javadoc \end{itemize} \item \texttt{tld/} → Tag library definitions \item \texttt{liferay-display.xml} → Sets the application category \item \texttt{liferay-plugin-package.properties} → Sets metadata and portal dependencies \item \texttt{liferay-portlet.xml} → Maps descriptive role names to roles \item \texttt{liferay-releng.properties} → (internal) Release properties \item \texttt{portlet.xml} → Defines the portlet and its initialization parameters and security roles \item \texttt{struts-config.xml} → Struts configuration \item \texttt{tiles-defs.xml} → Struts Tile definitions \item \texttt{validation.xml} → Defines form inputs for validation \item \texttt{validation-rules.xml} → Struts validation rules \item \texttt{web.xml} → Web application descriptor \end{itemize} \end{itemize} \item \texttt{build.xml} → Apache Ant build file \end{itemize} \end{itemize} Upgrading a Struts 1 portlet involves these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the portlet to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \end{enumerate} You've resolved the Sample Struts portlet's dependencies. It's ready to deploy. \noindent\hrulefill \textbf{Important}: Setting Portal property \texttt{jsp.page.context.force.get.attribute} (described in the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#JSP}{JSP section}) to \texttt{true} (default) forces calls to \texttt{com.liferay.taglib.servlet.PageContextWrapper\#findAttribute(String)} to use \texttt{getAttribute(String)}. Although this improves performance by avoiding unnecessary fall-backs, it can cause attribute lookup problems in Struts portlets. To use Struts portlets in your sites, makes sure to set the Portal property \texttt{jsp.page.context.force.get.attribute} to \texttt{false} in a file \texttt{{[}Liferay-Home{]}/portal-ext.properties}. \begin{verbatim} jsp.page.context.force.get.attribute=false \end{verbatim} \noindent\hrulefill On \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{deploying} a Struts portlet Web Application aRchive (WAR), Liferay DXP's Web Application Bundle (WAB) Generator creates an OSGi module (bundle) for the portlet and installs it to Liferay's OSGi framework. The server prints messages indicating the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} The Struts portlet is now available on your Liferay DXP instance. The Struts portlet behaves just as it did on previous versions on your 7.0 site. Congratulations on upgrading your Struts portlet to 7.0! \chapter{Upgrading Web Plugins}\label{upgrading-web-plugins} \begin{verbatim}

Upgrading Web Plugins

Step 1 of 1

\end{verbatim} Web plugins are regular \href{https://docs.oracle.com/cd/E19226-01/820-7627/bnadx/index.html}{Java EE web modules} designed to work with Liferay DXP. These plugins were stored in the \texttt{webs} folder of the legacy Plugins SDK. Upgrading a Liferay web plugin involves these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Adapt the plugin to 7.0's API using the Liferay Upgrade Planner. When running the planner's \emph{Fix Upgrade Problems} step, many of the existing issues are autocorrected. For remaining issues, the planner identifies code affected by the new API and ways to adapt it. \item \href{/docs/7-2/tutorials/-/knowledge_base/t/resolving-a-projects-dependencies}{Resolve its dependencies} \item \href{/docs/7-2/reference/-/knowledge_base/r/deploying-a-project}{Deploy it} \end{enumerate} After deploying the upgraded portlet, the server prints messages that indicate the following portlet status: \begin{itemize} \tightlist \item WAR processing \item WAB startup \item Availability to users \end{itemize} You've upgraded and deployed your Liferay web plugin on your 7.0 instance. Great job! \chapter{Upgrading Ext Plugins}\label{upgrading-ext-plugins} Ext plugins let you use internal APIs and even let you overwrite Liferay DXP core files. This puts your deployment at risk of being incompatible with security, performance, or feature updates released by Liferay. When upgrading to a new version of Liferay DXP, you must review all changes and manually modify your Ext projects to merge your changes with Liferay DXP's. During your upgrade to 7.0, it's highly recommended to leverage an extension point to customize Liferay DXP instead of using you existing Ext plugin, if possible. 7.0 provides many extension points that let you customize almost every detail of Liferay DXP. If there's a way to customize what you want with an extension point, do it that way instead. See \href{/docs/7-2/customization/-/knowledge_base/c/finding-extension-points}{Finding Extension Points} for more details. For more information on Ext projects, how to decide if you need one, and how to manage them, see the \href{/docs/7-2/customization/-/knowledge_base/c/customization-with-ext}{Customization with Ext} section. \chapter{Creating a Theme}\label{creating-a-theme} This tutorial takes you step-by-step through the process of creating a theme. You'll create a responsive theme for Liferay's Lunar Resort that demonstrates best practices and uses Liferay DXP's theme tools, extensions, and mechanisms. Several example files are referenced throughout this tutorial. You can download the \href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code}{\texttt{lunar-resort-theme.zip}} if you want to follow along locally. The Lunar Resort theme's files are also included in the \href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme}{\texttt{lunar-resort-theme}} folder of the Liferay Docs repo, if you would rather view them there. This tutorial covers these topics: \begin{itemize} \tightlist \item Generating the theme and configuring it to extend the Atlas base theme \item Customizing the Header and logo \item Customizing the Header navigation \item Customizing the Footer and embedding footer navigation \item Creating a color scheme variant \end{itemize} By the end of this tutorial, you'll be able to create the theme below: \begin{figure} \centering \includegraphics{./images/theme-tutorial-finished-theme.png} \caption{The finished Lunar Resort Theme uses Liferay DXP's tools to produce a user-friendly UI that is maintainable.} \end{figure} \chapter{Setting up the Theme}\label{setting-up-the-theme} In this section, you'll use the Liferay JS Theme Toolkit's Liferay Theme Generator to generate the theme's files. You'll complete these tasks: \begin{itemize} \tightlist \item Install the Liferay Theme Generator and its dependencies \item Generate a theme \item Configure the theme to extend the \href{/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes}{Atlas base theme}. \end{itemize} Atlas provides the look of the Classic theme. It builds on the default Clay Base theme and provides additional styles. Follow these steps to generate and configure the theme: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Install the Theme Generator. Since you're developing a theme for 7.0, install v9.x.x if it's not installed already. Run the command below: \begin{verbatim} npm install -g generator-liferay-theme@9.x.x \end{verbatim} \item Install the Yeoman and gulp dependencies: \begin{verbatim} npm install -g yo gulp \end{verbatim} \item Generate the starting theme with the Theme Generator. Enter \emph{Lunar Resort Theme} for the name and \emph{lunar-resort} for the ID, and answer no for the Font Awesome prompt. This theme uses Clay icons instead: \begin{verbatim} yo liferay-theme \end{verbatim} \begin{figure} \centering \includegraphics{./images/theme-tutorial-yeoman-prompt.png} \caption{Answer no for the Font Awesome Prompt} \end{figure} \item To develop the theme you must copy the default files from the theme's build and modify them. The \texttt{/src/css/} folder and \texttt{\_custom.scss} file are included by default. Run the command below from the theme's root folder to build the files: \begin{verbatim} gulp build \end{verbatim} \item Create a new \texttt{/src/templates/} folder and copy \texttt{portal\_normal.ftl} from the \texttt{build/templates/} folder into it. \item Configure the theme to extend the Atlas theme. Add a \texttt{clay.scss} file to the theme's \texttt{/src/css/} folder and add the import shown below: \begin{verbatim} @import "clay/atlas"; \end{verbatim} \item Create an \texttt{\_imports.scss} file in the \texttt{/src/css/} folder and add the imports shown below to it. This includes the default imports and replaces the \texttt{clay/base-variables} with the Atlas base variables: \begin{verbatim} @import "bourbon"; @import "mixins"; @import "compat/mixins"; @import "clay/atlas-variables"; \end{verbatim} \end{enumerate} You've generated the theme, prepared it for development, and configured it to extend the Atlas theme. Continue to the next section to build the Lunar Resort's Header and customize the logo. \chapter{Customizing the Lunar Resort's Header and Logo}\label{customizing-the-lunar-resorts-header-and-logo} The Header contains the navigation and logo for the site. In this section you'll customize the look and feel of the Header and add a custom logo. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open \texttt{portal\_normal.ftl} and replace the \texttt{\textless{}header\textgreater{}...\textless{}/header\textgreater{}} element and contents with the updated code snippet below. This updates the structure slightly, making the banner expand the full width of the Header, and adds a new \texttt{header\_css\_class} variable to the \texttt{class} attribute. This variable is defined in a later step. \begin{verbatim}
\end{verbatim} \item Replace the \texttt{\textless{}div\ class="container-fluid"\ id="wrapper"\textgreater{}} element with the updated code below to remove some margins and padding: \begin{verbatim}
\end{verbatim} And move the wrapper down, and place it directly above the \texttt{\textless{}section\ id="content"\textgreater{}} element: \begin{verbatim}
...
...
\end{verbatim} \item The logo's height is retrieved with the \texttt{\$\{site\_logo\_height\}} variable. The height of the logo is a bit too large for the Lunar Resort theme, so you must adjust it. Remove the \texttt{width} attribute from the logo's image so it defaults to \texttt{auto}: \begin{verbatim} ${logo_description} \end{verbatim} \item Create \texttt{init\_custom.ftl} in your theme's \texttt{/src/templates/} folder and assign the logo's \texttt{site\_logo\_height} variable to the value below: \begin{verbatim} <#assign site_logo_height = 56 /> \end{verbatim} \item Assign the new \texttt{header\_css\_class} variable you added in step one to the value below: \begin{verbatim} < #assign header_css_class = "navbar navbar-expand-md navbar-dark flex-column flex-md-row bd-navbar" /> \end{verbatim} This applies Bootstrap and Clay utility classes to provide the overall look and feel of the Header. Assigning the classes to a variable keeps \texttt{portal\_normal} clean and makes the code easy to maintain. If you want to update the classes, you just have to modify the variable (e.g.~\texttt{header\_css\_class\ =\ header\_css\_class\ +\ "\ my-new-class"}). \item Add the code snippet below to update the \texttt{logo\_css\_class} variable to use Bootstrap's \texttt{navbar-brand} class: \begin{verbatim} <#assign logo_css_class = logo_css_class + " navbar-brand" /> \end{verbatim} \item Before you upload the theme to see what it looks like so far, you must \href{/docs/7-2/frameworks/-/knowledge_base/f/creating-a-thumbnail-preview-for-your-theme}{create a theme thumbnail} so you can identify it. To save time, copy the \texttt{thumbnail.png} asset from the {[}\texttt{lunar-resort-build/assets{]}(./images/}{]}(https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets folder to a new \texttt{/src{]}(./images/} folder. Note that its dimensions are 480px by 270px. These dimensions are required to display the theme thumbnail properly. \item The theme isn't complete yet, but you'll deploy what you have so you can replace the default logo with the Lunar Resort logo. Enable \href{/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes}{Developer Mode} before deploying your theme, so the theme's files are not cached for future deployments. Start the server, if it's not already started, and deploy the theme with the command below: \begin{verbatim} gulp deploy \end{verbatim} \item Before you configure the pages, you must import the Lunar Resort's pages. Open the Control Menu and navigate to \emph{Publishing} → \emph{Import}. Click the Plus button to create a new import process. Click \emph{Select File} and import the \texttt{lunar\_resort\_pages.lar} from the \href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets}{\texttt{lunar-resort-build/assets/}} folder. Keep the default settings and click \emph{Import}. \item Open the Control Menu and navigate to \emph{Site Builder} → \emph{Pages}. Click the Gear icon next to \emph{Public Pages} to open the configuration menu. Under the \emph{Look and Feel} tab, scroll down and click the \emph{Change Current Theme} button and select the Lunar Resort Theme. Scroll to the Logo heading, click the \emph{Change} button, upload the \texttt{lunar-resort-logo.png} asset from the {[}\texttt{lunar-resort-build/assets{]}(./images/}{]}(https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets folder, and click the \emph{Save} button to apply the theme and logo. \end{enumerate} Great! You've customized the Lunar Resort's Header and applied a custom logo. Next, you'll configure and customize the theme's navigation. \chapter{Customizing the Navigation}\label{customizing-the-navigation} Navigation items (pages) are defined and configured in Liferay DXP. The Navigation template iterates through the existing navigation items (pages) and assigns the template's markup for each of them. Page updates therefore require no updates to the theme directly and can be made by a Site Administrator, thus reducing the maintenance costs. To customize the navigation, you can either use the default navigation provided in \texttt{navigation.ftl} and customize the markup template, or you can embed the navigation portlet in the theme and customize its preferences. Both approaches use the same overall markup. This section takes the former approach and customizes the default configuration in \texttt{navigation.ftl}. in the next section, you'll embed the navigation portlet in the Footer and configure its preferences to only display the top level (parent) navigation items. Follow these steps to configure the Header's navigation: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Copy the default \texttt{navigation.ftl} file from the \texttt{/src/build/templates/} folder into the theme's \texttt{/src/templates/} folder. The \texttt{build} folder was generated when you built the theme and again when you initially deployed the theme in the last section. \item By default, the User Personal Bar is hidden from the theme. You can either enable this via System Settings outside the scope of the theme, or you can include it in your theme. In this case, you'll include it in the theme. Open the \texttt{navigation.ftl} template you just copied and add this User Personal Bar markup to the top: \begin{verbatim}
<@liferay.user_personal_bar />
\end{verbatim} along with some utility classes to position and order the User Personal Bar, this also adds a custom \texttt{lunar-user} class, which you'll use later for styling. \item Modify the default template to use Bootstrap's \texttt{navbar} format. Wrap the \texttt{\textless{}nav\textgreater{}...\textless{}/nav\textgreater{}} element with the \texttt{\textless{}div\textgreater{}} shown below: \begin{verbatim} \end{verbatim} \item Open the \texttt{portal\_normal.ftl} template and find this conditional wrapper: \begin{verbatim} <#if has_navigation> <#include "${full_templates_path}/navigation.ftl" /> \end{verbatim} Update the conditional to include the menu toggler for the mobile navigation. This targets the \texttt{\#lunarNav} wrapper that you added in the previous step: \begin{verbatim} <#if has_navigation> <#include "${full_templates_path}/navigation.ftl" /> \end{verbatim} \item Open \texttt{navigation.ftl} and add the \texttt{navbar-nav} and \texttt{mr-auto} classes to the \texttt{\textless{}ul\textgreater{}} element at the top: \begin{verbatim}
    " class="navbar-nav mr-auto" role="menubar"> \end{verbatim} \item Open \texttt{navigation.ftl} and replace the first \texttt{\textless{}\#assign...\ /\textgreater{}} declaration with the one below. This adds the \texttt{nav-item} class to the \texttt{nav\_item\_css\_class} variable declaration in \texttt{navigation.ftl} and declares a new \texttt{nav\_item\_caret} variable: \begin{verbatim} <#assign nav_item_attr_has_popup = "" nav_item_css_class = "nav-item" nav_item_layout = nav_item.getLayout() nav_item_caret = "" /> \end{verbatim} \item Replace the \texttt{nav\_item.isSelected} conditional block with the one shown below. This adds the \texttt{selected} class to the existing \texttt{nav\_item\_css\_class} classes: \begin{verbatim} <#if nav_item.isSelected()> <#assign nav_item_attr_has_popup = "aria-haspopup='true'" nav_item_css_class = "${nav_item_css_class} selected" /> \end{verbatim} \item The Lunar Resort contains nested pages (child navigation items). By default, child navigation items are displayed at the block level. Instead, the Administrator wants to display these items in a dropdown list that is only displayed on hover of the parent navigation item. Add this conditional block directly below the \texttt{nav\_item.isSelected} block you just modified. This adds the \texttt{dropdown} class to the parent navigation item and updates the \texttt{nav\_item\_caret} variable to hold Clay caret icon markup to indicate the parent navigation has nested child items: \begin{verbatim} <#if nav_item.hasChildren()> <#assign nav_item_css_class = "${nav_item_css_class} dropdown" nav_item_caret = ' ' /> \end{verbatim} \item Locate the anchor's markup below: \begin{verbatim} <@liferay_theme["layout-icon"] layout=nav_item_layout /> ${nav_item.getName()} \end{verbatim} Replace it with the updated markup shown below to include the \texttt{\$\{nav\_item\_caret\}} variable: \begin{verbatim} <@liferay_theme["layout-icon"] layout=nav_item_layout /> ${nav_item.getName()} ${nav_item_caret} \end{verbatim} \item Add the \texttt{dropdown-menu} class to the \texttt{\textless{}ul\ class="child-menu"\ role="menu"\textgreater{}} element and replace the \texttt{nav\_child\_css\_class} variable declarations with the ones below to add the \texttt{nav-item} class to them: \begin{verbatim} <#assign nav_child_css_class = "nav-item" /> <#if nav_item.isSelected()> <#assign nav_child_css_class = "nav-item selected" /> \end{verbatim} \item Find the \texttt{\textless{}a\textgreater{}} element with the \texttt{aria-labelledby="layout\_\$\{nav\_child.getLayoutId()\}"} attribute and add the \texttt{class="nav-link"} attribute to it: \end{enumerate} \begin{verbatim} ```markup ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{11} \tightlist \item Add a call to action for the visitors to the Lunar Resort site so they can book their flight. Add the book now button's code below the closing \texttt{\textless{}/nav\textgreater{}} element. This uses some utility classes for the basic look and feel and ordering, as well as a custom \texttt{btn-orange} class that you'll provide styling for later: \end{enumerate} \begin{verbatim} ```html

    Book Now

    ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{12} \item The Lunar Resort's color scheme is comprised of three colors: orange, white, and blue. Since these colors are used throughout the theme, you'll store them in SASS variables in a separate file. Create a new file called \texttt{\_colors.scss} inside the theme's \texttt{/src/css/} folder and add these variables to it. Note that White is already defined as the global variable \texttt{\$white} by the Atlas theme. \begin{verbatim} $lunar-resort-orange: #dfa356; $lunar-resort-blue: #415fa7; $lunar-resort-link-teal: #00ccFF; \end{verbatim} \item Now that the main colors are defined, open \texttt{/src/css/\_custom.scss} and add the code snippet below. This imports the \texttt{\_colors.scss} file so you can use the variables you just created. It adds some basic styling for the Header and navigation, including a style to highlight the page that is currently active via the \texttt{selected} class. It also displays the child menu items at the block level on smaller devices with the \texttt{@include\ media-breakpoint-down} breakpoint: \begin{verbatim} @import 'colors'; body { a.btn-orange { background-color: $lunar-resort-orange; margin-right: 5px; &:hover { border-color: $white; } @include media-breakpoint-down(sm){ width: 100%; } } header { background-color: $lunar-resort-blue; .lunar-user a { color: $lunar-resort-link-teal; } .user-avatar-link .lexicon-icon { color: $lunar-resort-blue; } li.nav-item { & a.nav-link span { font-size: 1.5em; } &:hover ul.child-menu { background-color: $lunar-resort-blue; display: block; margin-top: -10px; } &.selected { background-color: $white; height: 73px; & a.nav-link { color: $lunar-resort-blue; font-weight: bold; &:hover { color: $lunar-resort-blue; font-weight: normal; padding-left: 9.619px; padding-right: 9.619px; } } } @include media-breakpoint-down(sm){ ul.child-menu { display: block; } } } } } \end{verbatim} \item The Control Menu is displayed on top of everything when the user is signed in, which covers the Header. You must update the \texttt{navigation.ftl} template to account for the Control Menu. Liferay DXP provides a unique class that is added to the \texttt{body} of the page when each product navigation (which includes the Control Menu) is visible. Use the \texttt{has-control-menu} class is added to the body when the Control Menu is visible. Open \texttt{\_custom.scss} and add this code snippet just above the closing bracket for the \texttt{body} to add a top margin to the Header that's equal to the height of the Control Menu: \begin{verbatim} &.has-control-menu { header { margin-top: 56px; } } \end{verbatim} \item The Control Menu's height is slightly smaller on mobile devices, so you must account for that responsiveness in your styling. Update the code snippet you just added to match the one below: \begin{verbatim} &.has-control-menu { header { margin-top: 56px; @include media-breakpoint-down(sm){ margin-top: 48px; } } } \end{verbatim} \end{enumerate} Great! The Header's navigation is customized. The updated Header and logo should look like the figure below: \begin{figure} \centering \includegraphics{./images/theme-tutorial-updated-navigation.png} \caption{The updated Header and navigation are much more user-friendly now.} \end{figure} Next, you'll define the Footer and embed a navigation portlet to display navigation. \chapter{Defining the Lunar Resort's Footer and Footer Navigation}\label{defining-the-lunar-resorts-footer-and-footer-navigation} You've configured the Header and its navigation, but at the moment the Footer is a bit bare bones. In this section, you'll update the Footer to include contact information for the Lunar Resort and include navigation with an embedded navigation portlet. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item To keep the Portal Normal template uncluttered, create a separate template to hold the Footer's markup. Create a new file called \texttt{footer.ftl} in the theme's \texttt{/src/templates/} folder. \item Copy the Footer markup (shown below) from \texttt{portal\_normal.ftl} into \texttt{footer.ftl}: \begin{verbatim}

    <@liferay.language key="powered-by" /> Liferay

    \end{verbatim} And update the \texttt{\textless{}p\textgreater{}} element in \texttt{footer.ftl} to include the classes shown below: \begin{verbatim}

    <@liferay.language key="powered-by" /> Liferay

    \end{verbatim} \item Add this \texttt{@liferay.navigation\_menu} macro snippet above the \texttt{powered-by} paragraph to embed the navigation portlet. This configuration stores the portlet preferences in a \texttt{preferencesMap} variable. The \texttt{displayDepth} of \texttt{1} specifies that the portlet must only render the top-level parent navigation, and \texttt{portletSetupPortletDecoratorId} sets the portlet decorator to \texttt{barebone}, which removes the portlet's wrapper and only renders the portlet's content: \begin{verbatim} \end{verbatim} \item The visitors need some social media links so they can keep tabs on the latest and greatest news from the Lunar Resort. Replace the snippet you just added with the one below. This uses \href{https://clayui.com/docs/components/icons.html}{Clay icons} and adds a wrapper to prepare for the next step. \begin{verbatim} \end{verbatim} \item Add this snippet below the closing \texttt{\textless{}/nav\textgreater{}} tag to add the Lunar Resort's contact information. Also, copy the \texttt{lunar-resort-logo-vertical.png} asset from the {[}\texttt{lunar-resort-build/assets{]}(./images/}{]}(https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets folder to the \texttt{/src{]}(./images/} folder so you can use it in the Footer: \begin{verbatim}
    lunar-resort-logo

    123 Mare Nectaris Lane
    Mare Nectaris, Moon Colony 10010

    Tel: 4-919-843-6666
    Fax: 4-919-843-6667
    info@thelunarresort.com

    \end{verbatim} \item The Administrator doesn't want to display the Footer on every page, so she would like the option to hide it. To do that, create a \href{/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings}{theme setting} to optionally show the Footer. Open your theme's \texttt{/src/WEB-INF/liferay-look-and-feel.xml} file and add this snippet just below the \texttt{\textless{}template-extension\textgreater{}ftl\textless{}/template-extension\textgreater{}} entry. This renders a togglable \emph{Show Footer} option in the \emph{Look and Feel} section for the theme's configuration. \begin{verbatim} \end{verbatim} \item Now you must define a FreeMarker variable to store the value of the \texttt{show-footer} theme setting so you can check for it in \texttt{portal\_normal.ftl}. Open \texttt{init\_custom.ftl} and add the variable declaration below to set the \texttt{show\_footer} variable to the value (true or false) of the \texttt{show-footer} theme setting: \begin{verbatim} <#assign show_footer = getterUtil.getBoolean(themeDisplay.getThemeSetting("show-footer")) /> \end{verbatim} \item Open \texttt{portal\_normal.ftl} and replace the Footer markup with the code snippet below to include the Footer template when the \texttt{show-footer} theme setting is \texttt{true}: \begin{verbatim} <#if show_footer> <#include "${full_templates_path}/footer.ftl" /> \end{verbatim} \item Open \texttt{\_custom.scss} and add this snippet above the \texttt{\&.has-control-menu} styling to style the Footer: \begin{verbatim} #footer { background-color: $lunar-resort-blue; color: $white; ul { margin-left: auto; margin-right: auto; &.navbar-nav { width: 410px; .nav-item.hover:after { width: auto; } a { color: $white; @include media-breakpoint-down(sm) { padding-left: 6px; padding-right: 6px; } } } } #socialMediaWrapper ul { width: 192px; li a { font-size: 2rem; } } p.powered-by a, .contact-info-container a { color: $lunar-resort-link-teal; } } \end{verbatim} \item The majority of the Lunar Resort's content is provided with \href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Fragments}. Since Fragments are out of the scope of this tutorial, you'll upload the completed fragments. Open the Control Menu and navigate to \emph{Site Builder} → \emph{Page Fragments}, and select the \emph{Import} option from the New dropdown menu. Import the \texttt{collections-lunar-resort.zip} asset from the \href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets}{\texttt{lunar-resort-build/assets/}} folder. \item Re-deploy the updated theme with the command below: \begin{verbatim} gulp deploy \end{verbatim} \end{enumerate} The updated Footer and navigation should look like the figure below: \begin{figure} \centering \includegraphics{./images/theme-tutorial-updated-footer.png} \caption{The updated Footer provides everything visitors need to follow and contact the Lunar Resort.} \end{figure} In the next section you'll learn how to create a color scheme for the Lunar Resort. \chapter{Adding a Color Scheme Variant for the Lunar Resort Theme}\label{adding-a-color-scheme-variant-for-the-lunar-resort-theme} In this section, you'll create a color scheme variant for the Lunar Resort Theme to apply during the Lunar Eclipse, when special discounts are available. You'll create a color scheme that reflects the reds and yellows present during a lunar eclipse. Since the majority of the Lunar Resort Site's content is created with page fragments, you must account for the color scheme styles in the page fragments as well. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the theme's \texttt{WEB-INF/liferay-look-and-feel.xml} file and add these color-scheme entries above the \texttt{\textless{}portlet-decorator\textgreater{}..\textless{}/portlet-decorator\textgreater{}} ones: \begin{verbatim} ftl true default ${images-path}/color_schemes/${css-class} eclipse ... \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 Open \texttt{/src/css/\_colors.scss} and update the colors to include the two new ones (eclipse yellow and eclipse red) for the color scheme: \begin{verbatim} $lunar-resort-orange: #dfa356; $lunar-resort-blue: #415fa7; $lunar-resort-link-teal: #00ccFF; $lunar-resort-eclipse-yellow: #dfd456; $lunar-resort-eclipse-red: #a75441; \end{verbatim} \item Create a \texttt{/src/css/color\_schemes/} folder for the color scheme, and add a \texttt{eclipse.scss} file to it for the Eclipse color scheme. The default color scheme's styles are included in \texttt{\_custom.scss}, so you don't need to create anything for them. \item The color scheme's class is added to the \texttt{\textless{}body\textgreater{}} element when the theme's color scheme is applied, so you must prefix the body styles with the \texttt{eclipse} class to target the proper color scheme. Open \texttt{/src/css/color\_schemes/eclipse.scss} and add this import and styles to it to use the new colors you defined: \begin{verbatim} @import '../colors'; body.eclipse { a.btn-orange { background-color: $lunar-resort-eclipse-yellow; } header { background-color: $lunar-resort-eclipse-red; .user-avatar-link .lexicon-icon { color: $lunar-resort-eclipse-red; } li.nav-item { ul.child-menu { background-color: $lunar-resort-eclipse-red; } &:hover ul.child-menu { background-color: $lunar-resort-eclipse-red; } &.selected { & a.nav-link { color: $lunar-resort-eclipse-red; &:hover { color: $lunar-resort-eclipse-red; } } } } } #footer { background-color: $lunar-resort-eclipse-red; } } \end{verbatim} \item Import the eclipse color scheme's CSS file into \texttt{\_custom.scss} so it's loaded with the rest of the custom styles: \begin{verbatim} @import "color_schemes/eclipse"; \end{verbatim} \item You must create thumbnails for each color scheme, just like you did the theme. To save time, copy the {[}\texttt{lunar-resort-build/assets{]}(./images/color\_schemes/}{]}(https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets folder to the theme's \texttt{/src{]}(./images/} folder. Note that the color scheme folder names match the color scheme CSS class names defined in \texttt{liferay-look-and-feel.xml}. \item Now that the color scheme is created, you must update the page fragments to use the eclipse color scheme class so they have the same look as the color scheme when it's applied to the page. The fragments don't have access to the SASS color variables, so you must use the hexadecimal color codes. To save time, import the updated page fragments from the \texttt{lunar-resort-build/assets/} folder. Open the Control Menu and navigate to \emph{Site Builder} → \emph{Page Fragments}, click the Actions menu next to COLLECTIONS, and select the \emph{Import} option. Import the \texttt{collections-lunar-resort-color-scheme.zip} asset from the \href{https://github.com/liferay/liferay-docs/tree/master/en/developer/tutorials/code/lunar-resort-theme/lunar-resort-build/assets}{\texttt{lunar-resort-build/assets/}} folder. Note that each fragment style that requires a color change is duplicated and prefixed with \texttt{body.eclipse}. A couple example configurations are shown below: \begin{verbatim} .fragment_35201 h3.text-center { background-color: #dfa356; color: #FFF; } body.eclipse .fragment_35201 h3.text-center { background-color: #dfd456; } \end{verbatim} \begin{verbatim} .fragment_35201 a.btn { background-color: #415fa7; } body.eclipse .fragment_35201 a.btn { background-color: #a75441; } \end{verbatim} \item Deploy the theme. Open the Control Menu, navigate to \emph{Site Builder} → \emph{Pages}, and click the Gear icon next to \emph{Public Pages}. Select the Eclipse color scheme under the \emph{LOOK AND FEEL} tab and save to apply the changes. \begin{figure} \centering \includegraphics{./images/theme-tutorial-color-schemes.png} \caption{Color schemes are a good way to subtly change the look and feel of your site.} \end{figure} The theme should look like the figure below with the Eclipse color scheme applied: \begin{figure} \centering \includegraphics{./images/theme-tutorial-eclipse-color-scheme.png} \caption{The finished color scheme gives the Lunar Resort site a fiery glow.} \end{figure} \end{enumerate} Great! You've seen how you can quickly change the look and feel of the Lunar Resort with just a simple color scheme. Now you know how to develop a theme to customize the overall look and feel of your site! See the \href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes section} for information on developing themes. ================================================ FILE: book/developing-liferay-dxp-72.tex ================================================ \documentclass[11pt,openright,twoside]{memoir} % \usepackage{ucs} \usepackage[english]{babel} \usepackage{fontspec} \usepackage{graphicx} \usepackage{calc} \usepackage{hyperref} % \usepackage{enumerate} \usepackage{enumitem} \usepackage{ctable} %\usepackage[labelformat=empty,font=small]{caption} \usepackage{titlesec} \usepackage{float} \usepackage{morefloats} \usepackage{wrapfig} \usepackage{longtable} \usepackage{geometry} \usepackage{framed} \defaultfontfeatures{Ligatures=TeX} % Begin stuff from Pandoc template \usepackage{amsmath,amssymb} \usepackage{iftex} \ifPDFTeX \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage{textcomp} % provide euro and other symbols \else % if luatex or xetex \usepackage{unicode-math} % this also loads fontspec \defaultfontfeatures{Scale=MatchLowercase} \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} \fi \usepackage{lmodern} \ifPDFTeX\else % xetex/luatex font selection \fi % Use upquote if available, for straight quotes in verbatim environments \IfFileExists{upquote.sty}{\usepackage{upquote}}{} \IfFileExists{microtype.sty}{% use microtype if available \usepackage[]{microtype} \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts }{} \makeatletter \@ifundefined{KOMAClassName}{% if non-KOMA class \IfFileExists{parskip.sty}{% \usepackage{parskip} }{% else \setlength{\parindent}{0pt} \setlength{\parskip}{6pt plus 2pt minus 1pt}} }{% if KOMA class \KOMAoptions{parskip=half}} \makeatother \usepackage{xcolor} \usepackage{color} \usepackage{fancyvrb} \newcommand{\VerbBar}{|} \newcommand{\VERB}{\Verb[commandchars=\\\{\}]} \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} % Add ',fontsize=\small' for more characters per line \newenvironment{Shaded}{}{} \newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} \newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}} \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\BuiltInTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{#1}} \newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}} \newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}} \newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} \newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}} \newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}} \newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} \newcommand{\ExtensionTok}[1]{#1} \newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}} \newcommand{\ImportTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{\textbf{#1}}} \newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} \newcommand{\NormalTok}[1]{#1} \newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} \newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}} \newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}} \newcommand{\RegionMarkerTok}[1]{#1} \newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}} \newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}} \newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \makeatletter \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} \def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} \makeatother % Scale images if necessary, so that they will not overflow the page % margins by default, and it is still possible to overwrite the defaults % using explicit options in \includegraphics[width, height, ...]{} \setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} % Set default figure placement to htbp \makeatletter \def\fps@figure{htbp} \makeatother \setlength{\emergencystretch}{3em} % prevent overfull lines \providecommand{\tightlist}{% \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} \setcounter{secnumdepth}{-\maxdimen} % remove section numbering \ifLuaTeX \usepackage{selnolig} % disable illegal ligatures \fi \IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} \IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available \urlstyle{same} % End stuff from Pandoc template %\geometry{paperwidth=191mm,paperheight=235mm, % hmargin={20mm,20mm},vmargin={20mm,20mm}} \geometry{letterpaper, hmargin={1in, 1in}, vmargin={1in,1in}} \setmainfont{Source Serif Pro} \setsansfont{Source Sans Pro} \setmonofont{IosevkaTermSlab Nerd Font} \newfontfamily\sectionfont{Source Sans Pro} \newfontfamily\subsectionfont{Source Sans Pro} \newfontfamily\subsubsectionfont{Source Sans Pro} \newfontfamily\captionfont{Source Sans Pro} \newcommand{\hruleafter}[1]{#1\hrule} \titleformat{\section}{\large\bfseries\sffamily\sectionfont}{\thesection}{1em}{\hruleafter} \titleformat*{\subsection}{\bfseries\sffamily\subsectionfont} \titleformat*{\subsubsection}{\itshape\subsubsectionfont} \captionnamefont{\tiny\sffamily} \captiontitlefont{\tiny\sffamily} \aliaspagestyle{part}{empty} \setlistdepth{9} \makeatletter \g@addto@macro\@verbatim\scriptsize \makeatother \newlength{\imgwidth} \newlength{\drop}% for my convenience \newcommand*{\titleGM}{\begingroup% Gentle Madness \drop = 0.1\textheight %\vspace*{\baselineskip} \vfill \hbox{% \hspace*{0.2\textwidth}% \rule{1pt}{\textheight} \hspace*{0.05\textwidth}% \parbox[b]{0.75\textwidth}{ \vbox{% \vspace{\drop} {\noindent\HUGE\bfseries Developing\\[0.5\baselineskip] Liferay DXP 7.2}\\[2\baselineskip] {\Large\itshape A Complete Guide}\\[4\baselineskip] {\Large THE LIFERAY DOCUMENTATION TEAM}\par {\small Richard Sezov, Jr.}\par {\small Jim Hinkey}\par {\small Stephen Kostas}\par {\small Jesse Rao}\par {\small Cody Hoag}\par {\small Nicholas Gaskill}\par {\small Michael Williams}\par \vspace{0.25\textheight} {\noindent Liferay Press}\\[\baselineskip] }% end of vbox }% end of parbox }% end of hbox \vfill \null \endgroup} \makeatletter \newcommand\thickhrulefill{\leavevmode \leaders \hrule height 1ex \hfill \kern \z@} \setlength\midchapskip{10pt} \makechapterstyle{VZ14}{ \renewcommand\chapternamenum{} \renewcommand\printchaptername{} \renewcommand\chapnamefont{\sffamily\Large\scshape} \renewcommand\printchapternum{% \chapnamefont\null\thickhrulefill\quad \@chapapp\space\thechapter\quad\thickhrulefill} \renewcommand\printchapternonum{% \par\thickhrulefill\par\vskip\midchapskip \hrule\vskip\midchapskip } \renewcommand\chaptitlefont{\sffamily\Huge\scshape\centering} \renewcommand\afterchapternum{% \par\nobreak\vskip\midchapskip\hrule\vskip\midchapskip} \renewcommand\afterchaptertitle{% \par\vskip\midchapskip\hrule\nobreak\vskip\afterchapskip} } \makeatother \newcommand\scalegraphics[1]{% \settowidth{\imgwidth}{\includegraphics{#1}}% \setlength{\imgwidth}{\minof{\imgwidth}{\textwidth}}% \includegraphics[width=\imgwidth]{#1}% } \usepackage{fancybox} \newenvironment{roundedframe}{% \def\FrameCommand{% \cornersize*{20pt}% \setlength{\fboxsep}{5pt}% \ovalbox}% \MakeFramed{\advance\hsize-\width \FrameRestore}}% {\endMakeFramed} \author{Richard L. Sezov, Jr. } \title{Developing Liferay DXP 7.2} \date{12/11/2020} \begin{document} \pagestyle{empty} \titleGM Developing Liferay DXP 7.2 by The Liferay Documentation Team Copyright \copyright 2020 by Liferay, Inc.\\[2\baselineskip] This work is offered under the following license: \\[2\baselineskip] Creative Commons Attribution-Share Alike Unported \scalegraphics{./images/cc-by-sa.png} You are free: \begin{enumerate} \item to share---to copy, distribute, and transmit the work \item to remix---to adapt the work \end{enumerate} Under the following conditions: \begin{enumerate} \item Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). \item Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. \end{enumerate} The full version of this license is here: \href{http://creativecommons.org/licenses/by-sa/3.0}{http://creativecommons.org/licenses/by-sa/3.0} This book was created out of material from the \href{https://github.com/liferay/liferay-docs}{Liferay Docs} repository. Where the content of this book and the repository differ, the site is more up to date. \clearpage \frontmatter \pagestyle{plain} \pagenumbering{roman} \chapterstyle{VZ14} \tableofcontents \chapter{Preface} Welcome to the world of the Liferay DXP development platform! This book was written for anyone who wants to create applications built on Liferay DXP. It contains everything you need to know about Liferay's development tools and projects. You'll learn all you need to know about plugins, OSGi, the Liferay Workspace, Service Builder, and more. Use this book as a handbook for everything you need to do to get your application running on Liferay DXP, and then keep it by your side as you update and add features to help your users work more effectively. \section{Conventions} The information contained herein has been organized in a way that makes it easy to locate information. The book has two parts. The first part, \textit{Developer Tutorials}, shows you how to work step-by-step with Liferay's technology. The second part, \textit{Developer Reference}, shows exhaustively the options and APIs you need. Sections are broken up into multiple levels of headings, and these are designed to make it easy to find information. Source code and configuration file directives are presented monospaced, as below. \begin{verbatim} Source code appears in a non-proportional font. \end{verbatim} \textit{Italics} represent links or buttons to be clicked on in a user interface. \texttt{Monospaced type} denotes Java classes, code, or properties within the text. \textbf{Bold} describes field labels and portlets. Page headers denote the chapters and the section within the chapter. \section{Publisher Notes} It is our hope that this book is valuable to you, and that it becomes an indispensable resource as you work with Liferay DXP. If you need assistance beyond what is covered in this book, Liferay offers training\footnote{https://learn.liferay.com}, consulting\footnote{https://www.liferay.com/consulting}, and support\footnote{https://help.liferay.com} services to fill any need that you might have. For up-to-date documentation on the latest versions of Liferay, please see the documentation pages on Liferay Learn.\footnote{https://learn.liferay.com} As always, we welcome feedback. If there is any way you think we could make this book better, please feel free to mention it on our forums or in the feedback on Liferay Learn. You can also use any of the email addresses on our Contact Us page.\footnote{\href{http://www.liferay.com/contact-us}{https://www.liferay.com/contact-us}} We are here to serve you, our users and customers, and to help make your experience using Liferay DXP the best it can be. \mainmatter \part{Developer Tutorials} \include{developer/tutorials} \part{Customization} \include{developer/customization} \part{Application Development Platform} \include{developer/appdev} \part{Liferay Frameworks} \include{developer/frameworks} \part{Reference} \include{developer/reference} \end{document} ================================================ FILE: book/readme.md ================================================ # Liferay Documentation Books Readme The documentation in this branch has been concatenated together in all its sections and converted to LaTeX to create a PDF for publishing on [Liferay Learn](https://learn.liferay.com). It represents "legacy" documentation now. Follow these steps to build these PDFs: 1. Make sure you have a LaTeX distribution installed. For Liferay, this was done with `lualatex` as shipped with Texlive. 2. Copy all the images for all the sections of docs into the images folder here. There will be duplicates; overwrite them. 3. Configure your `~/latexmkrc` file like this: ```bash # PDF-generating modes are: # 1: pdflatex, as specified by $pdflatex variable (still largely in use) # 2: postscript conversion, as specified by the $ps2pdf variable (useless) # 3: dvi conversion, as specified by the $dvipdf variable (useless) # 4: lualatex, as specified by the $lualatex variable (best) # 5: xelatex, as specified by the $xelatex variable (second best) $pdf_mode = 4; $dvi_mode = '0'; $pdf_previewer = 'start okular' ``` Substitute your PDF viewer of choice if you don't use Okular. 4. Run `latexmk` for each of the files: ```bash latexmk developing-liferay-dxp-72.tex latexmk using-liferay-dxp-72.tex ``` When `latexmk` finishes running, you should have a PDF. If the table of contents is blank, run `latexmk` on that file again. ================================================ FILE: book/user/deployment.aux ================================================ \relax \providecommand{\transparent@use}[1]{} \providecommand\zref@newlabel[2]{} \providecommand\hyper@newdestlabel[2]{} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {361}Deploying Liferay DXP}{1253}{chapter.361}\protected@file@percent } \newlabel{deploying-liferay-dxp}{{361}{1253}{Deploying Liferay DXP}{chapter.361}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {362}Obtaining Liferay DXP}{1255}{chapter.362}\protected@file@percent } \newlabel{obtaining-liferay-dxp}{{362}{1255}{Obtaining Liferay DXP}{chapter.362}{}} \@writefile{toc}{\contentsline {section}{\numberline {362.1}Liferay Tomcat Bundle}{1255}{section.362.1}\protected@file@percent } \newlabel{liferay-tomcat-bundle}{{362.1}{1255}{Liferay Tomcat Bundle}{section.362.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {362.2}Downloading the Liferay WAR and Dependency JARs}{1256}{section.362.2}\protected@file@percent } \newlabel{downloading-the-liferay-war-and-dependency-jars}{{362.2}{1256}{Downloading the Liferay WAR and Dependency JARs}{section.362.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {363}Preparing for Install}{1257}{chapter.363}\protected@file@percent } \newlabel{preparing-for-install}{{363}{1257}{Preparing for Install}{chapter.363}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.1}JDK Requirements}{1257}{section.363.1}\protected@file@percent } \newlabel{jdk-requirements}{{363.1}{1257}{JDK Requirements}{section.363.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.2}JVM Requirements}{1258}{section.363.2}\protected@file@percent } \newlabel{jvm-requirements}{{363.2}{1258}{JVM Requirements}{section.363.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.3}Preparing a Database}{1259}{section.363.3}\protected@file@percent } \newlabel{preparing-a-database}{{363.3}{1259}{Preparing a Database}{section.363.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.4}Using the Built-in Data Source}{1259}{section.363.4}\protected@file@percent } \newlabel{using-the-built-in-data-source}{{363.4}{1259}{Using the Built-in Data Source}{section.363.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.5}Using a Data Source on Your Application Server}{1260}{section.363.5}\protected@file@percent } \newlabel{using-a-data-source-on-your-application-server}{{363.5}{1260}{Using a Data Source on Your Application Server}{section.363.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {363.6}Limiting Database Access}{1260}{section.363.6}\protected@file@percent } \newlabel{limiting-database-access}{{363.6}{1260}{Limiting Database Access}{section.363.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {364}Installing Liferay DXP}{1263}{chapter.364}\protected@file@percent } \newlabel{installing-liferay-dxp}{{364}{1263}{Installing Liferay DXP}{chapter.364}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.1}Extracting a Liferay DXP Bundle}{1263}{section.364.1}\protected@file@percent } \newlabel{extracting-a-liferay-dxp-bundle}{{364.1}{1263}{Extracting a Liferay DXP Bundle}{section.364.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.2}Installing the JDBC Driver}{1263}{section.364.2}\protected@file@percent } \newlabel{installing-the-jdbc-driver}{{364.2}{1263}{Installing the JDBC Driver}{section.364.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.3}Running Liferay DXP for the First Time}{1264}{section.364.3}\protected@file@percent } \newlabel{running-liferay-dxp-for-the-first-time}{{364.3}{1264}{Running Liferay DXP for the First Time}{section.364.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.4}Using the Setup Wizard}{1264}{section.364.4}\protected@file@percent } \newlabel{using-the-setup-wizard}{{364.4}{1264}{Using the Setup Wizard}{section.364.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.5}Portal}{1264}{section.364.5}\protected@file@percent } \newlabel{portal}{{364.5}{1264}{Portal}{section.364.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {364.6}Administrator User}{1264}{section.364.6}\protected@file@percent } \newlabel{administrator-user}{{364.6}{1264}{Administrator User}{section.364.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {364.1}{\ignorespaces Supply the information for your portal and your portal's default administrator user on the Basic Configuration page.}}{1265}{figure.364.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {364.7}Database}{1265}{section.364.7}\protected@file@percent } \newlabel{database}{{364.7}{1265}{Database}{section.364.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {365}Installing Liferay DXP on Tomcat}{1267}{chapter.365}\protected@file@percent } \newlabel{installing-liferay-dxp-on-tomcat}{{365}{1267}{Installing Liferay DXP on Tomcat}{chapter.365}{}} \@writefile{toc}{\contentsline {section}{\numberline {365.1}Installing Dependencies}{1267}{section.365.1}\protected@file@percent } \newlabel{installing-dependencies}{{365.1}{1267}{Installing Dependencies}{section.365.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {365.2}Configuring Tomcat}{1268}{section.365.2}\protected@file@percent } \newlabel{configuring-tomcat}{{365.2}{1268}{Configuring Tomcat}{section.365.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {365.3}Database Configuration}{1272}{section.365.3}\protected@file@percent } \newlabel{database-configuration}{{365.3}{1272}{Database Configuration}{section.365.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {365.4}Mail Configuration}{1273}{section.365.4}\protected@file@percent } \newlabel{mail-configuration}{{365.4}{1273}{Mail Configuration}{section.365.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {365.5}Deploying Liferay DXP}{1273}{section.365.5}\protected@file@percent } \newlabel{deploying-liferay-dxp-1}{{365.5}{1273}{Deploying Liferay DXP}{section.365.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {366}Installing Liferay DXP on Wildfly}{1275}{chapter.366}\protected@file@percent } \newlabel{installing-liferay-dxp-on-wildfly}{{366}{1275}{Installing Liferay DXP on Wildfly}{chapter.366}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.1}Installing Dependencies}{1275}{section.366.1}\protected@file@percent } \newlabel{installing-dependencies-1}{{366.1}{1275}{Installing Dependencies}{section.366.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.2}Running Liferay DXP on Wildfly in Standalone Mode vs.~Domain Mode}{1276}{section.366.2}\protected@file@percent } \newlabel{running-liferay-dxp-on-wildfly-in-standalone-mode-vs.-domain-mode}{{366.2}{1276}{Running Liferay DXP on Wildfly in Standalone Mode vs.~Domain Mode}{section.366.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.3}Configuring Wildfly}{1277}{section.366.3}\protected@file@percent } \newlabel{configuring-wildfly}{{366.3}{1277}{Configuring Wildfly}{section.366.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.4}Database Configuration}{1280}{section.366.4}\protected@file@percent } \newlabel{database-configuration-1}{{366.4}{1280}{Database Configuration}{section.366.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.5}Mail Configuration}{1281}{section.366.5}\protected@file@percent } \newlabel{mail-configuration-1}{{366.5}{1281}{Mail Configuration}{section.366.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {366.6}Deploying Liferay DXP}{1282}{section.366.6}\protected@file@percent } \newlabel{deploying-liferay-dxp-2}{{366.6}{1282}{Deploying Liferay DXP}{section.366.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {367}Installing Liferay DXP on JBoss EAP}{1283}{chapter.367}\protected@file@percent } \newlabel{installing-liferay-dxp-on-jboss-eap}{{367}{1283}{Installing Liferay DXP on JBoss EAP}{chapter.367}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.1}Installing Dependencies}{1283}{section.367.1}\protected@file@percent } \newlabel{installing-dependencies-2}{{367.1}{1283}{Installing Dependencies}{section.367.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.2}Running Liferay DXP on JBoss EAP in Standalone Mode vs.~Domain Mode}{1285}{section.367.2}\protected@file@percent } \newlabel{running-liferay-dxp-on-jboss-eap-in-standalone-mode-vs.-domain-mode}{{367.2}{1285}{Running Liferay DXP on JBoss EAP in Standalone Mode vs.~Domain Mode}{section.367.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.3}Configuring JBoss}{1285}{section.367.3}\protected@file@percent } \newlabel{configuring-jboss}{{367.3}{1285}{Configuring JBoss}{section.367.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.4}Database Configuration}{1288}{section.367.4}\protected@file@percent } \newlabel{database-configuration-2}{{367.4}{1288}{Database Configuration}{section.367.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.5}Mail Configuration}{1289}{section.367.5}\protected@file@percent } \newlabel{mail-configuration-2}{{367.5}{1289}{Mail Configuration}{section.367.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {367.6}Deploy Liferay}{1290}{section.367.6}\protected@file@percent } \newlabel{deploy-liferay}{{367.6}{1290}{Deploy Liferay}{section.367.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {368}Installing Liferay DXP on WebLogic 12c R2}{1291}{chapter.368}\protected@file@percent } \newlabel{installing-liferay-dxp-on-weblogic-12c-r2}{{368}{1291}{Installing Liferay DXP on WebLogic 12c R2}{chapter.368}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.1}Configuring WebLogic's Node Manager}{1291}{section.368.1}\protected@file@percent } \newlabel{configuring-weblogics-node-manager}{{368.1}{1291}{Configuring WebLogic's Node Manager}{section.368.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.2}Configuring WebLogic}{1292}{section.368.2}\protected@file@percent } \newlabel{configuring-weblogic}{{368.2}{1292}{Configuring WebLogic}{section.368.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.3}Setting Liferay DXP Properties}{1293}{section.368.3}\protected@file@percent } \newlabel{setting-liferay-dxp-properties}{{368.3}{1293}{Setting Liferay DXP Properties}{section.368.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.4}Installing Liferay DXP Dependencies}{1293}{section.368.4}\protected@file@percent } \newlabel{installing-liferay-dxp-dependencies}{{368.4}{1293}{Installing Liferay DXP Dependencies}{section.368.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.5}Database Configuration}{1294}{section.368.5}\protected@file@percent } \newlabel{database-configuration-3}{{368.5}{1294}{Database Configuration}{section.368.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.6}Mail Configuration}{1295}{section.368.6}\protected@file@percent } \newlabel{mail-configuration-3}{{368.6}{1295}{Mail Configuration}{section.368.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {368.7}Deploying Liferay DXP}{1295}{section.368.7}\protected@file@percent } \newlabel{deploying-liferay-dxp-3}{{368.7}{1295}{Deploying Liferay DXP}{section.368.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {369}Installing Liferay DXP on WebSphere}{1297}{chapter.369}\protected@file@percent } \newlabel{installing-liferay-dxp-on-websphere}{{369}{1297}{Installing Liferay DXP on WebSphere}{chapter.369}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.1}Preparing WebSphere for Liferay DXP}{1297}{section.369.1}\protected@file@percent } \newlabel{preparing-websphere-for-liferay-dxp}{{369.1}{1297}{Preparing WebSphere for Liferay DXP}{section.369.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {369.1}{\ignorespaces Choose the Advanced profile option to specify your own settings.}}{1298}{figure.369.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {369.2}Configuring the WebSphere Application Server}{1299}{section.369.2}\protected@file@percent } \newlabel{configuring-the-websphere-application-server}{{369.2}{1299}{Configuring the WebSphere Application Server}{section.369.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.3}Setting up JVM Parameters for Liferay DXP}{1299}{section.369.3}\protected@file@percent } \newlabel{setting-up-jvm-parameters-for-liferay-dxp}{{369.3}{1299}{Setting up JVM Parameters for Liferay DXP}{section.369.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {369.2}{\ignorespaces Example of the settings before creating the profile.}}{1300}{figure.369.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {369.4}Removing the secureSessionCookie Tag}{1301}{section.369.4}\protected@file@percent } \newlabel{removing-the-securesessioncookie-tag}{{369.4}{1301}{Removing the secureSessionCookie Tag}{section.369.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.5}Installing Liferay DXP's Dependencies}{1301}{section.369.5}\protected@file@percent } \newlabel{installing-liferay-dxps-dependencies}{{369.5}{1301}{Installing Liferay DXP's Dependencies}{section.369.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.6}Ensuring that Liferay DXP's portlet.jar is loaded first}{1302}{section.369.6}\protected@file@percent } \newlabel{ensuring-that-liferay-dxps-portlet.jar-is-loaded-first}{{369.6}{1302}{Ensuring that Liferay DXP's portlet.jar is loaded first}{section.369.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.7}Database Configuration}{1302}{section.369.7}\protected@file@percent } \newlabel{database-configuration-4}{{369.7}{1302}{Database Configuration}{section.369.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {369.3}{\ignorespaces WebSphere JDBC providers}}{1303}{figure.369.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {369.4}{\ignorespaces Completed JDBC provider configurations.}}{1303}{figure.369.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {369.5}{\ignorespaces Modifying data source properties in WebSphere}}{1304}{figure.369.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {369.8}Mail Configuration}{1305}{section.369.8}\protected@file@percent } \newlabel{mail-configuration-4}{{369.8}{1305}{Mail Configuration}{section.369.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.9}Creating a WebSphere-Managed Mail Session (Optional)}{1305}{section.369.9}\protected@file@percent } \newlabel{creating-a-websphere-managed-mail-session-optional}{{369.9}{1305}{Creating a WebSphere-Managed Mail Session (Optional)}{section.369.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.10}Verifying WebSphere Mail Provider}{1306}{section.369.10}\protected@file@percent } \newlabel{verifying-websphere-mail-provider}{{369.10}{1306}{Verifying WebSphere Mail Provider}{section.369.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.11}Enable Cookies for HTTP Sessions}{1306}{section.369.11}\protected@file@percent } \newlabel{enable-cookies-for-http-sessions}{{369.11}{1306}{Enable Cookies for HTTP Sessions}{section.369.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.12}Enable UTF-8}{1306}{section.369.12}\protected@file@percent } \newlabel{enable-utf-8}{{369.12}{1306}{Enable UTF-8}{section.369.12}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.13}Deploy Liferay DXP}{1307}{section.369.13}\protected@file@percent } \newlabel{deploy-liferay-dxp}{{369.13}{1307}{Deploy Liferay DXP}{section.369.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {369.14}Setting the JDK Version for Compiling JSPs}{1307}{section.369.14}\protected@file@percent } \newlabel{setting-the-jdk-version-for-compiling-jsps}{{369.14}{1307}{Setting the JDK Version for Compiling JSPs}{section.369.14}{}} \@writefile{lof}{\contentsline {figure}{\numberline {369.6}{\ignorespaces Review your deployment options before deploying.}}{1308}{figure.369.6}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {369.15}Start Liferay DXP}{1309}{section.369.15}\protected@file@percent } \newlabel{start-liferay-dxp}{{369.15}{1309}{Start Liferay DXP}{section.369.15}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {370}Activating Liferay DXP}{1311}{chapter.370}\protected@file@percent } \newlabel{activating-liferay-dxp}{{370}{1311}{Activating Liferay DXP}{chapter.370}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {371}Setting Up Marketplace}{1313}{chapter.371}\protected@file@percent } \newlabel{setting-up-marketplace}{{371}{1313}{Setting Up Marketplace}{chapter.371}{}} \@writefile{toc}{\contentsline {section}{\numberline {371.1}Server is Firewalled without Access to the Internet}{1313}{section.371.1}\protected@file@percent } \newlabel{server-is-firewalled-without-access-to-the-internet}{{371.1}{1313}{Server is Firewalled without Access to the Internet}{section.371.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {371.2}Limited Database Access}{1314}{section.371.2}\protected@file@percent } \newlabel{limited-database-access}{{371.2}{1314}{Limited Database Access}{section.371.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {372}Trial Plugin Installation}{1315}{chapter.372}\protected@file@percent } \newlabel{trial-plugin-installation}{{372}{1315}{Trial Plugin Installation}{chapter.372}{}} \@writefile{toc}{\contentsline {section}{\numberline {372.1}Installation Process}{1315}{section.372.1}\protected@file@percent } \newlabel{installation-process}{{372.1}{1315}{Installation Process}{section.372.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {372.1}{\ignorespaces Hover over the Profile button and click \emph {Sign In/Create Account}.}}{1315}{figure.372.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {372.2}{\ignorespaces Click the \emph {Store} link and authorize Marketplace to access your local account.}}{1316}{figure.372.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {372.2}FAQ}{1316}{section.372.2}\protected@file@percent } \newlabel{faq}{{372.2}{1316}{FAQ}{section.372.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {373}Document Repository Configuration}{1319}{chapter.373}\protected@file@percent } \newlabel{document-repository-configuration}{{373}{1319}{Document Repository Configuration}{chapter.373}{}} \@writefile{lof}{\contentsline {figure}{\numberline {373.1}{\ignorespaces The File Storage page in System Settings lets you configure document repository storage.}}{1320}{figure.373.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {374}Using the Simple File System Store}{1321}{chapter.374}\protected@file@percent } \newlabel{using-the-simple-file-system-store}{{374}{1321}{Using the Simple File System Store}{chapter.374}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {375}Using the Advanced File System Store}{1323}{chapter.375}\protected@file@percent } \newlabel{using-the-advanced-file-system-store}{{375}{1323}{Using the Advanced File System Store}{chapter.375}{}} \@writefile{lof}{\contentsline {figure}{\numberline {375.1}{\ignorespaces The advanced file system store creates a more nested folder structure than the file system store.}}{1323}{figure.375.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {376}Using Amazon Simple Storage Service}{1325}{chapter.376}\protected@file@percent } \newlabel{using-amazon-simple-storage-service}{{376}{1325}{Using Amazon Simple Storage Service}{chapter.376}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {377}Using the DBStore}{1327}{chapter.377}\protected@file@percent } \newlabel{using-the-dbstore}{{377}{1327}{Using the DBStore}{chapter.377}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {378}Configuring Liferay DXP}{1329}{chapter.378}\protected@file@percent } \newlabel{configuring-liferay-dxp}{{378}{1329}{Configuring Liferay DXP}{chapter.378}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {379}Configuring Mail}{1331}{chapter.379}\protected@file@percent } \newlabel{configuring-mail}{{379}{1331}{Configuring Mail}{chapter.379}{}} \@writefile{toc}{\contentsline {section}{\numberline {379.1}Configuring Liferay DXP's Built-in Mail Session}{1331}{section.379.1}\protected@file@percent } \newlabel{configuring-liferay-dxps-built-in-mail-session}{{379.1}{1331}{Configuring Liferay DXP's Built-in Mail Session}{section.379.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {379.2}Built-in Mail Session in the Control Panel}{1332}{section.379.2}\protected@file@percent } \newlabel{built-in-mail-session-in-the-control-panel}{{379.2}{1332}{Built-in Mail Session in the Control Panel}{section.379.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {379.3}Built-in Mail Session Portal Properties}{1332}{section.379.3}\protected@file@percent } \newlabel{built-in-mail-session-portal-properties}{{379.3}{1332}{Built-in Mail Session Portal Properties}{section.379.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {379.4}Configuring a Mail Session on the Application Server}{1333}{section.379.4}\protected@file@percent } \newlabel{configuring-a-mail-session-on-the-application-server}{{379.4}{1333}{Configuring a Mail Session on the Application Server}{section.379.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {379.5}Configuring default email senders}{1333}{section.379.5}\protected@file@percent } \newlabel{configuring-default-email-senders}{{379.5}{1333}{Configuring default email senders}{section.379.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {380}Locales and Encoding Configuration}{1335}{chapter.380}\protected@file@percent } \newlabel{locales-and-encoding-configuration}{{380}{1335}{Locales and Encoding Configuration}{chapter.380}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.1}Time Zones}{1335}{section.380.1}\protected@file@percent } \newlabel{time-zones}{{380.1}{1335}{Time Zones}{section.380.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {380.1}{\ignorespaces You can change the default and available languages and the time zone in Instance Settings.}}{1336}{figure.380.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {380.2}Set the JVM Time Zone to GMT}{1336}{section.380.2}\protected@file@percent } \newlabel{set-the-jvm-time-zone-to-gmt}{{380.2}{1336}{Set the JVM Time Zone to GMT}{section.380.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.3}Friendly URLs and Locales}{1337}{section.380.3}\protected@file@percent } \newlabel{friendly-urls-and-locales}{{380.3}{1337}{Friendly URLs and Locales}{section.380.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.4}Modifying Language Keys}{1337}{section.380.4}\protected@file@percent } \newlabel{modifying-language-keys}{{380.4}{1337}{Modifying Language Keys}{section.380.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.5}Right to Left}{1337}{section.380.5}\protected@file@percent } \newlabel{right-to-left}{{380.5}{1337}{Right to Left}{section.380.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.6}Localizing User Names}{1338}{section.380.6}\protected@file@percent } \newlabel{localizing-user-names}{{380.6}{1338}{Localizing User Names}{section.380.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {380.7}Related Topics}{1338}{section.380.7}\protected@file@percent } \newlabel{related-topics}{{380.7}{1338}{Related Topics}{section.380.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {381}Liferay DXP Clustering}{1339}{chapter.381}\protected@file@percent } \newlabel{liferay-dxp-clustering}{{381}{1339}{Liferay DXP Clustering}{chapter.381}{}} \@writefile{lof}{\contentsline {figure}{\numberline {381.1}{\ignorespaces Liferay DXP is designed to scale to as large an installation as you need.}}{1340}{figure.381.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {382}Point all Nodes to the Same Liferay DXP Database}{1341}{chapter.382}\protected@file@percent } \newlabel{point-all-nodes-to-the-same-liferay-dxp-database}{{382}{1341}{Point all Nodes to the Same Liferay DXP Database}{chapter.382}{}} \@writefile{toc}{\contentsline {section}{\numberline {382.1}Read-Writer Database Configuration}{1341}{section.382.1}\protected@file@percent } \newlabel{read-writer-database-configuration}{{382.1}{1341}{Read-Writer Database Configuration}{section.382.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {382.2}JDBC}{1341}{section.382.2}\protected@file@percent } \newlabel{jdbc}{{382.2}{1341}{JDBC}{section.382.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {382.3}JNDI}{1343}{section.382.3}\protected@file@percent } \newlabel{jndi}{{382.3}{1343}{JNDI}{section.382.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {383}Configure Documents and Media the Same for all Nodes}{1345}{chapter.383}\protected@file@percent } \newlabel{configure-documents-and-media-the-same-for-all-nodes}{{383}{1345}{Configure Documents and Media the Same for all Nodes}{chapter.383}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {384}Clustering Search}{1347}{chapter.384}\protected@file@percent } \newlabel{clustering-search}{{384}{1347}{Clustering Search}{chapter.384}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {385}Enabling Cluster Link}{1349}{chapter.385}\protected@file@percent } \newlabel{enabling-cluster-link}{{385}{1349}{Enabling Cluster Link}{chapter.385}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.1}Enabling Cluster Link}{1349}{section.385.1}\protected@file@percent } \newlabel{enabling-cluster-link-1}{{385.1}{1349}{Enabling Cluster Link}{section.385.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {385.1}{\ignorespaces Liferay DXP's cache algorithm is extremely efficient.}}{1350}{figure.385.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {385.2}Multicast Over UDP}{1351}{section.385.2}\protected@file@percent } \newlabel{multicast-over-udp}{{385.2}{1351}{Multicast Over UDP}{section.385.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.3}Checkpoint:}{1351}{section.385.3}\protected@file@percent } \newlabel{checkpoint}{{385.3}{1351}{Checkpoint:}{section.385.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.4}Unicast over TCP}{1352}{section.385.4}\protected@file@percent } \newlabel{unicast-over-tcp}{{385.4}{1352}{Unicast over TCP}{section.385.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.5}JDBC Ping}{1353}{section.385.5}\protected@file@percent } \newlabel{jdbc-ping}{{385.5}{1353}{JDBC Ping}{section.385.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.6}S3 Ping}{1354}{section.385.6}\protected@file@percent } \newlabel{s3-ping}{{385.6}{1354}{S3 Ping}{section.385.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.7}Other Pings}{1354}{section.385.7}\protected@file@percent } \newlabel{other-pings}{{385.7}{1354}{Other Pings}{section.385.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.8}Using Different Control and Transport Channel Ports}{1354}{section.385.8}\protected@file@percent } \newlabel{using-different-control-and-transport-channel-ports}{{385.8}{1354}{Using Different Control and Transport Channel Ports}{section.385.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.9}Modifying the Cache Configuration with a Module}{1356}{section.385.9}\protected@file@percent } \newlabel{modifying-the-cache-configuration-with-a-module}{{385.9}{1356}{Modifying the Cache Configuration with a Module}{section.385.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {385.10}Conclusion}{1356}{section.385.10}\protected@file@percent } \newlabel{conclusion}{{385.10}{1356}{Conclusion}{section.385.10}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {386}Auto Deploy to All Nodes}{1357}{chapter.386}\protected@file@percent } \newlabel{auto-deploy-to-all-nodes}{{386}{1357}{Auto Deploy to All Nodes}{chapter.386}{}} \gdef \LT@iii {\LT@entry {1}{69.69774pt}\LT@entry {1}{224.26688pt}\LT@entry {1}{175.83119pt}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {387}Updating a Cluster}{1359}{chapter.387}\protected@file@percent } \newlabel{updating-a-cluster}{{387}{1359}{Updating a Cluster}{chapter.387}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {388}Rolling Restarts}{1363}{chapter.388}\protected@file@percent } \newlabel{rolling-restarts}{{388}{1363}{Rolling Restarts}{chapter.388}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.1}New Modules and Plugins}{1363}{section.388.1}\protected@file@percent } \newlabel{new-modules-and-plugins}{{388.1}{1363}{New Modules and Plugins}{section.388.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.2}Updating Existing Modules and Plugins}{1364}{section.388.2}\protected@file@percent } \newlabel{updating-existing-modules-and-plugins}{{388.2}{1364}{Updating Existing Modules and Plugins}{section.388.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.3}Applying Fix Packs (DXP only)}{1364}{section.388.3}\protected@file@percent } \newlabel{applying-fix-packs-dxp-only}{{388.3}{1364}{Applying Fix Packs (DXP only)}{section.388.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.4}Reverting Fix Packs (DXP only)}{1364}{section.388.4}\protected@file@percent } \newlabel{reverting-fix-packs-dxp-only}{{388.4}{1364}{Reverting Fix Packs (DXP only)}{section.388.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.5}Portal Properties controlled by \texttt {portal-ext.properties}}{1364}{section.388.5}\protected@file@percent } \newlabel{portal-properties-controlled-by-portal-ext.properties}{{388.5}{1364}{\texorpdfstring {Portal Properties controlled by \texttt {portal-ext.properties}}{Portal Properties controlled by portal-ext.properties}}{section.388.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.6}System Settings controlled by Configuration Admin Files}{1364}{section.388.6}\protected@file@percent } \newlabel{system-settings-controlled-by-configuration-admin-files}{{388.6}{1364}{System Settings controlled by Configuration Admin Files}{section.388.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.7}Application Server or JVM setting modifications}{1364}{section.388.7}\protected@file@percent } \newlabel{application-server-or-jvm-setting-modifications}{{388.7}{1364}{Application Server or JVM setting modifications}{section.388.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.8}Java Version Updates}{1365}{section.388.8}\protected@file@percent } \newlabel{java-version-updates}{{388.8}{1365}{Java Version Updates}{section.388.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {388.9}Related Topics}{1365}{section.388.9}\protected@file@percent } \newlabel{related-topics-1}{{388.9}{1365}{Related Topics}{section.388.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {389}Blue-Green Deployment}{1367}{chapter.389}\protected@file@percent } \newlabel{blue-green-deployment}{{389}{1367}{Blue-Green Deployment}{chapter.389}{}} \@writefile{toc}{\contentsline {section}{\numberline {389.1}Related Topics}{1367}{section.389.1}\protected@file@percent } \newlabel{related-topics-2}{{389.1}{1367}{Related Topics}{section.389.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {390}Configuring Remote Staging in a Clustered Environment}{1369}{chapter.390}\protected@file@percent } \newlabel{configuring-remote-staging-in-a-clustered-environment}{{390}{1369}{Configuring Remote Staging in a Clustered Environment}{chapter.390}{}} \@writefile{lof}{\contentsline {figure}{\numberline {390.1}{\ignorespaces This is the assumed setup for your clustered environment.}}{1370}{figure.390.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {390.2}{\ignorespaces When selecting the Remote Staging radio button, you're given a list of options to configure.}}{1372}{figure.390.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {391}Content Delivery Network}{1373}{chapter.391}\protected@file@percent } \newlabel{content-delivery-network}{{391}{1373}{Content Delivery Network}{chapter.391}{}} \@writefile{toc}{\contentsline {section}{\numberline {391.1}Using CDN for Performance Enhancements}{1373}{section.391.1}\protected@file@percent } \newlabel{using-cdn-for-performance-enhancements}{{391.1}{1373}{Using CDN for Performance Enhancements}{section.391.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {391.1}{\ignorespaces The red lines on the map represent the required distances traveled by requests from a server to the user. Using CDN allows a user to request static resources from a much closer local server, improving download times.}}{1374}{figure.391.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {391.2}Liferay CDN Requirements}{1374}{section.391.2}\protected@file@percent } \newlabel{liferay-cdn-requirements}{{391.2}{1374}{Liferay CDN Requirements}{section.391.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {391.3}Configuring Liferay DXP to Use a CDN}{1375}{section.391.3}\protected@file@percent } \newlabel{configuring-liferay-dxp-to-use-a-cdn}{{391.3}{1375}{Configuring Liferay DXP to Use a CDN}{section.391.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {391.2}{\ignorespaces The Control Panel lets you configure your portal's CDN.}}{1375}{figure.391.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {392}Tuning Guidelines}{1377}{chapter.392}\protected@file@percent } \newlabel{tuning-guidelines}{{392}{1377}{Tuning Guidelines}{chapter.392}{}} \@writefile{toc}{\contentsline {section}{\numberline {392.1}Database Connection Pool}{1377}{section.392.1}\protected@file@percent } \newlabel{database-connection-pool}{{392.1}{1377}{Database Connection Pool}{section.392.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {392.2}Deactivating Development Settings in the JSP Engine}{1378}{section.392.2}\protected@file@percent } \newlabel{deactivating-development-settings-in-the-jsp-engine}{{392.2}{1378}{Deactivating Development Settings in the JSP Engine}{section.392.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {392.3}Thread Pool}{1378}{section.392.3}\protected@file@percent } \newlabel{thread-pool}{{392.3}{1378}{Thread Pool}{section.392.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {393}Java Virtual Machine Tuning}{1381}{chapter.393}\protected@file@percent } \newlabel{java-virtual-machine-tuning}{{393}{1381}{Java Virtual Machine Tuning}{chapter.393}{}} \@writefile{toc}{\contentsline {section}{\numberline {393.1}Garbage Collector}{1381}{section.393.1}\protected@file@percent } \newlabel{garbage-collector}{{393.1}{1381}{Garbage Collector}{section.393.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {393.2}Code Cache}{1382}{section.393.2}\protected@file@percent } \newlabel{code-cache}{{393.2}{1382}{Code Cache}{section.393.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {393.3}Java Heap}{1382}{section.393.3}\protected@file@percent } \newlabel{java-heap}{{393.3}{1382}{Java Heap}{section.393.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {393.4}JVM Advanced Options}{1383}{section.393.4}\protected@file@percent } \newlabel{jvm-advanced-options}{{393.4}{1383}{JVM Advanced Options}{section.393.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {394}Installing a Search Engine}{1385}{chapter.394}\protected@file@percent } \newlabel{installing-a-search-engine}{{394}{1385}{Installing a Search Engine}{chapter.394}{}} \@writefile{toc}{\contentsline {section}{\numberline {394.1}Choosing a Search Engine}{1385}{section.394.1}\protected@file@percent } \newlabel{choosing-a-search-engine}{{394.1}{1385}{Choosing a Search Engine}{section.394.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {394.2}End User Feature Limitations of Liferay's Solr Integration}{1385}{section.394.2}\protected@file@percent } \newlabel{end-user-feature-limitations-of-liferays-solr-integration}{{394.2}{1385}{End User Feature Limitations of Liferay's Solr Integration}{section.394.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {394.3}Developer Feature Limitations of Liferay's Solr Integration}{1386}{section.394.3}\protected@file@percent } \newlabel{developer-feature-limitations-of-liferays-solr-integration}{{394.3}{1386}{Developer Feature Limitations of Liferay's Solr Integration}{section.394.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {394.4}Elasticsearch Java Distribution Compatibility}{1387}{section.394.4}\protected@file@percent } \newlabel{elasticsearch-java-distribution-compatibility}{{394.4}{1387}{Elasticsearch Java Distribution Compatibility}{section.394.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {395}Elasticsearch}{1389}{chapter.395}\protected@file@percent } \newlabel{elasticsearch}{{395}{1389}{Elasticsearch}{chapter.395}{}} \@writefile{lof}{\contentsline {figure}{\numberline {395.1}{\ignorespaces To see information about the currently connected search engine, go to \emph {Control Panel} → \emph {Configuration} → \emph {Search}.}}{1389}{figure.395.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {395.1}Embedded vs.~Remote Operation Mode}{1390}{section.395.1}\protected@file@percent } \newlabel{embedded-vs.-remote-operation-mode}{{395.1}{1390}{Embedded vs.~Remote Operation Mode}{section.395.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {395.2}Troubleshooting Elasticsearch Integration}{1390}{section.395.2}\protected@file@percent } \newlabel{troubleshooting-elasticsearch-integration}{{395.2}{1390}{Troubleshooting Elasticsearch Integration}{section.395.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {396}Preparing to Install Elasticsearch}{1393}{chapter.396}\protected@file@percent } \newlabel{preparing-to-install-elasticsearch}{{396}{1393}{Preparing to Install Elasticsearch}{chapter.396}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.1}Sizing Your Deployment}{1393}{section.396.1}\protected@file@percent } \newlabel{sizing-your-deployment}{{396.1}{1393}{Sizing Your Deployment}{section.396.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.2}CPU}{1393}{section.396.2}\protected@file@percent } \newlabel{cpu}{{396.2}{1393}{CPU}{section.396.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.3}Memory}{1394}{section.396.3}\protected@file@percent } \newlabel{memory}{{396.3}{1394}{Memory}{section.396.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.4}Disk}{1394}{section.396.4}\protected@file@percent } \newlabel{disk}{{396.4}{1394}{Disk}{section.396.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.5}Cluster Size}{1394}{section.396.5}\protected@file@percent } \newlabel{cluster-size}{{396.5}{1394}{Cluster Size}{section.396.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {396.6}Networking}{1394}{section.396.6}\protected@file@percent } \newlabel{networking}{{396.6}{1394}{Networking}{section.396.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {397}Installing Elasticsearch}{1395}{chapter.397}\protected@file@percent } \newlabel{installing-elasticsearch}{{397}{1395}{Installing Elasticsearch}{chapter.397}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.1}Step One: Download a Supported Version of Elasticsearch}{1396}{section.397.1}\protected@file@percent } \newlabel{step-one-download-a-supported-version-of-elasticsearch}{{397.1}{1396}{Step One: Download a Supported Version of Elasticsearch}{section.397.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.2}Step Two: Install Elasticsearch}{1397}{section.397.2}\protected@file@percent } \newlabel{step-two-install-elasticsearch}{{397.2}{1397}{Step Two: Install Elasticsearch}{section.397.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.3}Step Three: Install Elasticsearch Plugins}{1397}{section.397.3}\protected@file@percent } \newlabel{step-three-install-elasticsearch-plugins}{{397.3}{1397}{Step Three: Install Elasticsearch Plugins}{section.397.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.4}Step Four: Name Your Elasticsearch Cluster}{1398}{section.397.4}\protected@file@percent } \newlabel{step-four-name-your-elasticsearch-cluster}{{397.4}{1398}{Step Four: Name Your Elasticsearch Cluster}{section.397.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.5}Step Five: Configure Liferay DXP to Connect to your Elasticsearch Cluster}{1398}{section.397.5}\protected@file@percent } \newlabel{step-five-configure-liferay-dxp-to-connect-to-your-elasticsearch-cluster}{{397.5}{1398}{Step Five: Configure Liferay DXP to Connect to your Elasticsearch Cluster}{section.397.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {397.6}Step Six: Restart Liferay DXP and Reindex}{1399}{section.397.6}\protected@file@percent } \newlabel{step-six-restart-liferay-dxp-and-reindex}{{397.6}{1399}{Step Six: Restart Liferay DXP and Reindex}{section.397.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {397.1}{\ignorespaces To see information about the currently connected search engine, go to \emph {Control Panel → Configuration → Search}.}}{1399}{figure.397.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {398}Configuring the Liferay Elasticsearch Connector}{1401}{chapter.398}\protected@file@percent } \newlabel{configuring-the-liferay-elasticsearch-connector}{{398}{1401}{Configuring the Liferay Elasticsearch Connector}{chapter.398}{}} \@writefile{toc}{\contentsline {section}{\numberline {398.1}Starting Elasticsearch}{1402}{section.398.1}\protected@file@percent } \newlabel{starting-elasticsearch}{{398.1}{1402}{Starting Elasticsearch}{section.398.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {398.2}Configuring the Liferay Elasticsearch Connector}{1402}{section.398.2}\protected@file@percent } \newlabel{configuring-the-liferay-elasticsearch-connector-1}{{398.2}{1402}{Configuring the Liferay Elasticsearch Connector}{section.398.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {398.3}Configuring the Connector in the Control Panel}{1402}{section.398.3}\protected@file@percent } \newlabel{configuring-the-connector-in-the-control-panel}{{398.3}{1402}{Configuring the Connector in the Control Panel}{section.398.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {398.1}{\ignorespaces Use the System Settings application in Liferay DXP's Control Panel to configure the Elasticsearch connector.}}{1403}{figure.398.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {398.4}Configuring the Connector with an OSGi \texttt {.config} File}{1403}{section.398.4}\protected@file@percent } \newlabel{configuring-the-connector-with-an-osgi-.config-file}{{398.4}{1403}{\texorpdfstring {Configuring the Connector with an OSGi \texttt {.config} File}{Configuring the Connector with an OSGi .config File}}{section.398.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {398.2}{\ignorespaces Configure the Elasticsearch connector's settings. Make sure you set the Operation Mode to \emph {Remote}.}}{1404}{figure.398.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {398.5}Configuring a Remote Elasticsearch Host}{1405}{section.398.5}\protected@file@percent } \newlabel{configuring-a-remote-elasticsearch-host}{{398.5}{1405}{Configuring a Remote Elasticsearch Host}{section.398.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {398.6}Clustering Elasticsearch in Remote Operation Mode}{1406}{section.398.6}\protected@file@percent } \newlabel{clustering-elasticsearch-in-remote-operation-mode}{{398.6}{1406}{Clustering Elasticsearch in Remote Operation Mode}{section.398.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {398.7}Elasticsearch Connector System Settings, By Operation Mode}{1406}{section.398.7}\protected@file@percent } \newlabel{elasticsearch-connector-system-settings-by-operation-mode}{{398.7}{1406}{Elasticsearch Connector System Settings, By Operation Mode}{section.398.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {399}Advanced Configuration of the Liferay Elasticsearch Connector}{1407}{chapter.399}\protected@file@percent } \newlabel{advanced-configuration-of-the-liferay-elasticsearch-connector}{{399}{1407}{Advanced Configuration of the Liferay Elasticsearch Connector}{chapter.399}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.1}Adding Settings and Mappings to the Liferay Elasticsearch Connector}{1407}{section.399.1}\protected@file@percent } \newlabel{adding-settings-and-mappings-to-the-liferay-elasticsearch-connector}{{399.1}{1407}{Adding Settings and Mappings to the Liferay Elasticsearch Connector}{section.399.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.2}Additional Configurations}{1407}{section.399.2}\protected@file@percent } \newlabel{additional-configurations}{{399.2}{1407}{Additional Configurations}{section.399.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.3}Adding Index Configurations}{1408}{section.399.3}\protected@file@percent } \newlabel{adding-index-configurations}{{399.3}{1408}{Adding Index Configurations}{section.399.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.4}Adding Type Mappings}{1408}{section.399.4}\protected@file@percent } \newlabel{adding-type-mappings}{{399.4}{1408}{Adding Type Mappings}{section.399.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.5}Overriding Type Mappings}{1409}{section.399.5}\protected@file@percent } \newlabel{overriding-type-mappings}{{399.5}{1409}{Overriding Type Mappings}{section.399.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {399.6}Multi-line YAML Configurations}{1410}{section.399.6}\protected@file@percent } \newlabel{multi-line-yaml-configurations}{{399.6}{1410}{Multi-line YAML Configurations}{section.399.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {400}Elasticsearch Connector Settings: Reference}{1411}{chapter.400}\protected@file@percent } \newlabel{elasticsearch-connector-settings-reference}{{400}{1411}{Elasticsearch Connector Settings: Reference}{chapter.400}{}} \@writefile{toc}{\contentsline {section}{\numberline {400.1}Configurations only Affecting the Embedded Elasticsearch Server}{1413}{section.400.1}\protected@file@percent } \newlabel{configurations-only-affecting-the-embedded-elasticsearch-server}{{400.1}{1413}{Configurations only Affecting the Embedded Elasticsearch Server}{section.400.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {401}Installing Liferay Enterprise Search Security}{1415}{chapter.401}\protected@file@percent } \newlabel{installing-liferay-enterprise-search-security}{{401}{1415}{Installing Liferay Enterprise Search Security}{chapter.401}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.1}Enabling X-Pack Security}{1415}{section.401.1}\protected@file@percent } \newlabel{enabling-x-pack-security}{{401.1}{1415}{Enabling X-Pack Security}{section.401.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.2}Setting Up X-Pack Users}{1415}{section.401.2}\protected@file@percent } \newlabel{setting-up-x-pack-users}{{401.2}{1415}{Setting Up X-Pack Users}{section.401.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.3}Enabling Transport Layer Security}{1416}{section.401.3}\protected@file@percent } \newlabel{enabling-transport-layer-security}{{401.3}{1416}{Enabling Transport Layer Security}{section.401.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.4}Generate Node Certificates}{1416}{section.401.4}\protected@file@percent } \newlabel{generate-node-certificates}{{401.4}{1416}{Generate Node Certificates}{section.401.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.5}Enable TLS for Elasticsearch 7}{1417}{section.401.5}\protected@file@percent } \newlabel{enable-tls-for-elasticsearch-7}{{401.5}{1417}{Enable TLS for Elasticsearch 7}{section.401.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.6}Elasticsearch 6 TLS}{1418}{section.401.6}\protected@file@percent } \newlabel{elasticsearch-6-tls}{{401.6}{1418}{Elasticsearch 6 TLS}{section.401.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.7}Example Elasticsearch Security Configuration}{1418}{section.401.7}\protected@file@percent } \newlabel{example-elasticsearch-security-configuration}{{401.7}{1418}{Example Elasticsearch Security Configuration}{section.401.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.8}Install and Configure the Liferay Enterprise Search Security app}{1419}{section.401.8}\protected@file@percent } \newlabel{install-and-configure-the-liferay-enterprise-search-security-app}{{401.8}{1419}{Install and Configure the Liferay Enterprise Search Security app}{section.401.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {401.9}Disabling Elasticsearch Deprecation Logging}{1420}{section.401.9}\protected@file@percent } \newlabel{disabling-elasticsearch-deprecation-logging}{{401.9}{1420}{Disabling Elasticsearch Deprecation Logging}{section.401.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {402}Backing Up Elasticsearch}{1421}{chapter.402}\protected@file@percent } \newlabel{backing-up-elasticsearch}{{402}{1421}{Backing Up Elasticsearch}{chapter.402}{}} \@writefile{toc}{\contentsline {section}{\numberline {402.1}Creating a Repository}{1421}{section.402.1}\protected@file@percent } \newlabel{creating-a-repository}{{402.1}{1421}{Creating a Repository}{section.402.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {402.2}Taking Snapshots of the Cluster}{1422}{section.402.2}\protected@file@percent } \newlabel{taking-snapshots-of-the-cluster}{{402.2}{1422}{Taking Snapshots of the Cluster}{section.402.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {402.3}Restoring from a Snapshot}{1423}{section.402.3}\protected@file@percent } \newlabel{restoring-from-a-snapshot}{{402.3}{1423}{Restoring from a Snapshot}{section.402.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {403}Upgrading to Elasticsearch 6.5}{1425}{chapter.403}\protected@file@percent } \newlabel{upgrading-to-elasticsearch-6.5}{{403}{1425}{Upgrading to Elasticsearch 6.5}{chapter.403}{}} \@writefile{toc}{\contentsline {section}{\numberline {403.1}Re-index}{1425}{section.403.1}\protected@file@percent } \newlabel{re-index}{{403.1}{1425}{Re-index}{section.403.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {403.2}Reverting to Elasticsearch 6.1}{1426}{section.403.2}\protected@file@percent } \newlabel{reverting-to-elasticsearch-6.1}{{403.2}{1426}{Reverting to Elasticsearch 6.1}{section.403.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {404}Upgrading to Elasticsearch 6.8}{1427}{chapter.404}\protected@file@percent } \newlabel{upgrading-to-elasticsearch-6.8}{{404}{1427}{Upgrading to Elasticsearch 6.8}{chapter.404}{}} \@writefile{toc}{\contentsline {section}{\numberline {404.1}Re-index}{1427}{section.404.1}\protected@file@percent } \newlabel{re-index-1}{{404.1}{1427}{Re-index}{section.404.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {404.2}Reverting to Elasticsearch 6.1}{1428}{section.404.2}\protected@file@percent } \newlabel{reverting-to-elasticsearch-6.1-1}{{404.2}{1428}{Reverting to Elasticsearch 6.1}{section.404.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {405}Upgrading to Elasticsearch 7}{1429}{chapter.405}\protected@file@percent } \newlabel{upgrading-to-elasticsearch-7}{{405}{1429}{Upgrading to Elasticsearch 7}{chapter.405}{}} \@writefile{toc}{\contentsline {section}{\numberline {405.1}Backing up Application-Specific Indexes}{1430}{section.405.1}\protected@file@percent } \newlabel{backing-up-application-specific-indexes}{{405.1}{1430}{Backing up Application-Specific Indexes}{section.405.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {405.2}Blacklisting Elasticsearch 6}{1430}{section.405.2}\protected@file@percent } \newlabel{blacklisting-elasticsearch-6}{{405.2}{1430}{Blacklisting Elasticsearch 6}{section.405.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {405.3}Re-index}{1430}{section.405.3}\protected@file@percent } \newlabel{re-index-2}{{405.3}{1430}{Re-index}{section.405.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {405.4}Reverting to Elasticsearch 6}{1430}{section.405.4}\protected@file@percent } \newlabel{reverting-to-elasticsearch-6}{{405.4}{1430}{Reverting to Elasticsearch 6}{section.405.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {406}Installing Liferay Enterprise Search}{1433}{chapter.406}\protected@file@percent } \newlabel{installing-liferay-enterprise-search}{{406}{1433}{Installing Liferay Enterprise Search}{chapter.406}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {407}Installing Liferay Enterprise Search Monitoring}{1435}{chapter.407}\protected@file@percent } \newlabel{installing-liferay-enterprise-search-monitoring}{{407}{1435}{Installing Liferay Enterprise Search Monitoring}{chapter.407}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.1}Enable Encrypting Communication (TLS/SSL) in Elasticsearch and in Liferay DXP}{1436}{section.407.1}\protected@file@percent } \newlabel{enable-encrypting-communication-tlsssl-in-elasticsearch-and-in-liferay-dxp}{{407.1}{1436}{Enable Encrypting Communication (TLS/SSL) in Elasticsearch and in Liferay DXP}{section.407.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.2}Enable Data Collection}{1436}{section.407.2}\protected@file@percent } \newlabel{enable-data-collection}{{407.2}{1436}{Enable Data Collection}{section.407.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.3}Install Kibana}{1436}{section.407.3}\protected@file@percent } \newlabel{install-kibana}{{407.3}{1436}{Install Kibana}{section.407.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.4}Configure Kibana with Authentication}{1437}{section.407.4}\protected@file@percent } \newlabel{configure-kibana-with-authentication}{{407.4}{1437}{Configure Kibana with Authentication}{section.407.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.5}Configuring Kibana with Encryption (TLS/SSL)}{1437}{section.407.5}\protected@file@percent } \newlabel{configuring-kibana-with-encryption-tlsssl}{{407.5}{1437}{Configuring Kibana with Encryption (TLS/SSL)}{section.407.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.6}Configuring the Liferay Enterprise Search Monitoring app}{1438}{section.407.6}\protected@file@percent } \newlabel{configuring-the-liferay-enterprise-search-monitoring-app}{{407.6}{1438}{Configuring the Liferay Enterprise Search Monitoring app}{section.407.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.7}Monitoring in Liferay DXP}{1439}{section.407.7}\protected@file@percent } \newlabel{monitoring-in-liferay-dxp}{{407.7}{1439}{Monitoring in Liferay DXP}{section.407.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {407.8}Example Kibana Configuration}{1439}{section.407.8}\protected@file@percent } \newlabel{example-kibana-configuration}{{407.8}{1439}{Example Kibana Configuration}{section.407.8}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {408}Liferay Enterprise Search: Learning to Rank}{1441}{chapter.408}\protected@file@percent } \newlabel{liferay-enterprise-search-learning-to-rank}{{408}{1441}{Liferay Enterprise Search: Learning to Rank}{chapter.408}{}} \@writefile{toc}{\contentsline {section}{\numberline {408.1}Disabling Learning to Rank on a Search Page}{1441}{section.408.1}\protected@file@percent } \newlabel{disabling-learning-to-rank-on-a-search-page}{{408.1}{1441}{Disabling Learning to Rank on a Search Page}{section.408.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {408.2}Prerequisites}{1442}{section.408.2}\protected@file@percent } \newlabel{prerequisites}{{408.2}{1442}{Prerequisites}{section.408.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {408.3}Technical Overview}{1442}{section.408.3}\protected@file@percent } \newlabel{technical-overview}{{408.3}{1442}{Technical Overview}{section.408.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {408.4}Model Training}{1443}{section.408.4}\protected@file@percent } \newlabel{model-training}{{408.4}{1443}{Model Training}{section.408.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {409}Configuring Learning to Rank}{1445}{chapter.409}\protected@file@percent } \newlabel{configuring-learning-to-rank}{{409}{1445}{Configuring Learning to Rank}{chapter.409}{}} \@writefile{toc}{\contentsline {section}{\numberline {409.1}Step 1: Install the Learning to Rank Plugin on Elasticsearch}{1445}{section.409.1}\protected@file@percent } \newlabel{step-1-install-the-learning-to-rank-plugin-on-elasticsearch}{{409.1}{1445}{Step 1: Install the Learning to Rank Plugin on Elasticsearch}{section.409.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {409.2}Step 2: Training and Uploading a Model}{1445}{section.409.2}\protected@file@percent } \newlabel{step-2-training-and-uploading-a-model}{{409.2}{1445}{Step 2: Training and Uploading a Model}{section.409.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {409.3}Upload the Model to the Learning to Rank Plugin}{1446}{section.409.3}\protected@file@percent } \newlabel{upload-the-model-to-the-learning-to-rank-plugin}{{409.3}{1446}{Upload the Model to the Learning to Rank Plugin}{section.409.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {409.4}Step 3: Enable Learning to Rank}{1447}{section.409.4}\protected@file@percent } \newlabel{step-3-enable-learning-to-rank}{{409.4}{1447}{Step 3: Enable Learning to Rank}{section.409.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {409.1}{\ignorespaces Enable Learning to Rank in Liferay DXP from the System Settings entry.}}{1448}{figure.409.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {410}Installing Solr}{1449}{chapter.410}\protected@file@percent } \newlabel{installing-solr}{{410}{1449}{Installing Solr}{chapter.410}{}} \@writefile{toc}{\contentsline {section}{\numberline {410.1}Blacklisting Elasticsearch-Only Features}{1449}{section.410.1}\protected@file@percent } \newlabel{blacklisting-elasticsearch-only-features}{{410.1}{1449}{Blacklisting Elasticsearch-Only Features}{section.410.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {411}Installing Solr: Basic Installation}{1451}{chapter.411}\protected@file@percent } \newlabel{installing-solr-basic-installation}{{411}{1451}{Installing Solr: Basic Installation}{chapter.411}{}} \@writefile{toc}{\contentsline {section}{\numberline {411.1}Installing and Configuring Solr 7}{1451}{section.411.1}\protected@file@percent } \newlabel{installing-and-configuring-solr-7}{{411.1}{1451}{Installing and Configuring Solr 7}{section.411.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {411.2}Installing and Configuring the Liferay Solr Adapter}{1453}{section.411.2}\protected@file@percent } \newlabel{installing-and-configuring-the-liferay-solr-adapter}{{411.2}{1453}{Installing and Configuring the Liferay Solr Adapter}{section.411.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {411.3}Stopping the Elasticsearch Connector}{1453}{section.411.3}\protected@file@percent } \newlabel{stopping-the-elasticsearch-connector}{{411.3}{1453}{Stopping the Elasticsearch Connector}{section.411.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {411.4}Install and Configure the Solr Connector}{1454}{section.411.4}\protected@file@percent } \newlabel{install-and-configure-the-solr-connector}{{411.4}{1454}{Install and Configure the Solr Connector}{section.411.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {411.1}{\ignorespaces Once the Solr connector is installed, you can re-index your Liferay DXP data against your Solr server.}}{1454}{figure.411.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {411.2}{\ignorespaces You can configure Solr from Liferay DXP's System Settings application. This is most useful during development and testing.}}{1455}{figure.411.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {412}Installing Solr: High Availability with SolrCloud}{1457}{chapter.412}\protected@file@percent } \newlabel{installing-solr-high-availability-with-solrcloud}{{412}{1457}{Installing Solr: High Availability with SolrCloud}{chapter.412}{}} \@writefile{toc}{\contentsline {section}{\numberline {412.1}Configure the Solr Adapter for SolrCloud}{1459}{section.412.1}\protected@file@percent } \newlabel{configure-the-solr-adapter-for-solrcloud}{{412.1}{1459}{Configure the Solr Adapter for SolrCloud}{section.412.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {412.1}{\ignorespaces From the Solr 7 System Settings entry, set the \emph {Client Type} to \emph {Cloud}.}}{1459}{figure.412.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {413}Solr Connector Settings}{1461}{chapter.413}\protected@file@percent } \newlabel{solr-connector-settings}{{413}{1461}{Solr Connector Settings}{chapter.413}{}} \@writefile{toc}{\contentsline {section}{\numberline {413.1}Solr 7}{1461}{section.413.1}\protected@file@percent } \newlabel{solr-7}{{413.1}{1461}{Solr 7}{section.413.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {414}Securing Liferay DXP}{1463}{chapter.414}\protected@file@percent } \newlabel{securing-liferay-dxp}{{414}{1463}{Securing Liferay DXP}{chapter.414}{}} \@writefile{toc}{\contentsline {section}{\numberline {414.1}Authentication Overview}{1463}{section.414.1}\protected@file@percent } \newlabel{authentication-overview}{{414.1}{1463}{Authentication Overview}{section.414.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {414.2}Authorization and Permission Checking}{1464}{section.414.2}\protected@file@percent } \newlabel{authorization-and-permission-checking}{{414.2}{1464}{Authorization and Permission Checking}{section.414.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {414.3}Additional Security Features}{1464}{section.414.3}\protected@file@percent } \newlabel{additional-security-features}{{414.3}{1464}{Additional Security Features}{section.414.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {414.4}Secure Configuration and Run Recommendations}{1465}{section.414.4}\protected@file@percent } \newlabel{secure-configuration-and-run-recommendations}{{414.4}{1465}{Secure Configuration and Run Recommendations}{section.414.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {415}Logging into Liferay DXP}{1467}{chapter.415}\protected@file@percent } \newlabel{logging-into-liferay-dxp}{{415}{1467}{Logging into Liferay DXP}{chapter.415}{}} \@writefile{toc}{\contentsline {section}{\numberline {415.1}Authentication Types}{1467}{section.415.1}\protected@file@percent } \newlabel{authentication-types}{{415.1}{1467}{Authentication Types}{section.415.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {415.2}The Sign In Portlet}{1468}{section.415.2}\protected@file@percent } \newlabel{the-sign-in-portlet}{{415.2}{1468}{The Sign In Portlet}{section.415.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {415.1}{\ignorespaces By default, the Sign In portlet allows users to log in, create a new account, or request a password reset.}}{1468}{figure.415.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {416}Service Access Policies}{1471}{chapter.416}\protected@file@percent } \newlabel{service-access-policies}{{416}{1471}{Service Access Policies}{chapter.416}{}} \@writefile{lof}{\contentsline {figure}{\numberline {416.1}{\ignorespaces To get to a service, a request must pass through the door lock of user permissions, the padlock of the verification layer, the brick wall of service access policies, and finally the safe of predefined IP permissions.}}{1472}{figure.416.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {416.1}Managing Service Access Policies}{1472}{section.416.1}\protected@file@percent } \newlabel{managing-service-access-policies}{{416.1}{1472}{Managing Service Access Policies}{section.416.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {416.2}Service Access Policy Module}{1474}{section.416.2}\protected@file@percent } \newlabel{service-access-policy-module}{{416.2}{1474}{Service Access Policy Module}{section.416.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {416.3}Summary}{1475}{section.416.3}\protected@file@percent } \newlabel{summary}{{416.3}{1475}{Summary}{section.416.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {416.4}Related Topics}{1475}{section.416.4}\protected@file@percent } \newlabel{related-topics-3}{{416.4}{1475}{Related Topics}{section.416.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {417}Authentication Verifiers}{1477}{chapter.417}\protected@file@percent } \newlabel{authentication-verifiers}{{417}{1477}{Authentication Verifiers}{chapter.417}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.1}Authentication Verification Process Overview}{1477}{section.417.1}\protected@file@percent } \newlabel{authentication-verification-process-overview}{{417.1}{1477}{Authentication Verification Process Overview}{section.417.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.2}Basic Auth Header}{1478}{section.417.2}\protected@file@percent } \newlabel{basic-auth-header}{{417.2}{1478}{Basic Auth Header}{section.417.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.3}Digest Auth Header}{1479}{section.417.3}\protected@file@percent } \newlabel{digest-auth-header}{{417.3}{1479}{Digest Auth Header}{section.417.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.4}HTTP Tunnel Extender}{1479}{section.417.4}\protected@file@percent } \newlabel{http-tunnel-extender}{{417.4}{1479}{HTTP Tunnel Extender}{section.417.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.5}Image Request Authentication Verifier}{1479}{section.417.5}\protected@file@percent } \newlabel{image-request-authentication-verifier}{{417.5}{1479}{Image Request Authentication Verifier}{section.417.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.6}Portal Sessions Auth Verifiers}{1479}{section.417.6}\protected@file@percent } \newlabel{portal-sessions-auth-verifiers}{{417.6}{1479}{Portal Sessions Auth Verifiers}{section.417.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.7}Request Parameter Auth Verifiers}{1480}{section.417.7}\protected@file@percent } \newlabel{request-parameter-auth-verifiers}{{417.7}{1480}{Request Parameter Auth Verifiers}{section.417.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.8}Tunnel Authentication Verifiers}{1480}{section.417.8}\protected@file@percent } \newlabel{tunnel-authentication-verifiers}{{417.8}{1480}{Tunnel Authentication Verifiers}{section.417.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {417.9}Related Topics}{1480}{section.417.9}\protected@file@percent } \newlabel{related-topics-4}{{417.9}{1480}{Related Topics}{section.417.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {418}LDAP}{1481}{chapter.418}\protected@file@percent } \newlabel{ldap}{{418}{1481}{LDAP}{chapter.418}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.1}Connection}{1481}{section.418.1}\protected@file@percent } \newlabel{connection}{{418.1}{1481}{Connection}{section.418.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.2}Checkpoint}{1482}{section.418.2}\protected@file@percent } \newlabel{checkpoint-1}{{418.2}{1482}{Checkpoint}{section.418.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.3}Instance Settings vs.~System Settings}{1482}{section.418.3}\protected@file@percent } \newlabel{instance-settings-vs.-system-settings}{{418.3}{1482}{Instance Settings vs.~System Settings}{section.418.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.4}Security}{1483}{section.418.4}\protected@file@percent } \newlabel{security}{{418.4}{1483}{Security}{section.418.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.5}Configuring LDAP Import/Export}{1483}{section.418.5}\protected@file@percent } \newlabel{configuring-ldap-importexport}{{418.5}{1483}{Configuring LDAP Import/Export}{section.418.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.6}Users}{1483}{section.418.6}\protected@file@percent } \newlabel{users}{{418.6}{1483}{Users}{section.418.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.7}Groups}{1484}{section.418.7}\protected@file@percent } \newlabel{groups}{{418.7}{1484}{Groups}{section.418.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {418.1}{\ignorespaces You should see a list of users when you click the \emph {Test LDAP Users} button.}}{1485}{figure.418.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {418.8}Export}{1485}{section.418.8}\protected@file@percent } \newlabel{export}{{418.8}{1485}{Export}{section.418.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {418.9}Related Topics}{1486}{section.418.9}\protected@file@percent } \newlabel{related-topics-5}{{418.9}{1486}{Related Topics}{section.418.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {419}Configuring LDAP}{1487}{chapter.419}\protected@file@percent } \newlabel{configuring-ldap}{{419}{1487}{Configuring LDAP}{chapter.419}{}} \@writefile{toc}{\contentsline {section}{\numberline {419.1}Export}{1487}{section.419.1}\protected@file@percent } \newlabel{export-1}{{419.1}{1487}{Export}{section.419.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {419.2}General}{1487}{section.419.2}\protected@file@percent } \newlabel{general}{{419.2}{1487}{General}{section.419.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {419.3}Import}{1488}{section.419.3}\protected@file@percent } \newlabel{import}{{419.3}{1488}{Import}{section.419.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {419.1}{\ignorespaces Ziltoid and Rex have been imported because they logged in.}}{1488}{figure.419.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {419.4}Servers}{1489}{section.419.4}\protected@file@percent } \newlabel{servers}{{419.4}{1489}{Servers}{section.419.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {419.5}LDAP Options Available in System Settings}{1489}{section.419.5}\protected@file@percent } \newlabel{ldap-options-available-in-system-settings}{{419.5}{1489}{LDAP Options Available in System Settings}{section.419.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {420}Token-based Single Sign On Authentication}{1491}{chapter.420}\protected@file@percent } \newlabel{token-based-single-sign-on-authentication}{{420}{1491}{Token-based Single Sign On Authentication}{chapter.420}{}} \@writefile{toc}{\contentsline {section}{\numberline {420.1}Required SiteMinder Configuration}{1492}{section.420.1}\protected@file@percent } \newlabel{required-siteminder-configuration}{{420.1}{1492}{Required SiteMinder Configuration}{section.420.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {420.2}Summary}{1492}{section.420.2}\protected@file@percent } \newlabel{summary-1}{{420.2}{1492}{Summary}{section.420.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {421}Authenticating with OpenID Connect}{1493}{chapter.421}\protected@file@percent } \newlabel{authenticating-with-openid-connect}{{421}{1493}{Authenticating with OpenID Connect}{chapter.421}{}} \@writefile{toc}{\contentsline {section}{\numberline {421.1}Creating a Client in OpenID Connect Provider}{1493}{section.421.1}\protected@file@percent } \newlabel{creating-a-client-in-openid-connect-provider}{{421.1}{1493}{Creating a Client in OpenID Connect Provider}{section.421.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {421.2}Configuring an OpenID Connect Provider Connection}{1494}{section.421.2}\protected@file@percent } \newlabel{configuring-an-openid-connect-provider-connection}{{421.2}{1494}{Configuring an OpenID Connect Provider Connection}{section.421.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {421.3}Enabling OpenID Connect Authentication}{1495}{section.421.3}\protected@file@percent } \newlabel{enabling-openid-connect-authentication}{{421.3}{1495}{Enabling OpenID Connect Authentication}{section.421.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {421.4}Signing In With OpenID Connect}{1495}{section.421.4}\protected@file@percent } \newlabel{signing-in-with-openid-connect}{{421.4}{1495}{Signing In With OpenID Connect}{section.421.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {422}OpenAM Single Sign On Authentication}{1497}{chapter.422}\protected@file@percent } \newlabel{openam-single-sign-on-authentication}{{422}{1497}{OpenAM Single Sign On Authentication}{chapter.422}{}} \@writefile{lof}{\contentsline {figure}{\numberline {422.1}{\ignorespaces OpenSSO Configuration.}}{1498}{figure.422.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {423}CAS (Central Authentication Service) Single Sign On Authentication}{1501}{chapter.423}\protected@file@percent } \newlabel{cas-central-authentication-service-single-sign-on-authentication}{{423}{1501}{CAS (Central Authentication Service) Single Sign On Authentication}{chapter.423}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {424}Authenticating Using SAML}{1503}{chapter.424}\protected@file@percent } \newlabel{authenticating-using-saml}{{424}{1503}{Authenticating Using SAML}{chapter.424}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.1}What's new in Liferay Connector to SAML 2.0}{1503}{section.424.1}\protected@file@percent } \newlabel{whats-new-in-liferay-connector-to-saml-2.0}{{424.1}{1503}{What's new in Liferay Connector to SAML 2.0}{section.424.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.2}Important SAML Paths}{1504}{section.424.2}\protected@file@percent } \newlabel{important-saml-paths}{{424.2}{1504}{Important SAML Paths}{section.424.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.3}Single Sign On}{1504}{section.424.3}\protected@file@percent } \newlabel{single-sign-on}{{424.3}{1504}{Single Sign On}{section.424.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.4}Identity Provider Initiated SSO}{1504}{section.424.4}\protected@file@percent } \newlabel{identity-provider-initiated-sso}{{424.4}{1504}{Identity Provider Initiated SSO}{section.424.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {424.1}{\ignorespaces Identity Provider Initiated SSO}}{1505}{figure.424.1}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{The SSO Request to the IdP}{1505}{figure.424.1}\protected@file@percent } \newlabel{the-sso-request-to-the-idp}{{424.4}{1505}{The SSO Request to the IdP}{figure.424.1}{}} \@writefile{toc}{\contentsline {subsection}{The SSO Response from the IdP}{1505}{figure.424.1}\protected@file@percent } \newlabel{the-sso-response-from-the-idp}{{424.4}{1505}{The SSO Response from the IdP}{figure.424.1}{}} \@writefile{toc}{\contentsline {subsection}{The SP Processes the SSO Response}{1506}{figure.424.1}\protected@file@percent } \newlabel{the-sp-processes-the-sso-response}{{424.4}{1506}{The SP Processes the SSO Response}{figure.424.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.5}Service Provider Initiated SSO}{1506}{section.424.5}\protected@file@percent } \newlabel{service-provider-initiated-sso}{{424.5}{1506}{Service Provider Initiated SSO}{section.424.5}{}} \@writefile{toc}{\contentsline {subsection}{The SSO Request to the SP}{1506}{figure.424.2}\protected@file@percent } \newlabel{the-sso-request-to-the-sp}{{424.5}{1506}{The SSO Request to the SP}{figure.424.2}{}} \@writefile{toc}{\contentsline {subsection}{The AuthnRequest to the IdP}{1506}{figure.424.2}\protected@file@percent } \newlabel{the-authnrequest-to-the-idp}{{424.5}{1506}{The AuthnRequest to the IdP}{figure.424.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {424.2}{\ignorespaces Service Provider Initiated SSO}}{1507}{figure.424.2}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{The SSO Response from the IdP}{1507}{figure.424.2}\protected@file@percent } \newlabel{the-sso-response-from-the-idp-1}{{424.5}{1507}{The SSO Response from the IdP}{figure.424.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.6}Single Log Off}{1507}{section.424.6}\protected@file@percent } \newlabel{single-log-off}{{424.6}{1507}{Single Log Off}{section.424.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.7}Identity Provider Initiated SLO}{1508}{section.424.7}\protected@file@percent } \newlabel{identity-provider-initiated-slo}{{424.7}{1508}{Identity Provider Initiated SLO}{section.424.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {424.3}{\ignorespaces Identity Provider Initiated SLO}}{1508}{figure.424.3}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{The SLO Request to the IdP}{1508}{figure.424.3}\protected@file@percent } \newlabel{the-slo-request-to-the-idp}{{424.7}{1508}{The SLO Request to the IdP}{figure.424.3}{}} \@writefile{toc}{\contentsline {subsection}{The SLO Response from the SP}{1509}{figure.424.3}\protected@file@percent } \newlabel{the-slo-response-from-the-sp}{{424.7}{1509}{The SLO Response from the SP}{figure.424.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.8}Service Provider Initiated SLO}{1509}{section.424.8}\protected@file@percent } \newlabel{service-provider-initiated-slo}{{424.8}{1509}{Service Provider Initiated SLO}{section.424.8}{}} \@writefile{lof}{\contentsline {figure}{\numberline {424.4}{\ignorespaces Service Provider Initiated SLO}}{1509}{figure.424.4}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{The SLO Request to the SP}{1509}{figure.424.4}\protected@file@percent } \newlabel{the-slo-request-to-the-sp}{{424.8}{1509}{The SLO Request to the SP}{figure.424.4}{}} \@writefile{toc}{\contentsline {subsection}{The SLO Response from the SP}{1510}{figure.424.4}\protected@file@percent } \newlabel{the-slo-response-from-the-sp-1}{{424.8}{1510}{The SLO Response from the SP}{figure.424.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {424.9}Related Topics}{1510}{section.424.9}\protected@file@percent } \newlabel{related-topics-6}{{424.9}{1510}{Related Topics}{section.424.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {425}Setting up Liferay DXP as a SAML Identity Provider}{1511}{chapter.425}\protected@file@percent } \newlabel{setting-up-liferay-dxp-as-a-saml-identity-provider}{{425}{1511}{Setting up Liferay DXP as a SAML Identity Provider}{chapter.425}{}} \@writefile{toc}{\contentsline {section}{\numberline {425.1}Storing Your Keystore}{1511}{section.425.1}\protected@file@percent } \newlabel{storing-your-keystore}{{425.1}{1511}{Storing Your Keystore}{section.425.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {425.2}Configuring Liferay DXP as a SAML Identity Provider}{1512}{section.425.2}\protected@file@percent } \newlabel{configuring-liferay-dxp-as-a-saml-identity-provider}{{425.2}{1512}{Configuring Liferay DXP as a SAML Identity Provider}{section.425.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {425.1}{\ignorespaces Select a SAML role for Liferay and enter an entity ID.}}{1512}{figure.425.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {425.3}Changing the Identity Provider Settings}{1513}{section.425.3}\protected@file@percent } \newlabel{changing-the-identity-provider-settings}{{425.3}{1513}{Changing the Identity Provider Settings}{section.425.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {425.2}{\ignorespaces The General tab of the SAML Admin portlet displays information about the current certificate and private key and allows administrators to download the certificate or replace the certificate.}}{1514}{figure.425.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {425.4}Checkpoint}{1515}{section.425.4}\protected@file@percent } \newlabel{checkpoint-2}{{425.4}{1515}{Checkpoint}{section.425.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {426}Registering a SAML Service Provider}{1517}{chapter.426}\protected@file@percent } \newlabel{registering-a-saml-service-provider}{{426}{1517}{Registering a SAML Service Provider}{chapter.426}{}} \@writefile{toc}{\contentsline {section}{\numberline {426.1}Checkpoint}{1518}{section.426.1}\protected@file@percent } \newlabel{checkpoint-3}{{426.1}{1518}{Checkpoint}{section.426.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {427}Setting up Liferay DXP as a SAML Service Provider}{1521}{chapter.427}\protected@file@percent } \newlabel{setting-up-liferay-dxp-as-a-saml-service-provider}{{427}{1521}{Setting up Liferay DXP as a SAML Service Provider}{chapter.427}{}} \@writefile{toc}{\contentsline {section}{\numberline {427.1}Checkpoint}{1523}{section.427.1}\protected@file@percent } \newlabel{checkpoint-4}{{427.1}{1523}{Checkpoint}{section.427.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {427.2}Setting Up Liferay DXP as a SAML Service Provider in a Clustered Environment}{1523}{section.427.2}\protected@file@percent } \newlabel{setting-up-liferay-dxp-as-a-saml-service-provider-in-a-clustered-environment}{{427.2}{1523}{Setting Up Liferay DXP as a SAML Service Provider in a Clustered Environment}{section.427.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {428}Changing the Settings for Service Provider and Identity Provider Connections}{1525}{chapter.428}\protected@file@percent } \newlabel{changing-the-settings-for-service-provider-and-identity-provider-connections}{{428}{1525}{Changing the Settings for Service Provider and Identity Provider Connections}{chapter.428}{}} \@writefile{toc}{\contentsline {section}{\numberline {428.1}Changing the SAML Identity Provider Connection Settings}{1526}{section.428.1}\protected@file@percent } \newlabel{changing-the-saml-identity-provider-connection-settings}{{428.1}{1526}{Changing the SAML Identity Provider Connection Settings}{section.428.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {428.2}Configuring Negotiation Via metadata.xml}{1528}{section.428.2}\protected@file@percent } \newlabel{configuring-negotiation-via-metadata.xml}{{428.2}{1528}{Configuring Negotiation Via metadata.xml}{section.428.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {429}NTLM Single Sign On Authentication}{1531}{chapter.429}\protected@file@percent } \newlabel{ntlm-single-sign-on-authentication}{{429}{1531}{NTLM Single Sign On Authentication}{chapter.429}{}} \@writefile{toc}{\contentsline {section}{\numberline {429.1}Summary}{1532}{section.429.1}\protected@file@percent } \newlabel{summary-2}{{429.1}{1532}{Summary}{section.429.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {430}OpenID Single Sign On Authentication}{1533}{chapter.430}\protected@file@percent } \newlabel{openid-single-sign-on-authentication}{{430}{1533}{OpenID Single Sign On Authentication}{chapter.430}{}} \@writefile{toc}{\contentsline {section}{\numberline {430.1}OpenID at the System Scope}{1533}{section.430.1}\protected@file@percent } \newlabel{openid-at-the-system-scope}{{430.1}{1533}{OpenID at the System Scope}{section.430.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {430.2}OpenID at the Instance Scope}{1534}{section.430.2}\protected@file@percent } \newlabel{openid-at-the-instance-scope}{{430.2}{1534}{OpenID at the Instance Scope}{section.430.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {431}Authenticating with Kerberos}{1535}{chapter.431}\protected@file@percent } \newlabel{authenticating-with-kerberos}{{431}{1535}{Authenticating with Kerberos}{chapter.431}{}} \@writefile{toc}{\contentsline {section}{\numberline {431.1}How Kerberos Authentication Works}{1536}{section.431.1}\protected@file@percent } \newlabel{how-kerberos-authentication-works}{{431.1}{1536}{How Kerberos Authentication Works}{section.431.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {431.1}{\ignorespaces Kerberos authentication requires a web server in front of your Liferay DXP server.}}{1536}{figure.431.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {431.2}Configuring Kerberos Authentication}{1537}{section.431.2}\protected@file@percent } \newlabel{configuring-kerberos-authentication}{{431.2}{1537}{Configuring Kerberos Authentication}{section.431.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {431.3}Creating the User Keytab}{1537}{section.431.3}\protected@file@percent } \newlabel{creating-the-user-keytab}{{431.3}{1537}{Creating the User Keytab}{section.431.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {431.4}Configuring Your Web Server}{1537}{section.431.4}\protected@file@percent } \newlabel{configuring-your-web-server}{{431.4}{1537}{Configuring Your Web Server}{section.431.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {431.5}Connecting Liferay DXP to Active Directory over LDAP}{1538}{section.431.5}\protected@file@percent } \newlabel{connecting-liferay-dxp-to-active-directory-over-ldap}{{431.5}{1538}{Connecting Liferay DXP to Active Directory over LDAP}{section.431.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {431.6}Configuring your Clients}{1539}{section.431.6}\protected@file@percent } \newlabel{configuring-your-clients}{{431.6}{1539}{Configuring your Clients}{section.431.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {432}Configuring CORS}{1541}{chapter.432}\protected@file@percent } \newlabel{configuring-cors}{{432}{1541}{Configuring CORS}{chapter.432}{}} \@writefile{toc}{\contentsline {section}{\numberline {432.1}Enabling CORS for Liferay DXP Services}{1541}{section.432.1}\protected@file@percent } \newlabel{enabling-cors-for-liferay-dxp-services}{{432.1}{1541}{Enabling CORS for Liferay DXP Services}{section.432.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {432.1}{\ignorespaces The CORS system settings provide a way to configure CORS headers for Liferay services.}}{1542}{figure.432.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {432.2}Enabling CORS for JAX-RS Applications}{1543}{section.432.2}\protected@file@percent } \newlabel{enabling-cors-for-jax-rs-applications}{{432.2}{1543}{Enabling CORS for JAX-RS Applications}{section.432.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {432.2}{\ignorespaces There's a separate system settings category for CORS web contexts.}}{1544}{figure.432.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {433}AntiSamy}{1545}{chapter.433}\protected@file@percent } \newlabel{antisamy}{{433}{1545}{AntiSamy}{chapter.433}{}} \@writefile{toc}{\contentsline {section}{\numberline {433.1}Configuring AntiSamy}{1545}{section.433.1}\protected@file@percent } \newlabel{configuring-antisamy}{{433.1}{1545}{Configuring AntiSamy}{section.433.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {433.1}{\ignorespaces Liferay DXP's AntiSamy configuration options allow you to specify both a blacklist and a whitelist.}}{1546}{figure.433.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {433.2}Using Wildcards}{1546}{section.433.2}\protected@file@percent } \newlabel{using-wildcards}{{433.2}{1546}{Using Wildcards}{section.433.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {434}OAuth 2.0}{1547}{chapter.434}\protected@file@percent } \newlabel{oauth-2.0}{{434}{1547}{OAuth 2.0}{chapter.434}{}} \@writefile{toc}{\contentsline {section}{\numberline {434.1}Flow of OAuth 2.0}{1547}{section.434.1}\protected@file@percent } \newlabel{flow-of-oauth-2.0}{{434.1}{1547}{Flow of OAuth 2.0}{section.434.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {434.1}{\ignorespaces OAuth 2.0 takes advantage of web standards.}}{1548}{figure.434.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {434.2}OAuth 2.0 Terminology}{1549}{section.434.2}\protected@file@percent } \newlabel{oauth-2.0-terminology}{{434.2}{1549}{OAuth 2.0 Terminology}{section.434.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {434.3}Creating an Application}{1550}{section.434.3}\protected@file@percent } \newlabel{creating-an-application}{{434.3}{1550}{Creating an Application}{section.434.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {434.2}{\ignorespaces Adding an application registers it so users can authorize access to their data.}}{1550}{figure.434.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {435}OAuth2 Scopes}{1553}{chapter.435}\protected@file@percent } \newlabel{oauth2-scopes}{{435}{1553}{OAuth2 Scopes}{chapter.435}{}} \@writefile{toc}{\contentsline {section}{\numberline {435.1}Creating a Scope for a JSONWS Service}{1553}{section.435.1}\protected@file@percent } \newlabel{creating-a-scope-for-a-jsonws-service}{{435.1}{1553}{Creating a Scope for a JSONWS Service}{section.435.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {435.1}{\ignorespaces A Service Access Policy defines a scope for OAuth 2.0 applications.}}{1554}{figure.435.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {435.2}Creating the Authorization Page}{1554}{section.435.2}\protected@file@percent } \newlabel{creating-the-authorization-page}{{435.2}{1554}{Creating the Authorization Page}{section.435.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {435.2}{\ignorespaces Scopes named with the proper prefix appear in the Scopes tab of your application configuration.}}{1555}{figure.435.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {436}Authorizing Account Access with OAuth2}{1557}{chapter.436}\protected@file@percent } \newlabel{authorizing-account-access-with-oauth2}{{436}{1557}{Authorizing Account Access with OAuth2}{chapter.436}{}} \@writefile{toc}{\contentsline {section}{\numberline {436.1}Authorization Code Flow}{1557}{section.436.1}\protected@file@percent } \newlabel{authorization-code-flow}{{436.1}{1557}{Authorization Code Flow}{section.436.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {436.2}PKCE Extended Authorization Code Flow}{1558}{section.436.2}\protected@file@percent } \newlabel{pkce-extended-authorization-code-flow}{{436.2}{1558}{PKCE Extended Authorization Code Flow}{section.436.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {436.3}Client Credentials and Resource Owner Flows}{1559}{section.436.3}\protected@file@percent } \newlabel{client-credentials-and-resource-owner-flows}{{436.3}{1559}{Client Credentials and Resource Owner Flows}{section.436.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {436.4}Token Use}{1559}{section.436.4}\protected@file@percent } \newlabel{token-use}{{436.4}{1559}{Token Use}{section.436.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {436.5}Revoking Access}{1559}{section.436.5}\protected@file@percent } \newlabel{revoking-access}{{436.5}{1559}{Revoking Access}{section.436.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {436.1}{\ignorespaces Users have complete control over what applications have access to their data in their account profiles.}}{1560}{figure.436.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {436.2}{\ignorespaces All authorizations for an app appear in the Authorizations tab for the app.}}{1560}{figure.436.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {436.6}Summary}{1561}{section.436.6}\protected@file@percent } \newlabel{summary-3}{{436.6}{1561}{Summary}{section.436.6}{}} \gdef \LT@iv {\LT@entry {1}{298.46002pt}\LT@entry {1}{171.29498pt}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {437}Upgrading to 7.0}{1563}{chapter.437}\protected@file@percent } \newlabel{upgrading-to-7.0}{{437}{1563}{Upgrading to 7.0}{chapter.437}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {438}Planning for Deprecated Applications}{1567}{chapter.438}\protected@file@percent } \newlabel{planning-for-deprecated-applications}{{438}{1567}{Planning for Deprecated Applications}{chapter.438}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {439}Test Upgrading a Liferay DXP Backup Copy}{1569}{chapter.439}\protected@file@percent } \newlabel{test-upgrading-a-liferay-dxp-backup-copy}{{439}{1569}{Test Upgrading a Liferay DXP Backup Copy}{chapter.439}{}} \@writefile{toc}{\contentsline {section}{\numberline {439.1}Preparing a Test Server and Database}{1569}{section.439.1}\protected@file@percent } \newlabel{preparing-a-test-server-and-database}{{439.1}{1569}{Preparing a Test Server and Database}{section.439.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {439.2}Copy the Production Installation to a Test Server}{1570}{section.439.2}\protected@file@percent } \newlabel{copy-the-production-installation-to-a-test-server}{{439.2}{1570}{Copy the Production Installation to a Test Server}{section.439.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {439.3}Copy the Production Backup to the Test Database}{1570}{section.439.3}\protected@file@percent } \newlabel{copy-the-production-backup-to-the-test-database}{{439.3}{1570}{Copy the Production Backup to the Test Database}{section.439.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {440}Pruning the Database}{1571}{chapter.440}\protected@file@percent } \newlabel{pruning-the-database}{{440}{1571}{Pruning the Database}{chapter.440}{}} \@writefile{toc}{\contentsline {section}{\numberline {440.1}Remove Duplicate Web Content Structure Field Names}{1571}{section.440.1}\protected@file@percent } \newlabel{remove-duplicate-web-content-structure-field-names}{{440.1}{1571}{Remove Duplicate Web Content Structure Field Names}{section.440.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {440.2}Find and Remove Unused Objects}{1571}{section.440.2}\protected@file@percent } \newlabel{find-and-remove-unused-objects}{{440.2}{1571}{Find and Remove Unused Objects}{section.440.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {440.3}Objects From the Large/Populated Tables}{1572}{section.440.3}\protected@file@percent } \newlabel{objects-from-the-largepopulated-tables}{{440.3}{1572}{Objects From the Large/Populated Tables}{section.440.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {440.4}Common Object Types Worth Checking}{1572}{section.440.4}\protected@file@percent } \newlabel{common-object-types-worth-checking}{{440.4}{1572}{Common Object Types Worth Checking}{section.440.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {440.5}Test with the Pruned Database Copy}{1574}{section.440.5}\protected@file@percent } \newlabel{test-with-the-pruned-database-copy}{{440.5}{1574}{Test with the Pruned Database Copy}{section.440.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {441}Example: Removing Intermediate Journal Article Versions}{1575}{chapter.441}\protected@file@percent } \newlabel{example-removing-intermediate-journal-article-versions}{{441}{1575}{Example: Removing Intermediate Journal Article Versions}{chapter.441}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {442}Upgrading Your Test Server and Database}{1579}{chapter.442}\protected@file@percent } \newlabel{upgrading-your-test-server-and-database}{{442}{1579}{Upgrading Your Test Server and Database}{chapter.442}{}} \@writefile{toc}{\contentsline {section}{\numberline {442.1}Install Liferay on a Test Server and Configure It to Use the Pruned Database}{1579}{section.442.1}\protected@file@percent } \newlabel{install-liferay-on-a-test-server-and-configure-it-to-use-the-pruned-database}{{442.1}{1579}{Install Liferay on a Test Server and Configure It to Use the Pruned Database}{section.442.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {442.2}Tune Your Database for the Upgrade}{1579}{section.442.2}\protected@file@percent } \newlabel{tune-your-database-for-the-upgrade}{{442.2}{1579}{Tune Your Database for the Upgrade}{section.442.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {442.3}Upgrade the Database}{1579}{section.442.3}\protected@file@percent } \newlabel{upgrade-the-database}{{442.3}{1579}{Upgrade the Database}{section.442.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {442.4}Test the Upgraded Portal and Resolve Any Issues}{1580}{section.442.4}\protected@file@percent } \newlabel{test-the-upgraded-portal-and-resolve-any-issues}{{442.4}{1580}{Test the Upgraded Portal and Resolve Any Issues}{section.442.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {442.5}Checkpoint: You've Pruned and Upgraded a Production Database Copy}{1580}{section.442.5}\protected@file@percent } \newlabel{checkpoint-youve-pruned-and-upgraded-a-production-database-copy}{{442.5}{1580}{Checkpoint: You've Pruned and Upgraded a Production Database Copy}{section.442.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {443}Preparing to Upgrade the Liferay DXP Database}{1581}{chapter.443}\protected@file@percent } \newlabel{preparing-to-upgrade-the-liferay-dxp-database}{{443}{1581}{Preparing to Upgrade the Liferay DXP Database}{chapter.443}{}} \@writefile{toc}{\contentsline {section}{\numberline {443.1}Remove All Unused Objects You Identified Earlier}{1581}{section.443.1}\protected@file@percent } \newlabel{remove-all-unused-objects-you-identified-earlier}{{443.1}{1581}{Remove All Unused Objects You Identified Earlier}{section.443.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {443.2}Test Using the Pruned Database}{1581}{section.443.2}\protected@file@percent } \newlabel{test-using-the-pruned-database}{{443.2}{1581}{Test Using the Pruned Database}{section.443.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {443.3}Upgrade Your Marketplace Apps}{1582}{section.443.3}\protected@file@percent } \newlabel{upgrade-your-marketplace-apps}{{443.3}{1582}{Upgrade Your Marketplace Apps}{section.443.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {443.4}Publish all Staged Changes to Production}{1582}{section.443.4}\protected@file@percent } \newlabel{publish-all-staged-changes-to-production}{{443.4}{1582}{Publish all Staged Changes to Production}{section.443.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {443.5}Synchronize a Complete Backup}{1582}{section.443.5}\protected@file@percent } \newlabel{synchronize-a-complete-backup}{{443.5}{1582}{Synchronize a Complete Backup}{section.443.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {444}Preparing a New Liferay DXP Server for Data Upgrade}{1583}{chapter.444}\protected@file@percent } \newlabel{preparing-a-new-liferay-dxp-server-for-data-upgrade}{{444}{1583}{Preparing a New Liferay DXP Server for Data Upgrade}{chapter.444}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.1}Request an Upgrade Patch from Liferay Support (Liferay DXP only)}{1583}{section.444.1}\protected@file@percent } \newlabel{request-an-upgrade-patch-from-liferay-support-liferay-dxp-only}{{444.1}{1583}{Request an Upgrade Patch from Liferay Support (Liferay DXP only)}{section.444.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.2}Install Liferay}{1583}{section.444.2}\protected@file@percent } \newlabel{install-liferay}{{444.2}{1583}{Install Liferay}{section.444.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.3}Install the Latest Upgrade Patch or Fix Pack (Liferay DXP only)}{1584}{section.444.3}\protected@file@percent } \newlabel{install-the-latest-upgrade-patch-or-fix-pack-liferay-dxp-only}{{444.3}{1584}{Install the Latest Upgrade Patch or Fix Pack (Liferay DXP only)}{section.444.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.4}Migrate Your OSGi Configurations (7.0+)}{1584}{section.444.4}\protected@file@percent } \newlabel{migrate-your-osgi-configurations-7.0}{{444.4}{1584}{Migrate Your OSGi Configurations (7.0+)}{section.444.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.5}Migrate Your Portal Properties}{1584}{section.444.5}\protected@file@percent } \newlabel{migrate-your-portal-properties}{{444.5}{1584}{Migrate Your Portal Properties}{section.444.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.6}Update Your Portal Properties}{1584}{section.444.6}\protected@file@percent } \newlabel{update-your-portal-properties}{{444.6}{1584}{Update Your Portal Properties}{section.444.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.7}Convert Applicable Properties to OSGi Configurations}{1585}{section.444.7}\protected@file@percent } \newlabel{convert-applicable-properties-to-osgi-configurations}{{444.7}{1585}{Convert Applicable Properties to OSGi Configurations}{section.444.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.8}Update Your Database Driver}{1586}{section.444.8}\protected@file@percent } \newlabel{update-your-database-driver}{{444.8}{1586}{Update Your Database Driver}{section.444.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.9}Configure Your Documents and Media File Store}{1586}{section.444.9}\protected@file@percent } \newlabel{configure-your-documents-and-media-file-store}{{444.9}{1586}{Configure Your Documents and Media File Store}{section.444.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.10}Configure Kerberos in place of NTLM}{1587}{section.444.10}\protected@file@percent } \newlabel{configure-kerberos-in-place-of-ntlm}{{444.10}{1587}{Configure Kerberos in place of NTLM}{section.444.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {444.11}Disable Indexing}{1587}{section.444.11}\protected@file@percent } \newlabel{disable-indexing}{{444.11}{1587}{Disable Indexing}{section.444.11}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {445}Upgrading the Liferay DXP Data}{1589}{chapter.445}\protected@file@percent } \newlabel{upgrading-the-liferay-dxp-data}{{445}{1589}{Upgrading the Liferay DXP Data}{chapter.445}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {446}Tuning for the Data Upgrade}{1591}{chapter.446}\protected@file@percent } \newlabel{tuning-for-the-data-upgrade}{{446}{1591}{Tuning for the Data Upgrade}{chapter.446}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.1}Tuning the Database Upgrade Java Process}{1592}{section.446.1}\protected@file@percent } \newlabel{tuning-the-database-upgrade-java-process}{{446.1}{1592}{Tuning the Database Upgrade Java Process}{section.446.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.2}Tuning the Database Transaction Engine for Executing Updates}{1593}{section.446.2}\protected@file@percent } \newlabel{tuning-the-database-transaction-engine-for-executing-updates}{{446.2}{1593}{Tuning the Database Transaction Engine for Executing Updates}{section.446.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.3}IBM DB2}{1593}{section.446.3}\protected@file@percent } \newlabel{ibm-db2}{{446.3}{1593}{IBM DB2}{section.446.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.4}MariaDB}{1593}{section.446.4}\protected@file@percent } \newlabel{mariadb}{{446.4}{1593}{MariaDB}{section.446.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.5}Microsoft SQL Server}{1593}{section.446.5}\protected@file@percent } \newlabel{microsoft-sql-server}{{446.5}{1593}{Microsoft SQL Server}{section.446.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.6}MySQL}{1593}{section.446.6}\protected@file@percent } \newlabel{mysql}{{446.6}{1593}{MySQL}{section.446.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.7}Oracle Database}{1594}{section.446.7}\protected@file@percent } \newlabel{oracle-database}{{446.7}{1594}{Oracle Database}{section.446.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.8}PostgreSQL}{1594}{section.446.8}\protected@file@percent } \newlabel{postgresql}{{446.8}{1594}{PostgreSQL}{section.446.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.9}Tuning the Database Transaction Log}{1594}{section.446.9}\protected@file@percent } \newlabel{tuning-the-database-transaction-log}{{446.9}{1594}{Tuning the Database Transaction Log}{section.446.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.10}IBM DB2}{1594}{section.446.10}\protected@file@percent } \newlabel{ibm-db2-1}{{446.10}{1594}{IBM DB2}{section.446.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.11}MariaDB}{1594}{section.446.11}\protected@file@percent } \newlabel{mariadb-1}{{446.11}{1594}{MariaDB}{section.446.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.12}Microsoft SQL Server}{1594}{section.446.12}\protected@file@percent } \newlabel{microsoft-sql-server-1}{{446.12}{1594}{Microsoft SQL Server}{section.446.12}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.13}MySQL}{1595}{section.446.13}\protected@file@percent } \newlabel{mysql-1}{{446.13}{1595}{MySQL}{section.446.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.14}Oracle Database}{1595}{section.446.14}\protected@file@percent } \newlabel{oracle-database-1}{{446.14}{1595}{Oracle Database}{section.446.14}{}} \@writefile{toc}{\contentsline {section}{\numberline {446.15}PostgreSQL}{1595}{section.446.15}\protected@file@percent } \newlabel{postgresql-1}{{446.15}{1595}{PostgreSQL}{section.446.15}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {447}Configuring the Data Upgrade}{1597}{chapter.447}\protected@file@percent } \newlabel{configuring-the-data-upgrade}{{447}{1597}{Configuring the Data Upgrade}{chapter.447}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.1}Configuring the Core Upgrade}{1597}{section.447.1}\protected@file@percent } \newlabel{configuring-the-core-upgrade}{{447.1}{1597}{Configuring the Core Upgrade}{section.447.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.2}Configuring app-server.properties}{1598}{section.447.2}\protected@file@percent } \newlabel{configuring-app-server.properties}{{447.2}{1598}{Configuring app-server.properties}{section.447.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.3}Configuring portal-upgrade-database.properties}{1598}{section.447.3}\protected@file@percent } \newlabel{configuring-portal-upgrade-database.properties}{{447.3}{1598}{Configuring portal-upgrade-database.properties}{section.447.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.4}Configuring portal-upgrade-ext.properties}{1599}{section.447.4}\protected@file@percent } \newlabel{configuring-portal-upgrade-ext.properties}{{447.4}{1599}{Configuring portal-upgrade-ext.properties}{section.447.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.5}Example Upgrade Configuration}{1599}{section.447.5}\protected@file@percent } \newlabel{example-upgrade-configuration}{{447.5}{1599}{Example Upgrade Configuration}{section.447.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {447.6}Configuring Non-Core Module Data Upgrades}{1600}{section.447.6}\protected@file@percent } \newlabel{configuring-non-core-module-data-upgrades}{{447.6}{1600}{Configuring Non-Core Module Data Upgrades}{section.447.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {448}Upgrading the Core Using the Upgrade Tool}{1601}{chapter.448}\protected@file@percent } \newlabel{upgrading-the-core-using-the-upgrade-tool}{{448}{1601}{Upgrading the Core Using the Upgrade Tool}{chapter.448}{}} \@writefile{toc}{\contentsline {section}{\numberline {448.1}Upgrade Tool Usage}{1601}{section.448.1}\protected@file@percent } \newlabel{upgrade-tool-usage}{{448.1}{1601}{Upgrade Tool Usage}{section.448.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {448.2}Running and Managing the Core Upgrade}{1602}{section.448.2}\protected@file@percent } \newlabel{running-and-managing-the-core-upgrade}{{448.2}{1602}{Running and Managing the Core Upgrade}{section.448.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {449}Upgrading Modules Using Gogo Shell}{1603}{chapter.449}\protected@file@percent } \newlabel{upgrading-modules-using-gogo-shell}{{449}{1603}{Upgrading Modules Using Gogo Shell}{chapter.449}{}} \@writefile{toc}{\contentsline {section}{\numberline {449.1}Command Usage}{1603}{section.449.1}\protected@file@percent } \newlabel{command-usage}{{449.1}{1603}{Command Usage}{section.449.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {449.2}Listing module upgrade processes}{1604}{section.449.2}\protected@file@percent } \newlabel{listing-module-upgrade-processes}{{449.2}{1604}{Listing module upgrade processes}{section.449.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {449.3}Executing module upgrades}{1605}{section.449.3}\protected@file@percent } \newlabel{executing-module-upgrades}{{449.3}{1605}{Executing module upgrades}{section.449.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {449.4}Checking upgrade status}{1605}{section.449.4}\protected@file@percent } \newlabel{checking-upgrade-status}{{449.4}{1605}{Checking upgrade status}{section.449.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {449.5}Executing verify processes}{1606}{section.449.5}\protected@file@percent } \newlabel{executing-verify-processes}{{449.5}{1606}{Executing verify processes}{section.449.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {450}Executing Post-Upgrade Tasks}{1607}{chapter.450}\protected@file@percent } \newlabel{executing-post-upgrade-tasks}{{450}{1607}{Executing Post-Upgrade Tasks}{chapter.450}{}} \@writefile{toc}{\contentsline {section}{\numberline {450.1}Tuning Your Database for Production}{1607}{section.450.1}\protected@file@percent } \newlabel{tuning-your-database-for-production}{{450.1}{1607}{Tuning Your Database for Production}{section.450.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {450.2}Re-enabling Search Indexing and Re-indexing Search Indexes}{1607}{section.450.2}\protected@file@percent } \newlabel{re-enabling-search-indexing-and-re-indexing-search-indexes}{{450.2}{1607}{Re-enabling Search Indexing and Re-indexing Search Indexes}{section.450.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {450.3}Enabling Web Content View Permissions}{1608}{section.450.3}\protected@file@percent } \newlabel{enabling-web-content-view-permissions}{{450.3}{1608}{Enabling Web Content View Permissions}{section.450.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {451}Upgrading a Sharded Environment}{1609}{chapter.451}\protected@file@percent } \newlabel{upgrading-a-sharded-environment}{{451}{1609}{Upgrading a Sharded Environment}{chapter.451}{}} \@writefile{toc}{\contentsline {section}{\numberline {451.1}Add Configurations Before the Data Upgrade}{1609}{section.451.1}\protected@file@percent } \newlabel{add-configurations-before-the-data-upgrade}{{451.1}{1609}{Add Configurations Before the Data Upgrade}{section.451.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {451.2}Upgrade and Update Properties}{1610}{section.451.2}\protected@file@percent } \newlabel{upgrade-and-update-properties}{{451.2}{1610}{Upgrade and Update Properties}{section.451.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {452}Migrating From Audience Targeting to Segmentation and Personalization}{1613}{chapter.452}\protected@file@percent } \newlabel{migrating-from-audience-targeting-to-segmentation-and-personalization}{{452}{1613}{Migrating From Audience Targeting to Segmentation and Personalization}{chapter.452}{}} \gdef \LT@v {\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 {453}Migrating User Segments}{1615}{chapter.453}\protected@file@percent } \newlabel{migrating-user-segments}{{453}{1615}{Migrating User Segments}{chapter.453}{}} \@writefile{toc}{\contentsline {section}{\numberline {453.1}Upgrade Process}{1615}{section.453.1}\protected@file@percent } \newlabel{upgrade-process}{{453.1}{1615}{Upgrade Process}{section.453.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {453.1}{\ignorespaces A Liferay DXP 7.1 Audience Targeting Segment.}}{1616}{figure.453.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {453.2}{\ignorespaces A Liferay DXP 7.2 Segment}}{1617}{figure.453.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {454}Manually Migrating from Audience Targeting}{1619}{chapter.454}\protected@file@percent } \newlabel{manually-migrating-from-audience-targeting}{{454}{1619}{Manually Migrating from Audience Targeting}{chapter.454}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.1}User Attribute Rules}{1619}{section.454.1}\protected@file@percent } \newlabel{user-attribute-rules}{{454.1}{1619}{User Attribute Rules}{section.454.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.2}Session Rules}{1619}{section.454.2}\protected@file@percent } \newlabel{session-rules}{{454.2}{1619}{Session Rules}{section.454.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.3}Behavior Rules}{1619}{section.454.3}\protected@file@percent } \newlabel{behavior-rules}{{454.3}{1619}{Behavior Rules}{section.454.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.4}Migrating Custom Rules}{1620}{section.454.4}\protected@file@percent } \newlabel{migrating-custom-rules}{{454.4}{1620}{Migrating Custom Rules}{section.454.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.5}Migrating Display Portlets}{1620}{section.454.5}\protected@file@percent } \newlabel{migrating-display-portlets}{{454.5}{1620}{Migrating Display Portlets}{section.454.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.6}User Segment Content Display}{1620}{section.454.6}\protected@file@percent } \newlabel{user-segment-content-display}{{454.6}{1620}{User Segment Content Display}{section.454.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {454.7}Asset Publisher Personalization}{1620}{section.454.7}\protected@file@percent } \newlabel{asset-publisher-personalization}{{454.7}{1620}{Asset Publisher Personalization}{section.454.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {455}Deprecated Apps in 7.2: What to Do}{1621}{chapter.455}\protected@file@percent } \newlabel{deprecated-apps-in-7.2-what-to-do}{{455}{1621}{Deprecated Apps in 7.2: What to Do}{chapter.455}{}} \@writefile{toc}{\contentsline {section}{\numberline {455.1}Foundation}{1621}{section.455.1}\protected@file@percent } \newlabel{foundation}{{455.1}{1621}{Foundation}{section.455.1}{}} \gdef \LT@vi {\LT@entry {1}{66.80023pt}\LT@entry {1}{275.39513pt}\LT@entry {1}{127.55965pt}} \@writefile{toc}{\contentsline {section}{\numberline {455.2}Personalization}{1622}{section.455.2}\protected@file@percent } \newlabel{personalization}{{455.2}{1622}{Personalization}{section.455.2}{}} \gdef \LT@vii {\LT@entry {1}{66.80023pt}\LT@entry {1}{275.39513pt}\LT@entry {1}{127.55965pt}} \gdef \LT@viii {\LT@entry {1}{66.80023pt}\LT@entry {1}{275.39513pt}\LT@entry {1}{127.55965pt}} \gdef \LT@ix {\LT@entry {3}{56.1729pt}\LT@entry {1}{66.80472pt}\LT@entry {3}{149.97061pt}} \@writefile{toc}{\contentsline {section}{\numberline {455.3}Web Experience}{1623}{section.455.3}\protected@file@percent } \newlabel{web-experience}{{455.3}{1623}{Web Experience}{section.455.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {455.4}Forms}{1623}{section.455.4}\protected@file@percent } \newlabel{forms}{{455.4}{1623}{Forms}{section.455.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {455.5}Security}{1623}{section.455.5}\protected@file@percent } \newlabel{security-1}{{455.5}{1623}{Security}{section.455.5}{}} \gdef \LT@x {\LT@entry {1}{47.80994pt}\LT@entry {1}{262.7372pt}\LT@entry {1}{159.24869pt}} \gdef \LT@xi {\LT@entry {1}{71.41252pt}\LT@entry {1}{398.34248pt}} \@writefile{toc}{\contentsline {section}{\numberline {455.6}User and System Management}{1624}{section.455.6}\protected@file@percent } \newlabel{user-and-system-management}{{455.6}{1624}{User and System Management}{section.455.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {455.7}Related Topics}{1624}{section.455.7}\protected@file@percent } \newlabel{related-topics-7}{{455.7}{1624}{Related Topics}{section.455.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {456}Apps in Maintenance Mode}{1625}{chapter.456}\protected@file@percent } \newlabel{apps-in-maintenance-mode}{{456}{1625}{Apps in Maintenance Mode}{chapter.456}{}} \@writefile{toc}{\contentsline {section}{\numberline {456.1}Related Topics}{1625}{section.456.1}\protected@file@percent } \newlabel{related-topics-8}{{456.1}{1625}{Related Topics}{section.456.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {457}Maintaining Liferay DXP}{1627}{chapter.457}\protected@file@percent } \newlabel{maintaining-liferay-dxp}{{457}{1627}{Maintaining Liferay DXP}{chapter.457}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {458}Patching Liferay DXP}{1629}{chapter.458}\protected@file@percent } \newlabel{patching-liferay-dxp}{{458}{1629}{Patching Liferay DXP}{chapter.458}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {459}Patching Basics}{1631}{chapter.459}\protected@file@percent } \newlabel{patching-basics}{{459}{1631}{Patching Basics}{chapter.459}{}} \@writefile{toc}{\contentsline {section}{\numberline {459.1}Fix Packs}{1631}{section.459.1}\protected@file@percent } \newlabel{fix-packs}{{459.1}{1631}{Fix Packs}{section.459.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {459.2}Hotfixes}{1631}{section.459.2}\protected@file@percent } \newlabel{hotfixes}{{459.2}{1631}{Hotfixes}{section.459.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {459.3}Service Packs}{1632}{section.459.3}\protected@file@percent } \newlabel{service-packs}{{459.3}{1632}{Service Packs}{section.459.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {459.4}How Patches are Tested}{1632}{section.459.4}\protected@file@percent } \newlabel{how-patches-are-tested}{{459.4}{1632}{How Patches are Tested}{section.459.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {460}Using the Patching Tool}{1633}{chapter.460}\protected@file@percent } \newlabel{using-the-patching-tool}{{460}{1633}{Using the Patching Tool}{chapter.460}{}} \@writefile{toc}{\contentsline {section}{\numberline {460.1}Installing the Patching Tool}{1633}{section.460.1}\protected@file@percent } \newlabel{installing-the-patching-tool}{{460.1}{1633}{Installing the Patching Tool}{section.460.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {460.2}Executables}{1634}{section.460.2}\protected@file@percent } \newlabel{executables}{{460.2}{1634}{Executables}{section.460.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {461}Installing Patches}{1635}{chapter.461}\protected@file@percent } \newlabel{installing-patches}{{461}{1635}{Installing Patches}{chapter.461}{}} \@writefile{toc}{\contentsline {section}{\numberline {461.1}Handling Hotfixes and Patches}{1636}{section.461.1}\protected@file@percent } \newlabel{handling-hotfixes-and-patches}{{461.1}{1636}{Handling Hotfixes and Patches}{section.461.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {461.2}Fix Pack Dependencies}{1637}{section.461.2}\protected@file@percent } \newlabel{fix-pack-dependencies}{{461.2}{1637}{Fix Pack Dependencies}{section.461.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {461.3}Updating the Patching Tool}{1637}{section.461.3}\protected@file@percent } \newlabel{updating-the-patching-tool}{{461.3}{1637}{Updating the Patching Tool}{section.461.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {461.4}Cleaning Up}{1637}{section.461.4}\protected@file@percent } \newlabel{cleaning-up}{{461.4}{1637}{Cleaning Up}{section.461.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {462}Working with Patches}{1639}{chapter.462}\protected@file@percent } \newlabel{working-with-patches}{{462}{1639}{Working with Patches}{chapter.462}{}} \@writefile{toc}{\contentsline {section}{\numberline {462.1}Including support-info in Support Tickets}{1639}{section.462.1}\protected@file@percent } \newlabel{including-support-info-in-support-tickets}{{462.1}{1639}{Including support-info in Support Tickets}{section.462.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {462.2}Uninstalling Patches}{1639}{section.462.2}\protected@file@percent } \newlabel{uninstalling-patches}{{462.2}{1639}{Uninstalling Patches}{section.462.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {462.3}Showing collisions between patches and deployed plugins}{1640}{section.462.3}\protected@file@percent } \newlabel{showing-collisions-between-patches-and-deployed-plugins}{{462.3}{1640}{Showing collisions between patches and deployed plugins}{section.462.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {462.4}Separating Patches from the Installation}{1640}{section.462.4}\protected@file@percent } \newlabel{separating-patches-from-the-installation}{{462.4}{1640}{Separating Patches from the Installation}{section.462.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {462.5}Restoring the Separated Patch Files}{1641}{section.462.5}\protected@file@percent } \newlabel{restoring-the-separated-patch-files}{{462.5}{1641}{Restoring the Separated Patch Files}{section.462.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {463}Configuring the Patching Tool}{1643}{chapter.463}\protected@file@percent } \newlabel{configuring-the-patching-tool}{{463}{1643}{Configuring the Patching Tool}{chapter.463}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {464}Patching Tool Basic configuration}{1645}{chapter.464}\protected@file@percent } \newlabel{patching-tool-basic-configuration}{{464}{1645}{Patching Tool Basic configuration}{chapter.464}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {465}Patching Tool Advanced Configuration}{1647}{chapter.465}\protected@file@percent } \newlabel{patching-tool-advanced-configuration}{{465}{1647}{Patching Tool Advanced Configuration}{chapter.465}{}} \@writefile{toc}{\contentsline {section}{\numberline {465.1}Using Profiles with the Patching Tool}{1647}{section.465.1}\protected@file@percent } \newlabel{using-profiles-with-the-patching-tool}{{465.1}{1647}{Using Profiles with the Patching Tool}{section.465.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {466}Installing patches on the 7.0 WAR}{1649}{chapter.466}\protected@file@percent } \newlabel{installing-patches-on-the-7.0-war}{{466}{1649}{Installing patches on the 7.0 WAR}{chapter.466}{}} \@writefile{toc}{\contentsline {section}{\numberline {466.1}Prerequisites}{1649}{section.466.1}\protected@file@percent } \newlabel{prerequisites-1}{{466.1}{1649}{Prerequisites}{section.466.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {466.2}Install the patch on the Liferay DXP WAR and artifacts}{1649}{section.466.2}\protected@file@percent } \newlabel{install-the-patch-on-the-liferay-dxp-war-and-artifacts}{{466.2}{1649}{Install the patch on the Liferay DXP WAR and artifacts}{section.466.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {466.3}Related Topics}{1650}{section.466.3}\protected@file@percent } \newlabel{related-topics-9}{{466.3}{1650}{Related Topics}{section.466.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {467}Keeping up with Fix packs and Service Packs}{1651}{chapter.467}\protected@file@percent } \newlabel{keeping-up-with-fix-packs-and-service-packs}{{467}{1651}{Keeping up with Fix packs and Service Packs}{chapter.467}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {468}Backing up a Liferay DXP Installation}{1653}{chapter.468}\protected@file@percent } \newlabel{backing-up-a-liferay-dxp-installation}{{468}{1653}{Backing up a Liferay DXP Installation}{chapter.468}{}} \@writefile{toc}{\contentsline {section}{\numberline {468.1}Backing up Source Code}{1653}{section.468.1}\protected@file@percent } \newlabel{backing-up-source-code}{{468.1}{1653}{Backing up Source Code}{section.468.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {468.2}Backing up Liferay DXP's File System}{1654}{section.468.2}\protected@file@percent } \newlabel{backing-up-liferay-dxps-file-system}{{468.2}{1654}{Backing up Liferay DXP's File System}{section.468.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {468.3}Backing up Liferay DXP's Database}{1654}{section.468.3}\protected@file@percent } \newlabel{backing-up-liferay-dxps-database}{{468.3}{1654}{Backing up Liferay DXP's Database}{section.468.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {469}Monitoring Liferay DXP}{1655}{chapter.469}\protected@file@percent } \newlabel{monitoring-liferay-dxp}{{469}{1655}{Monitoring Liferay DXP}{chapter.469}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {470}Monitoring Garbage Collection and the JVM}{1657}{chapter.470}\protected@file@percent } \newlabel{monitoring-garbage-collection-and-the-jvm}{{470}{1657}{Monitoring Garbage Collection and the JVM}{chapter.470}{}} \@writefile{toc}{\contentsline {section}{\numberline {470.1}VisualVM}{1657}{section.470.1}\protected@file@percent } \newlabel{visualvm}{{470.1}{1657}{VisualVM}{section.470.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {470.2}JMX Console}{1657}{section.470.2}\protected@file@percent } \newlabel{jmx-console}{{470.2}{1657}{JMX Console}{section.470.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {470.1}{\ignorespaces VisualVM's Visual GC plugin shows the garbage collector in real-time.}}{1658}{figure.470.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {470.3}Garbage Collector Verbose Logging}{1658}{section.470.3}\protected@file@percent } \newlabel{garbage-collector-verbose-logging}{{470.3}{1658}{Garbage Collector Verbose Logging}{section.470.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {470.2}{\ignorespaces VisualVM monitors the JVM using Java Management Extensions.}}{1659}{figure.470.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {471}Managing Liferay DXP with Liferay Connected Services}{1661}{chapter.471}\protected@file@percent } \newlabel{managing-liferay-dxp-with-liferay-connected-services}{{471}{1661}{Managing Liferay DXP with Liferay Connected Services}{chapter.471}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {472}Getting Started with LCS}{1663}{chapter.472}\protected@file@percent } \newlabel{getting-started-with-lcs}{{472}{1663}{Getting Started with LCS}{chapter.472}{}} \@writefile{lof}{\contentsline {figure}{\numberline {472.1}{\ignorespaces Click \emph {Get Started} to begin the wizard.}}{1664}{figure.472.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.2}{\ignorespaces Select the LCS project you want to create the environment in, and click \emph {Next}.}}{1665}{figure.472.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.3}{\ignorespaces Name and describe the environment, then click \emph {Next}.}}{1666}{figure.472.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.4}{\ignorespaces Select the environment's subscription type, then click \emph {Next}.}}{1667}{figure.472.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.5}{\ignorespaces Select whether this is a clustered environment, then click \emph {Next}.}}{1668}{figure.472.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.6}{\ignorespaces Select whether this is an elastic environment, then click \emph {Next}.}}{1669}{figure.472.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.7}{\ignorespaces Enable or disable the LCS services you want to use for servers that connect to the environment, then click \emph {Next}.}}{1669}{figure.472.7}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {472.8}{\ignorespaces This form contains each of your selections from the previous steps. Make any changes you want, then click \emph {Create Environment}.}}{1670}{figure.472.8}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {473}LCS Preconfiguration}{1671}{chapter.473}\protected@file@percent } \newlabel{lcs-preconfiguration}{{473}{1671}{LCS Preconfiguration}{chapter.473}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.1}Downloading the LCS Client App}{1671}{section.473.1}\protected@file@percent } \newlabel{downloading-the-lcs-client-app}{{473.1}{1671}{Downloading the LCS Client App}{section.473.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {473.1}{\ignorespaces Click the app's \emph {Free} button to begin the purchase process.}}{1672}{figure.473.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {473.2}{\ignorespaces Liferay Marketplace displays your receipt for the LCS client app.}}{1673}{figure.473.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {473.2}Preconfiguring LCS to Connect Through a Proxy}{1673}{section.473.2}\protected@file@percent } \newlabel{preconfiguring-lcs-to-connect-through-a-proxy}{{473.2}{1673}{Preconfiguring LCS to Connect Through a Proxy}{section.473.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.3}JVM App Server Arguments}{1674}{section.473.3}\protected@file@percent } \newlabel{jvm-app-server-arguments}{{473.3}{1674}{JVM App Server Arguments}{section.473.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.4}LCS Client App Properties}{1674}{section.473.4}\protected@file@percent } \newlabel{lcs-client-app-properties}{{473.4}{1674}{LCS Client App Properties}{section.473.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.5}Ensuring Access to LCS}{1675}{section.473.5}\protected@file@percent } \newlabel{ensuring-access-to-lcs}{{473.5}{1675}{Ensuring Access to LCS}{section.473.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.6}NTP Server Synchronization}{1675}{section.473.6}\protected@file@percent } \newlabel{ntp-server-synchronization}{{473.6}{1675}{NTP Server Synchronization}{section.473.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.7}Configuring WebSphere}{1675}{section.473.7}\protected@file@percent } \newlabel{configuring-websphere}{{473.7}{1675}{Configuring WebSphere}{section.473.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.8}Installing the LCS Client App}{1676}{section.473.8}\protected@file@percent } \newlabel{installing-the-lcs-client-app}{{473.8}{1676}{Installing the LCS Client App}{section.473.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {473.9}Upgrading the LCS Client App}{1677}{section.473.9}\protected@file@percent } \newlabel{upgrading-the-lcs-client-app}{{473.9}{1677}{Upgrading the LCS Client App}{section.473.9}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {474}Registering Your Liferay DXP Server with LCS}{1679}{chapter.474}\protected@file@percent } \newlabel{registering-your-liferay-dxp-server-with-lcs}{{474}{1679}{Registering Your Liferay DXP Server with LCS}{chapter.474}{}} \@writefile{lof}{\contentsline {figure}{\numberline {474.1}{\ignorespaces Select your LCS project from the menu highlighted by the red box in this screenshot.}}{1679}{figure.474.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {474.2}{\ignorespaces You must register your server in an LCS environment. The red box in this screenshot highlights environments.}}{1680}{figure.474.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {474.3}{\ignorespaces An environment's Registration tab lets you manage the token file used to register your server in the environment.}}{1681}{figure.474.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {474.1}Determining Your Server's LCS Connection Status}{1682}{section.474.1}\protected@file@percent } \newlabel{determining-your-servers-lcs-connection-status}{{474.1}{1682}{Determining Your Server's LCS Connection Status}{section.474.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {474.4}{\ignorespaces The server is connected to LCS.}}{1683}{figure.474.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {475}Using LCS}{1685}{chapter.475}\protected@file@percent } \newlabel{using-lcs}{{475}{1685}{Using LCS}{chapter.475}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {476}What LCS Stores About Your Liferay DXP Servers}{1687}{chapter.476}\protected@file@percent } \newlabel{what-lcs-stores-about-your-liferay-dxp-servers}{{476}{1687}{What LCS Stores About Your Liferay DXP Servers}{chapter.476}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {477}Managing LCS Users in Your Project}{1689}{chapter.477}\protected@file@percent } \newlabel{managing-lcs-users-in-your-project}{{477}{1689}{Managing LCS Users in Your Project}{chapter.477}{}} \@writefile{lof}{\contentsline {figure}{\numberline {477.1}{\ignorespaces The Users tab lets you manage the LCS users in your project.}}{1690}{figure.477.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {477.1}Managing LCS Roles}{1690}{section.477.1}\protected@file@percent } \newlabel{managing-lcs-roles}{{477.1}{1690}{Managing LCS Roles}{section.477.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {477.2}{\ignorespaces You can assign or revoke a user's LCS Roles.}}{1691}{figure.477.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {478}Using the Dashboard}{1693}{chapter.478}\protected@file@percent } \newlabel{using-the-dashboard}{{478}{1693}{Using the Dashboard}{chapter.478}{}} \@writefile{toc}{\contentsline {section}{\numberline {478.1}Using the Project View}{1693}{section.478.1}\protected@file@percent } \newlabel{using-the-project-view}{{478.1}{1693}{Using the Project View}{section.478.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {478.1}{\ignorespaces The LCS project view shows an overview of your LCS project.}}{1694}{figure.478.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {479}Managing LCS Environments}{1695}{chapter.479}\protected@file@percent } \newlabel{managing-lcs-environments}{{479}{1695}{Managing LCS Environments}{chapter.479}{}} \@writefile{toc}{\contentsline {section}{\numberline {479.1}Creating Environments}{1695}{section.479.1}\protected@file@percent } \newlabel{creating-environments}{{479.1}{1695}{Creating Environments}{section.479.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {479.1}{\ignorespaces The New Environment form lets you create environments.}}{1696}{figure.479.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {479.2}Working with Environments}{1697}{section.479.2}\protected@file@percent } \newlabel{working-with-environments}{{479.2}{1697}{Working with Environments}{section.479.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {479.2}{\ignorespaces The LCS environment view shows an overview of an LCS environment.}}{1697}{figure.479.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {480}Managing LCS Servers}{1699}{chapter.480}\protected@file@percent } \newlabel{managing-lcs-servers}{{480}{1699}{Managing LCS Servers}{chapter.480}{}} \@writefile{toc}{\contentsline {section}{\numberline {480.1}Details}{1700}{section.480.1}\protected@file@percent } \newlabel{details}{{480.1}{1700}{Details}{section.480.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {480.1}{\ignorespaces The Details tab shows information about your server.}}{1700}{figure.480.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {480.2}Server Settings}{1700}{section.480.2}\protected@file@percent } \newlabel{server-settings}{{480.2}{1700}{Server Settings}{section.480.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {480.2}{\ignorespaces You can use the Server Settings tab to give your server a fun name.}}{1701}{figure.480.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {480.3}Page Analytics}{1701}{section.480.3}\protected@file@percent } \newlabel{page-analytics}{{480.3}{1701}{Page Analytics}{section.480.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {480.4}Snapshot Metrics}{1701}{section.480.4}\protected@file@percent } \newlabel{snapshot-metrics}{{480.4}{1701}{Snapshot Metrics}{section.480.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {480.3}{\ignorespaces The Page Analytics interface in the LCS Server view.}}{1702}{figure.480.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {480.4}{\ignorespaces The LCS application metrics show portlet performance statistics, like frequency of use and average load time.}}{1703}{figure.480.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {480.5}Fix Packs}{1703}{section.480.5}\protected@file@percent } \newlabel{fix-packs-1}{{480.5}{1703}{Fix Packs}{section.480.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {480.6}Portal Properties}{1703}{section.480.6}\protected@file@percent } \newlabel{portal-properties}{{480.6}{1703}{Portal Properties}{section.480.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {480.5}{\ignorespaces The LCS JVM metrics show performance data for memory and the garbage collector.}}{1704}{figure.480.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {480.6}{\ignorespaces The LCS server metrics show current threads and JDBC connection pools.}}{1705}{figure.480.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {480.7}{\ignorespaces The Fix Packs tab displays your server's available and installed fix packs.}}{1705}{figure.480.7}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {480.8}{\ignorespaces Click the gear icon to select the type of portal properties to show in the table.}}{1706}{figure.480.8}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {481}Managing Your LCS Account}{1707}{chapter.481}\protected@file@percent } \newlabel{managing-your-lcs-account}{{481}{1707}{Managing Your LCS Account}{chapter.481}{}} \@writefile{lof}{\contentsline {figure}{\numberline {481.1}{\ignorespaces You can add rules to determine the events that trigger notifications.}}{1708}{figure.481.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {481.2}{\ignorespaces You can change your LCS account's general preferences.}}{1709}{figure.481.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {481.1}Using Web Notifications}{1709}{section.481.1}\protected@file@percent } \newlabel{using-web-notifications}{{481.1}{1709}{Using Web Notifications}{section.481.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {481.3}{\ignorespaces Web notifications let you know what's happening in your LCS projects.}}{1710}{figure.481.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {482}Managing Liferay DXP Subscriptions}{1711}{chapter.482}\protected@file@percent } \newlabel{managing-liferay-dxp-subscriptions}{{482}{1711}{Managing Liferay DXP Subscriptions}{chapter.482}{}} \@writefile{lof}{\contentsline {figure}{\numberline {482.1}{\ignorespaces LCS lets you view and manage your subscriptions.}}{1712}{figure.482.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {482.1}Decommissioning Servers}{1713}{section.482.1}\protected@file@percent } \newlabel{decommissioning-servers}{{482.1}{1713}{Decommissioning Servers}{section.482.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {482.2}Elastic Subscriptions}{1713}{section.482.2}\protected@file@percent } \newlabel{elastic-subscriptions}{{482.2}{1713}{Elastic Subscriptions}{section.482.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {482.2}{\ignorespaces The \emph {Elastic Subscriptions} tab shows details about your project's elastic servers.}}{1714}{figure.482.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {483}Understanding Environment Tokens}{1715}{chapter.483}\protected@file@percent } \newlabel{understanding-environment-tokens}{{483}{1715}{Understanding Environment Tokens}{chapter.483}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {484}Troubleshooting Your LCS Connection}{1717}{chapter.484}\protected@file@percent } \newlabel{troubleshooting-your-lcs-connection}{{484}{1717}{Troubleshooting Your LCS Connection}{chapter.484}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.1}LCS Grace Periods}{1718}{section.484.1}\protected@file@percent } \newlabel{lcs-grace-periods}{{484.1}{1718}{LCS Grace Periods}{section.484.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.2}Connection Grace Period}{1718}{section.484.2}\protected@file@percent } \newlabel{connection-grace-period}{{484.2}{1718}{Connection Grace Period}{section.484.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {484.1}{\ignorespaces A warning message is displayed to administrators if the server can't connect to LCS to validate the subscription.}}{1718}{figure.484.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {484.3}Subscription Grace Period}{1719}{section.484.3}\protected@file@percent } \newlabel{subscription-grace-period}{{484.3}{1719}{Subscription Grace Period}{section.484.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {484.2}{\ignorespaces LCS sends you a notification prior to the expiration of your subscription.}}{1719}{figure.484.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {484.4}Troubleshooting}{1720}{section.484.4}\protected@file@percent } \newlabel{troubleshooting}{{484.4}{1720}{Troubleshooting}{section.484.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.5}Server Can't Reach LCS}{1720}{section.484.5}\protected@file@percent } \newlabel{server-cant-reach-lcs}{{484.5}{1720}{Server Can't Reach LCS}{section.484.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.6}Subscription Issues}{1720}{section.484.6}\protected@file@percent } \newlabel{subscription-issues}{{484.6}{1720}{Subscription Issues}{section.484.6}{}} \gdef \LT@xii {\LT@entry {1}{207.41331pt}\LT@entry {1}{262.34169pt}} \@writefile{toc}{\contentsline {section}{\numberline {484.7}Invalid Token}{1721}{section.484.7}\protected@file@percent } \newlabel{invalid-token}{{484.7}{1721}{Invalid Token}{section.484.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.8}Increasing Log Levels}{1721}{section.484.8}\protected@file@percent } \newlabel{increasing-log-levels}{{484.8}{1721}{Increasing Log Levels}{section.484.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.9}Control Panel}{1722}{section.484.9}\protected@file@percent } \newlabel{control-panel}{{484.9}{1722}{Control Panel}{section.484.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {484.10}Log4j}{1722}{section.484.10}\protected@file@percent } \newlabel{log4j}{{484.10}{1722}{Log4j}{section.484.10}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {485}Deployment Reference}{1725}{chapter.485}\protected@file@percent } \newlabel{deployment-reference}{{485}{1725}{Deployment Reference}{chapter.485}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {486}Liferay Home}{1727}{chapter.486}\protected@file@percent } \newlabel{liferay-home}{{486}{1727}{Liferay Home}{chapter.486}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {487}Portal Properties}{1729}{chapter.487}\protected@file@percent } \newlabel{portal-properties-1}{{487}{1729}{Portal Properties}{chapter.487}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {488}System Properties}{1731}{chapter.488}\protected@file@percent } \newlabel{system-properties}{{488}{1731}{System Properties}{chapter.488}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {489}Database Templates}{1733}{chapter.489}\protected@file@percent } \newlabel{database-templates}{{489}{1733}{Database Templates}{chapter.489}{}} \@writefile{toc}{\contentsline {section}{\numberline {489.1}MariaDB}{1733}{section.489.1}\protected@file@percent } \newlabel{mariadb-2}{{489.1}{1733}{MariaDB}{section.489.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {489.2}MySQL}{1733}{section.489.2}\protected@file@percent } \newlabel{mysql-2}{{489.2}{1733}{MySQL}{section.489.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {489.3}PostgreSQL}{1734}{section.489.3}\protected@file@percent } \newlabel{postgresql-2}{{489.3}{1734}{PostgreSQL}{section.489.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {490}Comparing Patch Levels}{1735}{chapter.490}\protected@file@percent } \newlabel{comparing-patch-levels}{{490}{1735}{Comparing Patch Levels}{chapter.490}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {491}Patching Tool Configuration Properties}{1737}{chapter.491}\protected@file@percent } \newlabel{patching-tool-configuration-properties}{{491}{1737}{Patching Tool Configuration Properties}{chapter.491}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {492}Troubleshooting Deployments}{1739}{chapter.492}\protected@file@percent } \newlabel{troubleshooting-deployments}{{492}{1739}{Troubleshooting Deployments}{chapter.492}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {493}Liferay DXP Failed to Initialize Because the Database Wasn't Ready}{1741}{chapter.493}\protected@file@percent } \newlabel{liferay-dxp-failed-to-initialize-because-the-database-wasnt-ready}{{493}{1741}{Liferay DXP Failed to Initialize Because the Database Wasn't Ready}{chapter.493}{}} \@writefile{toc}{\contentsline {section}{\numberline {493.1}Related Topics}{1741}{section.493.1}\protected@file@percent } \newlabel{related-topics-10}{{493.1}{1741}{Related Topics}{section.493.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {494}Sort Order Changed with a Different Database}{1743}{chapter.494}\protected@file@percent } \newlabel{sort-order-changed-with-a-different-database}{{494}{1743}{Sort Order Changed with a Different Database}{chapter.494}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {495}Using Files to Configure Module Components}{1745}{chapter.495}\protected@file@percent } \newlabel{using-files-to-configure-module-components}{{495}{1745}{Using Files to Configure Module Components}{chapter.495}{}} \@writefile{toc}{\contentsline {section}{\numberline {495.1}Configuration File Formats}{1745}{section.495.1}\protected@file@percent } \newlabel{configuration-file-formats}{{495.1}{1745}{Configuration File Formats}{section.495.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {495.2}Naming Configuration Files}{1746}{section.495.2}\protected@file@percent } \newlabel{naming-configuration-files}{{495.2}{1746}{Naming Configuration Files}{section.495.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {495.1}{\ignorespaces You can create multiple instances of components whose System Settings page has a \emph {Configuration Entries} section.}}{1746}{figure.495.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {495.3}Resolving Configuration File Deployment Failures}{1747}{section.495.3}\protected@file@percent } \newlabel{resolving-configuration-file-deployment-failures}{{495.3}{1747}{Resolving Configuration File Deployment Failures}{section.495.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {495.4}Related Articles}{1747}{section.495.4}\protected@file@percent } \newlabel{related-articles}{{495.4}{1747}{Related Articles}{section.495.4}{}} \@setckpt{user/deployment}{ \setcounter{page}{1748} \setcounter{equation}{0} \setcounter{enumi}{4} \setcounter{enumii}{3} \setcounter{enumiii}{3} \setcounter{enumiv}{0} \setcounter{footnote}{0} \setcounter{mpfootnote}{0} \setcounter{@memmarkcntra}{0} \setcounter{storedpagenumber}{1} \setcounter{book}{0} \setcounter{part}{2} \setcounter{chapter}{495} \setcounter{section}{4} \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}{1796} \setcounter{lastsheet}{1795} \setcounter{lastpage}{1747} \setcounter{figure}{1} \setcounter{lofdepth}{1} \setcounter{table}{0} \setcounter{lotdepth}{1} \setcounter{Item}{2273} \setcounter{Hfootnote}{5} \setcounter{bookmark@seq@number}{0} \setcounter{memhycontfloat}{0} \setcounter{mem@Hpagenote}{0} \setcounter{r@tfl@t}{0} \setcounter{float@type}{4} \setcounter{LT@tables}{12} \setcounter{LT@chunks}{3} \setcounter{@anim@ltxcnt}{0} \setcounter{parentequation}{0} \setcounter{FancyVerbLine}{0} } ================================================ FILE: book/user/deployment.tex ================================================ \chapter{Deploying Liferay DXP}\label{deploying-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP is one of the most flexible applications on the market today with respect to database and application server support. It supports a wide variety of databases and application servers, freeing you to use the ones you want. Liferay DXP also scales very well. You can install it on a shared hosting account, on a multi-node cluster running a commercial application server, or on anything in between. In fact, Liferay DXP is used successfully in all of these scenarios every day. You'll find that because of Liferay DXP's flexibility in its deployment options, it is also easy to install. If you already have an application server, you can use your application server's deployment tools to install Liferay DXP. If you don't already have an application server, Liferay provides several application server bundles from which to choose. These are pre-configured application servers with Liferay DXP already installed on them. With a small amount of configuration, these can be made into production-ready systems. There are some preparations to make before installing. You must create a database and install a supported Java Development Kit (JDK). It can also be worthwhile to pre-configure or gather information for configuring a data source, mail session, and more. You'll get guidance for these preparations. Lastly, you'll install and deploy Liferay DXP for the first time and then set up Marketplace. You can continue configuring and tuning as you desire. Read on to obtain the Liferay DXP installer that's right for you. \chapter{Obtaining Liferay DXP}\label{obtaining-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Before you begin, you should answer a few questions. \begin{itemize} \tightlist \item Which version of Liferay DXP will you install? \item Is your installation for development purposes or are you preparing to run in production? \item Are you installing Liferay DXP in a clustered environment? \item Which application server do you want Liferay DXP to run on? \item Are you installing on an existing application server? \end{itemize} Here you'll determine the installation that's best for you and download it. Anyone can download Liferay DXP from \href{https://www.liferay.com}{liferay.com}. Clicking \emph{Resources → Community Downloads} takes you to the \href{https://www.liferay.com/downloads-community}{Community Downloads page}, where you can access Liferay Portal CE or a trial of the enterprise supported Liferay DXP. The installers are available in several different formats. The formats include a Liferay Tomcat bundle (Liferay DXP bundled with Tomcat) as well as a Liferay DXP \texttt{.war} (WAR) file for installing @product@ on an existing application server of choice. Liferay enterprise subscribers can download Liferay DXP from the \href{https://help.liferay.com/hc}{Help Center}. Liferay DXP runs on a wide variety of application servers (Check the \href{https://help.liferay.com/hc/categories/360000894391-Product-Support}{Support page} for a complete listing). Here are the ways to install Liferay DXP: \begin{itemize} \item \hyperref[liferay-tomcat-bundle]{Install a Liferay Tomcat bundle} (Tomcat application server with Liferay DXP pre-installed). \item \hyperref[downloading-the-liferay-war-and-dependency-jars]{Install the Liferay WAR} (and supporting libraries) onto an existing application server. \end{itemize} Since the Liferay Tomcat bundle is the easiest way, it's described first. \section{Liferay Tomcat Bundle}\label{liferay-tomcat-bundle} The Liferay Tomcat bundle includes the Tomcat application server with Liferay DXP pre-installed. If you prefer using another application server with Liferay DXP, you must install it manually. If you don't currently have an application server preference, consider starting with the Tomcat bundle. Tomcat is one of the most lightweight and straightforward bundles to configure. \noindent\hrulefill \textbf{Note:} Application server bundles for proprietary application servers such as WebLogic or WebSphere aren't available because the licenses for these servers don't allow for redistribution. Liferay DXP's commercial offering, however, runs just as well on these application servers as it does on the others. \noindent\hrulefill Bundles are released as 7-Zip (\texttt{.7z}) and gzipped (\texttt{.tar.gz}) compressed file archive formats. \href{/docs/7-2/deploy/-/knowledge_base/d/installing-product}{Installing Liferay DXP} demonstrates installing Liferay DXP from a bundle. Follow its instructions once you've prepared for installing Liferay DXP (see the next article). Liferay DXP bundles might not be appropriate for you. Here are some reasons for installing the Liferay DXP WAR manually instead of using a bundle. \begin{itemize} \item There is no Liferay DXP bundle for the application server you want to use. \item You're installing Liferay DXP in a clustered environment. \item You're installing to an existing application server. \end{itemize} Manual installation is described next. \section{Downloading the Liferay WAR and Dependency JARs}\label{downloading-the-liferay-war-and-dependency-jars} Manual installation requires installing the Liferay DXP WAR and dependency JARs onto the application server. These files are available to download for \href{https://customer.liferay.com/downloads}{DXP} or \href{https://www.liferay.com/downloads-community}{Portal CE}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} After preparing for install (next), follow the Liferay DXP installation steps for your application server. There are specific configuration and script changes required on each application. The installation article titles follow this format, with \emph{{[}Application Server{]}} replaced by the application server name. \emph{Installing Liferay DXP on {[}Application Server{]}} \chapter{Preparing for Install}\label{preparing-for-install} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP doesn't require much to deploy. You need a Java Development Kit (JDK) and a database. Several configuration topics (e.g., \href{/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine}{search engine integration}, \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{document repository configuration}, \href{/docs/7-2/deploy/-/knowledge_base/d/securing-product}{security management}, \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{clustering}, and more) can be addressed \emph{after} deploying Liferay DXP. \noindent\hrulefill \textbf{Note:} If you are installing Liferay DXP to multiple machines (e.g., in a \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{cluster}) or prefer centralizing configuration in a file, using portal properties in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{{[}LIFERAY\_HOME{]}/portal-ext.properties} file} is the recommended way to configure. The install preparation topics here and the configuration topics throughout this guide demonstrate using applicable portal properties. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} \texttt{LIFERAY\_HOME} is the location from which Liferay DXP launches applications, applies configurations, loads JAR files, and generates logs. Liferay Home is customizable and can differ between application servers. The \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home reference} describes its folder structure. \noindent\hrulefill Start preparing for Liferay DXP install by installing a supported Java Development Kit. \section{JDK Requirements}\label{jdk-requirements} Liferay DXP deployment requires a JDK. The \href{https://help.liferay.com/hc/categories/360000894391-Product-Support}{Support page} lists the supported JDKs from various vendors. You must use one of these JDK versions: \begin{itemize} \tightlist \item JDK 8 \item JDK 11 \end{itemize} JDK 11 is backwards compatible with JDK 8 applications. Applications and customizations developed on JDK 8 run on JDK 8 or JDK 11 runtimes. This makes JDK 8 best for developing on 7.0. \section{JVM Requirements}\label{jvm-requirements} Liferay DXP requires that the application server JVM use the GMT time zone and UTF-8 file encoding. Include these JVM arguments to set the required values. \begin{verbatim} -Dfile.encoding=UTF8 -Duser.timezone=GMT \end{verbatim} On JDK 11, it's recommended to add this JVM argument to display four-digit years. \begin{verbatim} -Djava.locale.providers=JRE,COMPAT,CLDR \end{verbatim} \noindent\hrulefill \textbf{Note:} Since JDK 9, the Unicode Common Locale Data Repository (CLDR) is the default locales provider. CLDR, however, is not providing years in a four-digit format (see \href{https://issues.liferay.com/browse/LPS-87191}{LPS-87191}). The setting \texttt{java.locale.providers=JRE,COMPAT,CLDR} works around this issue by using JDK 8's default locales provider. \noindent\hrulefill The recommended maximum heap size is 2GB. Setting the minimum heap size to the maximum heap size value minimizes garbage collections. \begin{verbatim} -Xms2560m -Xmx2560m \end{verbatim} If you're using JDK 11, you may see \emph{Illegal Access} warnings like these: \begin{verbatim} WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.liferay.petra.reflect.ReflectionUtil (file:/Users/malei/dev/project/bundles/master-bundles/tomcat-9.0.10/lib/ext/com.liferay.petra.reflect.jar) to field java.lang.reflect.Field.modifiers WARNING: Please consider reporting this to the maintainers of com.liferay.petra.reflect.ReflectionUtil WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release \end{verbatim} This is a known issue: \href{https://issues.liferay.com/browse/LPS-87421}{LPS-87421}. As a workaround, you can eliminate these warnings by adding these properties after your application server JVM options: \begin{verbatim} --add-opens=java.base/java.io=ALL-UNNAMED \ --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-opens=java.base/java.net=ALL-UNNAMED \ --add-opens=java.base/java.nio=ALL-UNNAMED \ --add-opens=java.base/java.text=ALL-UNNAMED \ --add-opens=java.base/java.util=ALL-UNNAMED \ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \ --add-opens=java.desktop/java.awt.font=ALL-UNNAMED \ --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED \ --add-opens=java.xml/com.sun.org.apache.xerces.internal.parsers=ALL-UNNAMED \end{verbatim} If you're using JDK 11 on Linux or UNIX and are activating Liferay DXP using an LCS 5.0.0 client, you may see an error like this: \begin{verbatim} ERROR [LCS Worker 2][BaseScheduledTask:92] java.lang.reflect.InaccessibleObjectException: Unable to make public long com.sun.management.internal.OperatingSystemImpl.getOpenFileDescriptorCount() accessible: module jdk.management does not "opens com.sun.management.internal" to unnamed module @1a3325e5 java.lang.reflect.InaccessibleObjectException: Unable to make public long com.sun.management.internal.OperatingSystemImpl.getOpenFileDescriptorCount() accessible: module jdk.management does not "opens com.sun.management.internal" to unnamed module @1a3325e5 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java: at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java: at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198) at java.base/java.lang.reflect.Method.setAccessible(Method.java:192) \end{verbatim} To workaround this issue, add this property after your application server JVM options: \begin{verbatim} --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED \end{verbatim} It's time to prepare your database. \section{Preparing a Database}\label{preparing-a-database} The recommended way to set up your Liferay DXP database is also the simplest. Liferay DXP takes care of just about everything. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Create a blank database encoded with the character set UTF-8. Liferay DXP is a multilingual application and needs UTF-8 encoding to display all of its supported character sets. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If you plan to migrate from one database vendor to another, [configure the database to use the default query result order you expect for entities Liferay DXP lists](/docs/7-2/deploy/-/knowledge_base/d/sort-order-changed-with-a-different-database). \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item Create a database user for accessing this database. Grant this database user all rights, including the rights to create and drop tables, to the blank Liferay DXP database. \end{enumerate} Liferay DXP uses this database user's credentials to connect to the database either \hyperref[using-the-built-in-data-source]{directly} or \hyperref[using-a-data-source-on-your-application-server]{through its application server}. After you've configured the database connection, Liferay DXP creates its tables in the database automatically, complete with indexes. This is the recommended way to set up Liferay DXP. It enables @product@ to maintain its database automatically during upgrades or when various Liferay DXP plugins that create database tables of their own are installed. This method is by far the best way to set up your database. \noindent\hrulefill \textbf{Warning:} If you're using an Oracle database, use the \texttt{ojdbc8.jar} driver library with at least Oracle 12.2.0.1.0 JDBC 4.2 versioning because \href{https://issues.liferay.com/browse/LPS-79229}{data truncation issues} have been detected reading data from CLOB columns. \noindent\hrulefill You can connect Liferay DXP with your database using @product@'s built-in data source (recommended) or using a data source you create on your app server. \section{Using the Built-in Data Source}\label{using-the-built-in-data-source} You can configure the built-in data source from the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-product\#using-the-setup-wizard}{Basic Configuration page} (available when Liferay DXP starts up the first time) or by specifying it using portal properties. Here's how set it using portal properties: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file} if you haven't created one already. \item Copy a set of \texttt{jdbc.*} properties from one of the \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{JDBC templates} into your \texttt{portal-ext.properties} file. \item Modify the \texttt{jdbc.*} property values to specify your database and database user credentials. \item Put the \texttt{portal-ext.properties} file into your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{LIFERAY\_HOME} once you've established it based on your installation. \end{enumerate} Liferay DXP connects to the data source on startup. As an alternative to the built-in data source, you can use your application server's data source. \section{Using a Data Source on Your Application Server}\label{using-a-data-source-on-your-application-server} Here's how to use your application server's data source: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create your data source based on the instructions in the \emph{Installing Liferay DXP on {[}Application Server{]}} article (for your application server) and your application server's documentation. \item Create a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}, if you haven't created one already. \item Add the \texttt{jdbc.default.jndi.name} property set to the data source's JNDI name. Here's an example: \begin{verbatim} jdbc.default.jndi.name=jdbc/LiferayPool \end{verbatim} \item Put the \texttt{portal-ext.properties} file into your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{LIFERAY\_HOME}, once you've established your LIFERAY\_HOME based on your installation. \end{enumerate} Liferay DXP connects to your data source on startup. Allowing the database user you're using to initialize the Liferay DXP database to continue with all database rights is recommended. If you're fine with that user having the recommended permissions, skip the next section on limiting database access. \section{Limiting Database Access}\label{limiting-database-access} \noindent\hrulefill \textbf{Warning:} The instructions below are not ideal for Liferay DXP installations The following procedure is documented so that enterprises with more restrictive standards can install Liferay DXP with stricter (but sub-optimal) database settings. If it's at all possible, allow the database user that initializes the database to continue using the database with the same recommended permissions. The start of this section (\hyperref[preparing-a-database]{Database Preparation}) describes the recommended procedure for initializing the Liferay DXP database and preserving that user's permissions for maintaining the Liferay DXP database and updating the database as plugin installations and plugin updates require. \noindent\hrulefill Even though it's recommended for Liferay DXP to use the same database user to create and maintain its database automatically, your organizations might insist on revoking database initialization and maintenance permissions from that user once the database is initialized. If permissions for Select, Insert, Update and Delete operations are the only ones you allow for that user, you must initialize and maintain the database manually (even though it's not recommended). Here is the manual procedure: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a new, blank, database for Liferay DXP. \item Grant full rights for the Liferay DXP database user to do anything to the database. \item Install Liferay DXP and start it so that it automatically populates the database. \item Once the database has been populated with the Liferay DXP tables, remove all permissions from that user except permissions to perform Select, Insert, Update and Delete operations. \end{enumerate} There are some caveats to running Liferay DXP like this. Many plugins create new tables when they're deployed. Additionally, you must run the database upgrade function to upgrade Liferay DXP. If the @product@ database user doesn't have adequate rights to create/modify/drop tables in the database, you must grant those rights to that user before you deploy one of these plugins or start upgrading Liferay DXP. Once the tables are created or the upgrade completes, you can remove those rights until the next deploy or upgrade. Additionally, your own developers might create plugins that must create their own tables. These are just like Liferay DXP plugins that do the same thing, and they can only be installed if Liferay DXP can create database tables. Installing these plugins requires granting the Liferay DXP database user rights to create tables in the database before you attempt to install the plugins. Liferay DXP has many more configurable features; but they can wait until \emph{after} deployment. The \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-product}{Configuring Liferay DXP} section explains them. Now it's time to install Liferay DXP. \chapter{Installing Liferay DXP}\label{installing-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Now that you've \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{prepared for installing Liferay DXP}, you can install and deploy it! This involves uncompressing the archive (the 7-Zip or gzip bundle file), possibly copying a JDBC driver to your application server, and starting the application server. Lastly, Liferay DXP provides a setup wizard to configure portal essentials. \noindent\hrulefill \textbf{Note:} Since bundles are the easiest way to complete an installation, the installation steps here assume you're installing a Liferay DXP bundle. If you plan to install Liferay DXP manually, please refer to the \emph{Installing @product@ on {[}Application Server{]}} article for the application server you're installing on. \noindent\hrulefill \section{Extracting a Liferay DXP Bundle}\label{extracting-a-liferay-dxp-bundle} Extract your Liferay DXP bundle to the appropriate location on your server. This folder is the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}}. \section{Installing the JDBC Driver}\label{installing-the-jdbc-driver} If you're using a supported open source database or if you're setting up Liferay DXP to use the embedded HSQL database for demo purposes, you can skip this step. Otherwise, copy your database's JDBC driver \texttt{.jar} file to the folder your application server documentation specifies. On Tomcat, for example, the driver belongs in the \texttt{{[}Tomcat{]}/lib/ext} folder. \section{Running Liferay DXP for the First Time}\label{running-liferay-dxp-for-the-first-time} Start your application server using the start script bundled with your application server. For example, the Tomcat bundle provides the \texttt{startup.sh} script in \texttt{\$CATALINA\_HOME/bin}. \noindent\hrulefill \textbf{Note:} Liferay DXP writes log files to folder \texttt{{[}Liferay\ Home{]}/logs}. \noindent\hrulefill \noindent\hrulefill \textbf{Important:} Liferay DXP requires that the application server JVM use the GMT time zone and UTF-8 file encoding. They're preset in your Liferay DXP bundle. \noindent\hrulefill The first time Liferay DXP starts, it creates all of its database tables. On completing startup, it launches a web browser that displays the Basic Configuration page (the setup wizard). If for some reason your browser doesn't load the Basic Configuration page automatically, open your browser and navigate to your app server's address and port (for example, http://localhost:8080). \section{Using the Setup Wizard}\label{using-the-setup-wizard} The Basic Configuration page provides a convenient way to configure these things: \begin{itemize} \tightlist \item Portal name and default locale \item Administrator user \item Database \end{itemize} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/basic-configuration1.png}} \caption{Supply the information for your portal and your portal's default administrator user on the Basic Configuration page.} \end{figure} \section{Portal}\label{portal} Supply this basic portal information: \textbf{Portal Name:} name the installation you're powering with Liferay DXP. \textbf{Default Language:} choose your portal's default locale and click the \emph{Change} button. This immediately localizes your portal content, including the Basic Configuration page. \textbf{Time Zone:} select your Liferay DXP instance's default time zone. \section{Administrator User}\label{administrator-user} For the administrator, supply the following information: \textbf{First Name:} the default administrator user's first name \textbf{Last Name:} the default administrator user's last name \textbf{Email:} the default administrator user's email address \noindent\hrulefill \textbf{Note:} the administrator user's email domain is used as the Liferay DXP instance's default domain (i.e., the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Company}{\texttt{company.default.web.id}} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal property}). \noindent\hrulefill \section{Database}\label{database} This section lets you connect to Liferay DXP's built-in data source. \noindent\hrulefill \textbf{Important:} If you haven't created a database for Liferay DXP, create one now following \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#preparing-a-database}{database preparation instructions} in the preceding article. \noindent\hrulefill HSQL is selected as the default database, but it's primarily for demonstration or trial purposes. Click the \emph{Change} link if you want to use Liferay DXP's built-in data source and configure it to use the \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#preparing-a-database}{database you created earlier}. The database configuration section also has an \emph{Add Sample Data} checkbox for adding sample data to your database. This data includes Users, Sites, and Organizations for demo purposes. If you're installing Liferay DXP on your own machine to explore features, the sample data may be useful. If, however, you're installing Liferay DXP on a real server, start with a clean system by leaving this checkbox unselected. \noindent\hrulefill \textbf{Warning:} HSQL should not be used in production Liferay DXP instances. Configure Liferay DXP to use a different database; specify that database via the Basic Configuration page here or using portal properties. See \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#preparing-a-database}{Database Preparation} for details. \noindent\hrulefill Once you've filled out the Basic Configuration form, click \emph{Finish Configuration}. The setup wizard creates a \texttt{{[}LIFERAY\_HOME{]}/portal-setup-wizard.properties} file which stores the settings that you entered. When you begin customizing your portal's configuration, however, use a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}. The \href{@platform-ref@/7.2-latest/propertiesdoc}{Portal properties reference documentation} lists the default properties and describes all the properties you can set for Liferay DXP. \noindent\hrulefill \textbf{Tip:} The wizard is a helpful tool, especially if you're setting up Liferay DXP for the first time. If you're a veteran and you already have your various properties set up, you can disable the setup wizard. If you disable the setup wizard, you must configure everything manually from a portal properties file (e.g., \texttt{{[}LIFERAY\_HOME{]}/portal-ext.properties}). To disable the setup wizard, set \texttt{setup.wizard.enabled=false} in your portal properties file. Note that property values in \texttt{portal-setup-wizard.properties} (the file the setup wizards creates in Liferay Home) override property values in \texttt{portal-ext.properties}. \noindent\hrulefill On finishing basic configuration, Liferay DXP prompts you to restart your server. When you restart your application server, Liferay DXP initializes the database you specified. Now that Liferay DXP is up and running, you can continue configuring it as desired. Here are some suggestions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{Configure your mail session}, if you haven't already configured it. \item Install the Marketplace plugin, if it isn't already installed. If your machine has restricted access to the public network or if you restricted the Liferay DXP database user's permissions after initializing the database (not recommended), you can still set up Marketplace by following the \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-marketplace-and-portal-security}{Marketplace setup instructions}. \item Read the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-product}{Configuring Liferay DXP} articles for guidance in configuring Liferay DXP's default time zone, locales, logging, search engine, document repository, and more. \end{enumerate} You're on your way to providing your organization with terrific experiences on Liferay DXP. \chapter{Installing Liferay DXP on Tomcat}\label{installing-liferay-dxp-on-tomcat} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} 7.0 bundled with Tomcat 9 is available on the \href{https://customer.liferay.com/downloads}{Help Center} (DXP) or the \href{https://www.liferay.com/downloads-community}{Community Downloads page} (Portal CE). The Tomcat bundle contains JARs, scripts, and configuration files required for deploying Liferay DXP. Copying these files from the @product@ Tomcat bundle facilitates installing Liferay DXP on an existing Tomcat application server. Whether you copy bundle files (recommended) or download and create the files, you must download these files for \href{https://customer.liferay.com/downloads}{DXP} or \href{https://www.liferay.com/downloads-community}{Portal CE}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} \noindent\hrulefill \textbf{Important:} \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{Prepare for the install} before continuing. \noindent\hrulefill Here are the basic steps for installing Liferay DXP on Tomcat: \begin{itemize} \tightlist \item \hyperref[installing-dependencies]{Installing dependencies to your application server} \item \hyperref[configuring-tomcat]{Configuring your application server for Liferay DXP} \item \hyperref[deploying-product]{Deploying the Liferay DXP WAR file to your application server} \end{itemize} \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}} is Tomcat server's parent folder. \texttt{\$TOMCAT\_HOME} refers to your Tomcat server folder. It is usually named \texttt{tomcat-{[}version{]}} or \texttt{apache-tomcat-{[}version{]}}. \section{Installing Dependencies}\label{installing-dependencies} Liferay DXP depends on many JARs included by @product@ Tomcat bundle. Some of the bundle's JARs are not strictly required but can still be useful. If you don't have a bundle, you can download the Liferay JARs by downloading the \emph{Dependencies} archive and the \emph{OSGi Dependencies} archive, and you can download the third-party JARs as described below. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create the folder \texttt{\$TOMCAT\_HOME/lib/ext} if it doesn't exist and extract the JARs from the dependencies ZIP to it. \item Copy the following JARs from a Liferay DXP Tomcat bundle (or download them) to the \texttt{\$TOMCAT\_HOME/lib/ext} folder: \begin{itemize} \tightlist \item \href{http://www.oracle.com/technetwork/java/javase/jaf-136260.html}{\texttt{activation.jar}} \item \href{http://mvnrepository.com/artifact/javax.ccpp/ccpp/1.0}{\texttt{ccpp.jar}} \item \href{http://www.oracle.com/technetwork/java/docs-136352.html}{\texttt{jms.jar}} \item \href{http://www.oracle.com/technetwork/java/javaee/jta/index.html}{\texttt{jta.jar}} \item \href{http://mvnrepository.com/artifact/com.beetstra.jutf7/jutf7}{\texttt{jutf7.jar}} \item \href{http://www.oracle.com/technetwork/java/index-138643.html}{\texttt{mail.jar}} \item \href{http://mvnrepository.com/artifact/org.eclipse.persistence/javax.persistence/2.1.1}{\texttt{persistence.jar}} \item \href{http://mvnrepository.com/artifact/com.liferay.portal/com.liferay.support.tomcat}{\texttt{support-tomcat.jar}} \end{itemize} \item Copy the JDBC driver for your database to the \texttt{\$CATALINA\_BASE/lib/ext} folder. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \tightlist \item Create an \texttt{osgi} folder in your Liferay Home. Extract the folders (i.e., \texttt{configs}, \texttt{core}, and more) from OSGi ZIP file to the \texttt{osgi} folder. The \texttt{osgi} folder provides the necessary modules for Liferay DXP's OSGi runtime. \end{enumerate} \section{Configuring Tomcat}\label{configuring-tomcat} Configuring Tomcat to run Liferay DXP includes \begin{itemize} \tightlist \item Setting environment variables \item Specifying a web application context for Liferay DXP \item Setting properties and descriptors \end{itemize} Optionally, if you're not using Liferay DXP's built-in data source or mail session, you can configure Tomcat to manage them: \begin{itemize} \tightlist \item \hyperref[database-configuration]{Data source} \item \hyperref[mail-configuration]{Mail session} \end{itemize} Start with configuring Tomcat to run Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you have a Liferay DXP Tomcat bundle, copy the \texttt{setenv.bat}, \texttt{setenv.sh}, \texttt{startup.bat}, \texttt{startup.sh}, \texttt{shutdown.bat}, and \texttt{shutdown.sh} files from it to your \texttt{\$CATALINA\_BASE/bin} folder. If not, create the \texttt{setenv.bat} and \texttt{setenv.sh}scripts. The scripts set JVM options for Catalina, which is Tomcat's servlet container. Among these options is the location of the Java runtime environment. If this environment is not available on your server globally, you must set its location in in these files so Tomcat can run. Do this by pointing the \texttt{JAVA\_HOME} environment variable to a Liferay DXP-supported JRE: \begin{verbatim} export JAVA_HOME=/usr/lib/jvm/java-8-jdk export PATH=$JAVA_HOME/bin:$PATH \end{verbatim} Then configure Catalina's JVM options to support Liferay DXP. Unix: \begin{verbatim} CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" \end{verbatim} Windows: \begin{verbatim} set "CATALINA_OPTS=%CATALINA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" \end{verbatim} This sets the file encoding to UTF-8, prefers an IPv4 stack over IPv6, prevents Tomcat from working around garbage collection bugs relating to static or final fields (these bugs don't exist in Liferay DXP and working around them causes problems with the logging system), sets the time zone to GMT, gives the JVM 2GB of RAM, and limits Metaspace to 512MB. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Important:** Liferay DXP requires that the application server JVM use the GMT time zone and UTF-8 file encoding. \end{verbatim} \noindent\hrulefill \noindent\hrulefill \begin{verbatim} **Note:** On JDK 11, it's recommended to add this JVM argument to display four-digit years. ```properties -Djava.locale.providers=JRE,COMPAT,CLDR ``` \end{verbatim} \noindent\hrulefill \begin{verbatim} After installation, tune your system (including these JVM options) for performance. \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item If you have a Liferay DXP Tomcat bundle, copy its \texttt{\$CATALINA\_BASE/conf/Catalina/localhost/ROOT.xml} file to the corresponding location in your application server. Create the file path if it doesn't exist. If you don't have a Liferay DXP Tomcat bundle, create a \texttt{ROOT.xml} file. The \texttt{ROOT.xml} file specifies a web application context for Liferay DXP. \texttt{ROOT.xml} looks like this: \begin{verbatim} \end{verbatim} Setting \texttt{crossContext="true"} lets multiple web applications use the same class loader. This configuration includes commented instructions and tags for configuring a JAAS realm, disabling persistent sessions, and disabling sessions entirely. \item Provide Catalina access to the JARs in \texttt{\$CATALINA\_BASE/lib/ext} by opening your \texttt{\$CATALINA\_BASE/conf/catalina.properties} file and appending this value to the \texttt{common.loader} property: \begin{verbatim} ,"${catalina.home}/lib/ext/global","${catalina.home}/lib/ext/global/*.jar","${catalina.home}/lib/ext","${catalina.home}/lib/ext/*.jar" \end{verbatim} \item Make sure to use UTF-8 URI encoding consistently. If you have a Liferay DXP Tomcat bundle, copy the \texttt{\$CATALINA\_BASE/conf/server.xml} file to your server. If not, open your \texttt{\$CATALINA\_BASE/conf/server.xml} file and add the attribute \texttt{URIEncoding="UTF-8"} to HTTP and AJP connectors that use \texttt{redirectPort=8443}. Here are examples: Old: \begin{verbatim} \end{verbatim} New: \begin{verbatim} \end{verbatim} Old: \begin{verbatim} \end{verbatim} New: \begin{verbatim} \end{verbatim} \item Refrain from writing access logs (optional) by commenting out the access log \texttt{Valve} element in \texttt{\$CATALINA\_BASE/conf/server.xml}. It's commented out here: \begin{verbatim} \end{verbatim} \item Optionally, set the following log levels in your \texttt{\$CATALINA\_HOME/conf/logging.properties} file: \begin{verbatim} org.apache.catalina.startup.Catalina.level=INFO org.apache.catalina.startup.ClassLoaderFactory.level=SEVERE org.apache.catalina.startup.VersionLoggerListener.level=WARNING org.apache.level=WARNING \end{verbatim} \item In \texttt{\$CATALINA\_HOME/conf/web.xml}, set the JSP compiler to Java 8 and set Liferay DXP's \texttt{TagHandlerPool} class to manage the JSP tag pool. Do this by adding the following elements above the \texttt{jsp} servlet element's \texttt{\textless{}load-on-startup\textgreater{}} element. \begin{verbatim} compilerSourceVM 1.8 compilerTargetVM 1.8 tagpoolClassName com.liferay.support.tomcat.jasper.runtime.TagHandlerPool \end{verbatim} \item In \texttt{\$CATALINA\_HOME/conf/web.xml}, specify whether the application server should look for extra metadata, such as annotations in the application's JARs and classes. Setting \texttt{web-app} element's attribute \texttt{metadata-complete="true"} tells the application server there's no extra metadata. The application server starts up faster this way. The default is to check for extra metadata. \item If you're on Unix, Linux, or Mac OS, make the shell scripts in your \texttt{\$CATALINA\_HOME/bin} and \texttt{\$CATALINA\_BASE/bin} folders executable by running this command in each folder: \begin{verbatim} chmod a+x *.sh \end{verbatim} \end{enumerate} \textbf{Checkpoint:} Your application server is configured to run Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The file encoding, user time-zone, and preferred protocol stack are set in your \texttt{setenv.sh}. \item The default memory available and Metaspace limit are set. \item \texttt{\$CATALINA\_BASE/conf/Catalina/localhost/ROOT.xml} declares the web application context. \item The \texttt{common.loader} property in \texttt{\$CATALINA\_BASE/conf/catalina.properties} grants Catalina access to the JARs in \texttt{\$CATALINA\_BASE/lib/ext}. \item \texttt{\$CATALINA\_BASE/conf/server.xml} sets UTF-8 encoding. \item \texttt{\$CATALINA\_BASE/conf/server.xml} doesn't declare any valve for writing host access logs. (optional) \item \texttt{\$CATALINA\_HOME/conf/logging.properties} sets the desired log levels. \item \texttt{\$CATALINA\_HOME/conf/web.xml} sets the tag handler pool and sets Java 8 as the JSP compiler. \item \texttt{\$CATALINA\_HOME/conf/web.xml} specifies for the application server to refrain from looking for extra metadata. (optional) \item The scripts in Tomcat's \texttt{bin} folders are executable. \end{enumerate} \section{Database Configuration}\label{database-configuration} The easiest way to handle your database configuration is to let Liferay DXP manage your data source. If you want to use the \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#using-the-built-in-data-source}{built-in data source (recommended)}, skip this section. If you want Tomcat to manage your data source, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Make sure your database server is installed and working. If it's installed on a different machine, make sure your Liferay DXP machine can access it. \item Open \texttt{\$CATALINA\_BASE/conf/Catalina/localhost/ROOT.xml} and add your data source as a \texttt{Resource} in your web application \texttt{Context}: \begin{verbatim} ... \end{verbatim} \end{enumerate} Make sure to replace the database URL, user name, and password with the appropriate values. For example JDBC connection values, please see \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item In a \texttt{portal-ext.properties} file in your Liferay Home, specify your data source: \begin{verbatim} jdbc.default.jndi.name=jdbc/LiferayPool \end{verbatim} \end{enumerate} You created a data source for Tomcat to manage and configured Liferay DXP to use it. Mail session configuration is next. \section{Mail Configuration}\label{mail-configuration} As with database configuration, the easiest way to configure mail is to let Liferay DXP handle your mail session. If you want to use @product@'s \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{built-in mail session}, skip this section. If you want to manage your mail session with Tomcat, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open \texttt{\$CATALINA\_BASE/conf/Catalina/localhost/ROOT.xml} and add your mail session as a \texttt{Resource} in your web application \texttt{Context}. Make sure to replace the example mail session values with your own. \begin{verbatim} ... \end{verbatim} \item In your \texttt{portal-ext.properties} file in Liferay Home, reference your mail session: \begin{verbatim} mail.session.jndi.name=mail/MailSession \end{verbatim} \end{enumerate} You've created a mail session for Tomcat to manage and configured Liferay DXP to use it. \section{Deploying Liferay DXP}\label{deploying-liferay-dxp-1} Now you're ready to deploy Liferay DXP using the @product@ WAR file. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you are manually installing Liferay DXP on a clean Tomcat server, delete the contents of the \texttt{\$CATALINA\_BASE/webapps/ROOT} folder. This removes the default Tomcat home page. \item Extract the Liferay DXP \texttt{.war} file contents to \texttt{\$CATALINA\_BASE/webapps/ROOT}. It's time to launch Liferay DXP on Tomcat! \item Start Tomcat by navigating to \texttt{\$CATALINA\_HOME/bin} and executing \texttt{./startup.sh}. Alternatively, execute \texttt{./catalina.sh\ run} to tail Liferay DXP's log file. The log audits startup activities and is useful for debugging deployment. \end{enumerate} Congratulations on successfully installing and deploying Liferay DXP on Tomcat! \chapter{Installing Liferay DXP on Wildfly}\label{installing-liferay-dxp-on-wildfly} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Installing Liferay DXP on Wildfly 11 takes three steps: \begin{itemize} \tightlist \item \hyperref[installing-dependencies]{Installing dependencies to your application server} \item \hyperref[configuring-wildfly]{Configuring your application server for Liferay DXP} \item \hyperref[deploying-product]{Deploying the Liferay DXP WAR file to your application server} \end{itemize} \noindent\hrulefill \textbf{Important:} Before installing Liferay DXP, familiarize yourself with \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{preparing for install}. \noindent\hrulefill Now, \href{/docs/7-2/deploy/-/knowledge_base/d/obtaining-product\#downloading-the-liferay-war-and-dependency-jars}{download the Liferay DXP WAR and Dependency JARs}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} Note that \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}} is the folder containing your Wildfly server folder. After installing and deploying Liferay DXP, the Liferay Home folder contains the Wildfly server folder as well as \texttt{data}, \texttt{deploy}, \texttt{logs}, and \texttt{osgi} folders. \texttt{\$WILDFLY\_HOME} refers to your Wildfly server folder. It is usually named \texttt{wildfly-{[}version{]}}. \section{Installing Dependencies}\label{installing-dependencies-1} Liferay DXP depends on a driver for your database and the JARs in the Dependencies ZIP and OSGi Dependencies ZIP files you downloaded. Here's how to install them: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create the folder \texttt{\$WILDFLY\_HOME/modules/com/liferay/portal/main} if it doesn't exist and extract the Dependencies ZIP JARs to it. \item Download your database driver \texttt{.jar} file and copy it into the same folder. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item Create the file \texttt{module.xml} in the \texttt{\$WILDFLY\_HOME/modules/com/liferay/portal/main} folder and insert this configuration: \begin{verbatim} \end{verbatim} Replace \texttt{{[}place\ your\ database\ vendor\textquotesingle{}s\ jar\ here{]}} with the driver JAR for your database. \item Create an \texttt{osgi} folder in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder. Extract the OSGi Dependencies ZIP file that you downloaded into the \texttt{{[}Liferay\ Home{]}/osgi} folder. The \texttt{osgi} folder provides the necessary modules for Liferay DXP's OSGi runtime. \end{enumerate} \section{Running Liferay DXP on Wildfly in Standalone Mode vs.~Domain Mode}\label{running-liferay-dxp-on-wildfly-in-standalone-mode-vs.-domain-mode} Wildfly can be launched in either \emph{standalone} mode or \emph{domain} mode. Domain mode allows multiple application server instances to be managed from a single control point. A collection of such application servers is known as a \emph{domain}. For more information on standalone mode vs.~domain mode, please refer to the section on this topic in the \href{https://docs.jboss.org/author/display/WFLY/Admin+Guide\#AdminGuide-Operatingmodes}{Wildfly Admin Guide}. Liferay DXP fully supports Wildfly in standalone mode but not in domain mode. You can run Liferay DXP on Wildfly in domain mode, but this method is not fully supported. In particular, Liferay DXP's hot-deploy does not work with a managed deployment, since Wildfly manages the content of a managed deployment by copying files (exploded or non-exploded). This prevents JSP hooks and Ext plugins from working as intended. For example, JSP hooks don't work on Wildfly running in managed domain mode, since Liferay DXP's JSP override mechanism relies on the application server. Since JSP hooks and Ext plugins are deprecated, however, you may not be using them. The command line interface is recommended for domain mode deployments. \noindent\hrulefill \textbf{Note:} This does not prevent Liferay DXP from running in a clustered environment on multiple Wildfly servers. You can set up a cluster of Liferay DXP instances running on Wildfly servers running in standalone mode. Please refer to the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP clustering articles} for more information. \noindent\hrulefill \section{Configuring Wildfly}\label{configuring-wildfly} Configuring Wildfly to run Liferay DXP includes these things: \begin{itemize} \tightlist \item Setting environment variables \item Setting properties and descriptors \item Removing unnecessary configurations \end{itemize} Optionally, you can configure Wildfly to manage Liferay DXP's data source and mail session. Start with configuring Wildfly to run Liferay DXP. Make the following modifications to \texttt{\$WILDFLY\_HOME/standalone/configuration/standalone.xml}: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Locate the closing \texttt{\textless{}/extensions\textgreater{}} tag. Directly beneath that tag, insert the following system properties: \begin{verbatim} \end{verbatim} \item Add the following \texttt{\textless{}filter-spec\textgreater{}} tag within the \texttt{\textless{}console-handler\textgreater{}} tag, directly below the \texttt{\textless{}level\ name="INFO"/\textgreater{}} tag: \begin{verbatim} \end{verbatim} \item Add a timeout for the deployment scanner by setting \texttt{deployment-timeout="600"} as seen in the excerpt below. \begin{verbatim} \end{verbatim} \item Add the following JAAS security domain to the security subsystem \texttt{\textless{}security-domains\textgreater{}} defined in element \texttt{\textless{}subsystem\ xmlns="urn:jboss:domain:security:2.0"\textgreater{}}. \begin{verbatim} \end{verbatim} \item Remove the welcome content code snippets: \begin{verbatim} \end{verbatim} and \begin{verbatim} \end{verbatim} \item Find the \texttt{\textless{}jsp-config/\textgreater{}} tag and set the \texttt{development}, \texttt{source-vm}, and \texttt{target-vm} attributes in the tag. Once finished, the tag should look like this: \begin{verbatim} \end{verbatim} \end{enumerate} \textbf{Checkpoint:} Before continuing, verify the following properties have been set in the \texttt{standalone.xml} file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The new \texttt{\textless{}system-property\textgreater{}} is added. \item The new \texttt{\textless{}filter-spec\textgreater{}} is added. \item The \texttt{\textless{}deployment-timeout\textgreater{}} is set to \texttt{600}. \item The new \texttt{\textless{}security-domain\textgreater{}} is created. \item Welcome content is removed. \item The \texttt{\textless{}jsp-config\textgreater{}} tag contains its new attributes. \end{enumerate} Now you must configure your JVM and startup scripts. In the \texttt{\$WILDFLY\_HOME/bin/} folder, modify your standalone domain's configuration script file \texttt{standalone.conf} (\texttt{standalone.conf.bat} on Windows): \begin{itemize} \tightlist \item Set the file encoding to \texttt{UTF-8} \item Set the user time zone to \texttt{GMT} \item Set the preferred protocol stack \item Increase the default amount of memory available. \end{itemize} \noindent\hrulefill \textbf{Important:} For Liferay DXP to work properly, the application server JVM must use the \texttt{GMT} time zone and \texttt{UTF-8} file encoding. \noindent\hrulefill Make the following edits as applicable for your operating system: \textbf{Windows:} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Comment out the initial \texttt{JAVA\_OPTS} assignment like this: \begin{verbatim} rem set "JAVA_OPTS=-Xms64M -Xmx512M -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m" \end{verbatim} \item Add the following \texttt{JAVA\_OPTS} assignment one line above the \texttt{:JAVA\_OPTS\_SET} line found at end of the file: \begin{verbatim} set "JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=200m" \end{verbatim} \end{enumerate} \textbf{Unix:} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Below the \texttt{if\ {[}\ "x\$JAVA\_OPTS"\ =\ "x"\ {]};} statement, replace this \texttt{JAVA\_OPTS} statement: \begin{verbatim} JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" \end{verbatim} with this: \begin{verbatim} JAVA_OPTS="-Djava.net.preferIPv4Stack=true" \end{verbatim} \item Add the following statement to the bottom of the file: \begin{verbatim} JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=200m" \end{verbatim} \end{enumerate} This sets the file encoding to UTF-8, prefers an IPv4 stack over IPv6, sets the time zone to GMT, gives the JVM 2GB of RAM, and limits Metaspace to 512MB. On JDK 11, it's recommended to add this JVM argument to display four-digit years. \begin{verbatim} -Djava.locale.providers=JRE,COMPAT,CLDR \end{verbatim} After installation, tune your system (including these JVM options) for performance. \noindent\hrulefill \textbf{Important:} For Liferay DXP to work properly, the application server JVM must use the \texttt{GMT} time zone and \texttt{UTF-8} file encoding. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} If you plan on using the IBM JDK with your Wildfly server, you must complete some additional steps. First, navigate to the \texttt{\$WILDFLY\_HOME/modules/com/liferay/portal/main/module.xml} file and insert the following dependency within the \texttt{\textless{}dependencies\textgreater{}} element: \begin{verbatim} \end{verbatim} Then navigate to the \texttt{\$WILDFLY\_HOME/modules/system/layers/base/sun/jdk/main/module.xml} file and insert the following path names inside the \texttt{\textless{}paths\textgreater{}...\textless{}/paths\textgreater{}} element: \begin{verbatim} \end{verbatim} The added paths resolve issues with deployment exceptions and image uploading problems. \noindent\hrulefill \textbf{Checkpoint:} You've configured the application server's JVM settings. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The file encoding, user time-zone, preferred protocol stack have been set in the \texttt{JAVA\_OPTS} in the \texttt{standalone.conf.bat} file. \item The default amount of memory available has been increased. \end{enumerate} The prescribed script modifications are now complete for your Liferay DXP installation on Wildfly. Next you'll configure your database. \section{Database Configuration}\label{database-configuration-1} The easiest way to handle database configuration is to let Liferay DXP manage your data source. The \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#using-the-built-in-data-source}{Basic Configuration} page lets you configure Liferay DXP's built-in data source. If you want to use the built-in data source, skip this section. If using WildFly to manage the data source, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the data source inside the \texttt{\$WILDFLY\_HOME/standalone/configuration/standalone.xml} file's \texttt{\textless{}datasources\textgreater{}} element: \begin{verbatim} [place the URL to your database here] [place your driver name here] [place your user name here] [place your password here] \end{verbatim} Make sure to replace the database URL, user name, and password with the appropriate values. For example JDBC connection values, please see \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If the data source `jndi-name` must be changed, edit the `datasource` element in the `` tag. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the driver to the \texttt{standalone.xml} file's \texttt{\textless{}drivers\textgreater{}} element also found within the \texttt{\textless{}datasources\textgreater{}} element. \begin{verbatim} [JDBC driver class] \end{verbatim} A final data sources subsystem that uses MySQL should look like this: \begin{verbatim} jdbc:mysql://localhost/lportal mysql root root com.mysql.cj.jdbc.Driver \end{verbatim} \item In a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file in your Liferay Home, specify your data source: \begin{verbatim} jdbc.default.jndi.name=java:jboss/datasources/ExampleDS \end{verbatim} \end{enumerate} Now that you've configured your data source, the mail session is next. \section{Mail Configuration}\label{mail-configuration-1} As with database configuration, the easiest way to configure mail is to let Liferay DXP handle your mail session. If you want to use @product@'s built-in mail session, skip this section and \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{configure the mail session} in the Control Panel. If you want to manage your mail session with Wildfly, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Specify your mail subsystem in the \texttt{\$WILDFLY\_HOME/standalone/configuration/standalone.xml} file like this: \begin{verbatim} ... ... \end{verbatim} \item In your \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file in Liferay Home, reference your mail session: \begin{verbatim} mail.session.jndi.name=java:jboss/mail/MailSession \end{verbatim} \end{enumerate} Next, you'll deploy Liferay DXP to your Wildfly app server. \section{Deploying Liferay DXP}\label{deploying-liferay-dxp-2} Now you're ready to deploy Liferay DXP using the @product@ WAR file. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If the folder \texttt{\$WILDFLY\_HOME/standalone/deployments/ROOT.war} already exists in your Wildfly installation, delete all of its subfolders and files. Otherwise, create a new folder called \texttt{\$WILDFLY\_HOME/standalone/deployments/ROOT.war}. \item Unzip the Liferay DXP \texttt{.war} file into the \texttt{ROOT.war} folder. \item To trigger deployment of \texttt{ROOT.war}, create an empty file named \texttt{ROOT.war.dodeploy} in your \texttt{\$WILDFLY\_HOME/standalone/deployments/} folder. On startup, Wildfly detects this file and deploys it as a web application. \item Start the Wildfly application server by navigating to \texttt{\$WILDFLY\_HOME/bin} and running \texttt{standalone.bat} or \texttt{standalone.sh}. \end{enumerate} Congratulations; you've deployed Liferay DXP on Wildfly! \noindent\hrulefill \textbf{Note:} After deploying Liferay DXP, you may see excessive warnings and log messages, such as the ones below, involving \texttt{PhaseOptimizer}. These are benign and can be ignored. Make sure to adjust your app server's logging level or log filters to avoid excessive benign log messages. \begin{verbatim} May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass gatherExternProperties May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass checkControlFlow May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] \end{verbatim} \chapter{Installing Liferay DXP on JBoss EAP}\label{installing-liferay-dxp-on-jboss-eap} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Installing Liferay DXP on JBoss EAP 7.1 takes three steps: \begin{itemize} \tightlist \item Installing dependencies to your application server \item Configuring your application server for Liferay DXP \item Installing the Liferay DXP WAR file to your application server \end{itemize} \noindent\hrulefill \textbf{Important:} Before installing Liferay DXP, familiarize yourself with \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{preparing for install}. \noindent\hrulefill Now, \href{/docs/7-2/deploy/-/knowledge_base/d/obtaining-product\#downloading-the-liferay-war-and-dependency-jars}{download the Liferay DXP WAR and Dependency JARs}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} Not that \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}} is the folder containing your JBoss server folder. After installing and deploying Liferay DXP, the Liferay Home folder contains the JBoss server folder as well as \texttt{data}, \texttt{deploy}, \texttt{logs}, and \texttt{osgi} folders. \texttt{\$JBOSS\_HOME} refers to your JBoss server folder. This folder is usually named \texttt{jboss-eap-{[}version{]}}. \section{Installing Dependencies}\label{installing-dependencies-2} Liferay DXP depends on several Liferay-specific and third-party JARs. Download and install the required JARs as described below. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create the folder \texttt{\$JBOSS\_HOME/modules/com/liferay/portal/main} if it doesn't exist and extract the JARs from the dependencies ZIP to it. \item Download your database driver \texttt{.jar} file and copy it into the same folder. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item Create the file \texttt{module.xml} in the \texttt{\$JBOSS\_HOME/modules/com/liferay/portal/main} folder and insert this configuration: \begin{verbatim} \end{verbatim} Replace \texttt{{[}place\ your\ database\ vendor\textquotesingle{}s\ jar\ here{]}} with the driver JAR for your database. \item Create an \texttt{osgi} folder in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder. Extract the OSGi Dependencies ZIP file that you downloaded into the \texttt{{[}Liferay\ Home{]}/osgi} folder. The \texttt{osgi} folder provides the necessary modules for Liferay DXP's OSGi runtime. \end{enumerate} \textbf{Checkpoint:} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item The dependencies files have been unzipped into the \texttt{\$JBOSS\_HOME/modules/com/liferay/portal/main} folder and a database jar. \item The \texttt{module.xml} contains all JARs in the \texttt{\textless{}resource-root-path\textgreater{}} elements. \item The \texttt{osgi} dependencies have been unzipped into the \texttt{osgi} folder. \end{enumerate} \section{Running Liferay DXP on JBoss EAP in Standalone Mode vs.~Domain Mode}\label{running-liferay-dxp-on-jboss-eap-in-standalone-mode-vs.-domain-mode} JBoss EAP can be launched in either \emph{standalone} mode or \emph{domain} mode. Domain mode allows multiple application server instances to be managed from a single control point. A collection of such application servers is known as a \emph{domain}. For more information on standalone mode vs.~domain mode, please refer to the section on this topic in the \href{https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.1/html/introduction_to_jboss_eap/overview_of_jboss_eap\#operating_modes}{JBoss EAP Product Documentation}. Liferay DXP supports JBoss EAP when it runs in standalone mode but not when it runs in domain mode. Liferay DXP's hot-deploy does not work with a managed deployment, since JBoss manages the content of a managed deployment by copying files (exploded or non-exploded). This prevents JSP hooks and Ext plugins from working as intended. For example, JSP hooks don't work on JBoss EAP running in managed domain mode, since Liferay DXP's JSP override mechanism relies on the application server. Since JSP hooks and Ext plugins are deprecated, however, you may not be using them. The command line interface is recommended for domain mode deployments. \noindent\hrulefill \textbf{Note:} This does not prevent Liferay DXP from running in a clustered environment on multiple JBoss servers. You can set up a cluster of Liferay DXP instances running on JBoss EAP servers running in standalone mode. Please refer to the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP clustering articles} for more information. \noindent\hrulefill \section{Configuring JBoss}\label{configuring-jboss} Configuring JBoss to run Liferay DXP includes these things: \begin{itemize} \tightlist \item Setting environment variables \item Setting properties and descriptors \item Removing unnecessary configurations \end{itemize} Optionally, you can configure JBoss to manage Liferay DXP's data source and mail session. Start with configuring JBoss to run Liferay DXP. Make the following modifications to \texttt{\$JBOSS\_HOME/standalone/configuration/standalone.xml}: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Locate the closing \texttt{\textless{}/extensions\textgreater{}} tag. Directly beneath that tag, insert these system properties: \begin{verbatim} \end{verbatim} \item Add the following \texttt{\textless{}filter-spec\textgreater{}} tag within the \texttt{\textless{}console-handler\textgreater{}} tag, directly below the \texttt{\textless{}level\ name="INFO"/\textgreater{}} tag: \begin{verbatim} \end{verbatim} \item Add a timeout for the deployment scanner by setting \texttt{deployment-timeout="600"} as seen in the excerpt below. \begin{verbatim} \end{verbatim} \item Add the following JAAS security domain to the security subsystem \texttt{\textless{}security-domains\textgreater{}} defined in element \texttt{\textless{}subsystem\ xmlns="urn:jboss:domain:security:2.0"\textgreater{}}. \begin{verbatim} \end{verbatim} \item Remove the welcome content code snippets: \begin{verbatim} \end{verbatim} and \begin{verbatim} \end{verbatim} \item Find the \texttt{\textless{}jsp-config/\textgreater{}} tag and set the \texttt{development}, \texttt{source-vm}, and \texttt{target-vm} attributes in the tag. Once finished, the tag should look like this: \begin{verbatim} \end{verbatim} \end{enumerate} \textbf{Checkpoint:} Before continuing, verify the following properties have been set in the \texttt{standalone.xml} file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The new \texttt{\textless{}system-property\textgreater{}} is added. \item The new \texttt{\textless{}filter-spec\textgreater{}} is added. \item The \texttt{\textless{}deployment-timeout\textgreater{}} is set to \texttt{360}. \item The new \texttt{\textless{}security-domain\textgreater{}} is created. \item Welcome content is removed. \item The \texttt{\textless{}jsp-config\textgreater{}} tag contains its new attributes. \end{enumerate} Now you should configure your JVM and startup scripts. In the \texttt{\$WILDFLY\_HOME/bin/} folder, modify your standalone domain's configuration script file \texttt{standalone.conf} (\texttt{standalone.conf.bat} on Windows): \begin{itemize} \tightlist \item Set the file encoding to \texttt{UTF-8} \item Set the user time zone to \texttt{GMT} \item Set the preferred protocol stack \item Increase the default amount of memory available. \end{itemize} \noindent\hrulefill \textbf{Important:} For Liferay DXP to work properly, the application server JVM must use the \texttt{GMT} time zone and \texttt{UTF-8} file encoding. \noindent\hrulefill Make the following edits as applicable to your operating system: \textbf{Windows} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Comment out the initial \texttt{JAVA\_OPTS} assignment as demonstrated in the following line: \begin{verbatim} rem set "JAVA_OPTS=-Xms1G -Xmx1G -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m" \end{verbatim} \item Add the following \texttt{JAVA\_OPTS} assignment one line above the \texttt{:JAVA\_OPTS\_SET} line found at end of the file: \begin{verbatim} set "JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=768m" \end{verbatim} \end{enumerate} \textbf{Unix} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Below the \texttt{if\ {[}\ "x\$JAVA\_OPTS"\ =\ "x"\ {]};} statement, replace this \texttt{JAVA\_OPTS} statement: \begin{verbatim} JAVA_OPTS="-Xms1303m -Xmx1303m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m -Djava.net.preferIPv4Stack=true" \end{verbatim} with this: \begin{verbatim} JAVA_OPTS="-Djava.net.preferIPv4Stack=true" \end{verbatim} \item Add the following statement to the bottom of the file: \begin{verbatim} JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" \end{verbatim} \end{enumerate} On JDK 11, it's recommended to add this JVM argument to display four-digit years. \begin{verbatim} -Djava.locale.providers=JRE,COMPAT,CLDR \end{verbatim} \noindent\hrulefill \textbf{Note:} If you plan on using the IBM JDK with your JBoss server, you must complete some additional steps. First, navigate to the \texttt{\$JBOSS\_HOME/modules/com/liferay/portal/main/module.xml} file and insert the following dependency within the \texttt{\textless{}dependencies\textgreater{}} element: \begin{verbatim} \end{verbatim} Then navigate to the \texttt{\$JBOSS\_HOME/modules/system/layers/base/sun/jdk/main/module.xml} file and insert the following path names inside the \texttt{\textless{}paths\textgreater{}...\textless{}/paths\textgreater{}} element: \begin{verbatim} \end{verbatim} The added paths resolve issues with portal deployment exceptions and image uploading problems. \noindent\hrulefill \textbf{Checkpoint:} At this point, you've finished configuring the application server's JVM settings. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The file encoding, user time-zone, preferred protocol stack have been set in the \texttt{JAVA\_OPTS} in the \texttt{standalone.conf.bat} file. \item The default amount of memory available has been increased. \end{enumerate} The prescribed script modifications are now complete for your Liferay DXP installation on JBoss. Next you'll configure the database and mail. \section{Database Configuration}\label{database-configuration-2} The easiest way to handle your database configuration is to let Liferay DXP manage your data source. The \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#using-the-built-in-data-source}{Basic Configuration} page lets you configure Liferay DXP's built-in data source. If you want to use the built-in data source, skip this section. This section demonstrates configuring a MySQL database. If you're using a different database, modify the data source and driver snippets as necessary. If using JBoss to manage the data source, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the data source inside the \texttt{\$JBOSS\_HOME/standalone/configuration/standalone.xml} file's the \texttt{\textless{}datasources\textgreater{}} element. \begin{verbatim} [place the URL to your database here] [place the driver name here] [place your user name here] [place your password here] \end{verbatim} Make sure to replace the database URL, user name, and password with the appropriate values. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If the data source `jndi-name` must be changed, edit the `datasource` element in the `` tag. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Add your driver to the \texttt{standalone.xml} file's \texttt{\textless{}drivers\textgreater{}} element also found within the \texttt{\textless{}datasources\textgreater{}} element. \begin{verbatim} [place your JDBC driver class here] \end{verbatim} A final data sources subsystem that uses MySQL should look like this: \begin{verbatim} jdbc:mysql://localhost/lportal mysql root root \end{verbatim} \item In a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file in your Liferay Home, specify your data source: \begin{verbatim} jdbc.default.jndi.name=java:jboss/datasources/ExampleDS \end{verbatim} \end{enumerate} Now that you've configured your data source, the mail session is next. \section{Mail Configuration}\label{mail-configuration-2} As with database configuration, the easiest way to configure mail is to let Liferay DXP handle your mail session. If you want to use @product@'s built-in mail session, skip this section and \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{configure the mail session} in the Control Panel. If you want to manage your mail session with JBoss, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Specify your mail subsystem in the \texttt{\$JBOSS\_HOME/standalone/configuration/standalone.xml} file like this: \begin{verbatim} ... ... \end{verbatim} \item In your \texttt{portal-ext.properties} file in Liferay Home, reference your mail session: \begin{verbatim} mail.session.jndi.name=java:jboss/mail/MailSession \end{verbatim} \end{enumerate} You've got mail! Next, you'll deploy Liferay DXP to your JBoss app server. \section{Deploy Liferay}\label{deploy-liferay} Now you're ready to deploy Liferay DXP using the @product@ WAR file. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If the folder \texttt{\$JBOSS\_HOME/standalone/deployments/ROOT.war} already exists in your JBoss installation, delete all of its subfolders and files. Otherwise, create a new folder called \texttt{\$JBOSS\_HOME/standalone/deployments/ROOT.war}. \item Unzip the Liferay DXP \texttt{.war} file into the \texttt{ROOT.war} folder. \item To trigger deployment of \texttt{ROOT.war}, create an empty file named \texttt{ROOT.war.dodeploy} in your \texttt{\$JBOSS\_HOME/standalone/deployments/} folder. On startup, JBoss detects this file and deploys it as a web application. \item Start the JBoss application server by navigating to \texttt{\$JBOSS\_HOME/bin} and running \texttt{standalone.bat} or \texttt{standalone.sh}. \end{enumerate} Congratulations; you've now deployed Liferay DXP on JBoss! \noindent\hrulefill After deploying Liferay DXP, you may see excessive warnings and log messages, such as the ones below, involving \texttt{PhaseOptimizer}. These are benign and can be ignored. Make sure to adjust your app server's logging level or log filters to avoid excessive benign log messages. \begin{verbatim} May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass gatherExternProperties May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass checkControlFlow May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] \end{verbatim} \chapter{Installing Liferay DXP on WebLogic 12c R2}\label{installing-liferay-dxp-on-weblogic-12c-r2} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Although you can install Liferay DXP in a WebLogic Admin Server, this isn't recommended. It's a best practice to install web apps, including Liferay DXP, in a WebLogic Managed server. Deploying to a Managed Server lets you start or shut down Liferay DXP more quickly and facilitates transitioning into a cluster configuration. This article therefore focuses on installing Liferay DXP in a Managed Server. Before getting started, create your Admin and Managed Servers. See \href{http://www.oracle.com/technetwork/middleware/weblogic/documentation/index.html}{WebLogic's documentation} for instructions on setting up and configuring Admin and Managed Servers. Also familiarize yourself with \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{preparing for install}. Now, \href{/docs/7-2/deploy/-/knowledge_base/d/obtaining-product\#downloading-the-liferay-war-and-dependency-jars}{download the Liferay DXP WAR and Dependency JARs}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} \section{Configuring WebLogic's Node Manager}\label{configuring-weblogics-node-manager} WebLogic's Node Manager starts and stops managed servers. If you're running WebLogic on a UNIX system other than Solaris or Linux, use the Java Node Manager, instead of the native version of the Node Manager, by configuring these Node Manager properties in the \texttt{domains/your\_domain\_name/nodemanager/nodemanager.properties} file: \begin{verbatim} NativeVersionEnabled=false StartScriptEnabled=true \end{verbatim} \noindent\hrulefill \textbf{Note:} By default, SSL is used with Node Manager. If you want to disable SSL during development, for example, set \texttt{SecureListener=false} in your \texttt{nodemanager.properties} file. \noindent\hrulefill See Oracle's \href{https://docs.oracle.com/middleware/1212/wls/NODEM/java_nodemgr.htm\#NODEM173}{Configuring Java Node Manager} documentation for details. \section{Configuring WebLogic}\label{configuring-weblogic} Next, you must set some variables in two WebLogic startup scripts. These variables and scripts are as follows. Be sure to use \texttt{set} instead of \texttt{export} if you're on Windows. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \texttt{your-domain/startWebLogic.{[}cmd\textbar{}sh{]}}: This is the Admin Server's startup script. \item \texttt{your-domain/bin/startWebLogic.{[}cmd\textbar{}sh{]}}: This is the startup script for Managed Servers. Add the following variables to both \texttt{startWebLogic.{[}cmd\textbar{}sh{]}} scripts: \begin{verbatim} export DERBY_FLAG="false" export JAVA_OPTIONS="${JAVA_OPTIONS} -Dfile.encoding=UTF-8 -Duser.timezone=GMT -da:org.apache.lucene... -da:org.aspectj..." export MW_HOME="/your/weblogic/directory" export USER_MEM_ARGS="-Xmx2560m -Xms2560m" \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Important:** For Liferay DXP to work properly, the application server JVM must use the `GMT` time zone and `UTF-8` file encoding. \end{verbatim} \noindent\hrulefill \begin{verbatim} The `DERBY_FLAG` setting disables the Derby server built in to WebLogic, as Liferay DXP doesn't require this server. The remaining settings support Liferay DXP's memory requirements, UTF-8 requirement, Lucene usage, and Aspect Oriented Programming via AspectJ. Also make sure to set `MW_HOME` to the directory containing your WebLogic server on your machine. For example: ```bash export MW_HOME="/Users/ray/Oracle/wls12210" ``` \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item Some of the settings are also found in the \texttt{your-domain/bin/SetDomainEnv.{[}cmd\textbar{}sh{]}} . Add the following variables (Windows): \begin{verbatim} set WLS_MEM_ARGS_64BIT=-Xms2560m -Xmx2560m set WLS_MEM_ARGS_32BIT=-Xms2560m -Xmx2560m \end{verbatim} or on Mac or Linux: \begin{verbatim} WLS_MEM_ARGS_64BIT="-Xms2560m -Xmx2560m" export WLS_MEM_ARGS_64BIT WLS_MEM_ARGS_32BIT="-Xms2560m -Xmx2560m" export WLS_MEM_ARGS_32BIT \end{verbatim} \item Set the Java file encoding to UTF-8 in \texttt{your-domain/bin/SetDomainEnv.{[}cmd\textbar{}sh{]}} by appending \texttt{-Dfile.encoding=UTF-8} ahead of your other Java properties: \begin{verbatim} JAVA_PROPERTIES="-Dfile.encoding=UTF-8 ${JAVA_PROPERTIES} ${CLUSTER_PROPERTIES}" \end{verbatim} \item You must also ensure that the Node Manager sets Liferay DXP's memory requirements when starting the Managed Server. In the Admin Server's console UI, navigate to the Managed Server you want to deploy Liferay DXP to and select the \emph{Server Start} tab. Enter the following parameters into the \emph{Arguments} field: \begin{verbatim} -Xmx2560m -Xms2560m -XX:MaxMetaspaceSize=512m \end{verbatim} Click \emph{Save} when you're finished. \end{enumerate} Next, you'll set some Liferay DXP-specific properties for your @product@ installation. \section{Setting Liferay DXP Properties}\label{setting-liferay-dxp-properties} Before installing Liferay DXP, you must set the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}} folder's location via the \texttt{liferay.home} property in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file. You can also use this file to override \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{other Liferay DXP properties} that you may need. First, decide which folder you want to serve as Liferay Home. In WebLogic, your domain's folder is generally Liferay Home, but you can choose any folder on your machine. Then create your \texttt{portal-ext.properties} file and add the \texttt{liferay.home} property: \begin{verbatim} liferay.home=/full/path/to/your/liferay/home/folder \end{verbatim} Remember to change this file path to the location on your machine that you want to serve as Liferay Home. Now that you've created your \texttt{portal-ext.properties} file, you must put it inside the Liferay DXP WAR file. Expand the @product@ WAR file and place \texttt{portal-ext.properties} in the \texttt{WEB-INF/classes} folder. Later, you can deploy the expanded archive to WebLogic. Alternatively, you can re-WAR the expanded archive for later deployment. In either case, Liferay DXP reads your property settings once it starts up. If you need to make any changes to \texttt{portal-ext.properties} after Liferay DXP deploys, you can find it in your domain's \texttt{autodeploy/ROOT/WEB-INF/classes} folder. Note that the \texttt{autodeploy/ROOT} folder contains the Liferay DXP deployment. Next, you'll install Liferay DXP's dependencies. \section{Installing Liferay DXP Dependencies}\label{installing-liferay-dxp-dependencies} You must now install Liferay DXP's dependencies. Recall that earlier you downloaded two ZIP files containing these dependencies. Install their contents now: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Unzip the Dependencies ZIP file and place its contents in your WebLogic domain's \texttt{lib} folder. \item Unzip the OSGi Dependencies ZIP file and place its contents in the \texttt{Liferay\_Home/osgi} folder (create this folder if it doesn't exist). \end{enumerate} You must also add your database's driver JAR file to your domain's \texttt{lib} folder. \noindent\hrulefill \textbf{Note:} Although Hypersonic is fine for testing purposes, \textbf{do not} use it for production Liferay DXP instances. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} The \href{https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?}{Liferay DXP Compatibility Matrix} specifies supported databases and environments. \noindent\hrulefill Next, you'll configure your database. \section{Database Configuration}\label{database-configuration-3} Use the following procedure if you want WebLogic to manage your \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#using-the-built-in-data-source}{database} for Liferay DXP. You can skip this section if you want to use @product@'s built-in Hypersonic database. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Log in to your AdminServer console. \item In the \emph{Domain Structure} tree, find your domain and navigate to \emph{Services} → \emph{JDBC} → \emph{Data Sources}. \item To create a new data source, click \emph{New}. Fill in the \emph{Name} field with \texttt{Liferay\ Data\ Source} and the \emph{JNDI Name} field with \texttt{jdbc/LiferayPool}. Select your database type and driver. For example, MySQL is \emph{MySQL's Driver (Type 4) Versions:using com.mysql.cj.jdbc.Driver}. Click \emph{Next} to continue. \item Accept the default settings on this page and click \emph{Next} to move on. \item Fill in your database information for your MySQL database. \item If using MySQL, add the text \texttt{?useUnicode=true\&characterEncoding=UTF-8\&\textbackslash{}useFastDateParsing=false} to the URL line and test the connection. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Tip:** For more example URLs, see the `jdbc.default.url` values in [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). \end{verbatim} \noindent\hrulefill \begin{verbatim} If the connection works, click *Next*. \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{6} \item Select the target for the data source and click \emph{Finish}. \item You must now tell Liferay DXP about the JDBC data source. Create a \texttt{portal-ext.propreties} file in your Liferay Home directory, and add the line: \begin{verbatim} jdbc.default.jndi.name=jdbc/LiferayPool \end{verbatim} \end{enumerate} Alternatively, you can make the above configuration strictly via properties in the \texttt{portal-ext.properties} file. Please see the \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates} for example properties. Next, you'll configure your mail session. \section{Mail Configuration}\label{mail-configuration-3} If you want WebLogic to manage your \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{mail session}, use the following procedure. If you want to use Liferay's built-in mail session (recommended), skip this section. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Start WebLogic and log in to your Admin Server's console. \item Select \emph{Services} → \emph{Mail Sessions} from the \emph{Domain Structure} box on the left hand side of your Admin Server's console UI. \item Click \emph{New} to begin creating a new mail session. \item Name the session \emph{LiferayMail} and give it the JNDI name \texttt{mail/MailSession}. Then fill out the \emph{Session Username}, \emph{Session Password}, \emph{Confirm Session Password}, and \emph{JavaMail Properties} fields as necessary for your mail server. See the \href{http://docs.oracle.com/middleware/1221/wls/FMWCH/pagehelp/Mailcreatemailsessiontitle.html}{WebLogic documentation} for more information on these fields. Click \emph{Next} when you're done. \item Choose the Managed Server that you'll install Liferay DXP on, and click \emph{Finish}. Then shut down your Managed and Admin Servers. \item With your Managed and Admin servers shut down, add the following property to your \texttt{portal-ext.properties} file in Liferay Home: \begin{verbatim} mail.session.jndi.name=mail/MailSession \end{verbatim} \end{enumerate} Liferay DXP references your WebLogic mail session via this property setting. If you've already deployed Liferay DXP, you can find your \texttt{portal-ext.properties} file in your domain's \texttt{autodeploy/ROOT/WEB-INF/classes} folder. Your changes take effect upon restarting your Managed and Admin servers. \section{Deploying Liferay DXP}\label{deploying-liferay-dxp-3} As mentioned earlier, although you can deploy Liferay DXP to a WebLogic Admin Server, you should instead deploy it to a WebLogic Managed Server. Dedicating the Admin Server to managing other servers that run your apps is a best practice. Follow these steps to deploy Liferay DXP to a Managed Server: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Make sure the Managed Server you want to deploy Liferay DXP to is shut down. \item In your Admin Server's console UI, select \emph{Deployments} from the \emph{Domain Structure} box on the left hand side. Then click \emph{Install} to start a new deployment. \item Select the Liferay DXP WAR file or its expanded contents on your file system. Alternatively, you can upload the WAR file by clicking the \emph{Upload your file(s)} link. Click \emph{Next}. \item Select \emph{Install this deployment as an application} and click \emph{Next}. \item Select the Managed Server you want to deploy Liferay DXP to and click \emph{Next}. \item If the default name is appropriate for your installation, keep it. Otherwise, give it a name of your choosing and click \emph{Next}. \item Click \emph{Finish}. After the deployment finishes, click \emph{Save} if you want to save the configuration. \item Start the Managed Server where you deployed Liferay DXP. @product@ precompiles all the JSPs and then launches. \end{enumerate} Nice work! Now you're running Liferay DXP on WebLogic. \noindent\hrulefill After deploying Liferay DXP, you may see excessive warnings and log messages, such as the ones below, involving \texttt{PhaseOptimizer}. These are benign and can be ignored. Make sure to adjust your app server's logging level or log filters to avoid excessive benign log messages. \begin{verbatim} May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass gatherExternProperties May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass checkControlFlow May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] \end{verbatim} \chapter{Installing Liferay DXP on WebSphere}\label{installing-liferay-dxp-on-websphere} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} IBM ® WebSphere ® is a trademark of International Business Machines Corporation, registered in many jurisdictions worldwide. \noindent\hrulefill \textbf{Tip:} Throughout this installation and configuration process, WebSphere prompts you to click \emph{Save} to apply changes to the Master Configuration. Do \textbar{} so intermittently to save your changes. \noindent\hrulefill For Liferay DXP to work correctly, WebSphere 9 (Fix Pack 11 is the latest) must be installed. You can find more information about this fix pack \href{http://www-01.ibm.com/support/docview.wss?uid=swg24043005}{here}. Please also note that Liferay DXP doesn't support the WebSphere Application Liberty Profile. \noindent\hrulefill \textbf{Important:} Before installing Liferay DXP, familiarize yourself with \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install}{preparing for install}. \noindent\hrulefill Now, \href{/docs/7-2/deploy/-/knowledge_base/d/obtaining-product\#downloading-the-liferay-war-and-dependency-jars}{download the Liferay DXP WAR and Dependency JARs}: \begin{itemize} \tightlist \item Liferay DXP WAR file \item Dependencies ZIP file \item OSGi Dependencies ZIP file \end{itemize} Note that the \href{docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home} folder} is important to the operation of Liferay DXP. In Liferay Home, @product@ creates certain files and folders that it needs to run. On WebSphere, Liferay Home is typically \texttt{{[}Install\ Location{]}/WebSphere/AppServer/profiles/your-profile/liferay}. Without any further ado, get ready to install Liferay DXP in WebSphere! \section{Preparing WebSphere for Liferay DXP}\label{preparing-websphere-for-liferay-dxp} When the application server binaries have been installed, start the \emph{Profile Management Tool} to create a profile appropriate for Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Create\ldots{}}, choose \emph{Application Server}, and then click \emph{Next}. \item Click the \emph{Advanced} profile creation option and then click \emph{Next}. You need the advanced profile to specify your own values for settings such as the location of the profile and names of the profile, node and host, to assign your own ports, or to optionally choose whether to deploy the administrative console and sample application and also add web-server definitions for IBM HTTP Server. See the WebSphere documentation for more information about these options. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-01-profile.png}} \caption{Choose the Advanced profile option to specify your own settings.} \end{figure} \item Check the box \emph{Deploy the administrative console}. This gives you a web-based UI for working with your application server. Skip the default applications. You'd only install these on a development machine. Click \emph{Next}. \item Set the profile name and location. Ensure you specify a performance tuning setting other than \emph{Development}, since you're installing a production server. See the WebSphere documentation for more information about performance tuning settings. Click \emph{Next}. \item Choose node, server, and host names for your server. These are specific to your environment. Click \emph{Next}. \item Administrative security in WebSphere is a way to restrict who has access to the administrative tools. You may want to have it enabled in your environment so that a user name and password are required to administer the WebSphere server. See WebSphere's documentation for more information. Click \emph{Next}. \item Each profile needs a security certificate, which comes next in the wizard. If you don't have certificates already, choose the option to generate a personal certificate and a signing certificate and click \emph{Next}. \item Once the certificates are generated, set a password for your keystore. Click \emph{Next}. \item Now you can customize the ports this server profile uses. Be sure to choose ports that are open on your machine. When choosing ports, the wizard detects existing WebSphere installations and if it finds activity, it increments ports by one. \item Choose whether to start this profile when the machine starts. Click \emph{Next}. \item WebSphere ships with IBM HTTP Server, which is a re-branded version of Apache. Choose whether you want a web server definition, so that this JVM receives requests forwarded from the HTTP server. See WebSphere's documentation for details on this. When finished, click \emph{Next}. \item The wizard then shows you a summary of what you selected, enabling you to keep your choices or go back and change something. When you're satisfied, click \emph{Next}. \end{enumerate} WebSphere then creates your profile and finishes with a message telling you the profile was created successfully. Awesome! Your profile is complete. Now there are a few things you need to configure in your application server. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-02-profile.png}} \caption{Example of the settings before creating the profile.} \end{figure} \section{Configuring the WebSphere Application Server}\label{configuring-the-websphere-application-server} In this version of WebSphere, servlet filters are not initialized on web application startup, but rather, on first access. This can cause problems when deploying certain apps to Liferay DXP. To configure servlet filters to initialize on application startup (i.e., deployment), set the following \texttt{webcontainer} properties in your WebSphere application server: \begin{verbatim} com.ibm.ws.webcontainer.initFilterBeforeInitServlet = true com.ibm.ws.webcontainer.invokeFilterInitAtStartup = true \end{verbatim} To set \texttt{webcontainer} properties in the WebSphere application server, follow the instructions \href{http://www-01.ibm.com/support/docview.wss?rss=180&uid=swg21284395}{here in WebSphere's documentation}. \section{Setting up JVM Parameters for Liferay DXP}\label{setting-up-jvm-parameters-for-liferay-dxp} Next, in the WebSphere profile you created for Liferay DXP, you must set an argument that supports Liferay DXP's Java memory requirements. You'll modify this file: \begin{verbatim} [Install Location]/WebSphere/AppServer/profiles/your-profile/config/cells/your-cell/nodes/your-node/servers/your-server/server.xml \end{verbatim} Add \texttt{maximumHeapSize="2560"} inside the \texttt{jvmEntries} tag. For example: \begin{verbatim} \end{verbatim} \noindent\hrulefill \textbf{Note:} The JVM parameters used here are defaults intended for initial deployment of production systems. Administrators should change the settings to values that best address their specific environments. These must be tuned depending on need. \noindent\hrulefill Administrators can set the UTF-8 properties in the \texttt{\textless{}jvmEntries\ genericJvmArguments=.../\textgreater{}} attribute in \texttt{server.xml}. This is required or else special characters will not be parsed correctly. Set the maximum and minimum heap sizes to \texttt{2560m} there too. Add the following inside the \texttt{jvmEntries} tag: \begin{verbatim} \end{verbatim} \noindent\hrulefill \textbf{Important:} For Liferay DXP to work properly, the application server JVM must use the \texttt{GMT} time zone and \texttt{UTF-8} file encoding. \noindent\hrulefill Alternately, you can set the UTF-8 properties from the WebSphere Admin Console. (See below.) \section{Removing the secureSessionCookie Tag}\label{removing-the-securesessioncookie-tag} In the same profile, you should delete a problematic \texttt{secureSessionCookie} tag that can cause Liferay DXP startup errors. Note that this is just a default setting; once Liferay DXP is installed, you should tune it appropriately based on your usage. In \texttt{{[}Install\ Location{]}/WebSphere/AppServer/profiles/your-profile/config/cells/your-cell/cell.xml}, Delete the \texttt{secureSessionCookie} tag containing \texttt{xmi:id="SecureSessionCookie\_1"}. If this tag is not removed, an error similar to this may occur: \begin{verbatim} WSVR0501E: Error creating component com.ibm.ws.runtime.component.CompositionUnitMgrImpl@d74fa901 com.ibm.ws.exception.RuntimeWarning: com.ibm.ws.webcontainer.exception.WebAppNotLoadedException: Failed to load webapp: Failed to load webapp: SRVE8111E: The application, LiferayEAR, is trying to modify a cookie which matches a pattern in the restricted programmatic session cookies list [domain=*, name=JSESSIONID, path=/]. \end{verbatim} \section{Installing Liferay DXP's Dependencies}\label{installing-liferay-dxps-dependencies} You must now install Liferay DXP's dependencies. Recall that earlier you downloaded two ZIP files containing these dependencies. Install their contents now: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Unzip the Dependencies ZIP file and place its contents in your WebSphere application server's \texttt{{[}Install\ Location{]}/WebSphere/AppServer/lib/ext} folder. If you have a JDBC database driver \texttt{JAR}, copy it to this location as well. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item From the same archive, copy \texttt{portlet.jar}into \texttt{{[}Install\ Location{]}/WebSphere/AppServer/javaext} for WebSphere 9.0.0.x. WebSphere already contains an older version of \texttt{portlet.jar} which must be overridden at the highest class loader level. The new \texttt{portlet.jar} (version 3) is backwards-compatible. \item Unzip the OSGi Dependencies ZIP file and place its contents in the \texttt{{[}Liferay\ Home{]}/osgi} folder (create this folder if it doesn't exist). This is typically \texttt{{[}Install\ Location{]}/WebSphere/AppServer/profiles/your-profile/liferay/osgi}. \end{enumerate} \section{Ensuring that Liferay DXP's portlet.jar is loaded first}\label{ensuring-that-liferay-dxps-portlet.jar-is-loaded-first} In addition to placing the \texttt{portlet.jar} in the correct folder, you must configure the \texttt{config.ini} file so that it is loaded first. Navigate to \texttt{/IBM/WebSphere/AppServer/configuration/config.ini}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Find the property \texttt{com.ibm.CORBA,com.ibm} \item Insert the property \texttt{javax.portlet,javax.portlet.filter,javax.portlet.annotations} after \texttt{com.ibm.CORBA} and before \texttt{com.ibm}. \item Save the file. \end{enumerate} Once you've installed these dependencies and configured the \texttt{config.ini} file, start the server profile you created for Liferay DXP. Once it starts, you're ready to configure your database. \section{Database Configuration}\label{database-configuration-4} If you want WebSphere to manage the database connections, follow the instructions below. Note this is not necessary if you plan to use Liferay DXP's standard database configuration; in that case, skip this section. See the \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#using-the-built-in-data-source}{Using the Built-in Data Sources} section for more article. You'll set your database information in Liferay DXP's setup wizard after the install. \noindent\hrulefill \textbf{Note:} Although Liferay DXP's embedded database is fine for testing purposes, you \textbf{should not} use it for production Liferay DXP instances. \noindent\hrulefill \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-jdbc-providers.png}} \caption{WebSphere JDBC providers} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Start WebSphere. \item Open the Administrative Console and log in. \item Click \emph{Resources → JDBC Providers}. \item Select a scope and then click \emph{New}. \item Select your database type, provider type, and implementation type. If you select a predefined database, the wizard fills in the name and description fields for you. If the database you want to use isn't listed, select \emph{User-defined} from the \emph{Database type} field and then fill in the \emph{Implementation Class Name}. For example, if you use MySQL, select \emph{Database type} → \emph{User-defined}, and then enter \texttt{com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource} in \emph{Implementation Class Name}. Click \emph{Next} when you are finished. \item Clear any text in the class path settings. You already copied the necessary JARs to a location on the server's class path. Click \emph{Next}. \item Review your settings and click \emph{Finish}. The final configuration should look like this: \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-03.png}} \caption{Completed JDBC provider configurations.} \end{figure} \item Click your new provider configuration when it appears in the table, and then click \emph{Data Sources} under \emph{Additional Properties}. Click \emph{New}. \item Enter \emph{liferaydatabasesource} in the \emph{Data source name} field and \texttt{jdbc/LiferayPool} in the \emph{JNDI name} field. Click \emph{Next}. \item Click \emph{Next} in the remaining screens of the wizard to accept the default values. Then review your changes and click \emph{Finish}. \item Click the data source when it appears in the table and then click \emph{Custom Properties}. Now click the \emph{Show Filter Function} button. This is the second from last of the small icons under the \emph{New} and \emph{Delete} buttons. \item Type \emph{user} into the search terms and click \emph{Go}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-database-properties.png}} \caption{Modifying data source properties in WebSphere} \end{figure} \item Select the \emph{user} property and give it the value of the user name to your database. Click \emph{OK} and save to master configuration. \item Do another filter search for the \emph{url} property. Give this property a value that points to your database. For example, a MySQL URL would look like this: \begin{verbatim} jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT&useFastDateParsing=false \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Tip:** For more example URLs, see the `jdbc.default.url` values in [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). \end{verbatim} \noindent\hrulefill \begin{verbatim} Click *OK* and save to master configuration. \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{14} \item Do another filter search for the \emph{password} property. Enter the password for the user ID you added earlier as the value for this property. Click \emph{OK} and save to master configuration. \item Go back to the data source page by clicking it in the breadcrumb trail. Click the \emph{Test Connection} button. It should connect successfully. \end{enumerate} Once you've set up your database, you can set up your mail session. \section{Mail Configuration}\label{mail-configuration-4} If you want WebSphere to manage your mail sessions, use the following procedure. If you want to use Liferay DXP's built-in mail sessions, you can skip this section. See the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-mail}{Configuring Mail} article on how to use Liferay DXP's built-in mail sessions. \section{Creating a WebSphere-Managed Mail Session (Optional)}\label{creating-a-websphere-managed-mail-session-optional} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Resources → Mail → Mail Providers}. \item Click the Built-In Mail Provider for your node and server. \item Click \emph{Mail Sessions} and then click the \emph{New} button. \item Give your mail session a name of \emph{liferaymail} and a JNDI name of \texttt{mail/MailSession}. Fill in the correct information for your mail server in the sections \emph{Outgoing Mail Properties} and \emph{Incoming Mail Properties}. Click \emph{OK} and then save to the master configuration. \item Click your mail session when it appears in the table and select \emph{Custom Properties} under the \emph{Additional Properties} section. Set any other JavaMail properties required by your mail server, such as the protocol, ports, whether to use SSL, and so on. \item Click \emph{Security → Global Security} and de-select \emph{Use Java 2 security to restrict application access to local resources} if it is selected. Click \emph{Apply}. \end{enumerate} Note that you may also need to retrieve a SSL certificate from your mail server and add it to WebSphere's trust store. See WebSphere's documentation for instructions on this. \section{Verifying WebSphere Mail Provider}\label{verifying-websphere-mail-provider} To validate that the mail session has been configured correctly, there are a number of ways to test this once the WAR has been deployed, the server has started, and the user has signed in as the system administrator. One quick way to validate is to create a new user with a valid email account. The newly created user should receive an email notification. The logs should display that the SMTP server has been pinged with the correct port number listed. \section{Enable Cookies for HTTP Sessions}\label{enable-cookies-for-http-sessions} WebSphere restricts cookies to HTTPS sessions by default. If you're using HTTP instead, this prevents users from signing in to Liferay DXP and displays the following error in the console: \begin{verbatim} 20:07:14,021 WARN [WebContainer : 1][SecurityPortletContainerWrapper:341] User 0 is not allowed to access URL http://localhost:9081/web/guest/home and portlet com_liferay_login_web_portlet_LoginPortlet \end{verbatim} This occurs because Liferay DXP can't use the HTTPS cookie when you use HTTP. The end result is that new sessions are created on each page refresh. Follow these steps to resolve this issue in WebSphere: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Application Servers} → \emph{server1} → \emph{Session Management} → Enable Cookies \item De-select \emph{Restrict cookies to HTTPS sessions} \item Click \emph{Apply} \item Click \emph{Save} \end{enumerate} \section{Enable UTF-8}\label{enable-utf-8} If you did not add the \texttt{-Dfile.encoding=UTF-8} property in the \texttt{server.xml}, you can do so in the Administrative Console. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Application Servers} → \emph{server1} → \emph{Process definition}. \item Click \emph{Java Virtual Machine} under \emph{Additional Properties}. \item Enter \texttt{-Dfile.encoding=UTF-8} in the \emph{Generic JVM arguments} field. \item Click \emph{Apply} and then \emph{Save} to master configuration. \end{enumerate} Once the changes have been saved, Liferay DXP can parse special characters if there is localized content. \section{Deploy Liferay DXP}\label{deploy-liferay-dxp} Now you're ready to deploy Liferay DXP! \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In WebSphere's administrative console, click \emph{Applications} → \emph{New Application} → \emph{New Enterprise Application}. \item Browse to the Liferay DXP \texttt{.war} file, select it, and click \emph{Next}. \item Leave \emph{Fast Path} selected and click \emph{Next}. Ensure that \emph{Distribute Application} has been checked and click \emph{Next} again. \item Choose the WebSphere runtimes and/or clusters where you want Liferay DXP deployed. Click \emph{Next}. \item Select the virtual host to deploy Liferay DXP on and click \emph{Next}. \item Map Liferay DXP to the root context (\texttt{/}) and click \emph{Next}. \item Select the \emph{metadata-complete attribute} setting that you want to use and click \emph{Next}. \item Ensure that you have made all the correct choices and click \emph{Finish}. When Liferay DXP has installed, click \emph{Save to Master Configuration}. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/websphere-deploy-dxp.png}} \caption{Review your deployment options before deploying.} \end{figure} You've now installed Liferay DXP! \section{Setting the JDK Version for Compiling JSPs}\label{setting-the-jdk-version-for-compiling-jsps} Liferay DXP requires that its JSPs are compiled to the Java 8 bytecode format. To ensure that WebSphere does this, shut down WebSphere after you've deployed the Liferay DXP \texttt{.war} file. Navigate to the \texttt{WEB\_INF} folder and add the following setting to the \texttt{ibm-web-ext.xml} or in most cases the \texttt{ibm-web-ext.xmi} file: \begin{verbatim} \end{verbatim} The exact path to the \texttt{ibm-web-ext.xmi} file depends on your WebSphere installation location and Liferay DXP version, but here's an example: \begin{verbatim} /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/config/cells/localhostNode01Cell/applications/liferayXX.ear/deployments/liferayXX/liferayXX.war/WEB-INF/ibm-web-ext.xmi \end{verbatim} Note that the Liferay DXP \texttt{.war} comes pre-packaged with the \texttt{ibm-web-ext.xmi} file; this format is functionally the same as \texttt{.xml} and WebSphere recognizes both formats. For more general information on how WebSphere compiles JSPs see IBM's official documentation for \href{https://www.ibm.com/support/knowledgecenter/en/SSEQTP_9.0.0/com.ibm.websphere.base.doc/ae/rweb_jspengine.html}{WebSphere Application Server 9.0.0.x}. \section{Start Liferay DXP}\label{start-liferay-dxp} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you plan to use Liferay DXP's {[}setup wizard{]}(/docs/7-2/deploy/-/knowledge\_base/d/installing-product\#using-the-setup-wizard), skip to the next step. If you wish to use WebSphere's data source and mail session, create a file called \texttt{portal-ext.properties} in your Liferay Home folder. Place the following configuration in the file: \begin{verbatim} jdbc.default.jndi.name=jdbc/LiferayPool mail.session.jndi.name=mail/MailSession setup.wizard.enabled=false \end{verbatim} \item In the WebSphere administrative console, navigate to \emph{Enterprise Applications}, select the Liferay DXP application, and click \emph{Start}. While Liferay DXP is starting, WebSphere displays a spinning graphic. \item In Liferay DXP's setup wizard, select and configure your database type. Click \emph{Finish} when you're done. Liferay DXP then creates the tables it needs in the database. \end{enumerate} Congratulations! You've installed Liferay DXP on WebSphere! \noindent\hrulefill After deploying Liferay DXP, you may see excessive warnings and log messages, such as the ones below, involving \texttt{PhaseOptimizer}. These are benign and can be ignored. Make sure to adjust your app server's logging level or log filters to avoid excessive benign log messages. \begin{verbatim} May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass gatherExternProperties May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process WARNING: Skipping pass checkControlFlow May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] \end{verbatim} \chapter{Activating Liferay DXP}\label{activating-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} There are two ways to activate your Liferay DXP instance: \begin{itemize} \item With an XML activation key that you request and receive from Liferay Support. \item Online activation through Liferay Connected Services (LCS). Liferay DXP 7.0 introduced LCS as a way to activate Liferay DXP instances. LCS can also install fix packs, monitor each instance's performance, and help administrators automatically manage Liferay DXP subscriptions. See the \href{/docs/7-1/deploy/-/knowledge_base/d/managing-liferay-dxp-with-liferay-connected-services}{LCS documentation} for instructions on activating your instances with LCS. \end{itemize} \noindent\hrulefill \textbf{Note:} You must use LCS for activation of Elastic subscriptions. Otherwise, you don't have to use LCS for activation. You can instead request an XML activation key from Liferay Support. \noindent\hrulefill \chapter{Setting Up Marketplace}\label{setting-up-marketplace} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} \href{https://www.liferay.com/marketplace}{Liferay Marketplace} is more than just a store for Liferay applications. Under the hood, it provides both the store and Liferay DXP's application deployment features. For this reason, you must ensure that Marketplace can run and configure itself. Here are some scenarios to work around to ensure Marketplace works successfully: \begin{itemize} \tightlist \item Server is Firewalled without Access to the Internet \item Limited Database Access \end{itemize} The firewall scenario is discussed first. \section{Server is Firewalled without Access to the Internet}\label{server-is-firewalled-without-access-to-the-internet} Your server might be behind a firewall that prevents access to the Internet. Or your security policy might not allow direct download and installation from the Internet. In these cases, you have two options: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From an Internet-enabled computer, download the \href{https://www.liferay.com/marketplace/download}{Marketplace plugin}. Then deploy the plugin (\texttt{.lpkg} file) by copying it into the \texttt{deploy} folder in \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}. \item Alternately, once you have the downloaded \texttt{.lpkg} file, deploy it using the \href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps}{App Manager}. \end{enumerate} Next you'll learn how to work around database access restrictions. \section{Limited Database Access}\label{limited-database-access} Some production environments do not have the necessary database permissions for Liferay DXP, apps, modules, and plugins to maintain their tables. In these cases: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Grant the Liferay DXP database user temporary full rights to the database. \item Install Liferay DXP and start it so that it populates its database. \item Once the database is created, remove the permissions for creating tables and dropping tables from the Liferay DXP database user. \end{enumerate} See the \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install\#limiting-database-access}{database preparation instructions} for more information. Note that many sophisticated Liferay DXP apps---not just the Marketplace app---require new tables when deployed. If your environment restricts database access, you may need to repeat the above steps whenever you deploy a new app. You've prepared Liferay DXP for installing Marketplace and additional apps. \chapter{Trial Plugin Installation}\label{trial-plugin-installation} {This document has been updated and ported to \textless a href=``https://learn.liferay.com/dxp/latest/en/system-administration/installing-and-managing-apps/installing-apps/accessing-ee-plugins-during-a-trial-period.html) and is no longer maintained here.} For Liferay customers who are evaluating Liferay DXP on a trial basis, \textbf{the plugins can be accessed from within the \emph{Apps} → \emph{Store} (i.e., Marketplace) section of the Control Panel in your product installation}. \section{Installation Process}\label{installation-process} Follow the steps below to install a trial plugin: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Register a \texttt{liferay.com} account (LRDC) account by visiting Liferay's home page (if necessary). Do this by clicking \emph{Sign In/Create Account} button from the top right Profile button. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/liferay-com-sign-in.png}} \caption{Hover over the Profile button and click \emph{Sign In/Create Account}.} \end{figure} \item Start your Liferay DXP instance (trial license is OK). \item After signing in as an Admin in your Liferay DXP trial server, go to the Control Panel → \emph{Apps} → \emph{Store} and sign in to the Marketplace using your \texttt{liferay.com} (LRDC) account credentials. Authorize Marketplace to access your local account. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/dxp-store-link.png}} \caption{Click the \emph{Store} link and authorize Marketplace to access your local account.} \end{figure} \item Once signed into the Store, click on the \emph{Purchased} link, and then click on the \emph{EE} tab. Here you can see a list of Liferay DXP plugins that are installed, as well as options to update or install certain plugins. See \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{Using the Liferay Marketplace} for details. \end{enumerate} Next are answers to some common questions. \section{FAQ}\label{faq} \textbf{Q:} Where are the \emph{Liferay DXP Trial Plugins}? \textbf{A:} There is no such thing. The Liferay DXP plugins in Liferay Marketplace are the same ones that you get to try out with your Liferay DXP trial license for your portal. The Liferay DXP license (trial or official @product@ subscriber) gives you access to the Liferay DXP plugins. Also, there is no difference code-wise or release-wise between a Liferay DXP trial installation and a regular @product@ non-trial installation. The only difference is the license. \textbf{Q:} Why can't I go to liferay.com/marketplace? Why can't I \emph{purchase} from the Marketplace site? \textbf{A:} DXP trial users must use the Marketplace from within the product's Control Panel (instructions above). You do not need to \emph{purchase} any DXP plugins because if you access Marketplace from within the Control Panel, Marketplace sees that you have a DXP license installed and gives access to DXP plugins. Official DXP subscription customers (i.e., non trial) can log into \texttt{liferay.com} with their designated DXP subscriber login and access all DXP plugins through the Marketplace website. \textbf{Q:} Why are the plugins under the Purchased tab? If I click on the \emph{DXP Marketplace} link, it does not let me get the DXP plugins. \textbf{A:} Once you're signed into the Store, click on the \emph{Purchased} tab, then click on the \emph{EE} tab. \textbf{Q:} What happens when DXP trial customers become official Liferay Digital Experience subscribers? \textbf{A:} They can still complete the above process, or they can also visit the \href{https://www.liferay.com/marketplace}{Liferay Marketplace website}. \textbf{Q:} Do DXP trial customers get the DXP source code? \textbf{A:} No, they can only install the plugin. The DXP source code becomes available once they are official Liferay DXP Enterprise subscribers. \textbf{Q:} Can this process of installing DXP plugins be used from Liferay Portal CE (Community Edition)? \textbf{A:} No, the Marketplace must detect that you are running Liferay DXP. \chapter{Document Repository Configuration}\label{document-repository-configuration} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can configure file storage in several ways. Each option is a \emph{store} which can be configured through the \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file by setting the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Document\%20Library\%20Service}{\texttt{dl.store.impl=} property}. The default store is called Simple File Store. It stores \href{/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media}{documents and media} files on a file system (local or mounted). The store's default root folder is \texttt{{[}Liferay\ Home{]}/data/document\_library}. You can specify a different root directory from within \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System Settings}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Access System Settings by opening the \emph{Menu} (\pandocbounded{\includegraphics[keepaspectratio]{./images/icon-menu.png}}) and navigating to \emph{Control Panel → Configuration → System Settings}. \item In the \emph{Platform} section, click \emph{File Storage}. The File Storage page appears. \item Click \emph{Simple File System Store}. \item For the store's \emph{Root directory} value, specify its absolute path or its path relative to \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}. \item Click the \emph{Save} button. \end{enumerate} The document library store switches immediately to the new folder. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/file-storage.png}} \caption{The File Storage page in System Settings lets you configure document repository storage.} \end{figure} You can also use an entirely different method for storing documents and media files: \textbf{Simple File System Store}: uses the file system (local or a mounted share) to store files. \textbf{Advanced File System Store}: in addition to using the file system (local or a mounted share) to store files, Advanced File System Store nests the files into folders by version, for faster performance and to store more files. \textbf{S3 Store (Amazon Simple Storage)}: uses Amazon's cloud-based storage solution. \textbf{DBStore (Database Storage)}: stores the files in the database. DBStore's file (stored as a blob) size is 1 gigabyte. To store files larger than 1 gigabyte, use Simple File System Store or Advanced File System Store. These articles explain details for each one. \chapter{Using the Simple File System Store}\label{using-the-simple-file-system-store} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The simple file storage implementation is the default store. It uses a local folder to store files. You can use the file system for your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{clustered} configuration, but the folder you're pointing to must be shared by all nodes and handle concurrent requests and file locking. For this reason, you need to use a Storage Area Network or a clustered file system. The file system store was the first store used in Liferay DXP and is heavily bound to the Liferay DXP database. By default, documents are stored in a \texttt{document\_library} subfolder of the \texttt{data} folder. Of course, you can change this path to anything you want in \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System Settings}. The Simple File System store uses this folder path format for storing documents: \begin{verbatim} /companyId/folderId/numericFileEntryName/versionNumber \end{verbatim} The first folder name is the site's company ID. The second folder name is the Documents and Media folder's ID where the document resides. The third folder name is the document's numeric file entry name. Finally, the fourth name is a version number used for storing multiple versions of the document. \noindent\hrulefill \textbf{Note:} A document's numeric file entry name is distinct from the document ID; don't confuse the two! Each has an independent counter. The numeric file entry name is used in the folder path for storing the document but the document ID is not. The numeric file entry name is in the \texttt{name} column of the \texttt{DLFileEntry} table in Liferay DXP's database; the document ID is in the \texttt{fileEntryId} column of the same table. \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} If a database transaction rollback occurs in the Document Library, file system changes that have occurred since the start of the transaction aren't reversed. Inconsistencies between Document Library files and those in the file system store can occur and may require manual synchronization. \noindent\hrulefill The Simple File System Store binds documents very closely to Liferay DXP, and may not be exactly what you want. If you've been using the default settings for a while and must migrate your documents, there's a migration utility in the Control Panel in \emph{Server Administration} → \emph{Data Migration}. The utility facilitates moving documents from one store implementation to another. \chapter{Using the Advanced File System Store}\label{using-the-advanced-file-system-store} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The advanced file system store is similar to the simple file system store (the default store). Like that store, it saves files to the local file system---which, of course, could be a remote file system mount. It uses a slightly different folder structure to store files, which is pictured below. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/enterprise-adv-file-system-store.png}} \caption{The advanced file system store creates a more nested folder structure than the file system store.} \end{figure} So what makes the advanced file system store \emph{advanced}? Several operating systems have limitations on the number of files that can be stored in a particular folder. The advanced file system store overcomes this limitation by programmatically creating a structure that can expand to millions of files, by alphabetically nesting the files in folders. This not only allows for more files to be stored, but also improves performance as there are fewer files stored per folder. The same rules apply to the advanced file system store as apply to the simple file system store. To \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{cluster} this, you must point the store to a network mounted file system that all the nodes can access, and that networked file system must support concurrent requests and file locking. Otherwise, you may experience data corruption issues if two users attempt to write to the same file at the same time from two different nodes. To use the advanced file system store, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Configure \texttt{portal-ext.properties} with this property: \begin{verbatim} dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore \end{verbatim} \item Restart Liferay DXP. \item In the Control Panel, navigate to \emph{Configuration} → \emph{System Settings} → \emph{File Storage}. \item In the \emph{Advanced File System Store} screen, configure the store your way. \item Click \emph{Save}. \end{enumerate} Liferay DXP is using the advanced file system store. \noindent\hrulefill \textbf{Warning:} If a database transaction rollback occurs in the Document Library, file system changes that have occurred since the start of the transaction aren't reversed. Inconsistencies between Document Library files and those in the file system store can occur and may require manual synchronization. \noindent\hrulefill You may decide the advanced file system store for whatever reason doesn't serve your needs. If this is the case, you can of course mount other file systems into the documents and media library. In addition to this, you can also redefine the Liferay DXP store to use one of the other supported protocols. S3 store is next. \chapter{Using Amazon Simple Storage Service}\label{using-amazon-simple-storage-service} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Amazon's simple storage service (S3) is a cloud-based storage solution that you can use with Documents and Media. All you need is an account, and you can store your documents to the cloud from all nodes, seamlessly. When you sign up for the service, Amazon assigns you unique keys that link you to your account. In Amazon's interface, you can create ``buckets'' of data optimized by region. Here are the steps for configuring Liferay DXP to use your S3 account for file storage: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Amazon S3 requires a \texttt{SAXParser} from the application server to operate. If you are using an app server like Apache Tomcat that have one, you must include this property in a \href{/docs/7-2/deploy/-/knowledge_base/d/system-properties}{\texttt{system-ext.properties}} file: \begin{verbatim} org.xml.sax.driver=com.sun.org.apache.xerces.internal.parsers.SAXParser \end{verbatim} \item Place your \texttt{system-ext.properties} file in a folder that resides in your Liferay DXP installation's class path (e.g., \texttt{/WEB-INF/classes/}). \item Set the following property in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder: \begin{verbatim} dl.store.impl=com.liferay.portal.store.s3.S3Store \end{verbatim} \item Restart Liferay DXP. \item In the Control Panel, navigate to \emph{Configuration} → \emph{System Settings} → \emph{File Storage}. \item In the \emph{S3 Store Configuration} screen, configure the store your way. \item Click \emph{Save}. \end{enumerate} Your Liferay DXP instance is using the Amazon S3 store. \noindent\hrulefill \textbf{Warning:} If a database transaction rollback occurs in a Document Library that uses a file system based store, file system changes that have occurred since the start of the transaction aren't reversed. Inconsistencies between Document Library files and those in the file system store can occur and may require manual synchronization. All stores except DBStore are vulnerable to this limitation. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} No action is required to support AWS Signature Version 4 request authorization. \noindent\hrulefill Consult the Amazon Simple Storage documentation for additional details on using Amazon's service. \chapter{Using the DBStore}\label{using-the-dbstore} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can store Documents and Media files in your Liferay DXP database using DBStore. DBStore's maximum file (stored as a blob) size is 1 gigabyte. To store files larger than that, use Simple File System Store or Advanced File System Store. Here are the DBStore configuration steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set the following property in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder: \begin{verbatim} dl.store.impl=com.liferay.portal.store.db.DBStore \end{verbatim} \item Restart Liferay DXP. \end{enumerate} Documents and Media now uses Liferay DXP's database via DBStore. \chapter{Configuring Liferay DXP}\label{configuring-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Once you have Liferay DXP installed, it's time to configure it to the specifics of your environment. This means doing things like setting the time zone and language, configuring mail, configuring a cluster, configuring a Content Delivery Network, tuning, and more. These topics and more are discussed here. \chapter{Configuring Mail}\label{configuring-mail} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP uses a mail server and SMTP to email notifications. @product@'s built-in mail session is the easiest way to configure mail and it's recommended. You can configure the built-in mail session before or after deploying Liferay DXP. Alternatively, you can configure Liferay DXP to use a mail session on the application server. Creating a mail session in Liferay DXP or on the application server requires this information: \begin{itemize} \tightlist \item Incoming POP Server and port \item POP User Name \item POP Password \item Outgoing SMTP Server and port \item SMTP User Name \item SMTP Password \item All JavaMail properties you want to use \end{itemize} Built-in mail session setup is recommended and easiest. \section{Configuring Liferay DXP's Built-in Mail Session}\label{configuring-liferay-dxps-built-in-mail-session} The built-in mail session setup can be done using either of these methods: \begin{itemize} \item Control Panel \item Portal properties \end{itemize} \section{Built-in Mail Session in the Control Panel}\label{built-in-mail-session-in-the-control-panel} After deploying Liferay DXP, you can configure the mail session from the Control Panel. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Sign in as the administrative user (the user you specified on the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-product\#using-the-setup-wizard}{Basic Configuration page}). \item Navigate to \emph{Control Panel → Configuration → Server Administration → Mail}. \item Fill out the form. You're asked for the following information: \textbf{Incoming POP Server:} The hostname for a server running the Post Office Protocol. Liferay DXP checks this mailbox for incoming messages, such as message board replies. \textbf{Incoming Port:} The port on which the POP server is listening. \textbf{Use a Secure Network Connection:} Use an encrypted connection when connecting to the POP server. \textbf{User Name:} The user ID Liferay DXP should use to log into the POP server. \textbf{Password:} The password Liferay DXP should use to log into the POP server. \textbf{Outgoing SMTP Server:} The hostname for a server running the Simple Mail Transfer Protocol. Liferay DXP uses this server to send emails, such as password change emails and other notifications. \textbf{Outgoing Port:} The port on which the SMTP server is listening. \textbf{Use a Secure Network Connection:} Use an encrypted connection when connecting to the SMTP server. \textbf{User Name:} The user ID Liferay DXP should use to log into the SMTP server. \textbf{Password:} The password Liferay DXP should use to log into the SMTP server. \textbf{Manually specify additional JavaMail properties to override the above configuration:} If there are additional properties you need to specify, supply them here. \item Click \emph{Save}. \end{enumerate} Liferay DXP connects to the mail session immediately. \section{Built-in Mail Session Portal Properties}\label{built-in-mail-session-portal-properties} If you prefer specifying your mail session offline or before deploying Liferay DXP, use portal properties. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}, if you haven't already created one. \item Copy these default property settings into your \texttt{portal-ext.properties} file: \begin{verbatim} mail.session.mail=false mail.session.mail.pop3.host=localhost mail.session.mail.pop3.password= mail.session.mail.pop3.port=110 mail.session.mail.pop3.user= mail.session.mail.smtp.auth=false mail.session.mail.smtp.host=localhost mail.session.mail.smtp.password= mail.session.mail.smtp.port=25 mail.session.mail.smtp.user= mail.session.mail.store.protocol=pop3 mail.session.mail.transport.protocol=smtp \end{verbatim} \item Replace the default mail session values with your own. \item Put the \texttt{portal-ext.properties} file into your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{LIFERAY\_HOME}, once you've established it based on your installation. \end{enumerate} Liferay DXP connects to the mail session on the next startup. \section{Configuring a Mail Session on the Application Server}\label{configuring-a-mail-session-on-the-application-server} You can manage a mail session for Liferay DXP on your application server. Here's how: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a mail session on your application server, following your application server documentation. \item Point Liferay DXP to that mail session using the Control Panel or portal properties. Here are instructions for both: \begin{itemize} \item Configure the JNDI name in the \emph{Mail} page at \emph{Control Panel → Configuration → Server Administration → Mail}. \item Set a \texttt{mail.session.jndi.name} portal property in a \texttt{{[}LIFERAY\_HOME{]}/portal-ext.properties} file. Here's an example property: \begin{verbatim} mail.session.jndi.name=mail/MailSession \end{verbatim} \end{itemize} \end{enumerate} Lastly, configure your instance's email senders. \section{Configuring default email senders}\label{configuring-default-email-senders} Email senders are the default name and email address Liferay DXP uses to send administrative emails and announcement emails. Default email senders are configured in the \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}. \begin{itemize} \item Admin email configuration: \begin{verbatim} admin.email.from.name=Joe Bloggs admin.email.from.address=test@domain.invalid \end{verbatim} \item Announcements email configuration: \begin{verbatim} announcements.email.to.name= announcements.email.to.address=noreply@domain.invalid \end{verbatim} \end{itemize} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Replace the names and email addresses above with your values. \end{enumerate} \noindent\hrulefill \textbf{Note:} Following emails are blacklisted by default and cannot be used in any Liferay DXP installation: \begin{itemize} \tightlist \item \texttt{noreply@liferay.com} \item \texttt{test@liferay.com} \item \texttt{noreply@domain.invalid} \item \texttt{test@domain.invalid} \end{itemize} If you use them, Liferay DXP logs a \texttt{WARN} trace: \texttt{Email\ xxx\ will\ be\ ignored\ because\ it\ is\ included\ in\ mail.send.blacklist} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item Restart your server. \end{enumerate} Congratulations on configuring mail for Liferay DXP. \chapter{Locales and Encoding Configuration}\label{locales-and-encoding-configuration} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can display content based on language, time zone, ``right to left'' (that is, languages such as Hebrew, Arabic, and Persian), and you can localize user names and titles. Administrators can localize specific core UI messages so that the messages display in certain languages. \section{Time Zones}\label{time-zones} You can set time zones in the Control Panel and theoretically in the JVM (but this must be set to GMT: see below). Time zone configuration and default language customization are done in the Control Panel, at the Instance level. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Control Panel} → \emph{Configuration}. \item Click \emph{Instance Settings}. \item Click on the \emph{Miscellaneous} tab. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/instance-locales.png}} \caption{You can change the default and available languages and the time zone in Instance Settings.} \end{figure} The central left and right arrows let you add or remove available languages and locales. You can also set these as properties in your \texttt{portal-ext.properties} file in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder. The \texttt{portal.properties} reference document's \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Company}{Company} section defines the default locale. The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Languages\%20and\%20Time\%20Zones}{Languages and Time Zones} section defines the available and current locales. \begin{verbatim} company.default.locale=en_GB \end{verbatim} \noindent\hrulefill \textbf{Note:} The \texttt{company.default.locale} portal property is only intended for use on initial startup. To change the language settings on an existing instance, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings} and select the Localization category under the PLATFORM heading. Under the Language entry you can change the default language, as well as define the current locales. \noindent\hrulefill As an example, the above property changes the locale to English, Great Britain. \section{Set the JVM Time Zone to GMT}\label{set-the-jvm-time-zone-to-gmt} If you set the time zone in the JVM, it causes issues such as Calendar Events and Web Content articles displaying the wrong dates. This happens because the system assumes each date stored in the database is stored in GMT time. When the system needs to display one stored date to the end users, the display date is calculated by the application server's current date. This date is affected by the configured JVM level time zone and the stored GMT format date. To make sure the display date is calculated correctly, the time zone must be configured to GMT at the JVM level. Otherwise, an incorrect time zone offset at the JVM level causes the display date to be wrongly calculated and displayed. \section{Friendly URLs and Locales}\label{friendly-urls-and-locales} In addition to configuring instance settings, you can also define unique URLs for specific languages using the \texttt{I18nServlet} by editing Portal's \texttt{web.xml} file: \begin{verbatim} I18n Servlet /ar/* . . . I18n Servlet /de/* \end{verbatim} The defaults should be sufficient for nearly all circumstances. Because \texttt{web.xml} changes require stopping and possibly redeploying Liferay DXP (depending on your app server), test the defaults and make sure you really need to modify these settings. If you're clustered, you must make these changes on all nodes. \section{Modifying Language Keys}\label{modifying-language-keys} Developers can add or modify certain core UI messages (e.g.~\emph{Your request completed successfully.}) by \href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{modifying the language keys} that ship by default. \section{Right to Left}\label{right-to-left} For languages that are displayed right to left, use the following language properties settings: \begin{verbatim} lang.dir=rtl lang.line.begin=right lang.line.end=left \end{verbatim} To display right to left by default, \href{/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys}{override these properties globally}. \section{Localizing User Names}\label{localizing-user-names} Users can change the prefix and suffix values for a locale. For example, for Spanish, the \texttt{language\_es.properties} file contains these values: \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} For more information, see \href{/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings}{Using Liferay Language Settings}. \section{Related Topics}\label{related-topics} \href{/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings}{Using Liferay Language Settings} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys}{Overriding Global Language Keys} \href{/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys}{Overriding a Module's Language Keys} \chapter{Liferay DXP Clustering}\label{liferay-dxp-clustering} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP can serve everything from the smallest to the largest web sites. Out of the box, it's configured optimally for a single server environment. If one server isn't sufficient to serve your site's high traffic needs, Liferay DXP scales to the size you need. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/clustering-enterprise-configuration.png}} \caption{Liferay DXP is designed to scale to as large an installation as you need.} \end{figure} Liferay DXP works well in clusters of multiple machines (horizontal cluster) or in clusters of multiple VMs on a single machine (vertical cluster), or any mixture of the two. Once you have Liferay DXP installed on more than one application server node, there are several optimizations that must be made. At a minimum, Liferay DXP should be configured in the following way for a clustered environment: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/point-all-nodes-to-the-same-database}{All nodes should point to the same database or database cluster.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/configure-documents-and-media-the-same-for-all-nodes}{Documents and Media repositories must have the same configuration and be accessible to all nodes of the cluster.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/clustering-search}{Search should be on a separate search server that is optionally clustered.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/enabling-cluster-link}{Cluster Link must be enabled so the cache replicates across all nodes of the cluster.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/auto-deploy-to-all-nodes}{Applications must be auto-deployed to each node individually.} \end{enumerate} Many of these configuration changes can be made by adding or modifying properties in your \texttt{portal-ext.properties} file. Remember that this file overrides the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{defaults} in the \texttt{portal.properties} file. It's a best practice to copy the relevant section you want to modify from \texttt{portal.properties} into your \texttt{portal-ext.properties} file, and then modify the values there. \noindent\hrulefill \textbf{Note:} This documentation describes a Liferay DXP-specific cluster configuration without getting into specific implementations of third party software, such as Java EE application servers, HTTP servers, and load balancers. Please consult your documentation for those components of your cluster to configure those components. Before creating a Liferay DXP cluster, make sure your OS is not defining the hostname of your box to the local network at 127.0.0.1. \noindent\hrulefill Each step defined above is covered below to give you a step by step process for creating your cluster. Start with making all Nodes point to the same database. \chapter{Point all Nodes to the Same Liferay DXP Database}\label{point-all-nodes-to-the-same-liferay-dxp-database} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Each node should have a data source that points to one Liferay DXP database (or a database cluster) that all the nodes share. This means, of course, Liferay DXP cannot (and should not) use the embedded HSQL database that is shipped with the bundles (but you already knew that, right?). And, of course, the database server should be on a separate system from the Liferay DXP server. \section{Read-Writer Database Configuration}\label{read-writer-database-configuration} To improve database performance, you can use a read-writer database configuration. Instead of using the same data source for read and read-write operations, this strategy uses a separate data source for each operation type. DXP's Aspect Oriented Programming (AOP) transaction infrastructure directs read transactions to the read data source and read-write transactions to the write data source. Connections to separate read and read-write \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#JDBC}{data sources} are configured using JDBC or JNDI \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{Portal Properties} (e.g., in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties} file}), as explained in the following sections. The data sources should use separate instances of the DXP database, where the read-write database instance is replicated to the read database instance. \section{JDBC}\label{jdbc} Edit your \texttt{portal-ext.properties} file following these steps to connect directly to your separate read and write data sources using \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{JDBC}: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set the default connection pool provider. For provider information, see the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#JDBC}{JDBC properties reference}. The default setting specifies \href{https://github.com/brettwooldridge/HikariCP}{HikariCP} as the pool provider: \begin{verbatim} jdbc.default.liferay.pool.provider=hikaricp \end{verbatim} \item Configure JDBC connections to your separate read and write data sources. Here's an example: \begin{verbatim} jdbc.read.driverClassName=[place your driver name here] jdbc.read.url=[place the URL to your "read" database here] jdbc.read.username=[place your user name here] jdbc.read.password=[place your password here] jdbc.write.driverClassName=[place your driver name here] jdbc.write.url=[place the URL to your "read-write" database here] jdbc.write.username=[place your user name here] jdbc.write.password=[place your password here] \end{verbatim} For example JDBC connection values, please see \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates}. \item Configure DXP to use the write data source (the data source whose prefix is \texttt{jdbc.write.}) to create the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Counter}{Counter} data source. A separate data source is always dedicated to the counter. \begin{verbatim} counter.jdbc.prefix=jdbc.write. \end{verbatim} \item Optionally validate the data connections to make sure bad connections are handled gracefully. Some connection pools used with JDBC4 (check your driver's JDBC version) validate connections automatically. Other connection pools may require additional, vendor-specific connection validation properties---specify them in a Portal Properties file. Refer to your connection pool provider documentation for connection validation details. \item Enable the read-writer database configuration by copying the default \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{\texttt{spring.configs} and \texttt{spring.infrastructure.configs} Portal Properties} to your \texttt{portal-ext.properties} file and adding the following Spring configuration file paths to them. Add to \texttt{spring.configs}: \begin{verbatim} META-INF/dynamic-data-source-spring.xml \end{verbatim} Add to \texttt{spring.infrastructure.configs}: \begin{verbatim} META-INF/dynamic-data-source-infrastructure-spring.xml \end{verbatim} For more information, see the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{Spring configuration Portal Properties}. \end{enumerate} \section{JNDI}\label{jndi} Edit your \texttt{portal-ext.properties} file following these steps to connect to your read and write data sources on your app server using JNDI: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set your read and write JNDI data source user names and passwords. \begin{verbatim} jdbc.read.jndi.name=[place your "read" data source JNDI name here] jdbc.read.username=*[place your user name here] jdbc.read.password=[place your password here] jdbc.write.jndi.name=[place your "read-write" data source JNDI name here] jdbc.write.username=[place your user name here] jdbc.write.password=[place your password here] \end{verbatim} \item Configure DXP to use the write data source (the data source whose prefix is \texttt{jdbc.write.}) to create the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Counter}{Counter} data source. A separate data source is always dedicated to the counter. \begin{verbatim} counter.jdbc.prefix=jdbc.write. \end{verbatim} \item Optionally validate the data connections to make sure bad connections are handled gracefully. Some connection pools used with JDBC4 (check your driver's JDBC version) validate connections automatically. Other connection pools may require additional, vendor-specific connection validation properties---specify them in a Portal Properties file. Refer to your connection pool provider documentation for connection validation details. \item Enable the read-writer database configuration by copying the default \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{\texttt{spring.configs} and \texttt{spring.infrastructure.configs} Portal Properties} to your \texttt{portal-ext.properties} file and add the following Spring configuration file paths to them. Add to \texttt{spring.configs}: \begin{verbatim} META-INF/dynamic-data-source-spring.xml \end{verbatim} Add to \texttt{spring.infrastructure.configs}: \begin{verbatim} META-INF/dynamic-data-source-infrastructure-spring.xml \end{verbatim} For more information, see the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Spring}{Spring configuration Portal Properties}. \end{enumerate} DXP uses a read data source, a write data source, and a counter data source the next time it starts. \chapter{Configure Documents and Media the Same for all Nodes}\label{configure-documents-and-media-the-same-for-all-nodes} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} In a cluster, Documents and Media must use the same \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{document repository configuration} on all nodes. Note if you are using the \texttt{File\ System} or \texttt{Advanced\ File\ System} stores, the file system must be accessible from all nodes (i.e., a network share), support concurrent requests, and file locking. \textbf{Checkpoint}: Verify sharing works by executing these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item On Node 1 upload a document to the Documents and Media. \item On Node 2 download the document. The download should be successful. \item Repeat the test with reversed roles. \end{enumerate} \chapter{Clustering Search}\label{clustering-search} Search should always run on a separate environment from your Liferay DXP server. Liferay DXP supports \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{Elasticsearch}, and \href{/docs/7-2/deploy/-/knowledge_base/d/installing-solr}{Solr}. Both can also be clustered. For more information on how to cluster Elasticsearch, see \href{https://www.elastic.co/guide/en/elasticsearch/guide/current/distributed-cluster.html}{Elasticsearch's distributed cluster setup}. Once Liferay DXP servers have been properly configured as a cluster and the same for Elasticsearch, change Liferay DXP from \emph{embedded} mode to \emph{remote} mode. On the first connection, the two sets of clustered servers communicate with each other the list of all IP addresses; in case of a node going down, the proper failover protocols enable. Queries and indexes can continue to be sent for all nodes. For more information on how to cluster Solr, see \href{https://cwiki.apache.org/confluence/display/solr/SolrCloud}{Apache Solr Cloud} documentation. Once Liferay DXP servers have been properly configured as a cluster, deploy the Liferay Connector to Solr on all nodes. (This app is available for download from Liferay Marketplace) Create a Solr Cloud (cluster) managed by \emph{Apache Solr Zookeeper}. Connect the Liferay DXP cluster to Zookeeper and finish the final configurations to connect the two clusters. \chapter{Enabling Cluster Link}\label{enabling-cluster-link} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Enabling Cluster Link automatically activates distributed caching. The cache is distributed across multiple Liferay DXP nodes running concurrently. Cluster Link does \href{http://www.ehcache.org}{Ehcache} replication. The Ehcache global settings are in the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Ehcache}{\texttt{portal.properties} file}. By default Liferay does not copy cached entities between nodes. If an entity is deleted or changed, for example, Cluster Link sends a \emph{remove} message to the other nodes to invalidate this entity in their local caches. Requesting that entity on another node results in a cache \emph{miss}; the entity is then retrieved from the database and put into the local cache. Entities added to one node's local cache are not copied to local caches of the other nodes. An attempt to retrieve a new entity on a node which doesn't have that entity cached results in a cache \emph{miss}. The miss triggers the node to retrieve the entity from the database and store it in its local cache. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/clustering-cache-efficient-algorithm.png}} \caption{Liferay DXP's cache algorithm is extremely efficient.} \end{figure} Here are the Cluster Link topics: \begin{itemize} \tightlist \item \hyperref[enabling-cluster-link]{Enabling Cluster Link} \item \hyperref[multicast-over-udp]{Multicast Over UDP} \item \hyperref[unicast-over-tcp]{Unicast Over TCP} \item \hyperref[using-different-control-and-transport-channel-ports]{Using Different Control and Transport Channel Ports} \item \hyperref[modifying-the-cache-configuration-with-a-module]{Modifying the Cache Configuration with a Module} \item \hyperref[conclusion]{Conclusion} \end{itemize} \section{Enabling Cluster Link}\label{enabling-cluster-link-1} To enable Cluster Link, add this \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal property} to a \texttt{portal-ext.properties} file: \begin{verbatim} cluster.link.enabled=true \end{verbatim} The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Cluster\%20Link}{Cluster Link portal properties} provide a default configuration that you can override to fit your needs. Many of the defaults use \texttt{localhost}, instead of a real address. In some configurations, however, \texttt{localhost} is bound to the internal loopback network (\texttt{127.0.0.1} or \texttt{::1}), rather than the host's real address. If for some reason you need this configuration, you can make DXP auto detect the real address with this property: \begin{verbatim} cluster.link.autodetect.address=www.google.com:80 \end{verbatim} Set it to connect to some other host that's contactable by your server. By default, it points to Google, but this may not work if your server is behind a firewall. If you use each host's real address, you don't need to set the auto-detect address. Cluster Link depends on \href{http://www.jgroups.org}{JGroups} and provides an API for nodes to communicate. It can \begin{itemize} \tightlist \item Send messages to all nodes in a cluster \item Send messages to a specific node \item Invoke methods and retrieve values from all, some, or specific nodes \item Detect membership and notify when nodes join or leave \end{itemize} Cluster Link contains an enhanced algorithm that provides one-to-many type communication between the nodes. This is implemented by default with JGroups's UDP multicast, but unicast and TCP are also available. \section{Multicast Over UDP}\label{multicast-over-udp} When you enable Cluster Link, Liferay DXP's default clustering configuration is enabled. This configuration defines IP multicast over UDP. Liferay DXP uses two groups of \href{http://www.jgroups.org/manual4/index.html\#_channel}{channels from JGroups} to implement this: a control group and a transport group. If you want to customize the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Cluster\%20Link}{channel properties}, you can do so in \texttt{portal-ext.properties}: \begin{verbatim} cluster.link.channel.name.control=[your control channel name] cluster.link.channel.properties.control=[your control channel properties] \end{verbatim} Please see \href{http://www.jgroups.org/manual4/index.html\#protlist}{JGroups's documentation} for channel properties. The default configuration sets many properties whose settings are discussed there. Multicast broadcasts to all devices on the network. Clustered environments on the same network communicate with each other by default. Messages and information (e.g., scheduled tasks) sent between them can lead to unintended consequences. Isolate such cluster environments by either separating them logically or physically on the network, or by configuring each cluster's \texttt{portal-ext.properties} to use different sets of \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Multicast}{multicast group address and port values}. JGroups sets a bind address automatically. If you want to set a manual address, you can do this. By default, these are set to \texttt{localhost}: \begin{verbatim} cluster.link.bind.addr["cluster-link-control"]=localhost cluster.link.bind.addr["cluster-link-udp"]=localhost \end{verbatim} In some configurations, however, \texttt{localhost} is bound to the internal loopback network (\texttt{127.0.0.1} or \texttt{::1}), rather than the host's real address. If for some reason you need this configuration, you can make Liferay DXP auto detect its real address with this property: \begin{verbatim} cluster.link.autodetect.address=www.google.com:80 \end{verbatim} Set it to connect to some other host that's contactable by your server. By default, it points to Google, but this may not work if your server is behind a firewall. If you set the address manually using the properties above, you don't need to set the auto-detect address. Your network configuration may preclude the use of multicast over TCP, so below are some other ways you can get your cluster communicating. Note that these methods are all provided by JGroups. \section{Checkpoint:}\label{checkpoint} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you are binding the IP address instead of using \texttt{localhost}, make sure the right IP addresses are declared using these properties: \begin{verbatim} cluster.link.bind.addr["cluster-link-control"]=localhost cluster.link.bind.addr["cluster-link-udp"]=localhost \end{verbatim} \item Test your load and then optimize your settings if necessary. \end{enumerate} \section{Unicast over TCP}\label{unicast-over-tcp} If your network configuration or the geographical distance between nodes prevents you from using UDP Multicast clustering, you can configure TCP Unicast. You must use this if you have a firewall separating any of your nodes or if your nodes are in different geographical locations. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a parameter to your app server's JVM on each node: \begin{verbatim} -Djgroups.bind_addr=[node_ip_address] \end{verbatim} Use the node's IP address. \item Select a discovery protocol for the nodes to use to find each other. Here are the protocol choices: \begin{itemize} \tightlist \item TCPPing \item JDBCPing \item S3\_Ping \item Rackspace\_Ping \end{itemize} If you aren't sure which one to choose, use TCPPing. The rest of these steps use TCPPing \item Extract the \texttt{tcp.xml} file from \texttt{\$LIFERAY.HOME/osgi/marketplace/Liferay\ Foundation\ -\ Liferay\ Portal\ -\ Impl.lpkg/com\hspace{0pt}.\hspace{0pt}liferay\hspace{0pt}.\hspace{0pt}portal\hspace{0pt}.\hspace{0pt}cluster\hspace{0pt}.\hspace{0pt}multiple\hspace{0pt}-\hspace{0pt}{[}version{]}.\hspace{0pt}jar/lib\hspace{0pt}/\hspace{0pt}jgroups\hspace{0pt}-\hspace{0pt}{[}version{]}.\hspace{0pt}Final\hspace{0pt}.\hspace{0pt}jar/tcp.xml} to a location accessible to DXP, such as a folder called \texttt{jgroups} in the DXP web application's \texttt{WEB-INF/classes} folder. \begin{verbatim} WEB-INF/classes/jgroups/tcp.xml \end{verbatim} \item In the \texttt{tcp.xml} file, set the TCP bind port to an unused port on your node. Here's an example: \begin{verbatim} \end{verbatim} \item In the \texttt{tcp.xml} file, make each node discoverable to TCPPing by specifying its IP address and an unused port on that node. Building off of the previous step, here's an example \texttt{\textless{}TCPPing\textgreater{}} element: \begin{verbatim} \end{verbatim} \textbf{Regarding Initial Hosts:} \begin{itemize} \tightlist \item An alternative to specifying initial hosts in a TCP XML file is to specify them to your app server using a JVM argument like this: \texttt{-Djgroups.tcpping.initial\_hosts=192.168.224.154{[}7800{]},192.168.224.155{[}7800{]}}. \item Make sure the initial hosts value accounts for all your nodes. If \texttt{initial\_hosts} is not specified in a TCP XML file or in a JVM argument, \texttt{localhost} is the initial host. \end{itemize} \item Copy your \texttt{tcp.xml} file to each node, making sure to set the TCP bind port to the node's bind port. On the node with IP address \texttt{192.168.224.155}, for example, configure TCPPing like this: \begin{verbatim} \end{verbatim} \item Modify the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Cluster\%20Link}{Cluster Link properties} in the node's \texttt{portal-ext.properties} file to enable Cluster Link and point to the TCP XML file for each Cluster Link channel: \end{enumerate} \begin{verbatim} cluster.link.enabled=true cluster.link.channel.properties.control=/jgroups/tcp.xml cluster.link.channel.properties.transport.0=/jgroups/tcp.xml \end{verbatim} The JGroups configuration demonstrated above is typically all that Unicast over TCP requires. However, in a very specific case, if \emph{(and only if)} cluster nodes are deployed across multiple networks, then the parameter \texttt{external\_addr} must be set on each host to the external (public IP) address of the firewall. This kind of configuration is usually only necessary when nodes are geographically separated. By setting this, clustered nodes deployed to separate networks (e.g.~separated by different firewalls) can communicate together. This configuration may be flagged in security audits of your system. See \href{http://www.jgroups.org/manual4/index.html\#_transport_protocols}{JGroups documentation} for more information. \begin{quote} \textbf{Note:} The \texttt{singleton\_name} TCP attribute was deprecated in JGroups v4.0.0 and has therefore been removed since Liferay DXP 7.2 SP1 and Liferay Portal CE GA2 which use JGroups v 4.1.1-Final. \end{quote} You're now set up for Unicast over TCP clustering! Repeat either TCPPING process for each node you want to add to the cluster. \section{JDBC Ping}\label{jdbc-ping} Rather than use TCP Ping to discover cluster members, you can use a central database accessible by all the nodes to help them find each other. Cluster members write their own and read the other members' addresses from this database. To enable this configuration, replace the \texttt{TCPPING} tag with the corresponding \texttt{JDBC\_PING} tag: \begin{verbatim} \end{verbatim} For example JDBC connection values, please see \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates}. For further information about JDBC Ping, please see the \href{http://www.jgroups.org/manual4/index.html\#DiscoveryProtocols}{JGroups Documentation}. \section{S3 Ping}\label{s3-ping} Amazon S3 Ping can be used for servers running on Amazon's EC2 cloud service. Each node uploads a small file to an S3 bucket, and all the other nodes read the files from this bucket to discover the other nodes. When a node leaves, its file is deleted. To configure S3 Ping, replace the \texttt{TCPPING} tag with the corresponding \texttt{S3\_PING} tag: \begin{verbatim} \end{verbatim} Supply your Amazon keys as values for the parameters above. For further information about S3 Ping, please see the \href{http://www.jgroups.org/manual4/index.html\#_s3_ping}{JGroups Documentation}. \section{Other Pings}\label{other-pings} JGroups supplies other means for cluster members to discover each other, including Rackspace Ping, BPing, File Ping, and others. Please see the \href{http://www.jgroups.org/manual4/index.html\#DiscoveryProtocols}{JGroups Documentation} for information about these discovery methods. The control and transport channels can be configured to use different ports. \section{Using Different Control and Transport Channel Ports}\label{using-different-control-and-transport-channel-ports} Using separate control and transport channel ports lets you monitor control and transport traffic and helps you isolate information to diagnose problems. These steps use Unicast over TCPPing to demonstrate the approach. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a parameter to your app server's JVM on each node: \begin{verbatim} -Djgroups.bind_addr=[node_ip_address] \end{verbatim} \item Extract the \texttt{tcp.xml} file from \texttt{\$LIFERAY.HOME/osgi/marketplace/Liferay\ Foundation\ -\ Liferay\ Portal\ -\ Impl.lpkg/com\hspace{0pt}.\hspace{0pt}liferay\hspace{0pt}.\hspace{0pt}portal\hspace{0pt}.\hspace{0pt}cluster\hspace{0pt}.\hspace{0pt}multiple\hspace{0pt}-\hspace{0pt}{[}version{]}.\hspace{0pt}jar/lib\hspace{0pt}/\hspace{0pt}jgroups\hspace{0pt}-\hspace{0pt}{[}version{]}.\hspace{0pt}Final\hspace{0pt}.\hspace{0pt}jar/tcp.xml} to a location accessible to DXP, such as a folder called \texttt{jgroups} in the DXP web application's \texttt{WEB-INF/classes} folder. \item Make a copy of the \texttt{tcp.xml} in the same location and rename both files, designating one for the control channel and the other for the transport channel. For example, you could use these file names: \begin{itemize} \item \texttt{tcp-control.xml} \item \texttt{tcp-transport.xml} \end{itemize} \item Modify the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Cluster\%20Link}{Cluster Link properties} in the node's \texttt{portal-ext.properties} file to enable Cluster Link and point to the TCP XML file for each Cluster Link channel: \begin{verbatim} cluster.link.enabled=true cluster.link.channel.properties.control=/jgroups/tcp-control.xml cluster.link.channel.properties.transport.0=/jgroups/tcp-transport.xml \end{verbatim} \item Modify each \texttt{tcp-*.xml} file's the TCP and TCPPing elements to account for each node's IP address and bind port. If you're vertically clustering (i.e., you have multiple servers running on the same physical or virtual system), every channel must use a unique unused bind port for discovery communication. In each \texttt{tcp-*.xml} file, assign the TCP tag's \texttt{bind\_port} attribute to a unique, unused port. For example, your first two nodes might assign these bind ports: \end{enumerate} \noindent\hrulefill \begin{verbatim} Node | Properties File | Port | :----- | :------------------ | :----- | Node 1 | `tcp-control.xml` | `7800` | Node 1 | `tcp-transport.xml` | `7801` | Node 2 | `tcp-control.xml` | `7802` | Node 2 | `tcp-transport.xml` | `7803` | \end{verbatim} \noindent\hrulefill \begin{verbatim} Here are example TCP and TCPPing elements using the bind ports on nodes running on the same system (i.e., same IP address): **Node 1 `tcp-control.xml`** ```xml ``` **Node 1 `tcp-transport.xml`** ```xml ``` **Node 2 `tcp-control.xml`** ```xml ``` **Node 2 `tcp-transport.xml`** ```xml ``` \end{verbatim} If you have added entities that can be cached or you want to tune the cache configuration for your system, you can do so using a module. \section{Modifying the Cache Configuration with a Module}\label{modifying-the-cache-configuration-with-a-module} It's recommended to test your system under a load that best simulates the kind of traffic your system must handle. If you serve a lot of message board messages, your script should reflect that. If web content is the core of your site, your script should reflect that too. As a result of a load test, you may find that the default distributed cache settings aren't optimized for your site. In this case, tweak the settings using a module. Follow instructions for \href{/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache}{Overriding Cache}. You can install the module on each node and change the settings without taking down the cluster. This is a great benefit, but beware: since Ehcache doesn't allow for changes to cache settings while the cache is alive, reconfiguring a cache while the server is running flushes the cache. \section{Conclusion}\label{conclusion} Once you've configured your cluster, you can start it. A log file message shows your cluster's name (e.g., \texttt{cluster=liferay-channel-control}): \begin{verbatim} GMS: address=oz-52865, cluster=liferay-channel-control, physical address=192.168.1.10:50643 \end{verbatim} Congratulations! Your cluster is using Cluster Link. \chapter{Auto Deploy to All Nodes}\label{auto-deploy-to-all-nodes} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} All modules and WAR files you deploy onto the cluster must be deployed to all cluster nodes. Because Liferay DXP \href{/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator}{installs applications as OSGi bundles}, you cannot rely on your application server's means of installing WAR files (even if you only intend to install WAR files) to deploy an application to the entire cluster. Instead, place the application in each node's auto deploy folder (e.g., \texttt{{[}Liferay\ Home{]}/deploy}). This, as you might imagine, can be done with a script. Write a shell script that uploads applications to each node using sftp or some other service. This way, when you deploy an application, it uploads to each node's auto deploy folder and installs to Liferay DXP on each node. \chapter{Updating a Cluster}\label{updating-a-cluster} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Maintaining a \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{cluster} is a big responsibility. It includes deploying new and updated plugins and modules, applying \href{/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay}{fix packs}, making configuration changes, and more. Maximizing server uptime and minimizing risks take priority when applying changes. Liferay DXP supports using standard cluster maintenance techniques. \begin{itemize} \tightlist \item \href{/docs/7-2/deploy/-/knowledge_base/d/using-rolling-restarts}{Rolling restarts}: Nodes are shut down and updated one at a time. \item \href{/docs/7-2/deploy/-/knowledge_base/d/other-cluster-update-techniques}{Blue-green deployment}: Blue-green involves duplicating the current environment (\emph{blue} environment), updating the duplicate (\emph{green} environment), and cutting over users to the updated environment (green). \end{itemize} The techniques are compared below. \textbf{Cluster Update Techniques} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.1429}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.4762}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.3810}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright Update \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright ~Rolling Restart \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright ~Blue-green \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Plugin/module installation & Supported & Supported \\ Plugin/module update (backward-compatible data/schema changes) & Supported & Supported \\ Plugin/module update (non-backward-compatible data/schema changes) \hyperref[one]{1} & Not supported & Supported \\ Fix pack installation and removal (revertable fix pack) & Supported & Supported \\ Fix pack installation (non-revertible fix pack) & Not supported & Supported \\ Cluster code changes \hyperref[two]{2} & Not supported & Supported \\ Portal property changes & Supported & Supported \\ System Setting changes via configuration admin files & Supported & Supported \\ Application server updates & Supported & Supported \\ JVM setting changes & Supported & Supported \\ New Java version (minor) & Supported & Supported \\ New Java version (major) & Not supported & Supported \\ \end{longtable} \noindent\hrulefill {[}1{]} Data and data schema changes that are not backward-compatible include, but are not limited to these: \begin{itemize} \tightlist \item Modifying data in existing columns \item Dropping columns \item Changing column types \item Changing data formats used in columns (such as changing from XML to JSON) \item Updating a Service Builder service module's data schema to a version outside of the module's required data schema range. A module's \texttt{Liferay-Require-SchemaVersion} (specified in its \texttt{bnd.bnd}) must match the module's schema version value in the \texttt{Release\_} table. Installing a module with a new schema version updates the \texttt{Release\_} table with that schema version and triggers a data upgrade process. If you install such a module on one node, the schema version in the \texttt{Release\_} table no longer matches the \texttt{Liferay-Require-SchemaVersion} of the modules on the other nodes, and the module's Service Builder services become unavailable until the module is installed on the other nodes. Such changes cannot be reverted: the database must be restored from a backup. These schema version changes must be applied while all nodes are shut down. \end{itemize} {[}2{]} Cluster communication must stay intact. For this reason, cluster code must not be updated in rolling restarts. The Customer Portal identifies DXP fix packs that contain such changes as non-revertible. Here are packages you must not change in rolling restarts: \begin{itemize} \tightlist \item \texttt{com.liferay.portal.kernel.cluster} \item \texttt{com.liferay.portal.kernel.cluster.*} \item \texttt{com.liferay.portal.kernel.exception.NoSuchClusterGroupException} \item \texttt{com.liferay.portal.kernel.scheduler.multiple} \item \texttt{com.liferay.portal.kernel.scheduler.multiple.*} \item \texttt{com.liferay.portal.cache.multiple} \item \texttt{com.liferay.portal.cache.multiple.*} \item \texttt{com.liferay.portal.scheduler.multiple} \item \texttt{com.liferay.portal.scheduler.multiple.*} \end{itemize} Since eligible changes should be done with rolling restarts, it's explained first. \chapter{Rolling Restarts}\label{rolling-restarts} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The rolling restart cluster maintenance process involves shutting down and updating nodes one at a time (while the other nodes are running) until they're all updated. It maximizes uptime while you update your cluster. Rolling restarts can be used in container and image based environments. \noindent\hrulefill \textbf{Note:} Rolling restart does not include concepts for blue-green (separate, but identical environments) architectures, as these concepts specifically address multi-cluster style developments. \noindent\hrulefill Here are the rolling restart steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Shut down one cluster node (JVM instance). \item Update/modify the deployment for that node (see the maintenance scenarios that follow). \item Start the node. \item Repeat these steps for all other cluster nodes. \end{enumerate} Maintenance scenarios vary in how they behave in rolling restarts. For example, UI changes in a plugin update are only visible on the updated nodes. Users on nodes that haven't been updated don't see the UI changes. Maintenance scenarios might have specific cases that cannot be performed in rolling restarts---the scenario descriptions mention these cases. The maintenance scenarios eligible for rolling restart are described below. \section{New Modules and Plugins}\label{new-modules-and-plugins} For a new plugin or module (one that does not already exist in the cluster) to be eligible for rolling restart it must not modify data, or delete or rename database columns in a way that breaks compatibility with existing plugins or modules. \section{Updating Existing Modules and Plugins}\label{updating-existing-modules-and-plugins} For a new version of an existing plugin or module to be eligible for rolling restart, it must not modify data or delete or rename database columns in a way that breaks compatibility with the existing version of the plugin or module. \section{Applying Fix Packs (DXP only)}\label{applying-fix-packs-dxp-only} The Customer Portal identifies \href{/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay}{fix packs} that are not revertible, and therefore ineligible for rolling restart. All other fix packs are eligible. \section{Reverting Fix Packs (DXP only)}\label{reverting-fix-packs-dxp-only} Revertible fix packs can be removed in rolling restarts. \section{\texorpdfstring{Portal Properties controlled by \texttt{portal-ext.properties}}{Portal Properties controlled by portal-ext.properties}}\label{portal-properties-controlled-by-portal-ext.properties} \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{Portal Properties} file changes can be applied in rolling restarts. \section{System Settings controlled by Configuration Admin Files}\label{system-settings-controlled-by-configuration-admin-files} \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{System configuration} files can be applied in rolling restarts. \section{Application Server or JVM setting modifications}\label{application-server-or-jvm-setting-modifications} Modifications to application server and JVM settings can be done in rolling restarts. \section{Java Version Updates}\label{java-version-updates} Minor version updates of Java can be applied in rolling restarts. Major version updates are not supported in rolling restarts, and should instead be done when all cluster nodes are shut down. All rolling restart eligible updates can be applied using the rolling restart steps listed earlier. Other updates must be done differently as described next. \section{Related Topics}\label{related-topics-1} \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP Clustering} \href{/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay}{Maintaining Liferay DXP} \chapter{Blue-Green Deployment}\label{blue-green-deployment} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Blue-green is a deployment technique in which you duplicate your production environment (the \emph{blue} environment) and modify the duplicate (the \emph{green} environment) with software and data changes. When you've successfully tested the green environment, you cut users over from the blue environment to the green environment. Blue-green eliminates system down time. Data schema and data changes require special attention. Custom plugin/module data schema changes that break compatibility with existing code must be introduced over several releases in which the data is transitioned and maintained in old and new columns until the old columns are unnecessary. Data and schema changes require these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a new column. \item Copy the data to the new column. \item Maintain both columns until the old column is no longer used by any cluster nodes. \item Delete the column in the next release. \end{enumerate} For more information, refer to these blue-green deployment articles: \begin{itemize} \item \href{http://martinfowler.com/bliki/BlueGreenDeployment.html}{BlueGreenDeployment} \item \href{https://www.thoughtworks.com/insights/blog/implementing-blue-green-deployments-aws}{Implementing Blue-Green Deployments with AWS} \end{itemize} \section{Related Topics}\label{related-topics-2} \href{/docs/7-2/deploy/-/knowledge_base/d/using-rolling-restarts}{Rolling Restarts} \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP Clustering} \href{/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay}{Maintaining Liferay DXP} \chapter{Configuring Remote Staging in a Clustered Environment}\label{configuring-remote-staging-in-a-clustered-environment} If you're running Liferay DXP as a \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{clustered environment} and you want to use remote staging, you must configure it properly for a seamless experience. In this tutorial, you'll learn how to set up remote staging in an example clustered environment scenario. The example environment assumes you have \begin{itemize} \tightlist \item a Staging instance with database configurations and a file repository different from the cluster nodes. \item a balancer responsible for managing the traffic flow between the cluster's nodes. \item two nodes that call two Liferay app servers (e.g., \emph{App Server 1} and \emph{App Server 2}), both of which are connected to the same database. \end{itemize} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/remote-staging-clustering.png}} \caption{This is the assumed setup for your clustered environment.} \end{figure} The steps below also assume your web tier, application tier, and cluster environment are already configured. You may need to adjust the configurations in this tutorial to work with your specific configuration. Let's begin! \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item You must secure the communication made between your nodes and Staging server. Add the following property to both app servers' and Staging server's \texttt{portal-ext.properties} file: \begin{verbatim} tunneling.servlet.shared.secret=[secret] \end{verbatim} This secret key denies other portals access to your configured portal servers. If you'd like to set your secret key using hexadecimal encoding, also set the following property in your \texttt{portal-ext.properties} files: \begin{verbatim} tunneling.servlet.shared.secret.hex=true \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The following key lengths are supported by the available encryption algorithms: - *AES:* 128, 192, and 256-bit keys - *Blowfish:* 32-448 bit keys - *DESede (Triple DES):* 56, 112, or 168 bit keys (Liferay places an artificial limit on the minimum key length and does not support the 56-bit key length) For example, you can use [OpenSSL](https://www.openssl.org/) to generate a 128-bit AES key: openssl enc -aes-128-cbc -k abc123 -P -md sha1 \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item You must allow the connection between the configured IPs of your app servers and the Staging server. Open your remote Liferay server's \texttt{portal-ext.properties} file and add the following properties: \begin{verbatim} tunnel.servlet.hosts.allowed=127.0.0.1,SERVER_IP,[STAGING_IP] tunnel.servlet.https.required=false \end{verbatim} The \texttt{{[}STAGING\_IP{]}} variable must be replaced by the staging server's IP addresses. The \texttt{SERVER\_IP} constant can remain set for this property; it's automatically replaced by the Liferay server's IP addresses. \item If you're validating IPv6 addresses, you must configure the app server's JVM to not force the usage of IPv4 addresses. For example, if you're using Tomcat, add the following attribute in the \texttt{\$TOMCAT\_HOME\textbackslash{}bin\textbackslash{}setenv.{[}bat\textbar{}sh{]}} file. \begin{verbatim} `-Djava.net.preferIPv4Stack=false` \end{verbatim} \item Restart both app servers for the new properties to take effect. \item Configure the \emph{TunnelAuthVerifier} property for your nodes' app servers. There are two ways to do this: \begin{itemize} \item \textbf{Use a \texttt{.config} file (recommended):} In the \texttt{\$LIFERAY\_HOME/osgi/configs} folder of one of your node Liferay DXP instances, create (if necessary) a \texttt{com.liferay.portal.security.auth.verifier.tunnel.module.configuration.TunnelAuthVerifierConfiguration-default.config} file and insert the properties listed below. Creating one \texttt{.config} file configures all cluster nodes the same way. For more information on \texttt{.config} files, see the \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{Understanding System Configuration Files} article. \begin{verbatim} enabled=true hostsAllowed=127.0.0.1,SERVER_IP,STAGING_IP serviceAccessPolicyName=SYSTEM_USER_PASSWORD urlsIncludes=/api/liferay/do \end{verbatim} \item \textbf{Via System Settings:} Navigate to the \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Foundation} → \emph{Tunnel Auth Verifiers}. Click on the \emph{/api/liferay/do} configuration entry and add the Staging IP address to the \emph{Hosts allowed} field. If you choose to configure the \emph{TunnelAuthVerifier} this way, you \textbf{must} do this for all nodes (e.g., App Server 1 and App Server 2). \end{itemize} \item On your Staging instance, navigate to the Site Administration portion of the Product Menu and select \emph{Publishing} → \emph{Staging}. Then select \emph{Remote Live}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/remote-staging-menu.png}} \caption{When selecting the Remote Staging radio button, you're given a list of options to configure.} \end{figure} \item For the Remote Host/IP field, insert the balancer's IP of your web tier. Configuring the Staging instance with the balancer's IP ensures the availability of the environment at the time of publication from staging to live. \item Enter the port on which the balancer is running into the Remote Port field. \item Insert the remote site ID of your app servers into the Remote Site ID field. The site ID of all your app servers are the same since they are configured for the same database and are shared between nodes. Navigate to the Site Administration portion of the Product Menu and select \emph{Configuration} → \emph{Site Settings} to find the site ID. \item Save the Remote Live settings. \end{enumerate} That's it! You've configured remote staging in your clustered environment. \chapter{Content Delivery Network}\label{content-delivery-network} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} A Content Delivery Network (CDN) is an network of servers deployed in multiple data centers that contain your static content. When users hit your site, that static content is loaded from a server with geographical proximity to the user, speeding up requests. Here, you'll first discover the perks of using a CDN and learn about general guidelines for using a CDN with Liferay DXP. Then, you'll configure a CDN. It's time to distribute your content around the world! \section{Using CDN for Performance Enhancements}\label{using-cdn-for-performance-enhancements} A CDN serves static web resources to users. These resources (images, CSS files, JavaScript files, etc.) are stored on multiple servers around the world. When requested, the resources are retrieved from the server nearest to the user. The CDN functions as a caching proxy. This means that once static content is copied to a local server, it is stored in a cache for quick and easy retrieval. This drastically improves latency time, because browsers can download static resources from a local server down the street instead of halfway around the world. A user's request to the CDN for content is directed to specific server machine based on an algorithm that finds the server closest to the user. The figure below shows a visual representation of using geographical proximity to improve latency. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/cdn-map.png}} \caption{The red lines on the map represent the required distances traveled by requests from a server to the user. Using CDN allows a user to request static resources from a much closer local server, improving download times.} \end{figure} Because of the reduced wait time for requests and reduced load on your application server, a CDN is a great option to improve performance. Using a CDN with Liferay DXP, however, has some restrictions. \section{Liferay CDN Requirements}\label{liferay-cdn-requirements} Liferay DXP only works with CDNs that can dynamically retrieve requested resources. Dynamic resources change over time or via interaction with end users and thus cannot be cached. For this reason, check with your CDN provider to make sure you don't have to upload anything manually in order for the CDN to work. The CDN must automatically fetch the content. The CDN must work like a transparent proxy. A request first goes to the CDN. If the CDN doesn't have the requested resource, the CDN makes an identical request back to the origin (Liferay DXP), caches the resource, then serves the resource. Once you're using a CDN (see below), it serves both portal resources and plugin resources (e.g., theme resources or JavaScript files referenced from a plugin's \texttt{liferay-portlet.xml} file). The CDN only serves resources that are included in a plugin. It does not serve resources that are dynamically loaded from external sources. To get the CDN URL for a resource, developers should replace the portal host in the resource path with \texttt{themeDisplay.getCDNDynamicResourcesHost()}. Prefix resources with the CDN host name. Don't manually upload any resources to the CDN or put anything on the CDN which requires permission checking or complex policy access. There are several portal properties for configuring your CDN to suit your needs. You'll learn how to do this next. \section{Configuring Liferay DXP to Use a CDN}\label{configuring-liferay-dxp-to-use-a-cdn} Now that you understand what a CDN accomplishes and how it's used, it's time to set one up for yourself. You can set your CDN and its properties using two different methods: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item By editing your portal properties file \item By using the Control Panel \end{enumerate} To configure your CDN via a properties file, create a \texttt{portal-ext.properties} file in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder and set the appropriate \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Content\%20Delivery\%20Network}{Content Delivery Network properties}. Once you configure your CDN host, Liferay DXP generates URLs to the static assets that replace the old host with your new CDN host so they are automatically cached and served afterwards by the CDN. To configure your CDN in the Control Panel, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings}. In the main configuration, there are three fields related to CDNs: \begin{itemize} \tightlist \item \emph{CDN Host HTTP} \item \emph{CDN Host HTTPS} \item \emph{Enable CDN Dynamic Resources} \end{itemize} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/cdn-control-panel.png}} \caption{The Control Panel lets you configure your portal's CDN.} \end{figure} These properties are exactly the same as the ones you can specify in your \texttt{portal-ext.properties}. Make sure to visit the Content Delivery Network section of the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Content\%20Delivery\%20Network}{portal.properties} reference document if you don't know how to fill in the CDN fields. Once you're finished, click \emph{Save} and your old host is replaced with your new CDN host for static content. As you can see, configuring a CDN is easy and can drastically reduce latency time and improve performance. \chapter{Tuning Guidelines}\label{tuning-guidelines} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Although setting names may differ, these concepts apply to most application servers. To keep things simple, Tomcat is used as the example. For other application servers, consult the provider's documentation for specific settings. Here are the tuning topics: \begin{itemize} \tightlist \item Database Connection Pool \item Deactivating Development Settings in the JSP Engine \item Thread Pool \end{itemize} \section{Database Connection Pool}\label{database-connection-pool} The database connection pool should be roughly 30-40\% of the thread pool size. It provides a connection whenever Liferay DXP needs to retrieve data from the database (e.g., user login). If the pool size is too small, requests queue in the server waiting for database connections. If the size is too large, however, idle database connections waste resources. As with thread pools, monitor these settings and adjust them based on your performance tests. In Tomcat, the connection pools are configured in the Resource elements in \texttt{\$CATALINA\_HOME/conf/Catalina/localhost/ROOT.xml}. Liferay Engineering tests with this configuration: \begin{verbatim} \end{verbatim} This configuration starts with 10 threads and increments by 5 as needed to a maximum of 75 connections in the pool. There are a variety of database connection pool providers, including DBCP, C3P0, HikariCP, and Tomcat. You may also configure the Liferay JDBC settings in your \href{https://docs.liferay.com/ce/portal/7.2-latest/propertiesdoc/portal.properties.html}{\texttt{portal-ext.properties}} file. For example JDBC connection values, please see \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates} \section{Deactivating Development Settings in the JSP Engine}\label{deactivating-development-settings-in-the-jsp-engine} Many application servers' JSP Engines are in development mode by default. Deactivate these settings prior to entering production: \textbf{Development mode:} This makes the JSP container poll the file system for changes to JSP files. Since you won't change JSPs on the fly like this in production, turn off this mode. \textbf{Mapped File:} Generates static content with one print statement versus one statement per line of JSP text. To disable these in Tomcat, for example, update the \texttt{\$CATALINA\_HOME/conf/web.xml} file's JSP servlet configuration to this: \begin{verbatim} jsp org.apache.jasper.servlet.JspServlet development false mappedFile false 3 \end{verbatim} Development mode and mapped files are disabled. \section{Thread Pool}\label{thread-pool} Each request to the application server consumes a worker thread for the duration of the request. When no threads are available to process requests, the request is queued to wait for the next available worker thread. In a finely tuned system, the number of threads in the thread pool are balanced with the total number of concurrent requests. There should not be a significant amount of threads left idle to service requests. Use an initial thread pool setting of 50 threads and then monitor it within your application server's monitoring consoles. You may wish to use a higher number (e.g., 250) if your average page times are in the 2-3 second range. Too few threads in the thread pool might queue excessive requests; too many threads can cause excessive context switching. In Tomcat, the thread pools are configured in the \texttt{\$CATALINA\_HOME/conf/server.xml} file's \texttt{Connector} element. The \href{https://tomcat.apache.org/tomcat-9.0-doc/config/http.html}{Apache Tomcat documentation} provides more details. Liferay Engineering tests with this configuration: \begin{verbatim} \end{verbatim} Additional tuning parameters around Connectors are available, including the connector types, the connection timeouts, and TCP queue. Consult your application server's documentation for further details. \chapter{Java Virtual Machine Tuning}\label{java-virtual-machine-tuning} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Java Virtual Machine (JVM) tuning primarily focuses on adjusting the garbage collector and the Java memory heap. We used Oracle's 1.8 JVM for the reference architecture. You may choose other supported JVM versions and implementations. Please consult the \href{https://web.liferay.com/group/customer/dxp/support/compatibility-matrix}{Liferay DXP Compatibility Matrix} for additional compatible JVMs. Here are the JVM tuning topics: \begin{itemize} \tightlist \item \hyperref[garbage-collector]{Garbage Collector} \item \hyperref[code-cache]{Code Cache} \item \hyperref[java-heap]{Java Heap} \item \hyperref[jvm-advanced-options]{JVM Advanced Options} \end{itemize} Garbage collection is first. \section{Garbage Collector}\label{garbage-collector} Choosing the appropriate garbage collector (GC) helps improve the responsiveness of your Liferay DXP deployment. Use the concurrent low pause collectors: \begin{verbatim} -XX:+UseParNewGC -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSCompactWhenClearAllSoftRefs -XX:CMSInitiatingOccupancyFraction=85 -XX:+CMSScavengeBeforeRemark \end{verbatim} You may choose from other available GC algorithms, including parallel throughput collectors and G1 collectors. Start tuning using parallel collectors in the new generation and concurrent mark sweep (CMS) in the old generation. \textbf{Note:} the \texttt{ParallelGCThreads} value (e.g., \texttt{ParallelGCThreads=16}) varies based on the type of CPUs available. Set the value according to CPU specification. On Linux machines, report the number of available CPUs by running \texttt{cat\ /proc/cpuinfo}. \textbf{Note:} There are additional ``new'' algorithms like G1, but Liferay Engineering's tests for G1 indicated that it does not improve performance. Since your application performance may vary, you should add G1 to your testing and tuning plans. \section{Code Cache}\label{code-cache} Java's just-in-time (JIT) compiler generates native code to improve performance. The default size is \texttt{48m}. This may not be sufficient for larger applications. Too small a code cache reduces performance, as the JIT isn't able to optimize high frequency methods. For Liferay DXP, start with \texttt{64m} for the initial code cache size. \begin{verbatim} -XX:InitialCodeCacheSize=64m -XX:ReservedCodeCacheSize=96m \end{verbatim} Examine the efficacy of the parameter changes by adding the following logging parameters: \begin{verbatim} -XX:+PrintCodeCache -XX:+PrintCodeCacheOnCompilation \end{verbatim} \section{Java Heap}\label{java-heap} When most people think about tuning the Java memory heap, they think of setting the maximum and minimum memory of the heap. Unfortunately, most deployments require far more sophisticated heap tuning to obtain optimal performance, including tuning the young generation size, tenuring durations, survivor spaces, and many other JVM internals. For most systems, it's best to start with at least the following memory settings: \begin{verbatim} -server -XX:NewSize=700m -XX:MaxNewSize=700m -Xms2048m -Xmx2048m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=6 -XX:TargetSurvivorRatio=9 -XX:MaxTenuringThreshold=15 \end{verbatim} On systems that require large heap sizes (e.g., above 4GB), it may be beneficial to use large page sizes. You can activate large page sizes like this: \begin{verbatim} -XX:+UseLargePages -XX:LargePageSizeInBytes=256m \end{verbatim} You may choose to specify different page sizes based on your application profile. \textbf{Note:} To use large pages in the JVM, you must configure your underlying operating system to activate them. In Linux, run \texttt{cat\ /proc/meminfo} and look at ``huge page'' items. \noindent\hrulefill \textbf{Caution:} Avoid allocating more than 32GB to your JVM heap. Your heap size should be commensurate with the speed and quantity of available CPU resources. \noindent\hrulefill \section{JVM Advanced Options}\label{jvm-advanced-options} The following advanced JVM options were also applied in the Liferay benchmark environment: \begin{verbatim} -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseCompressedOops -XX:+DisableExplicitGC -XX:-UseBiasedLocking -XX:+BindGCTaskThreadsToCPUs -XX:UseFastAccessorMethods \end{verbatim} Please consult your JVM documentation for additional details on advanced JVM options. Combining the above recommendations together, makes this configuration: \begin{verbatim} -server -XX:NewSize=1024m -XX:MaxNewSize=1024m -Xms4096m -Xmx4096m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=12 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15 -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseParNewGC -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSCompactWhenClearAllSoftRefs -XX:CMSInitiatingOccupancyFraction=85 -XX:+CMSScavengeBeforeRemark -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseCompressedOops -XX:+DisableExplicitGC -XX:-UseBiasedLocking -XX:+BindGCTaskThreadsToCPUs -XX:+UseFastAccessorMethods -XX:InitialCodeCacheSize=32m -XX:ReservedCodeCacheSize=96m \end{verbatim} \noindent\hrulefill \textbf{Caution:} The above JVM settings should formulate a starting point for your performance tuning. Every system's final parameters vary due to many factors, including number of current users and transaction speed. \noindent\hrulefill Monitor the garbage collector statistics to ensure your environment has sufficient allocations for metaspace and also for the survivor spaces. Using the configuration above in the wrong environment could result in dangerous runtime scenarios like out of memory failures. Improperly tuned survivor spaces also lead to wasted heap space. \chapter{Installing a Search Engine}\label{installing-a-search-engine} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} A search engine is a critical component of your Liferay DXP installation. If you're here, you probably know the basics already and want to configure a search engine for your Liferay DXP deployment. Elasticsearch, a highly scalable, full-text search engine, is installed by default, as an embedded server. Elasticsearch is well-supported and almost certainly meets any search and indexing need you have, but you must not use the \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch\#embedded-vs-remote-operation-mode}{embedded version in your production deployment}. Learn to configure a remote Elasticsearch server or cluster \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{here}. \href{http://lucene.apache.org/solr}{Solr} is another capable and popular search engine supported in Liferay DXP. Learn to configure a remote Solr server or cluster \href{/docs/7-2/deploy/-/knowledge_base/d/installing-solr}{here}. But first, make sure you understand the disparity in functionality between the supported search engines. \section{Choosing a Search Engine}\label{choosing-a-search-engine} Elasticsearch and Solr are both supported, but there are limitations to Liferay's Solr integration. To make use of some features, you must choose Elasticsearch. \section{End User Feature Limitations of Liferay's Solr Integration}\label{end-user-feature-limitations-of-liferays-solr-integration} \begin{itemize} \tightlist \item \href{https://learn.liferay.com/commerce-2.x/index.html}{Liferay Commerce} \item \href{https://help.liferay.com/hc/en-us/articles/360029042071-Workflow-Metrics-The-Service-Level-Agreement-SLA-}{Workflow Metrics} \item \href{/docs/7-2/user/-/knowledge_base/u/filtering-search-results-with-the-custom-filter-widget}{Custom Filter search widget} \item \href{/docs/7-2/user/-/knowledge_base/u/low-level-search-options-searching-additional-or-alternate-indexes}{The Low Level Search Options widget} \item \href{https://help.liferay.com/hc/en-us/articles/360034473872-Search-Tuning-Customizing-Search-Results}{Search Tuning: Customizing Search Results} \item \href{https://help.liferay.com/hc/en-us/articles/360034473852-Search-Tuning-Synonym-Sets}{Search Tuning: Synonyms} \end{itemize} \section{Developer Feature Limitations of Liferay's Solr Integration}\label{developer-feature-limitations-of-liferays-solr-integration} Implementation for the following APIs may be added in the future, but they are not currently supported by Liferay's Solr connector. \begin{itemize} \tightlist \item From Portal Core (Module: \texttt{portal-kernel}, Artifact: \texttt{com.liferay.portal.kernel}): \begin{itemize} \tightlist \item \texttt{com.liferay.portal.kernel.search.generic.NestedQuery} \item \texttt{com.liferay.portal.kernel.search.filter}: \begin{itemize} \tightlist \item \texttt{ComplexQueryPart} \item \texttt{GeoBoundingBoxFilter} \item \texttt{GeoDistanceFilter} \item \texttt{GeoDistanceRangeFilter} \item \texttt{GeoPolygonFilter} \end{itemize} \end{itemize} \item From the Portal Search API (Module: \texttt{portal-search-api}, Artifact: \texttt{com.liferay.portal.search.api}): \begin{itemize} \tightlist \item \texttt{com.liferay.portal.search.filter}: \begin{itemize} \tightlist \item \texttt{ComplexQueryPart} \item \texttt{TermsSetFilter} \end{itemize} \item \texttt{com.liferay.portal.search.geolocation.*} \item \texttt{com.liferay.portal.search.highlight.*} \item \texttt{com.liferay.portal.search.query.function.*} \item \texttt{com.liferay.portal.search.query.*}: \item \texttt{com.liferay.portal.search.script.*} \item \texttt{com.liferay.portal.search.significance.*} \item \texttt{com.liferay.portal.search.sort.*}: only \texttt{Sort},\texttt{FieldSort}, and \texttt{ScoreSort} are supported \end{itemize} \item Portal Search Engine Adapter API (Module: \texttt{portal-search-engine-adapter-api}, Artifact: \texttt{com.liferay.portal.search.engine.adapter.api}) \begin{itemize} \tightlist \item \texttt{com.liferay.portal.search.engine.adapter.cluster.*} \item \texttt{com.liferay.portal.search.engine.adapter.document.UpdateByQueryDocumentRequest} \item \texttt{com.liferay.portal.search.engine.adapter.index.*}: only \texttt{RefreshIndexRequest} is supported \item \texttt{com.liferay.portal.search.engine.adapter.search.*}: \begin{itemize} \tightlist \item \texttt{MultisearchSearchRequest} \item \texttt{SuggestSearchRequest} \end{itemize} \item \texttt{com.liferay.portal.search.engine.adapter.snapshot.*} \end{itemize} \end{itemize} Liferay Commerce requires the \texttt{TermsSetFilter} implementation, only available in the Elasticsearch connector. \section{Elasticsearch Java Distribution Compatibility}\label{elasticsearch-java-distribution-compatibility} Another factor to consider in your search engine selection is JDK version. The search engine and Liferay DXP must use the same Java version and distribution (e.g., Oracle Open JRE 1.8.0\_201). Consult the \href{https://www.elastic.co/support/matrix\#matrix_jvm}{Elasticsearch compatibility matrix} and the \href{https://help.liferay.com/hc/en-us/sections/360002103292-Compatibility-Matrix}{Liferay DXP compatibility matrix} to learn more about supported JDK distributions and versions. This consideration is not necessary for Solr, because no JVM level serialization happens between the servers. All communication occurs at the HTTP level. \chapter{Elasticsearch}\label{elasticsearch} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Elasticsearch is an open source, highly scalable, full-text search and analytics engine. By default, Elasticsearch runs as an embedded search engine, which is useful for development and testing but is not supported in production. In production environments you must run Elasticsearch in remote mode, as a separate server or cluster. This guide walks you through the process of configuring Elasticsearch in remote mode. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/search-admin-engineinfo.png}} \caption{To see information about the currently connected search engine, go to \emph{Control Panel} → \emph{Configuration} → \emph{Search}.} \end{figure} \noindent\hrulefill \textbf{Note:} Although Elasticsearch 6.5 is shipped as the embedded Elasticsearch server version, Elasticsearch 7 is the most recent supported Elasticsearch version for 7.0. Installing Elasticsearch 7 requires that you are running Service Pack 1/Fix Pack 2 or later (GA2 or later for CE users). Elasticsearch 6.8.x is also supported. See the \href{https://help.liferay.com/hc/en-us/articles/360016511651}{compatibility matrix for exact versions}. \noindent\hrulefill If you'd rather use Solr, it's also supported. See the documentation on \href{/docs/7-2/deploy/-/knowledge_base/d/installing-solr}{Installing Solr} if you're interested. To get up and running quickly with Elasticsearch as a remote server, refer to the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{Installing Elasticsearch article}. Included there are basic instructions for installing and configuring Elasticsearch in a single server environment. Additional articles include more details and information on configuring and tuning Elasticsearch. These terms are useful to understand as you read this guide: \begin{itemize} \item \emph{Elasticsearch Home} refers to the root folder of your unzipped Elasticsearch installation (for example, \texttt{elasticsearch-7.4.1}). \item \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\emph{Liferay Home}} refers to the root folder of your Liferay DXP installation. It contains the \texttt{osgi}, \texttt{deploy}, \texttt{data}, and \texttt{license} folders, among others. \end{itemize} \section{Embedded vs.~Remote Operation Mode}\label{embedded-vs.-remote-operation-mode} When you start Liferay DXP, this message is displayed in the log: \begin{verbatim} 2019-04-29 09:59:02.276 WARN [Elasticsearch initialization thread][EmbeddedElasticsearchConnection:288] Liferay is configured to use embedded Elasticsearch as its search engine. Do NOT use embedded Elasticsearch in production. Embedded Elasticsearch is useful for development and demonstration purposes. Refer to the documentation for details on the limitations of embedded Elasticsearch. Remote Elasticsearch connections can be configured in the Control Panel. \end{verbatim} When you start Liferay DXP, Elasticsearch is already running in embedded mode. Liferay DXP runs an Elasticsearch node in the same JVM so it's easy to test-drive with minimal configuration. Running both servers in the same process has drawbacks: \begin{itemize} \tightlist \item Elasticsearch must use the same JVM options as Liferay DXP. \item Liferay DXP and Elasticsearch compete for the same system resources. \end{itemize} \noindent\hrulefill \textbf{Note:} While it's not a supported production configuration, installing Kibana to monitor the embedded Elasticsearch server is useful during development and testing. Just be aware that you must install the \href{https://www.elastic.co/downloads/kibana-oss}{OSS only Kibana build}. \noindent\hrulefill You wouldn't run an embedded database like HSQL in production, and you shouldn't run Elasticsearch in embedded mode in production either. Instead, run Elasticsearch in \emph{remote operation mode}, as a standalone server or cluster of server nodes. \section{Troubleshooting Elasticsearch Integration}\label{troubleshooting-elasticsearch-integration} Sometimes things don't go as planned. If you've set up Liferay DXP with Elasticsearch in remote mode, but Liferay DXP can't connect to Elasticsearch, check these things: \begin{description} \tightlist \item[\textbf{Cluster name:}] The value of the \texttt{cluster.name} property in \texttt{elasticsearch.yml} must match the \texttt{clusterName} property configured in the Liferay DXP Elasticsearch connector. \item[\textbf{Transport address:}] The value of the \texttt{transportAddresses} property in the Elasticsearch connector configuration must contain at least one valid host and port where an Elasticsearch node is running. If Liferay DXP is running in embedded mode, and you start a standalone Elasticsearch node or cluster, it detects that port \texttt{9300} is taken and switches to port \texttt{9301}. If you then set Liferay's Elasticsearch connector to remote mode, it continues to look for Elasticsearch at the default port (\texttt{9300}). \end{description} The following articles cover the Liferay Connector to Elasticsearch's configuration options in more detail. \begin{description} \tightlist \item[\textbf{Cluster Sniffing (Additional Configurations):}] Elasticsearch clusters can have multiple node \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-node.html\#modules-node}{types}. \href{https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html}{Cluster sniffing}, enabled by default in the Liferay DXP connector, looks for \texttt{data} nodes configured in the \texttt{transportAddresses} property. If none are available, the connector may throw a \texttt{NoNodeAvailableException} in the console log. If cluster sniffing is to remain enabled, be sure that your configuration allows for at least one \texttt{data} node's transport address to be ``sniffable'' at all times to avoid this error. \end{description} To disable cluster sniffing, add \texttt{clientTransportSniff=false} to the \texttt{.config} file or un-check the Client Transport Sniff property in System Settings. \chapter{Preparing to Install Elasticsearch}\label{preparing-to-install-elasticsearch} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} By default, 7.0 and its \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch\#embedded-vs-remote-operation-mode}{embedded Elasticsearch engine} run in the same JVM. Although this enables out-of-the-box search, it's only supported for development. For production use, Elasticsearch must run in a separate JVM. See the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{installation guide} for information on installing a remote Elasticsearch cluster. Because search engines benefit heavily from caching, their JVM memory profiles differ substantially from those of a JVM running Liferay DXP. Therefore, the two applications should always be kept separate in production environments. The following sections provide a synopsis of Elasticsearch configurations for 7.0. Prior to deployment, we strongly recommend reading \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html}{Elastic's documentation on production deployment}. \section{Sizing Your Deployment}\label{sizing-your-deployment} When sizing your Elasticsearch deployment, carefully consider CPU, memory, disk, and network capacity. To scale effectively and avoid using lots of machines, deploy Elasticsearch on medium to large machines (for example, machines with two to eight CPUs). Avoid running multiple Elasticsearch JVMs on the same operating system. \section{CPU}\label{cpu} We recommend allocating at least eight total CPU cores to the Elasticsearch engine, assuming only one Elasticsearch JVM is running on the machine. \section{Memory}\label{memory} At least 16 GB of memory is recommended, with 64 GB preferred. The precise memory allocation required depends on how much data is indexed. For index sizes 500 GB to 1 TB, 64 GB of memory suffices. \section{Disk}\label{disk} Search engines store their indexes on disk, so disk I/O capacity can impact search performance. Deploy Elasticsearch on SSD whenever possible. Otherwise use high-performance traditional hard disks (for example, 15k RPM). In either case, consider using RAID 0. Avoid using Network Attached Storage (NAS) whenever possible as the network overhead can be large. If you're using public cloud infrastructure like Amazon Web Services, use instance local storage instead of network storage, such as Elastic Block Store (EBS). Maintain 25 percent more disk capacity than the total size of your indexes. If your index is 60 GB, make sure you have at least 75 GB of disk space available. To estimate the disk space you need, you can index a representative sample of your production content and multiply that size by the fraction of your production content that it represents. For example, index 25 percent of your production content and then multiply the resulting index size by four. Keep in mind that indexing a 1 MB file doesn't result in 1 MB of disk space in the search index. \section{Cluster Size}\label{cluster-size} While Liferay DXP can work with an Elasticsearch cluster comprised of one or two nodes, the minimum cluster size recommended by Elastic for fault tolerance is three nodes. \section{Networking}\label{networking} Elasticsearch relies on clustering and sharding to deliver fast, accurate search results, and thus requires a fast and reliable network. Most modern data centers provide 1 GbE or 10 GbE between machines. \chapter{Installing Elasticsearch}\label{installing-elasticsearch} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP uses Elasticsearch to index its content. By default, it's installed as an embedded service. It works, but it's not a supported configuration for a production server. Feel free to use it while testing or developing, but when you're ready to put your site in production, you must run Elasticsearch as a standalone process. This is better anyway, because it frees you to design your infrastructure the way you want it. If you've got hardware or a VM to spare, you can separate your search infrastructure from Liferay DXP and reap some performance gains by putting search on a separate box. If you're more budget-conscious, you can still increase performance by running Elasticsearch in a separate, individually tunable JVM on the same box. Before installing Elasticsearch, refer to \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-install-elasticsearch}{Preparing to Install Elasticsearch} for guidance on configuring the servers to support an Elasticsearch deployment properly. Here's an overview of the installation steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Download a supported version of Elasticsearch. See \href{https://www.elastic.co}{Elastic's} website. \item Install Elasticsearch by extracting its archive to the system where you want it to run. \item Install some required Elasticsearch plugins. \item Name your Elasticsearch cluster. \item Configure Liferay DXP to connect to your Elasticsearch cluster. \item Restart Liferay DXP and reindex your search and spell check indexes. \end{enumerate} \noindent\hrulefill \textbf{Prerequisites:} Before continuing, make sure you have set the \href{https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/}{\texttt{JAVA\_HOME} environment variable}. If you have multiple JDKs installed, make sure Elasticsearch and Liferay DXP are using the same version and distribution (e.g., Oracle Open JDK 1.8.0\_201). You can specify this in \texttt{{[}Elasticsearch\ Home{]}/bin/elasticsearch.in.sh}: \texttt{JAVA\_HOME=/path/to/java}. \noindent\hrulefill \noindent\hrulefill \textbf{Replacing the Default Elasticsearch 6 Connector:} If you're installing Elasticsearch 6, use the connector application installed by default. If you're installing Elasticsearch 7, you'll need to download the connector from Liferay Marketplace for either \href{https://web.liferay.com/en/marketplace/-/mp/application/170642090}{CE} and \href{https://web.liferay.com/en/marketplace/-/mp/application/170390307}{DXP}. Always refer to the \href{https://www.liferay.com/documents/10182/246659966/Liferay+DXP+7.2+Compatibility+Matrix.pdf/ed234765-db47-c4ad-7c82-2acb4c73b0f9}{compatibility matrix to find the exact versions supported}. Before installing the connector, blacklist the Elasticsearch 6 connector and APIs. The \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-elasticsearch-7\#blacklisting-elasticsearch-6}{upgrade documentation} holds detailed blacklisting steps. \noindent\hrulefill When you perform these steps, you'll have a basic, production-ready instance of Liferay DXP and Elasticsearch up and running. But that's just the beginning of your server/connector configuration: \begin{itemize} \tightlist \item Read about \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{Configuring Elasticsearch} for Liferay DXP in more detail. \item Learn how to \href{/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-security}{Secure Elasticsearch}. \item {[}Liferay Enterprise Search{]} Learn how to configure \href{/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-monitoring}{Monitoring}. \end{itemize} For complete information on compatibility, check the \href{https://help.liferay.com/hc/en-us/articles/360028982631-Liferay-DXP-7-2-Compatibility-Matrix}{Liferay DXP compatibility matrix} and the \href{https://help.liferay.com/hc/en-us/articles/360016511651\#Liferay-Enterprise-Search}{Liferay Enterprise Search compatibility matrix} if you have a subscription. \section{Step One: Download a Supported Version of Elasticsearch}\label{step-one-download-a-supported-version-of-elasticsearch} If Liferay DXP isn't running, start it. Visit port 9200 on localhost to access the embedded Elasticsearch: \begin{verbatim} http://localhost:9200 \end{verbatim} A JSON document is returned that looks similar to this: \begin{verbatim} { "name" : "01BT8H4", "cluster_name" : "LiferayElasticsearchCluster", "cluster_uuid" : "ziPGEBeSToGHc7lVqaYHnA", "version" : { "number" : "6.5.0", "build_flavor" : "unknown", "build_type" : "unknown", "build_hash" : "816e6f6", "build_date" : "2018-11-09T18:58:36.352602Z", "build_snapshot" : false, "lucene_version" : "7.5.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" } \end{verbatim} The version of Elasticsearch that's running is the value of the \texttt{number} field. In this example, it's 6.5.0. You can install the embedded version, but it might not be the most up-to-date version of Elasticsearch that's supported with Liferay DXP. Consult the \href{https://help.liferay.com/hc/en-us/articles/360016511651}{Compatibility Matrix} for definitive information on what's supported. \noindent\hrulefill \textbf{Note:} Although the embedded server uses Elasticsearch 6.5, Elasticsearch 6.8.x has been tested with 7.0 GA1, and is fully supported. If you've upgraded to 7.0 Service Pack 1/Fix Pack 2 (or GA2 for CE users), Elasticsearch 7 is supported through the Liferay Connector to Elasticsearch 7, which can be downloaded from Liferay Marketplace for both \href{https://web.liferay.com/en/marketplace/-/mp/application/170642090}{CE} and \href{https://web.liferay.com/en/marketplace/-/mp/application/170390307}{DXP}. Always refer to the \href{https://help.liferay.com/hc/en-us/articles/360016511651}{compatibility matrix to find the exact versions supported}. \noindent\hrulefill Shut down the Liferay DXP server. In a local, single-machine testing environment, if you continue without shutting down, the Elasticsearch server you're about to install and start throws errors in the log if its cluster name and HTTP port match the already-running embedded Elasticsearch server. An alternative to shutting down Liferay DXP is to use a different cluster name (i.e., not \texttt{LiferayElasticsearchCluster}) and HTTP port (i.e., not \texttt{9200}) in the remote Elasticsearch server. When you know the version of Elasticsearch you need, go to \href{https://www.elastic.co}{Elastic's} website and download that version. \section{Step Two: Install Elasticsearch}\label{step-two-install-elasticsearch} Most of this step entails deciding where you want to run Elasticsearch. Do you want to run it on the same machine as Liferay DXP, or do you want to run it on its own hardware? The answer to this question comes down to a combination of the resources you have available and the size of your installation. Regardless of what you decide, either way you get the benefit of a separately tunable search infrastructure. Once you have a copy of the right version of Elasticsearch, extract it to a folder on the machine where you want it running. That's it! \section{Step Three: Install Elasticsearch Plugins}\label{step-three-install-elasticsearch-plugins} Install the following required Elasticsearch plugins: \begin{itemize} \tightlist \item \texttt{analysis-icu} \item \texttt{analysis-kuromoji} \item \texttt{analysis-smartcn} \item \texttt{analysis-stempel} \end{itemize} To install these plugins, navigate to Elasticsearch Home and enter \begin{verbatim} ./bin/elasticsearch-plugin install [plugin-name] \end{verbatim} Replace \emph{{[}plugin-name{]}} with the Elasticsearch plugin's name. \section{Step Four: Name Your Elasticsearch Cluster}\label{step-four-name-your-elasticsearch-cluster} A \emph{cluster} in Elasticsearch is a collection of nodes (servers) identified as a cluster by a shared cluster name. The nodes work together to share data and workload. A one node cluster is discussed here; to create a multi-node cluster, please refer to \href{https://www.elastic.co/guide/index.html}{Elastic's documentation}. Now that you've installed Elastic, it sits in a folder on your machine, which is referred to here as \texttt{{[}Elasticsearch\ Home{]}}. To name your cluster, you'll define the cluster name in both Elasticsearch and in Liferay DXP. First, define it in Elasticsearch. Edit the following file: \begin{verbatim} [Elasticsearch Home]/config/elasticsearch.yml \end{verbatim} Uncomment the line that begins with \texttt{cluster.name}. Set the cluster name to whatever you want to name your cluster: \begin{verbatim} cluster.name: LiferayElasticsearchCluster \end{verbatim} Of course, this isn't a very imaginative name; you may choose to name your cluster \texttt{finders\_keepers} or something else you can remember more easily. Save the file. \noindent\hrulefill \textbf{Elasticsearch 6.x:} On Elasticsearch 6.x, you must also disable X-Pack Security unless you have a Liferay Enterprise Search subscription. Add this to \texttt{elasticsearch.yml}: \texttt{xpack.security.enabled:\ false}. \noindent\hrulefill Now you can start Elasticsearch. Run the executable for your operating system from the \texttt{{[}Elasticsearch\ Home{]}/bin} folder: \begin{verbatim} ./elasticsearch \end{verbatim} Elasticsearch starts, and one of its status messages includes a transport address: \begin{verbatim} [2019-04-01T16:55:50,127][INFO ][o.e.t.TransportService ] [HfkqdKv] publish_address {127.0.0.1:9300}, bound_addresses {[::1]:9300}, {127.0.0.1:9300} \end{verbatim} Take note of this address; you'll need to give it to your Liferay DXP server so it can find Elasticsearch on the network. \section{Step Five: Configure Liferay DXP to Connect to your Elasticsearch Cluster}\label{step-five-configure-liferay-dxp-to-connect-to-your-elasticsearch-cluster} Now that you're ready to configure Liferay DXP, start it if you haven't already, log in, and then click on \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Search}. Enter the term \emph{elasticsearch} in the search bar and click the \emph{Elasticsearch {[}Version{]}} entry from the list of settings (at the time of writing, the version will either be \emph{6} or \emph{7}). Now you can configure it. Here are the configuration options to change: \textbf{Cluster Name:} Enter the name of the cluster as you defined it in Elasticsearch. \textbf{Operation Mode:} Defaults to EMBEDDED. Change it to REMOTE to connect to a standalone Elasticsearch. \textbf{Transport Addresses:} Enter a delimited list of transport addresses for Elasticsearch nodes. Here, you'll enter the transport address from the Elasticsearch server you started. The default value is \texttt{localhost:9300}, which will work. When finished, click \emph{Save}. You're almost done. \section{Step Six: Restart Liferay DXP and Reindex}\label{step-six-restart-liferay-dxp-and-reindex} If you're doing a local test installation, you probably only changed the Operation Mode in the connector configuration, so there's no need to restart; skip to re-indexing. If you've made more configuration changes in the connector's configuration, stop and restart Liferay DXP. When it's back up, log in as an administrative user and click on \emph{Control Panel} → \emph{Configuration} → \emph{Search} and click the \emph{Execute} button for \emph{Reindex all search indexes} and then \emph{Reindex all spell check indexes}. When you do that, you'll see some messages scroll up in the Elasticsearch log. When restarting Liferay DXP, \texttt{update\_mappings} messages will appear in the Elasticsearch logs: \begin{verbatim} [2019-04-01T17:08:57,462][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:57,474][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:58,393][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:58,597][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:09:07,040][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/OJidpYkgR5OcCD5dgWB8Aw] update_mapping [LiferayDocumentType] \end{verbatim} Once you reindex, more log messages appear in Elasticsearch: \begin{verbatim} [2019-04-01T17:11:17,338][INFO ][o.e.c.m.MetaDataDeleteIndexService] [HfkqdKv] [liferay-20101/OJidpYkgR5OcCD5dgWB8Aw] deleting index [2019-04-01T17:11:17,389][INFO ][o.e.c.m.MetaDataCreateIndexService] [HfkqdKv] [liferay-20101] creating index, cause [api], templates [], shards [1]/[0], mappings [LiferayDocumentType] [2019-04-01T17:11:17,471][INFO ][o.e.c.r.a.AllocationService] [HfkqdKv] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[liferay-20101][0]] ...]). [2019-04-01T17:11:17,520][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,047][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,133][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,204][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,249][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,215][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,262][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,268][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,275][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,282][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,373][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] \end{verbatim} Reindexing the spell check dictionaries produces log messages like these: \begin{verbatim} 2019-04-29 14:02:22.034 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:278] Start indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/en_US.txt 2019-04-29 14:02:34.166 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:299] Finished indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/en_US.txt 2019-04-29 14:02:34.167 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:278] Start indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/es_ES.txt 2019-04-29 14:02:39.379 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:299] Finished indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/es_ES.txt \end{verbatim} For additional confirmation that Liferay DXP recognizes the remote search engine, navigate to the Search Control Panel application and note the subtle change there: the vendor name is now simply \emph{Elasticsearch}, whereas prior to the installation of the remote Elasticsearch server, it said \emph{Elasticsearch (Embedded)}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/search-admin-engineinfo-remote.png}} \caption{To see information about the currently connected search engine, go to \emph{Control Panel → Configuration → Search}.} \end{figure} For additional details refer to the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/getting-started-install.html}{Elasticsearch installation guide}. \chapter{Configuring the Liferay Elasticsearch Connector}\label{configuring-the-liferay-elasticsearch-connector} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} For detailed Elasticsearch configuration information, refer to the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/settings.html}{Elasticsearch documentation}. The name of your Elasticsearch cluster is important. When you're running Elasticsearch in remote mode, the cluster name is used by Liferay DXP to recognize the Elasticsearch cluster. To learn about setting the Elasticsearch cluster name on the Liferay DXP side, refer below to the section called Configuring the Liferay Elasticsearch Connector. \noindent\hrulefill \textbf{Note:} The \texttt{http.enabled} setting in Elasticsearch corresponds to the \texttt{httpEnabled} setting in the Liferay Connector to Elasticsearch 6 application. As this setting was \href{https://www.elastic.co/guide/en/elasticsearch/reference/6.5/release-notes-6.3.0.html}{deprecated in Elasticsearch 6.3}, the connector's corresponding setting is now also deprecated. This setting was only used for configuring the embedded Elasticsearch server, so its deprecation should have minimal impact to production deployments. \noindent\hrulefill Elasticsearch's configuration files are written in \href{http://www.yaml.org}{YAML} and kept in the \texttt{{[}Elasticsearch\ Home{]}/config} folder. The main configuration file is \texttt{elasticsearch.yml}, used for configuring Elasticsearch modules. To set the name of the Elasticsearch cluster, open \texttt{{[}Elasticsearch\ Home{]}/config/elasticsearch.yml} and specify \begin{verbatim} cluster.name: LiferayElasticsearchCluster \end{verbatim} Since \texttt{LiferayElasticsearchCluster} is the default name given to the cluster in the Liferay DXP Elasticsearch connector, this works just fine. Of course, you can name your cluster whatever you want (we humbly submit the recommendation \texttt{clustery\_mcclusterface}).\hyperref[footnote1]{1} Configure your node name using the same syntax (setting the \texttt{node.name} property). There's no client setting for this, it exists only in each Elasticsearch node's \texttt{elasticsearch.yml} file. If you'd rather work from the command line than in the configuration file, navigate to Elasticsearch Home and enter \begin{verbatim} ./bin/elasticsearch --cluster.name clustery_mcclusterface --node.name nody_mcnodeface \end{verbatim} Feel free to change the node name or the cluster name. Once you configure Elasticsearch to your liking, start it up. \section{Starting Elasticsearch}\label{starting-elasticsearch} Start Elasticsearch by navigating to Elasticsearch Home and typing \begin{verbatim} ./bin/elasticsearch \end{verbatim} if you run Linux, or \begin{verbatim} \bin\elasticsearch.bat \end{verbatim} if you run Windows. To run as a daemon in the background, add the \texttt{-d} switch to either command: \begin{verbatim} ./bin/elasticsearch -d \end{verbatim} Once both Elasticsearch and Liferay DXP are installed and running, introduce them to each other. \section{Configuring the Liferay Elasticsearch Connector}\label{configuring-the-liferay-elasticsearch-connector-1} The Elasticsearch connector provides integration between Elasticsearch and the portal. Before configuring the connector, make sure Elasticsearch is running. There are two ways to configure the connector: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \hyperref[configuring-the-connector-in-the-control-panel]{Use the System Settings application in the Control Panel.} \item \hyperref[configuring-the-connector-with-an-osgi-config-file]{Manually create an OSGi configuration file.} \end{enumerate} It's convenient to configure the Elasticsearch connector from System Settings, but this is often only possible during development and testing. If you're not familiar with System Settings, read about it \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{here}. Remember that you can generate configuration files for deployment to other systems by configuring System Settings, and then exporting the \texttt{.config} file with your configuration. \section{Configuring the Connector in the Control Panel}\label{configuring-the-connector-in-the-control-panel} To configure the Elasticsearch connector from the System Settings application, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Start Liferay DXP. \item Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Platform}. \item Find the \emph{Elasticsearch} entry (scroll down and browse to it or use the search box) and click the Actions icon (\pandocbounded{\includegraphics[keepaspectratio]{./images/icon-actions.png}}), then \emph{Edit}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/cfg-elasticsearch-sys-settings.png}} \caption{Use the System Settings application in Liferay DXP's Control Panel to configure the Elasticsearch connector.} \end{figure} \item Make any edits to the configuration and click \emph{Save}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/cfg-elasticsearch-sys-settings2.png}} \caption{Configure the Elasticsearch connector's settings. Make sure you set the Operation Mode to \emph{Remote}.} \end{figure} \end{enumerate} \noindent\hrulefill \textbf{Note:} If you switch operation modes (\texttt{EMBEDDED} → \texttt{REMOTE}), you must trigger a re-index. Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Search}, and click \emph{Execute} next to \emph{Reindex all search indexes.} \noindent\hrulefill \section{\texorpdfstring{Configuring the Connector with an OSGi \texttt{.config} File}{Configuring the Connector with an OSGi .config File}}\label{configuring-the-connector-with-an-osgi-.config-file} When preparing a system for production deployment, you want to use a repeatable deployment process. Therefore, it's best to use the OSGi configuration file, where your configuration is maintained in a controlled source. Follow these steps to configure the Elasticsearch connector using a configuration file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create the following file: \begin{verbatim} [Liferay_Home]/osgi/configs/com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **Elasticsearch 6:** The name of the `.config` file for the Elasticsearch 6 connector is `com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config` \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Add configurations to the file, in the format \texttt{propertyName="Value"}. For example, \begin{verbatim} operationMode="REMOTE" # If running Elasticsearch from a different computer: #transportAddresses="ip.of.elasticsearch.node:9300" # Highly recommended for all non-production usage (e.g., practice, tests, diagnostics): #logExceptionsOnly="false" \end{verbatim} \item Start Liferay DXP or re-index if already running. \end{enumerate} As you can see from the System Settings entry for Elasticsearch, there are a lot more configuration options available that help you tune your system for optimal performance. What follows here are some known good configurations for clustering Elasticsearch. These, however, can't replace the manual process of tuning, testing under load, and tuning again, so we encourage you to examine the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/important-settings.html}{Elasticsearch documentation} and go through that process once you have a working configuration. \section{Configuring a Remote Elasticsearch Host}\label{configuring-a-remote-elasticsearch-host} In production systems Elasticsearch and Liferay DXP are installed on different servers. To make Liferay DXP aware of the Elasticsearch cluster, set \begin{verbatim} transportAddresses=[IP address of Elasticsearch Node]:9300 \end{verbatim} Here's an example that sets the IP address of two nodes in the Elasticsearch cluster: \begin{verbatim} transportAddresses=["192.168.1.1:9300","192.168.1.2:9300"] \end{verbatim} Set this in the Elasticsearch connector's OSGi configuration file. List as many or as few Elasticsearch nodes in this property as you want. This tells Liferay DXP the IP address or host name where search requests should be sent. If using System Settings, set the value in the \emph{Transport Addresses} property. \noindent\hrulefill \textbf{Note:} In an Elasticsearch cluster you can list the transport addresses for multiple Elasticsearch nodes as a comma-separated list in the \texttt{transportAddresses} property. If you set only one transport address, Liferay DXP loses contact with Elasticsearch if that node goes down. \noindent\hrulefill On the Elasticsearch side, set the \texttt{network.host} property in your \texttt{elaticsearch.yml} file. This property simultaneously sets both the \emph{bind host} (the host where Elasticsearch listens for requests) and the \emph{publish host} (the host name or IP address Elasticsearch uses to communicate with other nodes). See \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html}{here} for more information. \section{Clustering Elasticsearch in Remote Operation Mode}\label{clustering-elasticsearch-in-remote-operation-mode} To cluster Elasticsearch, first set \texttt{node.max\_local\_storage\_nodes} to be something greater than \texttt{1}. When you run the Elasticsearch start script, a new local storage node is added to the cluster. If you want four nodes running locally, for example, run \texttt{./bin/elasticsearch} four times. See \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-node.html\#max-local-storage-nodes}{here} for more information. Configure the number of shards and replicas in the Elasticsearch 6 connector, using the \texttt{indexNumberOfShards} and \texttt{indexNumberOfReplicas} properties to specify the number of primary shards and number of replica shards, respectively. Elasticsearch's default configuration works for a cluster of up to ten nodes, since the default number of shards is \texttt{5} and the default number of replica shards is \texttt{1}. \noindent\hrulefill \textbf{Note:} Elasticsearch uses the \href{https://www.elastic.co/guide/en/elasticsearch/reference/6.x/modules-discovery-zen.html}{Zen Discovery Module} by default, which provides unicast discovery. Additionally, nodes in the cluster communicate using the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-transport.html}{Transport Module}, through TCP. See the Elasticsearch documentation for the available properties (to be set in the \texttt{elasticsearch.yml} file), and the Liferay DXP Elasticsearch connector's settings for the connector's available settings. At a minimum, provide the list of hosts (as \texttt{host:port}) to act as gossip routers during unicast discovery in the \texttt{elasticsearch.yml}: \begin{verbatim} discovery.zen.ping.unicast.hosts: ["node1.ip.address", "node2.ip.address"] \end{verbatim} For example, \begin{verbatim} discovery.zen.ping.unicast.hosts: ["10.10.10.5", "10.10.10,.5:9305"] \end{verbatim} For more information on configuring an Elasticsearch cluster, see the documentation on \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html}{Elasticsearch Index Settings}. \noindent\hrulefill \section{Elasticsearch Connector System Settings, By Operation Mode}\label{elasticsearch-connector-system-settings-by-operation-mode} Some of the settings available for the Elasticsearch connector are applicable for only one operation mode (REMOTE or EMBEDDED). Refer to the table below: Connector Setting/Operation Mode \textbar{} EMBEDDED \textbar{} REMOTE \textbar{} \texttt{clusterName} \textbar{} x \textbar{} x \texttt{operationMode} \textbar{} x \textbar{} x \texttt{indexNamePrefix} \textbar{} x \textbar{} x \texttt{indexNumberOfReplicas*} \textbar{} x \textbar{} x \texttt{indexNumberOfShards*} \textbar{} x \textbar{} x \texttt{bootstrapMlockAll} \textbar{} x \textbar{} - \texttt{logExceptionsOnly} \textbar{} x \textbar{} x \texttt{retryOnConflict} \textbar{} x \textbar{} x \texttt{discoveryZenPingUnicastHostsPort} \textbar{} x \textbar{} - \texttt{networkHost} \textbar{} x \textbar{} - \texttt{networkBindHost} \textbar{} x \textbar{} - \texttt{networkPublishHost} \textbar{} x \textbar{} - \texttt{transportTcpPort} \textbar{} x \textbar{} - \texttt{transportAddresses} \textbar{} - \textbar{} x \texttt{clientTransportSniff} \textbar{} - \textbar{} x \texttt{clientTransportIgnoreClusterName} \textbar{} - \textbar{} x \texttt{clientTransportPingTimeout*} \textbar{} - \textbar{} x \texttt{clientTransportNodesSamplerInterval} \textbar{} - \textbar{} x \texttt{httpEnabled} \textbar{} x \textbar{} - \texttt{httpCORSEnabled} \textbar{} x \textbar{} - \texttt{httpCORSAllowOrigin} \textbar{} x \textbar{} - \texttt{httpCORSConfigurations} \textbar{} x \textbar{} - \texttt{additionalConfigurations} \textbar{} x \textbar{} x \texttt{additionalIndexConfigurations} \textbar{} x \textbar{} x \texttt{additionalTypeMappings} \textbar{} x \textbar{} x \texttt{overrideTypeMappings} \textbar{} x \textbar{} x 1 This is, of course, a nod to all those fans of \href{http://www.theatlantic.com/international/archive/2016/05/boaty-mcboatface-parliament-lessons/482046}{Boaty Mcboatface}. \chapter{Advanced Configuration of the Liferay Elasticsearch Connector}\label{advanced-configuration-of-the-liferay-elasticsearch-connector} The default configuration for Liferay's Elasticsearch connector module is set in a Java class called \texttt{ElasticsearchConfiguration}. While the Elasticsearch connector has a lot of configuration options out of the box, you might find an Elasticsearch configuration you need that isn't provided by default. In this case, add the configuration options you need. If something is configurable for Elasticsearch, it's configurable using the Elasticsearch connector. \section{Adding Settings and Mappings to the Liferay Elasticsearch Connector}\label{adding-settings-and-mappings-to-the-liferay-elasticsearch-connector} Think of the available configuration options as being divided into two groups: the most common ones that are easily configured, and more complex configurations requiring a more brute-force approach: these include the \texttt{additionalConfigurations}, \texttt{additionalIndexConfigurations}, \texttt{additionalTypeMappings}, and \texttt{overrideTypeMappings} settings. \href{./images/cfg-elasticsearch-additional-configs.png}{Figure 1: You can add Elasticsearch configurations to the ones currently available in System Settings.} \section{Additional Configurations}\label{additional-configurations} The \texttt{additionalConfigurations} configuration defines extra settings (in YAML) for the embedded Elasticsearch. This is only useful for testing environments using the embedded Elasticsearch server. Any node settings normally set in \texttt{elasticsearch.yml} can be declared here. See the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html}{Elasticsearch documentation} for a description of all possible node settings. \section{Adding Index Configurations}\label{adding-index-configurations} The \texttt{additionalIndexConfigurations} configuration defines extra settings (in JSON or YAML) that are applied to the Liferay DXP index when it's created. For example, you can create custom analyzers and filters using this setting. For a complete list of available settings, see the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html}{Elasticsearch reference}. Here's an example that shows how to configure \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules-analysis.html}{analysis} that can be applied to a field or a dynamic template (see \hyperref[overriding-type-mappings]{below} for an example application to a dynamic template). \begin{verbatim} { "analysis": { "analyzer": { "kuromoji_liferay_custom": { "filter": [ "cjk_width", "kuromoji_baseform", "pos_filter" ], "tokenizer": "kuromoji_tokenizer" } }, "filter": { "pos_filter": { "type": "kuromoji_part_of_speech" } } } } \end{verbatim} \section{Adding Type Mappings}\label{adding-type-mappings} \texttt{additionalTypeMappings} defines extra mappings for the \texttt{LiferayDocumentType} type definition. These are applied when the index is created. Add the mappings using JSON syntax. For more information see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping.html}{here} and \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/indices-put-mapping.html}{here}. Use \texttt{additionalTypeMappings} for new field (\texttt{properties}) mappings and new dynamic templates, but don't try to override existing mappings. If any of the mappings set here overlap with existing mappings, index creation fails. Use \texttt{overrideTypeMappings} to replace default mappings. As with dynamic templates, you can add sub-field mappings to Liferay DXP's type mapping. These are referred to as \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/properties.html}{properties} in Elasticsearch. \begin{verbatim} { "LiferayDocumentType": { "properties": { "fooName": { "index": "true", "store": "true", "type": "keyword" } } } } \end{verbatim} See \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping-types.html}{here} for more details on Elasticsearch's field datatypes. The above example shows how a \texttt{fooName} field might be added to Liferay DXP's type mapping. Because \texttt{fooName} is not an existing property in the mapping, it works fine. If you try to override an existing property mapping, index creation fails. Instead use the \texttt{overrideTypeMappings} setting to override \texttt{properties} in the mapping. To see that your additional mappings have been added to the \texttt{LiferayDocumentType}, use \texttt{curl} to access this URL after saving your additions and re-indexing: \begin{verbatim} curl http://[HOST]:[ES_PORT]/liferay-[COMPANY_ID]/_mapping/LiferayDocumentType?pretty \end{verbatim} Here's what it would look like for an Elasticsearch instance running on \texttt{localhost:9200}, with a Liferay DXP Company ID of \texttt{20116}: \begin{verbatim} curl http://localhost:9200/liferay-20116/_mapping/LiferayDocumentType?pretty \end{verbatim} In the above URL, \texttt{liferay-20116}is the index name. Including it indicates that you want to see the mappings that were used to create the index with that name. \section{Overriding Type Mappings}\label{overriding-type-mappings} Use \texttt{overrideTypeMappings} to override Liferay DXP's default type mappings. This is an advanced feature that should be used only if strictly necessary. If you set this value, the default mappings used to define the Liferay Document Type in Liferay DXP source code (for example, \texttt{liferay-type-mappings.json}) are ignored entirely, so include the whole mappings definition in this property, not just the segment you're modifying. To make a modification, find the entire list of the current mappings being used to create the index by navigating to the URL \begin{verbatim} http://[HOST]:[ES_PORT]/liferay-[COMPANY_ID]/_mapping/LiferayDocumentType?pretty \end{verbatim} Copy the contents in as the value of this property (either into System Settings or your OSGi configuration file). Leave the opening curly brace \texttt{\{}, but delete lines 2-4 entirely: \begin{verbatim} "liferay-[COMPANY_ID]": { "mappings" : { "LiferayDocumentType" : { \end{verbatim} Then, from the end of the mappings, delete the concluding three curly braces. \begin{verbatim} } } } \end{verbatim} Now modify whatever mappings you'd like. The changes take effect once you save the changes and trigger a re-index from Server Administration. Here's a partial example, showing a \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/dynamic-templates.html}{dynamic template} that uses the analysis configuration from \texttt{additionalIndexConfigurations} to analyze all string fields that end with \texttt{\_ja}. You'd include this with all the other default mappings, replacing the provided \texttt{template\_ja} with this custom one: \begin{verbatim} { "LiferayDocumentType": { "dynamic_templates": [ { "template_ja": { "mapping": { "analyzer": "kuromoji_liferay_custom", "index": "analyzed", "store": "true", "term_vector": "with_positions_offsets", "type": "string" }, "match": "\\w+_ja\\b|\\w+_ja_[A-Z]{2}\\b", "match_mapping_type": "string", "match_pattern": "regex" } ... } ] } } \end{verbatim} \section{Multi-line YAML Configurations}\label{multi-line-yaml-configurations} If you configure the settings from the last section using an OSGi configuration file, you might find yourself needing to write YAML snippets that span multiple lines. The syntax for that is straightforward and just requires appending each line with \texttt{\textbackslash{}n\textbackslash{}}, like this: \begin{verbatim} additionalConfigurations=\ cluster.routing.allocation.disk.threshold_enabled: false\n\ cluster.service.slow_task_logging_threshold: 600s\n\ index.indexing.slowlog.threshold.index.warn: 600s\n\ index.search.slowlog.threshold.fetch.warn: 600s\n\ index.search.slowlog.threshold.query.warn: 600s\n\ monitor.jvm.gc.old.warn: 600s\n\ monitor.jvm.gc.young.warn: 600s \end{verbatim} From simple configurations to overriding existing type mappings, Elasticsearch and Liferay's connector to Elasticsearch are configurable. \chapter{Elasticsearch Connector Settings: Reference}\label{elasticsearch-connector-settings-reference} Elasticsearch is the default search engine for 7.0. The connection is managed through the \emph{Liferay Connector to Elasticsearch {[}Version{]}}, and is configurable through System Settings or an OSGi configuration file named \begin{verbatim} com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config \end{verbatim} If you are using Elasticsearch 7, your configuration file must be named \begin{verbatim} com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config \end{verbatim} Deploy the file to \texttt{{[}Liferay\_Home{]}/osgi/configs} and a listener auto-detects it. The list below is all the configuration settings for Liferay's default Elasticsearch connector, in the order they appear in the System Settings application (The \emph{Elasticsearch {[}Version{]}} entry under the \emph{Search} category): \begin{description} \tightlist \item[\texttt{clusterName=LiferayElasticsearchCluster}] A String value that sets the name of the cluster to integrate with. This name should match the remote cluster when Operation Mode is set to remote. (See also: remote operation mode) \item[\texttt{operationMode=EMBEDDED}] There are two operation modes you can choose from: EMBEDDED or REMOTE. Set to REMOTE to connect to a remote standalone Elasticsearch cluster. Set to EMBEDDED to start Liferay with an internal Elasticsearch instance. Embedded operation mode is unsupported for production environments. \item[\texttt{indexNamePrefix=liferay-}] Set a String value to use as the prefix for the search index name. The default value should not be changed under normal conditions. If you change it, you must also perform a \emph{reindex all} operation for the portal and then manually delete the old index using the Elasticsearch administration console. \end{description} \texttt{indexNumberOfReplicas=} Set the number of replicas for each index. If left unset, no replicas are used. A full reindex is required to make changes take effect. \texttt{indexNumberOfShards=} Set the number of index shards to use when a Liferay index is created. If left unset, a single shard is used. A full reindex is required to make changes take effect. \begin{description} \tightlist \item[\texttt{bootstrapMlockAll=false}] A boolean setting that, when set to \texttt{true}, tries to lock the process address space into RAM, preventing any Elasticsearch memory from being swapped out (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-configuration-memory.html\#bootstrap-memory_lock}{here}) for more information) \item[\texttt{logExceptionsOnly=true}] A boolean setting that, when set to true, only logs exceptions from Elasticsearch, and does not rethrow them. \item[\texttt{retryOnConflict=5}] Set an int value for the number of retries to attempt if a version conflict occurs because the document was updated between getting it and updating it (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-update.html\#docs-update-api-query-params}{here} for more information). \item[\texttt{discoveryZenPingUnicastHostsPort=9300-9400}] Set a String value for the range of ports to use when building the value for discovery.zen.ping.unicast.hosts. Multiple Elasticsearch nodes on a range of ports can act as gossip routers at the same computer (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-discovery-hosts-providers.html}{here} for more information). \item[\texttt{networkHost=}] Set this String value to instruct the node to bind to this hostname or IP address and publish (advertise) this host to other nodes in the cluster. This is a shortcut which sets the bind host and the publish host at the same time (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html\#common-network-settings}{here} for more information). \item[\texttt{networkBindHost=}] Set the String value of the network interface(s) a node should bind to in order to listen for incoming requests (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html\#advanced-network-settings}{here} for more information). \item[\texttt{networkPublishHost=}] Set the String value of a single interface that the node advertises to other nodes in the cluster, so that those nodes can connect to it (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html\#advanced-network-settings}{here} for more information). \item[\texttt{transportTcpPort=}] Set the String value for the port to bind for communication between nodes. Accepts a single value or a range (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-transport.html\#_tcp_transport}{here} for more information). \item[\texttt{transportAddresses=localhost:9300}] Set the String values for the addresses of the remote Elasticsearch nodes to connect to. This value is required when Operation Mode is set to remote (see \href{https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html}{here} for more information). Specify as many or few nodes as you see fit. \item[\texttt{clientTransportSniff=true}] Set this boolean to true to enable cluster sniffing and dynamically discover available data nodes in the cluster (see \href{https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html}{here} for more information). \item[\texttt{clientTransportIgnoreClusterName=false}] Set this boolean to true to ignore cluster name validation of connected nodes (see \href{https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html}{here} for more information). \end{description} \texttt{clientTransportPingTimeout=} The time (in seconds) the client node waits for a ping response from a node. If unset, the default Elasticsearch \texttt{client.transport.ping\_timeout} is used. \begin{description} \tightlist \item[\texttt{clientTransportNodesSamplerInterval=}] Set this String value to instruct the client node on how often to sample / ping the nodes listed and connected (see \href{https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html}{here} for more information). \item[\texttt{httpEnabled=true}] Set this boolean to false to disable the http layer entirely on nodes which are not meant to serve REST requests directly. As this setting was \href{https://www.elastic.co/guide/en/elasticsearch/reference/6.7/release-notes-6.3.0.html\#deprecation-6.3.0}{deprecated in Elasticsearch 6.3}, the connector's corresponding setting is now also deprecated. This setting was only used for configuring the embedded Elasticsearch server, so its deprecation should have minimal impact to production deployments. \item[\texttt{httpCORSEnabled=true}] Set this boolean to false to disable cross-origin resource sharing, i.e.~whether a browser on another origin can do requests to Elasticsearch. If disabled, web front end tools like elasticsearch-head may be unable to connect (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html\#_settings}{here} for more information). \item[\texttt{httpCORSAllowOrigin=/https?:\textbackslash{}\textbackslash{}/\textbackslash{}\textbackslash{}/localhost(:{[}0-9{]}+)?/}] Set the String origins to allow when HTTP CORS is enabled (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html\#_settings}{here} for more information). \item[\texttt{httpCORSConfigurations=}] Set the String values for custom settings for HTTP CORS, in YML format (\texttt{elasticsearch.yml}) (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html\#_settings}{here} for more information). \item[\texttt{additionalConfigurations=}] Set the String values for custom settings for embedded Elasticsearch, in YML format. See: Adding Settings to the Liferay Elasticsearch Connector \item[\texttt{additionalIndexConfigurations=}] Set the String values for custom settings for the Liferay index, in JSON or YML format (refer to the Elasticsearch Create Index API for more information). See: Adding Settings to the Liferay Elasticsearch Connector \item[\texttt{additionalTypeMappings=}] Set the String values for custom mappings for the \texttt{LiferayDocumentType}, in JSON format (refer to the Elasticsearch Put Mapping API for more information) See: Adding Settings to the Liferay Elasticsearch Connector \end{description} \texttt{overrideTypeMappings=} Settings here override Liferay DXP's default type mappings. This is an advanced feature that should be used only if strictly necessary. If you set this value, the default mappings used to define the Liferay Document Type in Liferay DXP source code (for example, \texttt{liferay-type-mappings.json}) are ignored entirely, so include the whole mappings definition in this property, not just the segment you're modifying. \section{Configurations only Affecting the Embedded Elasticsearch Server}\label{configurations-only-affecting-the-embedded-elasticsearch-server} These settings (defined above) are only meant to use while configuring the embedded Elasticsearch server. Configuring these will elicit no effect on remote Elasticsearch installations: \begin{itemize} \tightlist \item \texttt{bootstrapMlockAll} \item \texttt{discoveryZenPingUnicastHostsPort} \item \texttt{networkHost} \item \texttt{networkBindHost} \item \texttt{networkPublishHost} \item \texttt{transportTcpPort} \item \texttt{httpEnabled} \item \texttt{httpCORSEnabled} \item \texttt{httpCORSAllowOrigin} \item \texttt{httpCORSConfigurations} \end{itemize} You can easily configure these settings in the System Setting application, or as mentioned above, you can specify them in a deployable OSGi \texttt{.config} file. \chapter{Installing Liferay Enterprise Search Security}\label{installing-liferay-enterprise-search-security} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The very first thing you must do to secure Elasticsearch is enable X-Pack Security. After that you can begin configuring authentication and Transport Layer Security. \noindent\hrulefill \textbf{Elasticsearch 6.x:} If you're using Elasticsearch 6, you'll need a Liferay Enterprise Search (LES) subscription to use X-Pack. Starting with the Liferay Connector to Elasticsearch 7 (available on Liferay Marketplace), X-Pack security is included by default. X-Pack monitoring still requires LES. \noindent\hrulefill \section{Enabling X-Pack Security}\label{enabling-x-pack-security} To enable security, add this setting in \texttt{elasticsearch.yml}: \begin{verbatim} xpack.security.enabled: true \end{verbatim} Now you can set up X-Pack users. \section{Setting Up X-Pack Users}\label{setting-up-x-pack-users} In a system using X-Pack Security and X-Pack Monitoring, these built-in X-Pack users are important: \begin{itemize} \tightlist \item \texttt{kibana} \item \texttt{elastic} \end{itemize} Set the passwords for all X-Pack's \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/built-in-users.html}{built-in users}. The \texttt{setup-passwords} command is the simplest method to set the built-in users' first-use passwords for the first time. To update a password subsequently, use Kibana's UI or the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/security-api-change-password.html}{Change Password API}. The \texttt{interactive} argument sets the passwords for all built-in users. The configuration shown in these articles assumes you set all passwords to \emph{liferay}. Of course, that's not recommended for production systems. \begin{verbatim} ./bin/elasticsearch-setup-passwords interactive \end{verbatim} Elastic's \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-passwords.html}{setup-passwords command} documentation describes additional options. Since you're securing Elasticsearch, remember the \texttt{elastic} user's password. Enable transport layer security on each node. \section{Enabling Transport Layer Security}\label{enabling-transport-layer-security} The following instructions for enabling TLS use \texttt{liferay} as the password whenever one is needed. Use your own passwords for your installation. \noindent\hrulefill \textbf{Important:} Elasticsearch and Liferay DXP must share the keys and certificates used to configure TLS. Copy them between servers and point to the local copy in the corresponding configuration files. \noindent\hrulefill \section{Generate Node Certificates}\label{generate-node-certificates} \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html\#node-certificates}{Generate a node certificate} for each node. Alternatively, use a Certificate Authority to obtain node certificates. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a certificate authority, using \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/certutil.html}{X-Pack's \texttt{certutil}} command: \begin{verbatim} ./bin/elasticsearch-certutil ca --pem --ca-dn CN=localhost \end{verbatim} This generates a ZIP file. Unzip the contents in the \texttt{{[}Elasticsearch\ Home{]}/config/certs} folder. \item Generate X.509 certificates and private keys using the CA from Step 1: \begin{verbatim} ./bin/elasticsearch-certutil cert --pem --ca-cert /path/to/ca.crt --ca-key /path/to/ca.key --dns localhost --ip 127.0.0.1 --name localhost \end{verbatim} This generates another ZIP file. Extract the contents in the \texttt{{[}Elasticsearch\ Home{]}/config/certs} folder. \end{enumerate} \noindent\hrulefill \textbf{Note:} The \texttt{certutil} command defaults to using the \emph{PKSC\#12} format for certificate generation. Since Kibana does not work with PKSC\#12 certificates, the \texttt{-\/-pem} option (generates the certificate in PEM format) is important if you're using X-Pack monitoring. \noindent\hrulefill \textbf{Checkpoint:} You now have the following files in your \texttt{{[}Elasticsearch\ Home{]}/config/certs} folder: \begin{verbatim} ca.crt ca.key localhost.crt localhost.key \end{verbatim} \section{Enable TLS for Elasticsearch 7}\label{enable-tls-for-elasticsearch-7} \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html\#enable-ssl}{Enable TLS} on each node via its \texttt{elasticsearch.yml}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enable transport layer TLS with these settings in \texttt{elasticsearch.yml} for inter-node communication: \begin{verbatim} xpack.security.transport.ssl.enabled: true \end{verbatim} \item Add the certificate, key and certificate authority paths to each node's \texttt{elasticsearch.yml}: \begin{verbatim} xpack.security.transport.ssl.certificate: certs/localhost.key xpack.security.transport.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.security.transport.ssl.key: certs/localhost.crt xpack.security.transport.ssl.verification_mode: certificate \end{verbatim} The example paths above assume you added the certificate to \texttt{Elasticsearch\ Home/config/}. \item Enable TLS on the HTTP layer to encrypt client communication: \begin{verbatim} xpack.security.http.ssl.enabled: true \end{verbatim} \item Configure the certificate, key, and certificate authority paths to each node's \texttt{elasticsearch.yml}: \begin{verbatim} xpack.security.http.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.security.http.ssl.certificate: certs/localhost.crt xpack.security.http.ssl.key: certs/localhost.key xpack.security.http.ssl.verification_mode: certificate \end{verbatim} \end{enumerate} \section{Elasticsearch 6 TLS}\label{elasticsearch-6-tls} The settings on Elasticsearch 6 were slightly different than those presented above for Elasticsearch 7. \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html\#enable-ssl}{Enable TLS} on each node via its \texttt{elasticsearch.yml}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the certificate, key and certificate authority paths to each node's \texttt{elasticsearch.yml}: \begin{verbatim} xpack.ssl.certificate: certs/localhost.crt xpack.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.ssl.key: certs/localhost.key xpack.ssl.verification_mode: certificate \end{verbatim} The example paths above assume you added the certificate to \texttt{Elasticsearch\ Home/config/}. \item Enable transport layer TLS with these settings in \texttt{elasticsearch.yml}: \begin{verbatim} xpack.security.transport.ssl.enabled: true \end{verbatim} \item Enable TLS on the HTTP layer to encrypt client communication: \begin{verbatim} xpack.security.http.ssl.enabled: true \end{verbatim} \end{enumerate} After X-Pack is installed and TLS is enabled, configure the X-Pack Security adapter in Liferay DXP. \section{Example Elasticsearch Security Configuration}\label{example-elasticsearch-security-configuration} For ease of copying and pasting, here is the complete Elasticsearch configuration (\texttt{elasticsearch.yml}) used in this guide (with the Elasticsearch 6 example commented out): \begin{verbatim} # For Elasticsearch 7.3/7.4 cluster.name: LiferayElasticsearchCluster # X-Pack Security xpack.security.enabled: true ## TLS/SSL settings for Transport layer xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.key: certs/localhost.key xpack.security.transport.ssl.certificate: certs/localhost.crt xpack.security.transport.ssl.certificate_authorities : [ "certs/ca.crt" ] # TLS/SSL settings for HTTP layer xpack.security.http.ssl.enabled: true xpack.security.http.ssl.verification_mode: certificate xpack.security.http.ssl.key: certs/localhost.key xpack.security.http.ssl.certificate: certs/localhost.crt xpack.security.http.ssl.certificate_authorities : [ "certs/ca.crt" ] # Comment out when Kibana and Liferay's X-Pack Monitoring are also configured #xpack.monitoring.collection.enabled: true # For Elasticsearch 6.5/6.8 #cluster.name: LiferayElasticsearchCluster # # X-Pack Security #xpack.security.enabled: true # # Enable TLS/SSL #xpack.security.transport.ssl.enabled: true # To enable Transport level SSL for internode-communication #xpack.security.http.ssl.enabled: true # To enable HTTP level SSL required by Kibana # ## General TLS/SSL settings for both Transport and HTTP levels #xpack.ssl.verification_mode: certificate #xpack.ssl.key: certs/localhost.key #xpack.ssl.certificate: certs/localhost.crt #xpack.ssl.certificate_authorities : [ "certs/ca.crt" ] # # Comment out when Kibana and Liferay's X-Pack Monitoring are also configured #xpack.monitoring.collection.enabled: true \end{verbatim} For both Elasticsearch 6 and Elasticsearch 7, the Liferay Connector settings are the same. \section{Install and Configure the Liferay Enterprise Search Security app}\label{install-and-configure-the-liferay-enterprise-search-security-app} If you have a Liferay Enterprise Search subscription, \href{https://web.liferay.com/group/customer/dxp/downloads/enterprise-search}{download} the Liferay Enterprise Search Security app . Install the LPKG file by copying it into the \texttt{Liferay\ Home/deploy} folder. To configure the X-Pack adapter, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. Find the \emph{Search} category and click on the \emph{X-Pack Security} entry. You can enter the property values here, but it's more common to use a \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{configuration file} deployed to \texttt{{[}Liferay\ Home{]}/osgi/configs}. For the X-Pack security connector, create a file called \begin{verbatim} com.liferay.portal.search.elasticsearch7.configuration.XPackSecurityConfiguration.config \end{verbatim} The exact contents of the file depend on your X-Pack setup. To configure the adapter according to the Elasticsearch setup documented here, populate the file like this: \begin{verbatim} sslKeyPath="/path/to/localhost.key" sslCertificatePath="/path/to/localhost.crt" certificateFormat="PEM" requiresAuthentication="true" username="elastic" password="liferay" sslCertificateAuthoritiesPaths="/path/to/ca.crt" transportSSLVerificationMode="certificate" transportSSLEnabled="true" \end{verbatim} The \texttt{password} should match what you set during the X-Pack password setup above. The certificate and key files referenced here are the same ones used on the Elasticsearch server. Copy them to the Liferay DXP server and update their paths in the configuration accordingly. Enable authentication by setting \texttt{requiresAuthentication} to \texttt{true} and providing the credentials for the Elasticsearch user. For TLS, enable transport TLS, set the certificate verification mode and certificate format, and provide the path to the certificate, key, and certificate authority. Of course, the exact values depend on your X-Pack configuration. Here's the complete list of configuration options for the X-Pack Connector: \begin{itemize} \tightlist \item \texttt{sslKeyPath} \item \texttt{sslCertificatePath} \item \texttt{sslCertificateAuthoritiesPaths} \item \texttt{certificateFormat} \item \texttt{requiresAuthentication} \item \texttt{username} \item \texttt{password} \item \texttt{transportSSLVerificationMode} \item \texttt{transportSSLEnabled} \item \texttt{sslKeystorePath} \item \texttt{sslKeystorePassword} \item \texttt{sslTruststorePath} \item \texttt{sslTruststorePassword} \end{itemize} When you're finished configuring X-Pack Security, restart Elasticsearch. These steps require a full cluster restart. \section{Disabling Elasticsearch Deprecation Logging}\label{disabling-elasticsearch-deprecation-logging} Some Elasticsearch APIs used by Liferay's Elasticsearch 6 connector were deprecated as of Elasticsearch 6.6 and 6.7. This can result WARN log entries in Elasticsearch's deprecation log when Liferay DXP is configured with Elasticsearch 6.8.x and X-Pack Security is enabled: \begin{verbatim} 2019-07-16T14:47:05,779][WARN ][o.e.d.c.j.Joda ] [ ode_name]'y' year should be replaced with 'u'. Use 'y' for year-of-era. Prefix your date format with '8' to use the new specifier. [2019-07-16T14:47:06,007][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.certificate] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,007][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.certificate_authorities] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,008][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.key] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,463][WARN ][o.e.d.x.c.s.SSLService ] [ ode_name]SSL configuration [xpack.http.ssl] relies upon fallback to another configuration for [key configuration, trust configuration], which is deprecated. [2019-07-16T14:47:06,464][WARN ][o.e.d.x.c.s.SSLService ] [ ode_name]SSL configuration [xpack.security.transport.ssl.] relies upon fallback to another configuration for [key configuration, trust configuration], which is deprecated. \end{verbatim} These warnings do not signal any functional issues, and can be disabled (see \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/logging.html\#deprecation-logging}{here} to learn how). \chapter{Backing Up Elasticsearch}\label{backing-up-elasticsearch} \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html\#index-modules-settings}{Elasticsearch replicas} protect against a node going down, but they won't help you with a catastrophic failure. Only good backup practices can help you then. Back up and restore your Elasticsearch cluster in three steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Configure a repository \item Make a snapshot of the cluster \item Restore from the snapshot \end{enumerate} For more detailed information, refer to the \href{https://www.elastic.co/guide/en/elasticsearch/guide/master/administration.html}{Elasticsearch administration guide}, and in particular to the documentation on the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshot-restore.html}{Snapshot and Restore module}. \section{Creating a Repository}\label{creating-a-repository} First \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-register-repository.html}{create a repository} to store your snapshots. Several repository types are supported: \begin{itemize} \tightlist \item Shared file system, such as a Network File System or NAS \item Amazon S3 \item HDFS (Hadoop Distributed File System) \item Azure Cloud \end{itemize} If using a shared file system repository type, first register the path to the shared file system in each node's \texttt{elasticsearch.yml} using \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-register-repository.html\#snapshots-filesystem-repository}{the path.repo setting}. \begin{verbatim} path.repo: ["path/to/shared/file/system/"] \end{verbatim} Once the path to the folder hosting the repository is registered (make sure the folder exists), create the repository with a PUT command. For example, \begin{verbatim} curl -X PUT "localhost:9200/_snapshot/test_backup" -H 'Content-Type: application/json' -d' { "type": "fs", "settings": { "location": "/path/to/shared/file/system/" } }' \end{verbatim} Replace \texttt{localhost:9200} with the proper \texttt{hostname:port} combination for your system, replace \texttt{test\_backup} with the name of the repository to create, and use the absolute path to your shared file system in the \texttt{location}. If the repository is set up successfully, you see this message: \begin{verbatim} {"acknowledged":true} \end{verbatim} Once the repository exists, you can start creating snapshots. \section{Taking Snapshots of the Cluster}\label{taking-snapshots-of-the-cluster} The easiest snapshot approach is to create a \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-take-snapshot.html}{snapshot of all the indexes in your cluster}. For example, \begin{verbatim} curl -XPUT localhost:9200/_snapshot/test_backup/snapshot_1 \end{verbatim} If \texttt{\{"accepted":true\}} appears in the terminal, the snapshot was a success. It's possible to be more selective when taking snapshots. For example, if you use LES Monitoring, you can exclude the monitoring indexes. Explicitly declare the indexes to include in the snapshot. For example, \begin{verbatim} curl -XPUT localhost:9200/_snapshot/test_backup/snapshot_2 { "indices": "liferay-0,liferay-20116" } \end{verbatim} \textbf{Note:} For a list of all the Elasticsearch indexes, use this command: \begin{verbatim} curl -X GET "localhost:9200/_cat/indices?v" \end{verbatim} This shows the index metrics: \begin{verbatim} health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open liferay-20099 obqiNE1_SDqfuz7rincrGQ 1 0 195 0 303.1kb 303.1kb green open liferay-47206 3YEjtye1S9OVT0i0EZcXcw 1 0 7 0 69.7kb 69.7kb green open liferay-0 shBWwpkXRxuAmGEaE475ug 1 0 147 1 390.9kb 390.9kb \end{verbatim} It's important to note that Elasticsearch uses a \emph{smart snapshots} approach. To understand what that means, consider a single index. The first snapshot includes a copy of the entire index, while subsequent snapshots only include the delta between the first, complete index snapshot and the current state of the index. Eventually you'll end up with a lot of snapshots in your repository, and no matter how cleverly you name the snapshots, you may forget what some snapshots contain. For this purpose, the Elasticsearch API provides getting information about any snapshot. For example: \begin{verbatim} curl -XGET localhost:9200/_snapshot/test_backup/snapshot_1 \end{verbatim} returns \begin{verbatim} {"snapshots":[ {"snapshot":"snapshot_1", "uuid":"WlSjvJwHRh-xlAny7zeW3w", "version_id":6.80399, "version":"6.8.2", "indices":["liferay-20099","liferay-0","liferay-47206"], "state":"SUCCESS", "start_time":"2018-08-15T21:40:17.261Z", "start_time_in_millis":1534369217261, "end_time":"2018-08-15T21:40:17.482Z", "end_time_in_millis":1534369217482, "duration_in_millis":221, "failures":[], "shards":{ "total":3, "failed":0, "successful":3 } } ]} \end{verbatim} There's lots of useful information here, including which indexes were included in the snapshot. If you want to get rid of a snapshot, use the \texttt{DELETE} command. \begin{verbatim} curl -XDELETE localhost:9200/_snapshot/test_backup/snapshot_1 \end{verbatim} You might trigger creation of a snapshot and regret it (for example, you didn't want to include all the indexes in the snapshot). If your snapshots contain a lot of data, this can cost time and resources. To cancel the ongoing creation of a snapshot, use the same \texttt{DELETE} command. The snapshot process is terminated and the partial snapshot is deleted from the repository. \section{Restoring from a Snapshot}\label{restoring-from-a-snapshot} What good is a snapshot if you can't use it to \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-restore-snapshot.html}{restore your search indexes} in case of catastrophic failure? Use the \texttt{\_restore} API to restore all the snapshot's indexes: \begin{verbatim} curl -XPOST localhost:9200/_snapshot/test_backup/snapshot_1/_restore \end{verbatim} Restore only specific indexes from a snapshot by passing in the \texttt{indices} option, and rename the indexes using the \texttt{rename\_pattern} and \texttt{rename\_replacement} options: \begin{verbatim} curl -XPOST localhost:9200/_snapshot/test_backup/snapshot_1/_restore { "indices": "liferay-20116", "rename_pattern": "liferayindex_(.+)", "rename_replacement": "restored_liferayindex_$1" } \end{verbatim} This restores only the index named \texttt{liferay-20116index\_1} from the snapshot. The \texttt{rename...} settings specify that the beginning \texttt{liferayindex\_} are replaced with \texttt{restored\_liferayindex\_}, so \texttt{liferay-20116index\_1} becomes \texttt{restored\_liferay-20116index\_1}. As with the process for taking snapshots, an errant restored index can be canceled with the \texttt{DELETE} command: \begin{verbatim} curl -XDELETE localhost:9200/restored_liferay-20116index_3 \end{verbatim} Nobody likes catastrophic failure on a production system, but Elasticsearch's API for taking snapshots and restoring indexes can help you rest easy knowing that your search cluster can be restored if disaster strikes. For more details and options, read Elastic's documentation on the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshot-restore.html}{Snapshot and Restore Module}. \chapter{Upgrading to Elasticsearch 6.5}\label{upgrading-to-elasticsearch-6.5} Elasticsearch 6.5.x is the default, most up-to-date supported version of Elasticsearch for Liferay DXP. If you're upgrading @product@ and still running Elasticsearch 6.1, it's time to upgrade your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6.1.x server, follow the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{installation guide} to install Elasticsearch and the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{configuration guide} to configure the Elasticsearch adapter. Here, you'll learn to upgrade an existing Elasticsearch 6.1.x server (or cluster) to Elasticsearch 6.5.x: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch}{Install and configure Elasticsearch 6.5.x}. \item Disable X-Pack Security in \texttt{elasticsearch.yml} unless you have an Liferay Enterprise Search subscription, which gives you access to the Liferay Enterprise Search Security app: \begin{verbatim} xpack.security.enabled: false \end{verbatim} \item Configure the bundled Liferay Connector to Elasticsearch 6. \item Re-index all search and spell check indexes. \end{enumerate} \noindent\hrulefill \textbf{Before Proceeding,} back up your existing data before upgrading Elasticsearch. If something goes wrong during or after the upgrade, roll back to the previous version using the uncorrupted index snapshots. See \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch}{here} for more information. \noindent\hrulefill Learn about configuring Elasticsearch \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{here}. \section{Re-index}\label{re-index} Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration}, and click \emph{Execute} for the \emph{Reindex all search indexes} entry. You must also re-index the spell check indexes. \section{Reverting to Elasticsearch 6.1}\label{reverting-to-elasticsearch-6.1} Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 6.5, roll back to Elasticsearch 6.1 and regroup. Since your 6.1 and 6.5 are currently two separate installations, this procedure is straightforward: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Stop the Liferay Connector to Elasticsearch 6. \item Stop Elasticsearch 6.5 and make sure that the Elasticsearch 6.1 \texttt{elasticsearch.yml} and the connector app are configured to use the same port (9200 by default). \item Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. \end{enumerate} \chapter{Upgrading to Elasticsearch 6.8}\label{upgrading-to-elasticsearch-6.8} Elasticsearch 6.8.x is supported for 7.0. If you're upgrading Liferay DXP and still running Elasticsearch 6.1, it's time to upgrade your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6.1.x server, follow the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{installation guide} to install Elasticsearch and the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{configuration guide} to configure the Elasticsearch adapter. Here, you'll learn to upgrade an existing Elasticsearch 6.1.x server (or cluster) to Elasticsearch 6.8.x: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch}{Install and configure Elasticsearch 6.8.x}. \item Disable X-Pack Security in \texttt{elasticsearch.yml} unless you have an Liferay Enterprise Search subscription which gives you access to the LES Security app: \begin{verbatim} xpack.security.enabled: false \end{verbatim} \item Configure the bundled Liferay Connector to Elasticsearch 6. \item Re-index all search and spell check indexes. \end{enumerate} \noindent\hrulefill \textbf{Before Proceeding,} back up your existing data before upgrading Elasticsearch. If something goes wrong during or after the upgrade, roll back to the previous version using the uncorrupted index snapshots. See \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch}{here} for more information. \noindent\hrulefill Learn about configuring Elasticsearch \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{here}. \section{Re-index}\label{re-index-1} Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration}, and click \emph{Execute} for the \emph{Reindex all search indexes} entry. You must also re-index the spell check indexes. \section{Reverting to Elasticsearch 6.1}\label{reverting-to-elasticsearch-6.1-1} Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 6.8, roll back to Elasticsearch 6.1 and regroup. Since your 6.1 and 6.8 are currently two separate installations, this procedure is straightforward: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Stop the Liferay Connector to Elasticsearch 6. \item Stop Elasticsearch 6.8 and make sure that the Elasticsearch 6.1 \texttt{elasticsearch.yml} and the connector app are configured to use the same port (9200 by default). \item Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. \end{enumerate} \chapter{Upgrading to Elasticsearch 7}\label{upgrading-to-elasticsearch-7} Elasticsearch 7 is supported for 7.0. If you're upgrading Liferay DXP and still running Elasticsearch 6, consider upgrading your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6 server, follow the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{installation guide} to install Elasticsearch and the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{configuration guide} to configure the Elasticsearch adapter. \noindent\hrulefill \textbf{Before Proceeding,} back up your existing data before upgrading Elasticsearch. If something goes wrong during or after the upgrade, roll back to the previous version using the uncorrupted index snapshots. See \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch}{here} for more information. \noindent\hrulefill Here, you'll learn to upgrade an existing Elasticsearch 6 server (or cluster) to Elasticsearch 7: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{Install and configure Elasticsearch 7}. \item Back up the application specific indexes for Workflow Metrics and Result Rankings. \item In 7.0, security is now provided out of the box. If you're using X-Pack security, enable it (it's disabled by default): \begin{verbatim} xpack.security.enabled: true \end{verbatim} \item Blacklist the bundled Liferay Connector to Elasticsearch 6. \item Install and configure the Liferay Connector to Elasticsearch 7. \item Re-index all search and spell check indexes. \end{enumerate} \noindent\hrulefill \textbf{Known Issue:} See \href{https://issues.liferay.com/browse/LPS-103938}{LPS-103938}. The Liferay Connector to Elasticsearch 7 throws an exception in the log when the LPKG file is deployed. There are no known functional impacts. If unexpected errors occur, re-start the Liferay DXP server. \noindent\hrulefill Learn about configuring Elasticsearch \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{here}. \section{Backing up Application-Specific Indexes}\label{backing-up-application-specific-indexes} To preserve data stored in application-specific indexes, use a \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/rolling-upgrades.html}{rolling upgrade} for each index you need to preserve across the upgrade. \noindent\hrulefill \textbf{Synonym Sets:} If you follow the workaround for the bug \href{https://issues.liferay.com/browse/LPS-100272}{LPS-100272}, your Synonym sets are preserved across the upgrade, as they are stored in the index settings directly, and not in their own index. \noindent\hrulefill \section{Blacklisting Elasticsearch 6}\label{blacklisting-elasticsearch-6} To blacklist Elasticsearch 6, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a configuration file named \begin{verbatim} com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config \end{verbatim} \item Give it these contents: \begin{verbatim} blacklistBundleSymbolicNames=[ \ "com.liferay.portal.search.elasticsearch6.api", \ "com.liferay.portal.search.elasticsearch6.impl", \ "com.liferay.portal.search.elasticsearch6.spi", \ "com.liferay.portal.search.elasticsearch6.xpack.security.impl", \ "Liferay Connector to X-Pack Security [Elastic Stack 6.x] - Impl", \ "Liferay Enterprise Search Security - Impl" \ ] \end{verbatim} \end{enumerate} \section{Re-index}\label{re-index-2} Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Search}, and click \emph{Execute} for the \emph{Reindex all search indexes} entry. You must also re-index the spell check indexes. \section{Reverting to Elasticsearch 6}\label{reverting-to-elasticsearch-6} Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 7, roll back to Elasticsearch 6 and regroup. Since your Elasticsearch 6 and 7 are currently two separate installations, this procedure takes only a few steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Stop the Liferay Connector to Elasticsearch 6. \item Stop Elasticsearch 7 and make sure that the Elasticsearch 6 \texttt{elasticsearch.yml} and the connector app are configured to use the same port (9200 by default). \item Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. \end{enumerate} \chapter{Installing Liferay Enterprise Search}\label{installing-liferay-enterprise-search} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} A Liferay Enterprise Search (LES) subscription gets you additional features beyond what's available out of the box with your Liferay DXP subscription. It includes \begin{itemize} \tightlist \item Liferay Enterprise Search Security* \item Liferay Enterprise Search Monitoring \item Liferay Enterprise Search Learning to Rank \end{itemize} * A LES subscription is not necessary if using Elasticsearch 7 via the \_Liferay Connector to Elasticsearch 7\_as X-Pack's security features are bundled. See the \href{https://help.liferay.com/hc/en-us/articles/360016511651\#Liferay-Enterprise-Search}{LES compatibility matrix} for more information. X-Pack is an \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-xpack.html}{Elasticsearch extension} for securing and monitoring Elasticsearch clusters. If you use Elasticsearch, you should secure it with X-Pack. The security features of X-Pack include authenticating access to the Elasticsearch cluster's data and encrypting Elasticsearch's internal and external communications. These are necessary security features for most production systems. A LES subscription gets you access to two connectors if you're using Elasticsearch 6: monitoring and security. Elasticsearch 7 bundles these security features, and Liferay has followed suit. Therefore, security is bundled with the Liferay Connector to Elasticsearch 7, and no LES subscription is necessary. Because of this, the documentation for \href{/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-security}{installing Liferay Enterprise Search Security} on Liferay DXP has been moved from the LES documentation section (this section) to the \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch}{Elasticsearch} installation and configuration guide. Contact \href{https://www.liferay.com/contact-us\#contact-sales}{Liferay's Sales department for more information}. Here's an overview of using the LES applications with Liferay DXP: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Get an \href{https://help.liferay.com/hc/en-us/articles/360014400932}{Enterprise Search subscription}. \item You'll receive a license for X-Pack monitoring. Install it on your Elasticsearch servers. \textbf{Note:} If using Elasticsearch 6, you'll also need a LES subscription for X-Pack security. \item Download and install the Liferay Enterprise Search apps you purchased. Find them in the \href{https://customer.liferay.com/en/downloads}{Help Center Downloads page}, choosing Enterprise Search from the Product drop-down menu. \item Configure the connectors with the proper credentials, encryption information, and settings. \item Restart Elasticsearch. These steps require a full cluster restart. \end{enumerate} More detailed installation instructions are available in the article for each LES feature. Elastic's documentation explains additional configuration options, features, and the architecture of \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-security.html}{X-Pack}. \noindent\hrulefill \textbf{Note:} Out of the box, X-Pack comes with a \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/start-trial.html}{30-day trial}. This can be useful if there's a delay between your subscription and receipt of your production X-Pack license. \noindent\hrulefill Now configure security, monitoring, and/or Learning to Rank, depending on your needs. \chapter{Installing Liferay Enterprise Search Monitoring}\label{installing-liferay-enterprise-search-monitoring} First configure security if you're using X-Pack's security features. Then come back here for instructions on installing and configuring Kibana (the monitoring server) with X-Pack so that Elasticsearch (secured with X-Pack), Kibana (secured with X-Pack), and Liferay DXP can communicate effortlessly and securely. A Liferay Enterprise Search (LES) subscription is necessary for this integration. Contact \href{https://www.liferay.com/contact-us\#contact-sales}{Liferay's Sales department for more information}. \noindent\hrulefill \textbf{The same monitoring connector is used for Elasticsearch 6 and 7}: When first created, the Liferay Enterprise Search Monitoring app was intended to be used only with Elasticsearch 6. However, testing revealed that the same connector works with Elasticsearch 7, so a new connector is not necessary if you're using Elasticsearch 7. \noindent\hrulefill To install X-Pack monitoring, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Tell Elasticsearch to enable data collection. \item Download and install Kibana. \item Configure Kibana with the proper security settings. \item Install the Liferay Enterprise Search Monitoring app. \item Configure the connector to communicate with Elasticsearch. \end{enumerate} This document assumes you're enabling security (with authentication and encrypted communication) \emph{and} monitoring for Elasticsearch 7, but differences in the process for Elasticsearch 6 are noted where necessary. \section{Enable Encrypting Communication (TLS/SSL) in Elasticsearch and in Liferay DXP}\label{enable-encrypting-communication-tlsssl-in-elasticsearch-and-in-liferay-dxp} Start by following the steps in this \href{/docs/7-2/deployment/-/knowledge_base/u/installing-liferay-enterprise-search-security}{article} to enable TLS/SSL in your Elasticsearch and Liferay DXP installation. Then continue by enabling data collection in Elasticsearch. \section{Enable Data Collection}\label{enable-data-collection} Monitoring is enabled on Elasticsearch by default, but data collection isn't. Enable data collection by adding this line to \texttt{elasticsearch.yml}. \begin{verbatim} xpack.monitoring.collection.enabled: true \end{verbatim} Now install Kibana. \section{Install Kibana}\label{install-kibana} Make sure to install the correct version of Kibana. Check the \href{https://help.liferay.com/hc/en-us/articles/360016511651\#Liferay-Enterprise-Search}{Liferay Enterprise Search compatibility matrix} for details. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{https://www.elastic.co/downloads/kibana}{Download Kibana} and extract it. The root folder is referred to as \emph{Kibana Home}. \item Tell Kibana where to send monitoring data by setting Elasticsearch's URL in \texttt{kibana.yml}: \begin{verbatim} elasticsearch.hosts: [ "https://localhost:9200" ] \end{verbatim} On 6.5 and below, use \begin{verbatim} elasticsearch.url: "https://localhost:9200" \end{verbatim} If TLS/SSL is not enabled on Elasticsearch, this is an \texttt{http} URL, otherwise use \texttt{https}. \item If not using X-Pack security, start Kibana by opening a command prompt to Kibana Home and entering this command: \begin{verbatim} ./bin/kibana \end{verbatim} \end{enumerate} If you're using X-Pack's security features on the Elasticsearch server, there's additional configuration required before starting Kibana. \section{Configure Kibana with Authentication}\label{configure-kibana-with-authentication} If X-Pack requires authentication to access the Elasticsearch cluster, follow these steps or refer to \href{https://www.elastic.co/guide/en/kibana/7.x/monitoring-xpack-kibana.html}{Elastic's documentation}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set the password for the built-in \texttt{kibana} user in \texttt{{[}Kibana\ Home{]}/config/kibana.yml}: \begin{verbatim} elasticsearch.username: "kibana" elasticsearch.password: "liferay" \end{verbatim} Use your \texttt{kibana} user password from your X-Pack setup. Once Kibana is installed, you can change the built-in user passwords from the \emph{Management} user interface. \item If you're not encrypting communication with the Elasticsearch cluster, start Kibana from Kibana home. \begin{verbatim} ./bin/kibana \end{verbatim} \item Go to \texttt{http://localhost:5601} and make sure you can sign in as a \href{https://www.elastic.co/guide/en/elasticsearch/reference/current/realms.html}{user} who has the \texttt{kibana\_user} \href{https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-roles.html}{role} or a superuser (like the \texttt{elastic} user). \end{enumerate} \section{Configuring Kibana with Encryption (TLS/SSL)}\label{configuring-kibana-with-encryption-tlsssl} Follow these steps to configure Kibana if X-Pack encrypts communication with the Elasticsearch cluster. Consult \href{https://www.elastic.co/guide/en/kibana/7.x/using-kibana-with-security.html\#using-kibana-with-security}{Elastic's guide} for more information. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Copy the \texttt{{[}Elasticsearch\ Home{]}/config/certs} folder into the \texttt{{[}Kibana\ Home{]}/config/} folder. This example reuses the certificate files \href{/docs/7-2/deployment/-/knowledge_base/u/installing-liferay-enterprise-search-security}{created for Elasticsearch itself}. If you wish to generate a separate certificate for your Kibana instance, make sure it is signed by the same CA as the Elasticsearch node certificates. \item Add these settings to \texttt{kibana.yml}: \begin{verbatim} xpack.security.encryptionKey: "xsomethingxatxleastx32xcharactersx" xpack.security.sessionTimeout: 600000 elasticsearch.hosts: [ "https://localhost:9200" ] elasticsearch.ssl.verificationMode: certificate elasticsearch.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] elasticsearch.ssl.certificate: config/certs/localhost.crt elasticsearch.ssl.key: config/certs/localhost.key server.ssl.enabled: true server.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] server.ssl.certificate: config/certs/localhost.crt server.ssl.key: config/certs/localhost.key \end{verbatim} \end{enumerate} Elasticsearch/Kibana 6.5 and below use a different property for specifying the host URL. Replace the \texttt{elasticsearch.hosts} property with \begin{verbatim} elasticsearch.url: "https://localhost:9200" \end{verbatim} For more information about monitoring and security best practices in a clustered environment, refer to \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-xpack.html}{Elastic's documentation}. After this step you can access Kibana at \texttt{https://localhost:5601} and sign in with a Kibana user. The last step is to connect Kibana to Liferay DXP. \section{Configuring the Liferay Enterprise Search Monitoring app}\label{configuring-the-liferay-enterprise-search-monitoring-app} If you have a LES subscription, download the Liferay Enterprise Search Monitoring app . Install the LPKG file by copying it into the \texttt{Liferay\ Home/deploy} folder. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Once the connector is installed and Kibana and Elasticsearch are securely configured, create a \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{configuration file} named \begin{verbatim} com.liferay.portal.search.elasticsearch6.xpack.monitoring.web.internal.configuration.XPackMonitoringConfiguration.config \end{verbatim} \item Place these settings in the \texttt{.config} file: \begin{verbatim} kibanaPassword="liferay" kibanaUserName="elastic" kibanaURL="https://localhost:5601" \end{verbatim} The values depend on your Kibana configuration. For example, use a URL such as \texttt{kibanaURL="http://localhost:5601"} if you are not using X-Pack Security TLS/SSL features. Alternatively, configure the monitoring adapter from \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System Settings}. Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} and find the X-Pack Monitoring entry in the Search category. All the configuration options for the monitoring connector appear there. \item Deploy this configuration file to \texttt{Liferay\ Home/osgi/configs}, and your running instance applies the settings. There's no need to restart the server. \item There are two more settings to add to Kibana itself. The first forbids Kibana from rewriting requests prefixed with \texttt{server.basePath}. The second sets Kibana's base path for the Monitoring portlet to act as a proxy for Kibana's monitoring UI. Add this to \texttt{kibana.yml}: \begin{verbatim} server.rewriteBasePath: false server.basePath: "/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy" \end{verbatim} Note that once you set the \texttt{server.basePath}, you cannot access the Kibana UI through Kibana's URL (e.g., \texttt{https://localhost:5601}). All access to the Kibana UI is through the Monitoring portlet, which is only accessible to signed in Liferay DXP users. Navigate directly to the portlet using this URL: \url{http://localhost:8080/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy/app/monitoring} \item Because you're using the Monitoring portlet in Liferay DXP as a proxy to Kibana's UI, if you are using X-Pack Security with TLS/SSL, you must configure the application server's startup JVM parameters to recognize a valid \emph{truststore} and \emph{password}. First, navigate to Elasticsearch Home and generate a PKSC\#12 certificate from the CA you created when setting up X-Pack security: \begin{verbatim} ./bin/elasticsearch-certutil cert --ca-cert /path/to/ca.crt --ca-key /path/to/ca.key --ip 127.0.0.1 --dns localhost --name localhost --out /path/to/Elasticsearch_Home/config/localhost.p12 \end{verbatim} Next use the \texttt{keytool} command to generate a truststore: \begin{verbatim} keytool -importkeystore -deststorepass liferay -destkeystore /path/to/truststore.jks -srckeystore /path/to/Elasticsearch_Home/config/localhost.p12 -srcstoretype PKCS12 -srcstorepass liferay \end{verbatim} Add the trustore path and password to your application server's startup JVM parameters. Here are example truststore and path parameters for appending to a Tomcat server's \texttt{CATALINA\_OPTS}: \begin{verbatim} -Djavax.net.ssl.trustStore=/path/to/truststore.jks -Djavax.net.ssl.trustStorePassword=liferay \end{verbatim} \end{enumerate} Restart Liferay DXP and Kibana. \section{Monitoring in Liferay DXP}\label{monitoring-in-liferay-dxp} Once Kibana and X-Pack are successfully installed and configured and all the servers are running, add the X-Pack Monitoring portlet to a page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Add} menu on a page and choose \emph{Widgets} \item Search for \emph{monitoring} and drag the \emph{X-Pack Monitoring} widget from the Search category onto the page. \end{enumerate} See the Elastic documentation for information on \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/es-monitoring.html}{monitoring Elasticsearch}. \section{Example Kibana Configuration}\label{example-kibana-configuration} Here are the \texttt{kibana.yml} properties demonstrated in this article, for convenient copy/pasting: \begin{verbatim} # X-Pack Security enabled (Basic Auth) elasticsearch.username: "kibana" elasticsearch.password: "liferay" # When TLS/SSL is enabled in X-Pack Security xpack.security.encryptionKey: "xsomethingxatxleastx32xcharactersx" xpack.security.sessionTimeout: 600000 # If on Elasticsearch 6.5 or below, replace the next property with: # elasticsearch.url: "http://localhost:9200" elasticsearch.hosts: [ "https://localhost:9200" ] # Enable TLS/SSL for out-bound traffic: from Kibana to Elasticsearch elasticsearch.ssl.verificationMode: certificate elasticsearch.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] elasticsearch.ssl.certificate: config/certs/localhost.crt elasticsearch.ssl.key: config/certs/localhost.key # Enable TLS/SSL for in-bound traffic: from browser or # DXP (X-Pack Monitoring widget, proxy) to Kibana server.ssl.enabled: true server.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] server.ssl.certificate: config/certs/localhost.crt server.ssl.key: config/certs/localhost.key # To use Kibana inside the X-Pack Monitoring widget server.rewriteBasePath: false server.basePath: "/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy" \end{verbatim} \chapter{Liferay Enterprise Search: Learning to Rank}\label{liferay-enterprise-search-learning-to-rank} Search engines like Elasticsearch have well-tuned relevance algorithms, good for general search purposes. Sometimes, this ``generally good'' relevance scoring just isn't good enough. You can attain more perfect search results by employing machine learning. Learning to Rank harnesses machine learning to improve search result rankings. It combines the expertise of data scientists with machine learning to produce a smarter scoring function that's applied to search queries. 7.0, Service Pack 1/Fix Pack 2 and later, supports Learning to Rank through its support of Elasticsearch versions 6.x and 7.4.x. It requires a \href{https://help.liferay.com/hc/en-us/articles/360014400932}{LES} subscription. It's important to understand that the \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/index.html}{Elasticsearch Learning to Rank plugin} is not produced by Elastic, and there is not a pre-built plugin for all of Liferay DXP's supported Elasticsearch versions. \section{Disabling Learning to Rank on a Search Page}\label{disabling-learning-to-rank-on-a-search-page} Learning to Rank does not work with the \href{/docs/7-2/user/-/knowledge_base/u/sorting-search-results-with-the-sort-widget}{Sort widget}. If you must use Learning to Rank in your Liferay DXP instance, but want to disable it on a particular Search page (perhaps to use the Sort widget), you can: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a \href{/docs/7-2/user/-/knowledge_base/u/low-level-search-options-searching-additional-or-alternate-indexes}{Low Level Search Options} widget to the Search page. \item Open the widget's Configuration screen by clicking \emph{Configure additional low level search options in this page.} \item In the \emph{Contributors to Exclude} field, enter \texttt{com.liferay.portal.search.learning.to.rank} \end{enumerate} Now the Learning to Rank re-scoring process is skipped for queries entered into the page's Search Bar, and results are sortable in the Sort widget and returned using the default relevance algorithm. \section{Prerequisites}\label{prerequisites} There are some prerequisites for using Learning to Rank to re-score Liferay queries sent to Elasticsearch: \begin{itemize} \item If using Elasticsearch 7, 7.0 Service Pack 1/Fix Pack 2 or later is required, with the appropriate Elasticsearch Connector version installed. \item If using Elasticsearch 6, 7.0 Fix Pack 3 or later is required, with the appropriate Elasticsearch Connector version installed. \item A \href{https://help.liferay.com/hc/en-us/articles/360014400932}{Liferay Enterprise Search} (LES) subscription is required for Learning to Rank. Once you have a subscription, \href{https://customer.liferay.com/downloads}{download the Liferay Enterprise Search Learning to Rank} LPKG file and \href{/docs/7-2/user/-/knowledge_base/u/installing-apps-manually\#installing-apps-manually}{install it to your Liferay DXP server.} \item A remote Elasticsearch server, with your data indexed into it. \item The corresponding version of the \href{https://github.com/o19s/elasticsearch-learning-to-rank}{Elasticsearch Learning to Rank} plugin installed into Elasticsearch. \item A \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/training-models.html}{trained model} uploaded into the Learning to Rank plugin. \end{itemize} To understand more about the compatibility requirements for LES, see its \href{https://help.liferay.com/hc/en-us/articles/360016511651\#Liferay-Enterprise-Search}{compatibility matrix}. How does Learning to Rank work? \section{Technical Overview}\label{technical-overview} In a normal search, the User sends a query to the search engine via Liferay DXP's \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-bar}{Search Bar}. The order of returned results is dictated by the search engine's \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules-similarity.html\#bm25}{relevance scoring algorithm}. Here's where Learning to Rank intervenes and makes that process different: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item User enters a query into the search bar. \item Liferay sends the query to Elasticsearch, and retrieves the first 1000 results as usual, using the search engine's relevance algorithm. \item The top 1000 results are not returned as search hits, but are used by Elasticsearch for \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-request-body.html\#request-body-search-rescore}{re-scoring} via the \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/searching-with-your-model.html\#rescore-top-n-with-sltr}{rescore functionality}. \item The results are re-scored by the \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/searching-with-your-model.html}{SLTR query}, which includes the keywords and the trained model to use for re-scoring. \item Once the trained model re-ranks the results, they're returned in Liferay's \href{/docs/7-2/user/-/knowledge_base/u/search-results}{Search Results} in their new order. \end{enumerate} Though it's just a sub-bullet point in the ordered list above, much of the work in this paradigm is in creating and honing the trained model. That's beyond the scope of Liferay's role, but we'll help you get all the pieces in place to orchestrate the magic of machine learning on your Liferay queries. Here's a brief overview of what constitutes \emph{model training}. \section{Model Training}\label{model-training} A useful trained model is produced when a good judgment list and a good feature set are fed to a Learning to Rank algorithm (this is the machine learning part of the puzzle). Therefore, it's incumbent on you to assemble \begin{itemize} \item The Learning to Rank algorithm you wish to use for creating a training model. This demonstration uses \href{https://sourceforge.net/p/lemur/wiki/RankLib/}{RankLib}. \item A \emph{judgment list}, containing a graded list of search results. The algorithm is designed to produce a model that honors the ordering of the judgment list. \item A feature set, containing all the \emph{features} you're handing to the Learning to Rank algorithm, which it uses in conjunction with the judgment list to produce a reliable model. An example feature set for Liferay DXP data is shown in the next article. \end{itemize} \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html\#judgments-expression-of-the-ideal-ordering}{Judgment lists} are lists of graded search results. \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html\#features-the-raw-material-of-relevance}{Features} are the variables that the algorithm uses to create a function that can score results in a smarter way. If you don't give enough, or the correct, relevant features, your model will not be ``smart'' enough to provide improved results. In the next article you'll see the steps required to configure Learning to Rank with Liferay DXP. \chapter{Configuring Learning to Rank}\label{configuring-learning-to-rank} Before beginning, you must have a remote \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-elasticsearch-7}{Elasticsearch 6 or 7} cluster communicating with 7.0. See the \href{https://help.liferay.com/hc/en-us/articles/360016511651\#Liferay-Enterprise-Search}{compatibility matrix for more information} \noindent\hrulefill \textbf{Helpful hint:} Use \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-suggestions}{Suggestions} to discover the most common queries (this can be one way to decide which queries to create Learning to Rank models for). \noindent\hrulefill \section{Step 1: Install the Learning to Rank Plugin on Elasticsearch}\label{step-1-install-the-learning-to-rank-plugin-on-elasticsearch} See \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/\#installing}{the Elasticsearch Learning to Rank plugin documentation} to learn about installing the Learning to Rank plugin. You'll be running a command like this one, depending on the plugin version you're installing: \begin{verbatim} ./bin/elasticsearch-plugin install http://es-learn-to-rank.labs.o19s.com/ltr-1.1.0-es6.5.4.zip \end{verbatim} If using X-Pack security in your Elasticsearch cluster, there \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/x-pack.html}{may be additional steps.} \section{Step 2: Training and Uploading a Model}\label{step-2-training-and-uploading-a-model} Detailed instructions on training models is outside the scope of this guide. This requires the intervention of data scientists, who can recommend appropriate tools and models. Use what works for you. In doing so, you'll almost certainly be compiling \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html\#judgments-expression-of-the-ideal-ordering}{Judgment lists} and \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/building-features.html}{feature sets} that can be used by the training tool you select to generate a model that produces good search results. This can be a long journey, but once you get to the end of it, you'll want to upload the model to the Learning to Rank plugin. \section{Upload the Model to the Learning to Rank Plugin}\label{upload-the-model-to-the-learning-to-rank-plugin} You'll upload the model using a \texttt{POST} request, but first you need to make sure you have a \texttt{\_ltr} index and a feature set uploaded to the Learning to Rank plugin. Use Kibana (or even better, the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-monitoring}{Monitoring widget}), to make these tasks easier. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you don't already have an \texttt{\_ltr} index, create one: \begin{verbatim} PUT _ltr \end{verbatim} \item Add a feature set to the \texttt{\_ltr} index. In this example the set is called \texttt{liferay}: \begin{verbatim} POST _ltr/_featureset/liferay { "featureset": { "name": "liferay", "features": [ { "name": "title", "params": [ "keywords" ], "template": { "match": { "title_en_US": "{{keywords}}" } } }, { "name": "content", "params": [ "keywords" ], "template": { "match": { "content_en_US": "{{keywords}}" } } }, { "name": "asset tags", "params": [ "keywords" ], "template": { "match": { "assetTagNames": "{{keywords}}" } } } ] } } \end{verbatim} Take note of the syntax used here, since it's required. \item Add the trained model to the feature set: \begin{verbatim} POST _ltr/_featureset/liferay/_createmodel { "model": { "name": "linearregression", "model": { "type": "model/ranklib", "definition": """ # Linear Regression # Lambda = 1.0E-10 0:-0.717621803830712 1:-0.717621803830712 2:-2.235841905601106 3:19.546816765721594 """ } } } \end{verbatim} \end{enumerate} This is a very high level set of instructions, because there's not much to do in Liferay DXP. To learn in more detail about what's required, see the \href{https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/index.html}{Learning to Rank plugin's documentation}. Keep reworking those judgment lists! \section{Step 3: Enable Learning to Rank}\label{step-3-enable-learning-to-rank} Enable Learning to Rank from Control Panel → Configuration → System Settings → Search → Learning to Rank. There's a simple on/off configuration and a text field where you must enter the name of the trained model to apply to search queries. The model in the previous step was named \texttt{linearregression}, so that's what you'd enter. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/search-learning-to-rank.png}} \caption{Enable Learning to Rank in Liferay DXP from the System Settings entry.} \end{figure} That's all the configuration required to get the Elasticsearch Learning to Rank plugin ingesting a trained model, a feature set, and search queries from Liferay DXP. \chapter{Installing Solr}\label{installing-solr} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Solr is a popular enterprise search platform built on Apache Lucene. It's reliable, scalable, and fault tolerant. Read more about it \href{http://lucene.apache.org/solr/}{here}. \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{Elasticsearch} is the default search engine that ships with Liferay DXP, and some Liferay Search features are only available on Elasticsearch. It's valid, however, to use Solr instead. In particular, if you've already been using Solr with a previous version of Liferay DXP, or your platform (for example, your OS or JVM) \href{https://www.elastic.co/support/matrix}{isn't supported by Elasticsearch}, you might choose to use Solr to search and index your Liferay DXP data. There are circumstances that force you to use Elasticsearch instead of Solr. Read \href{/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine\#choosing-a-search-engine}{here} for more information. Liferay DXP 7.2, Fix Pack 1, supports Solr 7.5.x through the Liferay Connector to Solr 7 application, version 2.0.0. Liferay DXP 7.2, Service Pack 1/Fix Pack 2 and later, supports Solr 7.5.x through the Liferay Connector to Solr 7 application, version 2.0.1. Liferay Portal CE 7.2, GA2 and later (not available at time of writing), support Solr 7.5.x through the Liferay CE Connector to Solr 7 application. \noindent\hrulefill \textbf{Upgrading to Service Pack 1 or Fix Pack 2 (or later) requires installation of a new Solr connector:} If you were running version 2.0.0 of the Liferay Connector to Solr 7 application, and you want to install Service Pack 1/Fix Pack 1 (or later), you must install version 2.0.1 of the Liferay Connector to Solr 7 application. \noindent\hrulefill \section{Blacklisting Elasticsearch-Only Features}\label{blacklisting-elasticsearch-only-features} Before installing Solr, you must \href{/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components}{blacklist} certain DXP \href{/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine\#choosing-a-search-engine}{features that only work with Elasticsearch}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a configuration file named \begin{verbatim} com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config \end{verbatim} \item Give it these contents: \begin{verbatim} blacklistBundleSymbolicNames=["com.liferay.portal.search.tuning.web.api","com.liferay.portal.search.tuning.web","com.liferay.portal.search.tuning.synonyms.web","com.liferay.portal.search.tuning.rankings.web"] \end{verbatim} \item Place the file in \texttt{Liferay\ Home/osgi/configs}. \end{enumerate} It is required during the Solr installation process to also \href{https://portal.liferay.dev/docs/7-2/deploy/-/knowledge_base/d/installing-solr-basic-installation\#stopping-the-elasticsearch-connector}{stop the Elasticsearch Connectors} that ship with Liferay DXP. If you're ready to blacklist those bundles now, use these contents in the blacklist configuration file: \begin{verbatim} blacklistBundleSymbolicNames=["com.liferay.portal.search.tuning.web.api","com.liferay.portal.search.tuning.web","com.liferay.portal.search.tuning.synonyms.web","com.liferay.portal.search.tuning.rankings.web","com.liferay.portal.search.elasticsearch6.spi","com.liferay.portal.search.elasticsearch6.api","com.liferay.portal.search.elasticsearch6.impl","Liferay Enterprise Search Monitoring ","Liferay Enterprise Search Security "] \end{verbatim} The Liferay Enterprise Search bundles must be excluded if you don't have a LES subscription. \chapter{Installing Solr: Basic Installation}\label{installing-solr-basic-installation} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} There are two ways to install the Liferay Connector to Solr 7: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Navigate to \href{https://web.liferay.com/marketplace/}{Liferay Marketplace} and download the app that corresponds to your portal. \end{enumerate} Once the app LPKG is downloaded, copy it to \texttt{Liferay\_Home/osgi/marketplace}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item In your running portal instance, navigate to \emph{Control Panel} → \emph{Apps} → \emph{Store}. Sign in using your credentials, search for Solr Search Engine, and purchase (it's free) the Liferay Connector to Solr 7 entry. \end{enumerate} As you proceed, remember these terms: \emph{Solr Home}: The center of the Solr system (pun intended). This directory is \texttt{solr-{[}version{]}/server/solr}. \emph{Liferay Home}: The root folder of your Liferay DXP installation. It contains the \texttt{osgi}, \texttt{deploy}, \texttt{data}, and \texttt{license} folders, among others. There are two installation steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Installing and configuring Solr 7. \item Installing and configuring the Solr 7 connector for Liferay DXP. \end{enumerate} Before configuring Liferay DXP for Solr, install and set up Solr. \section{Installing and Configuring Solr 7}\label{installing-and-configuring-solr-7} To install and properly configure Solr for Liferay DXP: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Download \href{http://archive.apache.org/dist/lucene/solr/7.5.0/}{Solr} and unzip it. \item Navigate to \texttt{solr-{[}version{]}/server/solr}. This is Solr Home. \item Create a new folder called \texttt{liferay}. \item In the \texttt{liferay} folder, create two new folders: \texttt{conf} and \texttt{data}. \item Copy the contents of \texttt{Solr\_Home/configsets/\_default/conf} to \texttt{Solr\_Home/liferay/conf}. \item Open the Liferay Connector to Solr 7's LPKG file with an archive manager. Open the \texttt{com.liferay.portal.search.solr7.impl.jar} file, and extract \begin{verbatim} META-INF/resources/solrconfig.xml \end{verbatim} and \begin{verbatim} META-INF/resources/schema.xml \end{verbatim} to \begin{verbatim} Solr_Home/liferay/conf \end{verbatim} This replaces the current \texttt{solrconfig.xml} and \texttt{schema.xml} files with ones that tell Solr how to index data coming from Liferay DXP. \item Create a \texttt{core.properties} file in \texttt{Solr\_Home/liferay} and add this configuration: \begin{verbatim} config=solrconfig.xml dataDir=data name=liferay schema=schema.xml \end{verbatim} \item Checkpoint: your \texttt{Solr\_Home/liferay} folder now has this structure: \begin{verbatim} liferay ├── conf │   ├── lang │   │   ├── contractions_ca.txt │   │   ├── ....txt │   ├── managed-schema │   ├── params.json │   ├── protwords.txt │   ├── schema.xml │   ├── solrconfig.xml │   ├── stopwords.txt │   └── synonyms.txt ├── core.properties └── data \end{verbatim} \item Start the Solr server by entering \begin{verbatim} ./bin/solr start -f \end{verbatim} from the top-level folder of your Solr installation (\texttt{solr-{[}version{]}}). \item The Solr server listens on port \texttt{8983} by default. Navigate to \texttt{http://localhost:8983/solr/\#/\textasciitilde{}cores} (assuming you're testing locally with \texttt{localhost} as your host), and confirm that the \texttt{liferay} core is available. \end{enumerate} Solr is now installed. Next install and configure the Solr connector. \section{Installing and Configuring the Liferay Solr Adapter}\label{installing-and-configuring-the-liferay-solr-adapter} Since Elasticsearch is the default search engine, the Elasticsearch connector is already installed and running. You must stop it before configuring the Solr connector. \section{Stopping the Elasticsearch Connector}\label{stopping-the-elasticsearch-connector} Stop the Elasticsearch connector bundle using the App Manager, the Felix Gogo shell, or the bundle blacklist. If you're a Liferay DXP customer, use the blacklist feature as described below. The App Manager and Gogo shell rely on the \texttt{osgi/state} folder to ``remember'' the state of the bundle. If you delete this folder (recommended during patching) the Elasticsearch connector is reinstalled and started automatically. Navigate to Control Panel → Apps → App Manager. Once in the App Manager, search for \emph{elasticsearch}. Find the Liferay Connector to Elasticsearch 6 module and click the Actions ((\pandocbounded{\includegraphics[keepaspectratio]{./images/icon-actions.png}})) button. Choose the Deactivate option. This leaves the bundle installed, but stops it in the OSGi runtime. Alternatively, use the \href{/developer/tutorials/-/knowledge_base/7-2/using-the-felix-gogo-shell}{Felix Gogo shell} to stop the Elasticsearch connector. Enter \begin{verbatim} lb elasticsearch \end{verbatim} You'll see two active bundles for the Liferay Connector to Elasticsearch 6: an API and an IMPL bundle. \begin{verbatim} ID|State |Level|Name 476|Active | 10|Liferay (CE) Connector to Elasticsearch 6 - API (3.0.0) 478|Active | 10|Liferay Portal Search Elasticsearch 6 API (3.0.4) 480|Active | 10|Liferay Portal Search Elasticsearch 6 SPI (3.2.1) 706|Active | 10|Liferay (CE) Connector to Elasticsearch 6 - Impl (3.0.0) 707|Active | 10|Liferay Portal Search Elasticsearch 6 Implementation (3.0.15) \end{verbatim} Stop the API bundle by entering \begin{verbatim} stop [bundle ID] \end{verbatim} In the example above, the \texttt{{[}bundle\ ID{]}} is \texttt{476}. \textbf{Liferay DXP:} DXP customers should \href{/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components}{blacklist} the Elasticsearch, Shield, and Marvel plugins. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \begin{verbatim} com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config \end{verbatim} file with these contents: \begin{verbatim} blacklistBundleSymbolicNames=["com.liferay.portal.search.elasticsearch6.spi","com.liferay.portal.search.elasticsearch6.api","com.liferay.portal.search.elasticsearch6.impl","Liferay Enterprise Search Monitoring ","Liferay Enterprise Search Security "] \end{verbatim} If the LES Security and Monitoring LPKG files are installed, you must blacklist these too. \item Place the file in \texttt{Liferay\ Home/osgi/configs}. \end{enumerate} \section{Install and Configure the Solr Connector}\label{install-and-configure-the-solr-connector} Now you're ready to install the connector: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Start Liferay DXP, then deploy the Solr connector by copying the LPKG you downloaded to \texttt{Liferay\_Home/deploy}. You'll see a \texttt{STARTED} message in your Liferay DXP log once the Solr connector is installed. Here's what the log message looks like: \begin{verbatim} 2018-11-06 19:59:49.396 INFO [pipe-start 943 944][BundleStartStopLogger:39] STARTED com.liferay.portal.search.solr7.api_2.0.5 [943] 2018-11-06 19:59:49.490 INFO [pipe-start 943 944][BundleStartStopLogger:39] STARTED com.liferay.portal.search.solr7.impl_2.0.11 [944] \end{verbatim} \item To re-index against Solr, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Search}, and click \emph{Execute} next to the \emph{Reindex all search indexes} option. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/solr-reindex.png}} \caption{Once the Solr connector is installed, you can re-index your Liferay DXP data against your Solr server.} \end{figure} In production deployments, specify your edits to the Solr connector's default configurations using a configuration file deployed to the \texttt{Liferay\_Home/osgi/configs} folder. Name the file \begin{verbatim} com.liferay.portal.search.solr7.configuration.SolrConfiguration.config \end{verbatim} During testing and development, use the Solr 7 System Settings entry Control Panel → Configuration → System Settings for editing the default configurations. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/solr-system-settings.png}} \caption{You can configure Solr from Liferay DXP's System Settings application. This is most useful during development and testing.} \end{figure} The next article covers clustering Solr with SolrCloud. \chapter{Installing Solr: High Availability with SolrCloud}\label{installing-solr-high-availability-with-solrcloud} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Use SolrCloud if you need a cluster of Solr servers. Note that to use SolrCloud in production, you should set up an \href{https://cwiki.apache.org/confluence/display/solr/Setting+Up+an+External+ZooKeeper+Ensemble}{external ZooKeeper ensemble}. \href{http://zookeeper.apache.org/}{ZooKeeper} is a centralized coordination service for managing distributed systems like your SolrCloud cluster. The steps included here should be considered the bare minimum of what must be done to configure SolrCloud with Liferay DXP. For example, these instructions cover configuring SolrCloud on a single machine, whereas a production environment would feature multiple physical or virtual machines. These instructions also assume you've followed the earlier section on \emph{Installing and Configuring Solr 7}. Refer to the \href{https://cwiki.apache.org/confluence/display/solr/SolrCloud}{SolrCloud guide for more information}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Stop the Solr server if it's running. \item Navigate to the \texttt{Solr\_Home/configsets} folder and create a folder called \begin{verbatim} liferay_configs \end{verbatim} \item Copy the \texttt{conf} folder from \texttt{Solr\_Home/liferay} to the \texttt{liferay\_configs} folder you just created. The \texttt{configset/liferay\_configs} folder contains the SolrCloud Liferay DXP collection configuration and is uploaded to ZooKeeper. By copying the \texttt{conf} folder from the \texttt{liferay} server configured earlier, you're using the \texttt{schema.xml} and \texttt{solrconfig.xml} files provided with the Liferay Solr Adapter. \item Next launch an interactive SolrCloud session to configure your SolrCloud cluster. Use this command: \begin{verbatim} ./bin/solr -e cloud \end{verbatim} \item Complete the setup wizard. These steps demonstrate creating a two-node cluster: \begin{itemize} \item Enter \texttt{2} for the number of nodes. \item Specify ports \texttt{8983} and \texttt{7574} (the defaults). Both nodes are started with the start commands printed in the log: \begin{verbatim} Starting up Solr on port 8983 using command: "bin/solr" start -cloud -p 8983 -s "example/cloud/node1/solr" Waiting up to 180 seconds to see Solr running on port 8983 [|] [-] Started Solr server on port 8983 (pid=8846). Happy searching! Starting up Solr on port 7574 using command: "bin/solr" start -cloud -p 7574 -s "example/cloud/node2/solr" -z localhost:9983 Waiting up to 180 seconds to see Solr running on port 7574 [|] [/] Started Solr server on port 7574 (pid=9026). Happy searching! \end{verbatim} \item Name the collection \emph{liferay}. \item Split the collection into two shards. \item Specify two replicas per shard. \item When prompted to choose a configuration, enter \emph{liferay\_configs}. You should see a log message that concludes like this when the cluster has been started: \end{itemize} \begin{verbatim} SolrCloud example running, please visit http://localhost:8983/solr \end{verbatim} \end{enumerate} Now you have a new collection called \emph{liferay} in your local SolrCloud cluster. Verify its status by running the \emph{status} command: \begin{verbatim} ./bin/solr status \end{verbatim} You'll see log output like this: \begin{verbatim} Found 2 Solr nodes: Solr process 12828 running on port 8983 INFO - 2019-07-18 16:46:35.137; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop { "solr_home":"/home/russell/liferay-bundles/liferay-portal-7.2.10-ga1/solr-7.5.0/example/cloud/node1/solr", "version":"7.5.0 b5bf70b7e32d7ddd9742cc821d471c5fabd4e3df - jimczi - 2018-09-18 13:07:55", "startTime":"2019-07-18T20:44:13.138Z", "uptime":"0 days, 0 hours, 2 minutes, 22 seconds", "memory":"56.4 MB (%11.5) of 490.7 MB", "cloud":{ "ZooKeeper":"localhost:9983", "liveNodes":"2", "collections":"1"}} Solr process 12995 running on port 7574 INFO - 2019-07-18 16:46:35.848; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop { "solr_home":"/home/russell/liferay-bundles/liferay-portal-7.2.10-ga1/solr-7.5.0/example/cloud/node2/solr", "version":"7.5.0 b5bf70b7e32d7ddd9742cc821d471c5fabd4e3df - jimczi - 2018-09-18 13:07:55", "startTime":"2019-07-18T20:44:16.847Z", "uptime":"0 days, 0 hours, 2 minutes, 19 seconds", "memory":"108.2 MB (%22.1) of 490.7 MB", "cloud":{ "ZooKeeper":"localhost:9983", "liveNodes":"2", "collections":"1"}} \end{verbatim} To stop Solr while running in SolrCloud mode, use the \emph{stop} command, like this: \begin{verbatim} bin/solr stop -all \end{verbatim} \section{Configure the Solr Adapter for SolrCloud}\label{configure-the-solr-adapter-for-solrcloud} There's only one thing left to do: specify the client type as \emph{CLOUD} in Liferay's Solr connector. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From System Settings or your OSGi configuration file, set the \emph{Client Type} to \emph{CLOUD}. \begin{verbatim} clientType="CLOUD" \end{verbatim} \item Start Liferay DXP if it's not running already. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/solr-client-type.png}} \caption{From the Solr 7 System Settings entry, set the \emph{Client Type} to \emph{Cloud}.} \end{figure} Now you can configure Liferay DXP for Solr and Solr for @product@. Remember that Elasticsearch is the default search engine, so if you're not constrained to use Solr or already a Solr expert, consider Elasticsearch for your search engine requirements. If you do use Solr, tell all your colleagues that your Liferay DXP installation's search capability is Solr powered (pun intended). \chapter{Solr Connector Settings}\label{solr-connector-settings} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Solr can be configured for use with 7.0. Liferay Marketplace includes a Solr connector app called the Liferay Connector to Solr 7. The connector is configurable through System Settings or an OSGi configuration file named \texttt{com.liferay.portal.search.solr7.configuration.SolrConfiguration.config} and deployed to \texttt{{[}Liferay\_Home{]}/osgi/configs}. The list below is all the configuration settings for Liferay's Solr connector, in the order they appear in the System Settings application: \section{Solr 7}\label{solr-7} \begin{description} \tightlist \item[\texttt{authenticationMode=BASIC}] A String with the value of \emph{BASIC} or \emph{CERT}. Use BASIC when connecting using the \href{https://cwiki.apache.org/confluence/display/solr/Basic+Authentication+Plugin}{Basic Authentication plugin}, otherwise select CERT to connect using \href{https://cwiki.apache.org/confluence/display/solr/Enabling+SSL}{2-way SSL authentication}. \item[\texttt{clientType=REPLICATED}] A String with the value of \emph{REPLICATED} or \emph{CLOUD}. Use the default (REPLICATED) when connecting to a single-node Solr server. Specify CLOUD to connect to SolrCloud (see the next section, titled \emph{High Availability with SolrCloud} for more information). \item[\texttt{logExceptionsOnly=true}] A boolean value that, when set to true, only logs exceptions from Solr, without rethrowing them. \item[\texttt{readURL=http://localhost:8983/solr/liferay}] A String array with the URLs to which Liferay will send search requests. This will be different from the \texttt{writeURL} if you use separate servers for indexing (write) and searching (read). \item[\texttt{writeURL=http://localhost:8983/solr/liferay}] A String array with the URLs to which Liferay will send indexing requests. This is different from the \texttt{readURL} if you use separate servers for indexing (write) and searching (read). \item[\texttt{zkHost=localhost:9983}] A String with the ZooKeeper host and port. This is required when using the adapter in CLOUD mode. \end{description} \chapter{Securing Liferay DXP}\label{securing-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay follows the OWASP Top 10 (2013) and CWE/SANS Top 25 lists to ensure Liferay DXP is as secure as possible. Following these recommendations protects the product against known kinds of attacks and security vulnerabilities. For example, Liferay DXP's persistence layer is generated and maintained by the Service Builder framework which prevents SQL Injection using Hibernate and parameter based queries. To prevent Cross Site Scripting (XSS), user-submitted values are escaped on output. To support integration features, Liferay DXP doesn't encode input. Data is stored in the original form as submitted by the user. Liferay DXP includes built-in protection against CSRF attacks, Local File Inclusion, Open Redirects, Uploading and serving files of dangerous types, Content Sniffing, Clickjacking, Path Traversal, and many other common attacks. To stay on top, Liferay DXP also contains fixes for state-of-the-art attacks and techniques to improve product security. For example, Liferay DXP uses PBKDF2 to store passwords. Liferay DXP also contains mitigation for Quadratic Blowup XXE attack, Rosetta Flash vulnerability, Reflected File Download, and other kinds of attacks. This section of tutorials shows you how to configure various security and login features, such as LDAP, single sign-on, Service Access Policies, and more. What follows is an overview of what's available. \section{Authentication Overview}\label{authentication-overview} Liferay DXP user authentication can take place using any of a variety of prepared solutions: \begin{itemize} \tightlist \item Form authentication using the Sign In Portlet with extensible adapters for checking and storing credentials (portal database, LDAP) \item Single-Sign-On (SSO) solutions - NTLM, CAS, SiteMinder, OpenSSO, OpenID, Facebook \item \href{https://www.liferay.com/marketplace/-/mp/application/15188711}{SAML plugin} \item JAAS integration with application server \end{itemize} Note: Although Liferay's SSO solutions are incompatible with WebDAV, they can be used with Liferay Sync. See the \href{/docs/7-1/user/-/knowledge_base/u/publishing-files}{Publishing Files} article for more information on WebDAV and Liferay Sync. You can authenticate and authorize apps remotely using the \texttt{AuthVerifier} layer: \begin{itemize} \tightlist \item Password based HTTP Basic + Digest authentication \item Token based OAuth plugin \item Portal session based solution for JavaScript applications \end{itemize} Both user authentication and remote application authentication are \href{/docs/7-2/frameworks/-/knowledge_base/f/authentication-pipelines}{extensible}. Developers can create custom Login portlets and plugins, extend the default Login portlet \texttt{auth.pipeline}, create \texttt{AutoLogin} extensions for SSO, or create custom \texttt{AuthVerifier} implementations. \section{Authorization and Permission Checking}\label{authorization-and-permission-checking} There are several adjustable authorization layers in place to prevent unauthorized or unsecured access to data: \begin{itemize} \tightlist \item Remote IP and HTTPS transport check to limit access to Liferay DXP's Java servlets \item Extensible Access Control Policies layer to perform any portal service related authorization check \item Extensible role-based permission framework for almost any portal entity or data (stored in the portal database or elsewhere) \item Portlet Container security checks to control portlet access \item Remote IP check for portal remote API authentication methods \item Service Access Policies to control access to portal remote API \end{itemize} \section{Additional Security Features}\label{additional-security-features} Users can be assigned to sites, teams, user groups, or organizations. Custom roles can be created, permissions can be assigned to those roles, and those roles can be applied to users. Roles are scoped to apply only with a specific context like a site, an organization, or globally. See the \href{/docs/7-1/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions} documentation for more information. \noindent\hrulefill Note: Liferay DXP relies on the application server for sanitizing CRLF in HTTP headers. You should, therefore, make sure this is configured properly in your application server, or you may experience false positives in reports from automatic security verification software such as Veracode. There is one exception to this for Resin, which does not have support for this feature. In this case, Liferay DXP sanitizes HTTP headers. \noindent\hrulefill There are additional security plugins available from \href{https://www.liferay.com/marketplace}{Liferay Marketplace}. For example, you can find an Audit plugin for tracking user actions or an AntiSamy plugin for clearing HTML from XSS vectors. There are many ways to fine-tune or disable various security features. Here are a few examples of these kinds of configuration actions: \begin{itemize} \tightlist \item Disable the Sign In portlet's \emph{Create Account} link \item Configure Liferay DXP's HTTPS web server address \item Configure the list of allowed servers to which users can be redirected \item Configure the list of portlets that can be accessed from any page \item Configure the file types allowed to be uploaded and downloaded \end{itemize} \section{Secure Configuration and Run Recommendations}\label{secure-configuration-and-run-recommendations} Liferay DXP is built using the ``secure by default'' concept in mind. It's not recommended to disable built-in protections or to allow all values in security white-lists. Such acts may lead to security misconfiguration and an insecure deployment. Also, customers are advised to deploy security patches as described on the \href{https://www.liferay.com/group/customer/products/portal/security-vulnerability}{customer portal}. For community and CE deployments, the stay secure by always using the latest community version, which contains all previous security patches. \chapter{Logging into Liferay DXP}\label{logging-into-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} One of the primary functions of a security system is to make pages, content, and web applications accessible only to the appropriate users. A student logging into a university portal should not be able to access the same resources a professor can. A patient logging into a health care portal should not be able to access a doctor's resources. Some content (at least a login page) should be available to everybody, including unauthenticated users (called \emph{guest} users). To learn more about how Liferay DXP restricts access to portal resources to different users, please see the \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions} documentation. \section{Authentication Types}\label{authentication-types} There are three authentication types: by email address, screen name, or user ID. To choose an authentication type, navigate to the Control Panel, click on \emph{Configuration} → \emph{Instance Settings} → \emph{Platform} → \emph{User Authentication} and use the \emph{How do users authenticate?} selector to make a selection. Alternatively, add the following lines to your \texttt{portal-ext.properties} file, uncomment the appropriate line, comment out the others, and restart your server. \begin{verbatim} company.security.auth.type=emailAddress #company.security.auth.type=screenName #company.security.auth.type=userId \end{verbatim} The default authentication type is by email address, but you can choose screen names or user IDs instead. Users choose screen names when they create their accounts or administrators can choose them. User IDs are auto-generated when the account is created. Regardless of which authentication type is configured, users must always enter a password. For information on adding restrictions on the kinds of passwords that are allowed or required (e.g., to require a minimum password length or require special characters), please see the \href{/docs/7-2/user/-/knowledge_base/u/password-policies}{Password Policies} documentation. \section{The Sign In Portlet}\label{the-sign-in-portlet} Sign In portlet is how users log in. By default, the Sign In portlet can create new accounts or request a password reset. The default home page contains a Sign In portlet. You can access this page at \url{http://localhost:8080/web/guest/home}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/sign-in-portlet.png}} \caption{By default, the Sign In portlet allows users to log in, create a new account, or request a password reset.} \end{figure} If the Sign In portlet doesn't appear on any page, you can still access it here: \url{localhost:8080/c/portal/login} By default, guest users can create accounts by clicking on the \emph{Create Account} link in the Sign In portlet, completing the form, and submitting it. If a user has an account but has forgotten its password, the user can click the \emph{Forgot Password} link to request a password reset. Both the \emph{Create Account} form and the \emph{Forgot Password} form include a CAPTCHA-based text verification field. Using \href{http://www.captcha.net}{CAPTCHA} prevents bots from submitting these forms. You can use \href{https://www.google.com/recaptcha/intro/index.html}{reCAPTCHA} instead of CAPTCHA. One advantage of reCAPTCHA is that it can allow visually impaired users to pass the test. To use reCAPTCHA, navigate to the Control Panel, then click on \emph{Configuration} → \emph{System Settings} → \emph{CAPTCHA}. You can prevent guest users from creating new user accounts, if your site requires users be registered by administrators. Navigate to the Control Panel → \emph{Configuration} → \emph{Instance Settings} → \emph{Platform} → \emph{User Authentication} and uncheck the \emph{Allow strangers to create accounts?} box. You can also prevent users from requesting forgotten passwords or from requesting password reset links by unchecking the appropriate boxes. With these options, the Create Account and Forgot Password links no longer appear in the Sign-In portlet. Remember that the Sign In portlet is the default way for users to log in, but it's not the only way. User accounts can be imported from and exported to LDAP directories. You can use single-sign-on (SSO) solutions or token-based authentication, which allows remote web applications to authenticate. Please refer to the other articles in this section for more information. Finally, remember that user authentication and remote application authentication mechanisms are \href{/docs/7-2/frameworks/-/knowledge_base/f/authentication-pipelines}{extensible}. \chapter{Service Access Policies}\label{service-access-policies} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} \emph{Service access policies} comprise a layer of web service security that defines services or service methods that can be invoked remotely. You can apply many of them at once to produce a combined effect. To help you understand how service access policies fit into the big picture, here's a summary of Liferay DXP's web service security layers: \textbf{IP permission layer:} The IP address from which a web service invocation request originates must be white-listed in the portal properties file. A web service invocation coming from a non-whitelisted IP address automatically fails. \textbf{Service access policy layer:} Methods corresponding to a web service invocation request must be whitelisted by each service access policy that's in effect. You can use wildcards to reduce the number of service classes and methods that must be explicitly whitelisted. \textbf{Authentication/verification layer (browser-only):} If a web service invocation request comes from a browser, the request must include an authentication token. This authentication token is the value of the \texttt{p\_auth} URL parameter. The token is generated by Liferay DXP and associated with your browser session. The \texttt{p\_auth} parameter is automatically supplied when you invoke a Liferay DXP web service via the JSON web services API page or via JavaScript using \texttt{Liferay.Service(...)}. If Liferay DXP cannot associate the caller's authentication token with a User, the web service invocation request fails. \textbf{User permission layer:} Properly implemented web services have permission checks. The user invoking a web service must have permission to invoke the service. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/service-access-policies-security-layers.png}} \caption{To get to a service, a request must pass through the door lock of user permissions, the padlock of the verification layer, the brick wall of service access policies, and finally the safe of predefined IP permissions.} \end{figure} Note that service access policies respect the permissions system. If a service access policy grants a user access to a remote service, the user must still have the appropriate permissions to invoke that service. Service access policies are especially useful when remote applications such as mobile devices or Liferay Sync instances must access web services. Administrators can use service access policies to ensure that these devices can only invoke remote services from approved lists that can be modified at runtime. \section{Managing Service Access Policies}\label{managing-service-access-policies} Navigate to the Control Panel and click on \emph{Service Access Policy} under the Configuration heading. Here, you can see the default service access policies and add new ones. When creating or editing service access policies, keep these points in mind: \begin{itemize} \item Service access policy names must be unique per portal instance. \item Service access policy names can include only these allowed characters: \begin{verbatim} 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#:@-./_ \end{verbatim} \item Service access policy titles can be localized; service access policy names cannot be localized. \item Allowed service signatures must be entered one per line. Wildcards (\texttt{*}) are allowed for both class names and method names. The \texttt{\#} symbol must be used to separate a class name from a method name. \end{itemize} For example, \texttt{com.liferay.portal.kernel.service.UserService} allows any method from the \texttt{UserService} class to be invoked. \texttt{com.liferay.document.library.kernel.service.DLAppService\#get*} allows any method from the \texttt{DLAppService} that starts with \texttt{get} to be invoked. Thus, \begin{verbatim} com.liferay.portal.kernel.service.UserService com.liferay.document.library.kernel.service.DLAppService#get* \end{verbatim} allows any method from the \texttt{UserService} class to be invoked and any method from the \texttt{DLAppService} whose name starts with \texttt{get} to be invoked. There are 16 service access policies that are enabled by default. Six of these have to do with the system: \textbf{ASSET\_ENTRY\_DEFAULT:} Allows the view counter for assets to be updated when an asset is retrieved. \textbf{CALENDAR\_DEFAULT:} Makes it possible to search public events in the calendar. \textbf{SYNC\_DEFAULT:} Allows only the \texttt{com.liferay.sync.service.SyncDLObjectService.getSyncContext} method. It applies to every Liferay Sync request, including unauthenticated Sync requests. \textbf{SYNC\_TOKEN:} Allows \texttt{com.liferay.sync.service.*}, meaning that any API function that's a method of a class in this package can be invoked. It applies to Sync requests which are accompanied by an authentication token. \textbf{SYSTEM\_DEFAULT:} Allows access to country/region services by JavaScript calls, so users can switch languages on the fly. Applies to every request, including unauthenticated requests. \textbf{SYSTEM\_USER\_PASSWORD:} Allows any method to be invoked. Of course, since API functions include permission checks, this call works only if the user has the required permission. It applies to requests for which \texttt{AuthVerifierResult.isPasswordBasedAuthentication} is \texttt{true}: i.e., whenever user authentication took place using a password. If you want to completely disallow certain API functions from being invoked, you can change the \texttt{SYSTEM\_USER\_PASSWORD} policy to something more restrictive than \texttt{*}. \texttt{SYNC\_DEFAULT} and \texttt{SYSTEM\_DEFAULT}, as their names suggest, are default service access policies. Default service access policies are applied to all incoming requests, including unauthenticated requests. The other 10 policies have to do with OAuth and JSON web services: \textbf{OAUTH2\_analytics.read/write:} Integrates with \href{https://www.liferay.com/products/analytics-cloud}{Liferay Analytics Cloud}, allowing it access to JSON web services. \textbf{OAUTH2\_everything/read/documents/userprofile/write:} The Everything policies grant access to all the JSON web services for various reasons. Everything is everything: all JSON web services (matches \texttt{*}). The others match method signatures appropriate to their description. For example, OAUTH2\_everything.read matches all methods starting with \texttt{fetch}, \texttt{get}, \texttt{has}, \texttt{is}, or \texttt{search}. \textbf{OAUTH\_READ/WRITE:} These provide access to JSON web services via the OAuth 1.0a plugin. The default configuration makes available corresponding scopes that provide access to all web services shipped with the system. The scopes must be assigned to OAuth 1 or 2 applications before they become usable. Administrators should review the ones you want to use and disable the others. You can create new default service access policies: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Configuration} → \emph{Service Access Policy} section of the Control Panel. \item Click \emph{Add} (\pandocbounded{\includegraphics[keepaspectratio]{./images/icon-add.png}}). \item Give your policy a name. \item Flip the switch to enable your policy. \item If you want the policy applied to unauthenticated requests as well as authenticated requests, flip the switch labeled \emph{Default}. \item Give your policy a localized title. \item Under Allowed Service Signatures, start typing the fully qualified name of a service class that's installed. Code completion helps you find the class. For example, if you're creating a policy for Liferay's Knowledge Base application, you could enter \texttt{com.liferay.knowledge.base.service.KBArticleService}. \item Under Method Name, start typing a service method call. Again, code completion helps you. For Knowledge Base, you could enter \texttt{getKBArticle}. \item To specify another service or method, click the plus icon to add another entry. \item When done, click \emph{Save}. \end{enumerate} \noindent\hrulefill \textbf{Note:} If you know all the method signatures ahead of time, you can click \emph{Switch to Advanced Mode} and enter them all in one field on separate lines. \noindent\hrulefill Liferay applications can declare their own default policies (the \texttt{SYNC\_DEFAULT} policy is a good example). This policy can then be changed or disabled by administrators. In this case, the plugin can still verify that the policy exists so there is no need to redefine or update it. By default, Liferay's tunneling servlet uses the \texttt{SYSTEM\_USER\_PASSWORD} service access policy. You can, however, create your own policy for the tunneling servlet and use the property \texttt{service.access.policy.name} for the \texttt{TunnelingServletAuthVerifier} to specify that your policy should be used instead. \section{Service Access Policy Module}\label{service-access-policy-module} Liferay's service access policy functionality is provided by the Service Access Policy module. This module includes the following important classes: \begin{itemize} \tightlist \item \texttt{com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicy}: defines the public interface for \texttt{ServiceAccessPolicy}. \item \texttt{com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyManager}: defines the public interface for retrieving instances of \texttt{ServiceAccessPolicy}. \item \texttt{com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyManagerUtil}: bridges service access policy functionality to the parts of Liferay's core that have not yet been modularized. \item \texttt{com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyThreadLocal}: makes \texttt{ServiceAccessPolicy} instances active. \end{itemize} Liferay's Service Access Policy module resides in the \texttt{modules/apps/service-access-policy} folder in the source code. When running, these three bundles provide the service access policy functionality (they're in the \texttt{{[}Liferay\ Home{]}/osgi/modules} folder): \begin{itemize} \tightlist \item \texttt{com.liferay.service.access.policy.api.jar} \item \texttt{com.liferay.service.access.policy.service.jar} \item \texttt{com.liferay.service.access.policy.web.jar} \end{itemize} These modules provide the service access policy management UI that's accessible from the Control Panel. They also provide the interface and default implementation for \texttt{ServiceAccessPolicy}. To configure the Service Access Policy module, navigate to the Control Panel, click on \emph{System Settings}, and find the \emph{Service Access Policies} module in the Security section. Click on its name to edit it. Here, you can edit the default service access policy configuration. You can also force a default policy to be applied even when no policies are applied by the \texttt{AuthVerifier}. There's also an \texttt{AuthenticatedAccessControlPolicy}. This policy doesn't do anything if a \texttt{ServiceAccessPolicyManager} implementation is present. If the service access policy module is disabled, however, the \texttt{AuthenticatedAccessControlPolicy} provides a fallback that still requires authenticated access for web services. \section{Summary}\label{summary} Great! Now you know service access policies can restrict access to Liferay DXP's web services. Custom service access policies can be created by portal administrators. They are applied by the portal's token authenticator, e.g., by OAuth. \section{Related Topics}\label{related-topics-3} \href{/docs/7-2/frameworks/-/knowledge_base/f/service-access-policies}{Creating Service Access Policies} \chapter{Authentication Verifiers}\label{authentication-verifiers} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The Authentication Verification Layer is a centralized and extensible way to authenticate remote invocations of Liferay DXP's API. The main responsibilities of the authentication verification layer are to \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Verify provided credentials using registered \texttt{AuthVerifier} instances \item Create portal authorization contexts based on verification results \end{enumerate} If no available \texttt{AuthVerifier} can verify request credentials, an authorization context supporting non-authenticated access is created for a guest user. This allows each API to expose only a single API endpoint. In contrast, legacy (prior to 6.2) versions of Liferay DXP exposed two API endpoints for each API: the \texttt{/api/endpoint} URI was for non-authenticated access and the URI \texttt{/api/secure/endpoint} was for authenticated access. There are built-in \texttt{AuthVerifier} implementations for the most common situations, such as when remote clients use HTTP Basic or HTTP Digest authentication, send credentials in request parameters, send authenticated \texttt{JSESSIONID}s, or use shared secrets to establish trust. Other \texttt{AuthVerifier} implementations can be deployed as modules containing implementations of the \texttt{AuthVerifier} interface that are registered as services in the OSGi runtime. Note: The authentication verification layer's focus is on verifying authentication, not on providing credentials. It does NOT issue tokens, credentials, or display Sign In portlets. Instead, the layer verifies existing credentials and authenticated sessions and is therefore a complement to authentication endpoints. To ensure backwards compatibility, however, the default implementations support requests providing user name and password credentials. Thus, the authentication verification layer stands on the border between authentication and authorization. \section{Authentication Verification Process Overview}\label{authentication-verification-process-overview} This layer and surrounding processes are provided by the \texttt{AuthVerifierFilter} class that implements the \texttt{javax.servlet.Filter} interface. \textbf{Step 1: Verify Request Credentials} The layer uses the chain of responsibility design pattern to support both built-in and third party \texttt{AuthVerifier} implementations. Each \texttt{AuthVerifier} can provide configurations where it specifies mapped URLs and other properties. Each incoming request is matched against all registered \texttt{AuthVerifier}s to select the final list of \texttt{AuthVerifier}s that is used to process the request. It's the responsibility of each \texttt{AuthVerifier} to verify the incoming request credentials. \textbf{Step 2: Create an Authorization Context} When a request is processed by all matching \texttt{AuthVerifier}s, Liferay DXP creates an authorization context for the resolved user. This encompasses setting the \texttt{HttpServletRequest} \texttt{remoteUser} to return the resolved user ID setting \texttt{ThreadLocal}s to the resolved user. The resolved user can be the user returned by one of the \texttt{AuthVerifier} instances or a guest user if no instance was able to verify the provided credentials. \texttt{AuthVerifier}s are created by developers, and are processed automatically as long as they're registered in the OSGi runtime. Each Auth Verifier gets its own configuration in \emph{Control Panel} → \emph{System Settings} → \emph{Security} → \emph{API Authentication}. Configuration for Auth Verifiers that ship with the product include \begin{itemize} \tightlist \item Basic Auth Header \item Digest Authentication \item HTTP Tunnel Extender \item Image Request \item Portal Sessions \item Request Parameter \item Tunnel Auth \end{itemize} The following Auth Verifiers are enabled by default and can be used to access remote API out-of-the-box: \begin{itemize} \tightlist \item Basic Auth Header \item Portal Sessions \end{itemize} \section{Basic Auth Header}\label{basic-auth-header} This Auth Verifier allows the remote client to authenticate using \href{https://en.wikipedia.org/wiki/Basic_access_authentication}{HTTP Basic Authentication}. Configure it by providing URL paths that should be authenticated this way. When Force Basic Authentication field is checked then HTTP Basic Authentication is required. The default URLs are \texttt{/api/*,/xmlrpc*} for web services. The mapping excludes \texttt{/api/liferay*} to prevent accessing \texttt{TunnelServlet}. For more information please see Tunnel Authentication Verifiers. \section{Digest Auth Header}\label{digest-auth-header} This Auth Verifier allows the remote client to authenticate using \href{https://en.wikipedia.org/wiki/Digest_access_authentication}{HTTP Digest Authentication}. Configure it by providing URL paths that should be authenticated this way. When Force Digest Authentication field is checked then HTTP Basic Authentication is required. This Auth Verifier is not enabled by default. \section{HTTP Tunnel Extender}\label{http-tunnel-extender} As Liferay embraced modularity, this extender was written to enable modules to be part of \texttt{TunnelServlet}. It maps \texttt{TunnelServlet} and \texttt{TunnelingServletAuthVerifier} to the module servlet context. Modules with \texttt{Http-Tunnel} in the manifest can make use of the Tunnel Servlet, and can expose the API via \texttt{/o/\_module\_/api/liferay/do}. Configure it by setting client IP addresses allowed to tunnel. For more information, please see \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#HTTP\%20Tunneling}{the properties documentation} as well as \href{/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging}{remote staging}. Note that this is not a recommended way to export remote APIs; it's far better to expose remote services using JAX-RS or Liferay JSON Web Service technologies. \section{Image Request Authentication Verifier}\label{image-request-authentication-verifier} When connected to LibreOffice/OpenOffice, the Office process must download images from Liferay DXP to render docs with images. To do this, a \href{https://jwt.io}{JWT Token} is created to access the images securely. Configure this by setting the Hosts Allowed, URLs included, and URLs excluded if necessary. This Auth Verifier is not enabled by default. \section{Portal Sessions Auth Verifiers}\label{portal-sessions-auth-verifiers} Enables JavaScript in a browser to access Liferay JSON Web Services using an existing portal session. In the default configuration, the URLs included field shields access to the legacy JSON remote services layer: \texttt{/api/json*,/api/jsonws*,/c/portal/json\_service*}. \section{Request Parameter Auth Verifiers}\label{request-parameter-auth-verifiers} For backwards compatibility with \texttt{RequestParameterAutoLogin} you can authenticate and access portal endpoints with credentials inside HTTP request parameters \texttt{parameterAutoLoginLogin} and \texttt{parameterAutoLoginPassword}. This Auth Verifier is not enabled by default. \section{Tunnel Authentication Verifiers}\label{tunnel-authentication-verifiers} \texttt{TunnelServlet} is a legacy remote API endpoint mapped at \texttt{/api/liferay/do} to provide access to the portal remote services. The Tunnel Auth Verifier allows trusted remote clients authenticated access using any user ID provided, on behalf of the user. An example of a trusted remote client is the Staging remote publishing feature. Trusted remote clients authenticate using a shared secret stored in the portal property \texttt{tunneling.servlet.shared.secret}. The default value is empty and forbids all access. Even though the default configuration is enabled by default, access is limited to localhost only. Configure it by setting client IP addresses allowed to tunnel. For more information, please see \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#HTTP\%20Tunneling}{the properties documentation} as well as \href{/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging}{remote staging}. \section{Related Topics}\label{related-topics-4} \href{/docs/7-2/deploy/-/knowledge_base/d/service-access-policies}{Service Access Policies} \chapter{LDAP}\label{ldap} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} LDAP is a common user store for Liferay DXP. You can configure LDAP at the system scope in System Settings or at the instance scope in Instance settings. Users can be imported from LDAP or exported to LDAP. To access LDAP configuration settings, navigate to \emph{Control Panel → Configuration} → \emph{Instance Settings}. At the bottom of the list on the left, click \emph{Servers}. Click the \emph{Add} button to add an LDAP server connection. If you have more than one LDAP server, you can arrange the servers by order of preference using the up/down arrows. Regardless of how many LDAP servers you add, each server has the same configuration options. \textbf{Server Name:} Enter a name for your LDAP server. \textbf{Default Values:} Several common directory servers appear here. If you use one of these, select it. The rest of the form is populated with default values for that directory. \section{Connection}\label{connection} These settings cover the connection to LDAP. \textbf{Base Provider URL:} The link to the LDAP server. Make sure the Liferay DXP server can communicate with the LDAP server. If there is a firewall between the two systems, make sure the appropriate ports are opened. \textbf{Base DN:} The Base Distinguished Name for your LDAP directory. It is usually modeled after your organization. It may look similar to this: \texttt{dc=companynamehere,dc=com}. \textbf{Principal:} The default LDAP administrator user ID is populated here. If your administrator ID differs, use that credential instead. You need an administrative credential because Liferay DXP uses this ID to synchronize user accounts to and from LDAP. \textbf{Credentials:} This is the password for the LDAP administrative user. This is all you need to make a regular connection to an LDAP directory. The rest of the configuration, however, may need to be customized, as it represents ``best guesses'' as to correct defaults. The default attribute mappings usually provide enough data to synchronize back to the Liferay DXP database when a user attempts to log in. To test the connection to your LDAP server, click the \emph{Test LDAP Connection} button. \section{Checkpoint}\label{checkpoint-1} Before proceeding to fine tune Liferay DXP's LDAP connections, ensure the following steps have been taken: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The LDAP connection is enabled. Depending on your needs, LDAP authentication may be required so that only users who have been bound may log in. \item \emph{Export/Import}: for users in a clustered environment, Enable Import/Export on Startup should be disabled so that there are no massive imports on every node upon start up. \item When adding the LDAP server, the \emph{Server Name}, \emph{Default Values}, \emph{Connection} values are correct. It is always a good idea to click the \emph{Test LDAP Connection} before saving. \end{enumerate} \section{Instance Settings vs.~System Settings}\label{instance-settings-vs.-system-settings} You can define an LDAP server connection at the System Settings scope as well. Because this user interface is auto-generated, it's not as helpful as the one in Instance Settings. For this reason, you should define and troubleshoot your settings in Instance Settings first. If you decide you want your LDAP connection at the system scope, you can copy your configuration from Instance Settings and then delete the server from Instance Settings. Of course, you can also configure LDAP servers at the system scope using OSGi \texttt{.config} files. The easiest way to do this is to use the GUI and export the configuration. Then you can use the resulting \texttt{.config} file anywhere you need it (such as other nodes in a cluster). \textbf{Note:} To use \texttt{config} files for LDAP server configuration, you must specify the Virtual Instance ID (in the source, the variable name is \texttt{companyId}) in the exported configuration file, because servers are defined at the instance scope, not the system scope. To do this, specify the virtual instance ID somewhere in the file like this: \begin{verbatim} companyId=1234 \end{verbatim} You can find your Virtual Instance ID in Control Panel → Configuration → Virtual Instances. \section{Security}\label{security} If you run your LDAP directory in SSL mode to encrypt credential information on the network, you must perform extra steps to share the encryption key and certificate between the two systems. For example, if your LDAP directory is Microsoft Active Directory on Windows Server 2003, you'd share the certificate like this: Click \emph{Start} → \emph{Administrative Tools} → \emph{Certificate Authority}. Highlight the machine that is the certificate authority, right-click on it, and click \emph{Properties}. From the General menu, click \emph{View Certificate}. Select the Details view, and click \emph{Copy To File}. Use the resulting wizard to save the certificate as a file. You must also import the certificate into the \emph{cacerts keystore} like this: \begin{verbatim} keytool -import -trustcacerts -keystore /some/path/java-8-jdk/jre/lib/security/cacerts -storepass changeit -noprompt -alias MyRootCA -file /some/path/MyRootCA.cer \end{verbatim} The \texttt{keytool} utility ships as part of the Java SDK. Once this is done, go back to the LDAP page in the Control Panel. Modify the LDAP URL in the Base DN field to the secure version by changing the protocol to \texttt{ldaps} and the port to \texttt{636} like this: \begin{verbatim} ldaps://myLdapServerHostname:636 \end{verbatim} Save the changes. Communication to LDAP is now encrypted. \section{Configuring LDAP Import/Export}\label{configuring-ldap-importexport} The other settings configure mappings between LDAP and Liferay DXP so users and groups can be imported. \section{Users}\label{users} This section contains settings for finding users in your LDAP directory. \textbf{Authentication Search Filter:} Use this search filter box to determine the search criteria for user logins. By default, Liferay DXP uses users' email addresses for their login names. The value here must use the authentication method you use. For example, if you changed Liferay DXP's authentication method to use screen names instead of the email addresses, you would modify the search filter so it can match the entered log in name: \begin{verbatim} (cn=@screen_name@) \end{verbatim} \textbf{Import Search Filter:} Depending on the LDAP schema, there are different ways to identify the user. The default setting is usually fine: \begin{verbatim} (objectClass=inetOrgPerson) \end{verbatim} If you want to search for only a subset of users or users that have different LDAP object classes, you can change this. \textbf{User Mapping:} Next, you can define mappings from LDAP attributes to Liferay fields. Though LDAP user attributes may be different from LDAP server to LDAP server, there are five fields Liferay DXP requires to be mapped for the user to be recognized: \begin{itemize} \tightlist \item \emph{Screen Name} (e.g., \texttt{uid} or \texttt{cn}) \item \emph{Password} (e.g., \texttt{userPassword}) \item \emph{Email Address} (e.g., \texttt{mail} or \texttt{email}) \item \emph{First Name} (e.g., \texttt{name} or \texttt{givenName}) \item \emph{Last Name} (e.g., \texttt{sn}) \end{itemize} \textbf{Note:} If you intend to create or import users with no email addresses, you must set \texttt{users.email.address.required=false} in \texttt{portal-ext.properties}. With this set, Liferay auto-generates an email address combining the user ID plus the suffix defined in the property \texttt{users.email.address.auto.suffix=}. Finally, make sure to set Liferay and LDAP authentication to something other than email address. If you want to import LDAP groups as Liferay DXP user groups, make sure define a mapping for the Liferay DXP group field so that membership information is preserved: \begin{itemize} \tightlist \item \emph{Group} (e.g., \emph{member}) \end{itemize} The other LDAP user mapping fields are optional. The Control Panel provides default mappings for commonly used LDAP attributes. You can also add your own mappings. \textbf{Test LDAP Users:} Once you have your attribute mappings set up (see above), click the \emph{Test LDAP Users} button and Liferay DXP attempts to pull LDAP users and match them with their mappings as a preview. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/testing-ldap-users.png}} \caption{You should see a list of users when you click the \emph{Test LDAP Users} button.} \end{figure} \section{Groups}\label{groups} This section contains settings for mapping LDAP groups to Liferay DXP user groups. \textbf{Import Search Filter:} This is the filter for mapping LDAP groups to Liferay DXP user groups. For example, \begin{verbatim} (objectClass=groupOfNames) \end{verbatim} Enter the LDAP group attributes you want retrieved for this mapping. The following attributes can be mapped. The \emph{Group Name} and \emph{User} fields are required, the \emph{Description} is optional. \begin{itemize} \item \emph{Group Name} (e.g., \texttt{cn} or \texttt{o}) \item \emph{Description} (e.g., \texttt{description}) \item \emph{User} (e.g., \texttt{member}) \end{itemize} \textbf{Test LDAP Groups:} Click the \emph{Test LDAP Groups} button to display a list of the groups returned by your search filter. \section{Export}\label{export} This section contains settings for exporting user data from LDAP. \textbf{Users DN:} Enter the location in your LDAP tree where the users are stored. Liferay DXP exports the users to this location. \textbf{User Default Object Classes:} Users are exported with the listed default object classes. To find out what your default object classes are, use an LDAP browser tool such as Apache Directory Studio to locate a user and view the Object Class attributes stored in LDAP for that user. \textbf{Groups DN:} Enter the location in your LDAP tree where the groups are stored. When Liferay DXP does an export, it exports the groups to this location. \textbf{Group Default Object Classes:} When a group is exported, the group is created with the listed default object classes. To find out what your default object classes are, use an LDAP browser tool such as \href{https://directory.apache.org/studio}{Apache Directory Studio} to locate a group and view the Object Class attributes stored in LDAP for that group. When you've set all your options and tested your connection, click \emph{Save}. \noindent\hrulefill \textbf{Note:} If a user changes a value like a password in Liferay DXP, that change is passed to the LDAP server, provided Liferay DXP has enough schema access to make the change. \noindent\hrulefill Now you know how to connect an LDAP server to Liferay DXP and how to configure user import behavior, export behavior, and other LDAP settings. \section{Related Topics}\label{related-topics-5} \href{/docs/7-0/deploy/-/knowledge_base/d/liferay-portal-security-overview}{Liferay DXP Security Overview} \href{/docs/7-0/deploy/-/knowledge_base/d/logging-in-to-liferay}{Logging into Liferay DXP} \chapter{Configuring LDAP}\label{configuring-ldap} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} In this article, you'll learn how to configure import settings, export settings, and related LDAP configuration settings. To access LDAP configuration settings, navigate to \emph{Control Panel → Configuration} → \emph{Instance Settings} → \emph{Security} → \emph{LDAP}. There are four categories on the left: Export, General, Import, and Servers. The Servers category was covered in \href{/docs/7-2/deploy/-/knowledge_base/d/ldap}{the last article}. The rest are covered below. \section{Export}\label{export-1} \textbf{Enable Export:} Check this box to export user accounts to LDAP. A listener tracks changes made to the \texttt{User} object and pushes updates to the LDAP server whenever a \texttt{User} object is modified. Note that by default on every login, fields such as \texttt{lastLoginDate} are updated. When export is enabled, this causes a user export every time the user logs in. You can prevent updates to users' \texttt{lastLoginDate} fields from triggering LDAP user exports by setting the following property in your \texttt{portal-ext.properties} file: \begin{verbatim} users.update.last.login=false \end{verbatim} \textbf{Enable Group Export:} Export groups to LDAP. \section{General}\label{general} \textbf{Enabled:} Check this box to enable LDAP Authentication. \textbf{Required:} Check this box if LDAP authentication is required. Users can't log in unless they can bind to the LDAP directory successfully. Uncheck this box if users with Liferay DXP accounts but no LDAP accounts can log in. \textbf{Use LDAP Password Policy:} Liferay DXP uses its own password policy by default. This can be configured on the Control Panel's Password Policies page. Check the \emph{Use LDAP Password Policy} box if you want to use the password policies defined by your LDAP directory. Once this is enabled, the Password Policies tab states that you are not using a local password policy. You must now use your LDAP directory's mechanism for setting password policies. Liferay DXP cannot enforce these policies; the best it can do is pass through the messages returned by your LDAP server. It does this by parsing the messages in the LDAP controls the server returns. By default, Liferay DXP is configured to parse the messages returned by the Fedora Directory Server. If you use a different LDAP server, you must customize the messages in \emph{System Settings} → \emph{Security} → \emph{LDAP} → \emph{Connection}. \textbf{Method:} Choose \emph{Bind} (the default) or \emph{Password Compare}. Bind does a standard LDAP bind; Password Compare attempts to compare Liferay and LDAP passwords using the encryption algorithm specified in the field below. Password Compare is rarely used. \textbf{Password Encryption Algorithm:} Choose the password encryption algorithm your LDAP server uses to encrypt passwords so they can be compared if using the Password Compare bind method. This is rarely used. \section{Import}\label{import} You can import user data from LDAP directories using the following options: \textbf{Enable Import:} Check this box to do a mass import from your LDAP directories. Otherwise, Users are imported as they log in. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/imported-ldap-users.png}} \caption{Ziltoid and Rex have been imported because they logged in.} \end{figure} \textbf{Enable Import on Startup:} Check this box to do the mass import when Liferay DXP starts. Note: this box only appears if you check \textbf{Enable Import}, described above. Definitely leave this unchecked if you have a Liferay DXP cluster, or all your nodes will do a mass import when each of them starts up. \textbf{Import Interval:} When mass importing users, import users every X minutes. \textbf{Import Method:} Set either User or Group. If you set this to User, Liferay DXP imports all users from the location specified in the server connection. If you set this to Group, Liferay DXP searches all the groups and imports the users in each group. If you have users who do not belong to any groups, they are not imported. \textbf{Lock Expiration Time:} Set the account lock expiration time for LDAP User import. The default is one day. \textbf{Import User Sync Strategy:} Set the strategy used to sync user accounts. Options are Auth Type (i.e., the way the user authenticates, like with screen name) and UUID (requires a UUID attribute in LDAP). \textbf{Enable User Password on Import:} Assign a default password (see below) when users are imported, so they can be synced between the two systems. \textbf{Autogenerate User Password on Import:} Create a random password on user import. \textbf{Default User Password:} Enter the default password users are assigned when they first log in via LDAP. \textbf{Enable Group Cache on Import:} Cache the imported groups so import isn't slowed by database access. \textbf{Create Role per Group on Import:} For every LDAP group, create a corresponding Liferay Role. \section{Servers}\label{servers} \textbf{LDAP Servers:} Liferay DXP supports connections to multiple LDAP servers. Use the \emph{Add} button to add LDAP servers. Each LDAP server has several configuration options \href{/docs/7-2/deploy/-/knowledge_base/d/ldap}{explained here}. Once you've finished configuring LDAP, click the \emph{Save} button. \section{LDAP Options Available in System Settings}\label{ldap-options-available-in-system-settings} Although most LDAP configuration can be done from Instance Settings, there are several configuration parameters that are only available in System Settings. There are also settings duplicated from the ones in Instance Settings. These change the \emph{default} settings for new virtual instances (see note below). \noindent\hrulefill \textbf{Note}: When you make a change in System Settings, it takes effect for the virtual instance you're in. If after changing a setting you create a new virtual instance, that virtual instance inherits the settings of the one it was created from as defaults. For example, say you have virtual instances named A, B, and C. From A, you modify \emph{Error password history keywords}. This change appears only in A, not in B or C. Then from A, you create virtual instance D. The change to \emph{Error password history keywords} appears in D (not B or C), since D defaults to A's settings because you created it from A. \noindent\hrulefill If you must change any of these options, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. Go to the \emph{Security} section and find the entries with LDAP in the title. The only new settings here are in the \emph{Connection} entry. Use the \emph{Connection} entry to manage error properties like \emph{Error password age keywords} which lets you set a list of phrases from error messages which can possibly be returned by the LDAP server. When a user binds to LDAP, the server returns \emph{controls} with its response of success or failure. These controls contain a message describing the error or the information that is returned with the response. Though the controls are the same across LDAP servers, the messages can be different. The properties described here contain snippets of words from those messages and work with Red Hat's Fedora Directory Server. If you are not using that server, the word snippets may not work with your LDAP server. If they don't, you can replace the values of these properties with phrases from your server's error messages. This enables Liferay DXP to recognize them. \chapter{Token-based Single Sign On Authentication}\label{token-based-single-sign-on-authentication} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Token-based SSO authentication was introduced in Liferay DXP 7.0 to standardize support for Shibboleth, SiteMinder, Oracle OAM, or any other SSO product that works by propagating a token via one of the following mechanisms: \begin{itemize} \tightlist \item HTTP request parameter \item HTTP request header \item HTTP cookie \item Session attribute \end{itemize} Since these providers have a built-in web server module, you should use the Token SSO configuration. The authentication token contains the Liferay DXP user's screen name or email address, whichever Liferay DXP has been configured to use for the particular company (portal instance). Recall that Liferay DXP supports three authentication methods: \begin{itemize} \tightlist \item By email address \item By screen name \item By user ID \end{itemize} Token-based authentication only supports email address and screen name. If Liferay DXP is configured to use user ID when a token-based authentication is attempted, the \texttt{TokenAutoLogin} class logs this warning: \begin{verbatim} Incompatible setting for: company.security.auth.type \end{verbatim} Please note that the above sources are fully trusted. Furthermore, you must use a security mechanism external to Liferay DXP, such as a fronting web server like Apache. The chosen fronting solution must prevent malicious Liferay DXP user impersonation that otherwise might be possible by sending HTTP requests directly to Liferay DXP from the client's web browser. Token-based authentication is disabled by default. To manage token- based SSO authentication, navigate to Control Panel → \emph{System Settings}, → \emph{Security} → \emph{SSO}. Token Based SSO appears in Virtual Instance Scope at the bottom. Here are the configuration options for the Token Based SSO module: \textbf{Enabled:} Check this box to enable token-based SSO authentication. \textbf{Import from LDAP:} Check this box to import users automatically from LDAP if they don't exist. \textbf{User token name:} Set equal to the name of the token. This is retrieved from the specified location. (Example: SM\_USER) \textbf{Token location:} Set this to the location of the user token. As mentioned earlier, the options are: \begin{itemize} \tightlist \item HTTP request parameter \item HTTP request header \item HTTP cookie \item Session attribute \end{itemize} \textbf{Authentication cookies:} Set this to the cookie names that must be removed after logout. (Example: \texttt{SMIDENTITY}, \texttt{SMSESSION}) \textbf{Logout redirect URL:} When user logs out of Liferay DXP, the user is redirected to this URL. Remember to click \emph{Save} to activate Token Based SSO. \section{Required SiteMinder Configuration}\label{required-siteminder-configuration} If you use SiteMinder, note that Liferay DXP sometimes uses the tilde character in its URLs. By default, SiteMinder treats the tilde character (and others) as bad characters and returns an HTTP 500 error if it processes a URL containing any of them. To avoid this issue, change this default setting in the SiteMinder configuration to this one: \begin{verbatim} BadUrlChars //,./,/.,/*,*.,\,%00-%1f,%7f-%ff,%25 \end{verbatim} The configuration above is the same as the default except the \texttt{\textasciitilde{}} was removed from the bad URL character list. Restart SiteMinder to make your configuration update take effect. For more information, please refer to SiteMinder's \href{https://support.ca.com/cadocs/0/CA\%20SiteMinder\%20r6\%200\%20SP6-ENU/Bookshelf_Files/HTML/index.htm?toc.htm?258201.html}{documentation} \section{Summary}\label{summary-1} Liferay DXP's token-based SSO authentication mechanism is highly flexible and compatible with any SSO solution that provides it with a valid Liferay DXP user's screen name or email address. These include Shibboleth and SiteMinder. \chapter{Authenticating with OpenID Connect}\label{authenticating-with-openid-connect} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} OpenID Connect is a lightweight authentication layer built on top of the \href{/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0}{OAuth 2.0} authorization protocol. It compliments local accounts by enabling users to authenticate using accounts they have on other systems. Users who avoid signing up for new accounts can then use an account they already have to sign into your website. By using OpenID Connect, you \emph{delegate} user authentication to other providers, making it easy for users with existing accounts to authenticate to your system. \noindent\hrulefill \textbf{Note:} You can add multiple providers to your installation, but Liferay DXP can't be an OpenID Connect provider. \noindent\hrulefill Because OpenID Connect is built on OAuth 2.0, its token flow is similar. OAuth 2.0 is only an authorization protocol, so it sends an \emph{access token} that grants access to particular APIs. OpenID Connect adds to this an \emph{identity token} that passes user information like name and email, provided the user has authenticated and granted permission. \section{Creating a Client in OpenID Connect Provider}\label{creating-a-client-in-openid-connect-provider} To use OpenID Connect, you must first register it as a client in your provider. This is an OAuth 2.0 client. The process varies by provider: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the provider's website and create a client. \item During the creation process, you must supply an \emph{authorized redirect URL} that can process the tokens sent from the provider. Liferay DXP's URL is \begin{verbatim} https://[server.domain]/c/portal/login/openidconnect \end{verbatim} \item The provider sends several pieces of information. Some of these, like the Discovery Endpoint, Authorization Endpoint, or Issuer URL are the same regardless of the client. The two pieces of information unique to your request are the \texttt{client\_id} and the \texttt{client\_secret}. \end{enumerate} Collect the information from the provider. You'll need it create the provider next. \section{Configuring an OpenID Connect Provider Connection}\label{configuring-an-openid-connect-provider-connection} Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Security} → \emph{SSO} and select \textbf{\emph{OpenID Connect Provider}} under the \emph{System Scope} and follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the provider by clicking the \emph{Add} button. \item Use the information you received from the provider to fill out the form: \end{enumerate} \textbf{Provider Name:} This name appears in the Sign-In Portlet when users use OpenID Connect to log in. \textbf{OpenID Client ID:} Provide the OAuth 2.0 Client ID you received from your provider. \textbf{OpenID Connect Client Secret:} Provide the OAuth 2.0 Client Secret you received from your provider. \textbf{Scopes:} Leave the default, which requests the user name and the email. Your provider may offer other scopes of user information. \textbf{Discovery Endpoint:} Other URLs may be obtained from this URL, and they vary by provider. \textbf{Discovery Endpoint Cache in Milliseconds:} Cache the endpoints (URLs) discovered for this amount of time. \textbf{Authorization Endpoint:} This URL points to the provider's URL for authorizing the user (i.e., signing the user in). \textbf{Issuer URL:} The provider's URL that points to information about the provider who is issuing the user information. \textbf{JWKS URI:} A URL that points to the provider's JSON Web Key Set that contains the public keys that can verify the provider's tokens. \textbf{ID Token Signing Algorithms:} Set the supported ID token algorithms manually. Normally, this is ``discovered'' at the discovery endpoint. You can add as many of these as you need. \textbf{Subject Types:} A Subject Identifier is a unique and never reassigned identifier the provider uses to establish who the user is, and is consumed by the client (i.e., Liferay DXP). There are two types: public (provides the same value to all clients) and private (provides a different value to each client). \textbf{Token Endpoint:} The provider's URL where tokens can be requested. \textbf{User Information Endpoint:} The OAuth 2.0 protected URL from which user information can be obtained. Once you've filled out the form, click \emph{Save}, and you're ready to enable OpenID Connect authentication. \noindent\hrulefill System Settings configuration file: \begin{verbatim} com.liferay.portal.security.sso.openid.connect.internal.configuration.OpenIdConnectProviderConfiguration-[name].config \end{verbatim} where \texttt{{[}name{]}} is a descriptive, but unique name for example \texttt{provider1}. \noindent\hrulefill \section{Enabling OpenID Connect Authentication}\label{enabling-openid-connect-authentication} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Security} → \emph{SSO} and select \textbf{\emph{OpenID Connect}} under \emph{Virtual Instance Scope}. \item Click the \emph{Enabled} check box, and then click \emph{Save}. \end{enumerate} \textbf{Note:} You can also enable OpenID Connect authentication for the given virtual instance through the \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{OpenID Connect} tab. \noindent\hrulefill System Settings configuration file: \begin{verbatim} com.liferay.portal.security.sso.openid.connect.configuration.OpenIdConnectConfiguration.config \end{verbatim} \noindent\hrulefill Now users can sign in with OpenID Connect. \section{Signing In With OpenID Connect}\label{signing-in-with-openid-connect} There's a new link in the Sign-In Portlet for signing in with OpenID Connect: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the Sign-In Portlet, click the OpenID Connect link at the bottom. \item Choose a provider and click \emph{Sign In}. \item This takes you to your provider's sign in page. Enter your credentials and log in. \item Upon successful authentication, you're redirected back to Liferay DXP in an authenticated state. \end{enumerate} OpenID is a standards-based, secure way to authenticate users from other systems. \chapter{OpenAM Single Sign On Authentication}\label{openam-single-sign-on-authentication} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} OpenAM is an open source single sign-on solution that comes from the code base of Sun's System Access Manager product. Liferay DXP integrates with OpenAM, allowing you to use OpenAM to integrate Liferay DXP into an infrastructure that contains a multitude of different authentication schemes against different repositories of identities. Note that OpenAM relies on cookie sharing between applications. Thus, in order for OpenAM to work, \textbf{all applications that require SSO must be in the same web domain}. You should also add the following property if you have enabled HTTPOnly cookies due to the way some web containers (like Apache Tomcat™) parse cookies that contain special characters: \begin{verbatim} com.iplanet.am.cookie.encode=true \end{verbatim} You can install OpenAM on the same or different server as Liferay DXP. Be sure to review the context path and server hostname for your OpenAM server. If you want to install OpenAM on the same server as Liferay DXP, you must deploy the OpenAM \texttt{.war}, downloadable from \href{https://backstage.forgerock.com/downloads/browse/am/archive/productId:openam}{here}. Otherwise, follow the instructions at the \href{https://backstage.forgerock.com/docs/openam/13/install-guide/}{OpenAM 13 site} to install OpenAM. \noindent\hrulefill \textbf{Note}: OpenAM 12 and below work with Liferay DXP, but are at end of life. Because of this, we recommend only OpenAM 13 for production use. \noindent\hrulefill Once you have it installed, create the Liferay DXP administrative user in it. Users are mapped back and forth by screen names. By default, the Liferay DXP administrative user has a screen name of \emph{test}, so if you were to use that account, register the user in OpenAM with the ID of \emph{test} and the email address specified in the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Admin\%20Portlet}{\texttt{admin.email.from.address}} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal property}). Once you have the user set up, log in to OpenAM using this user. In the same browser window, log in to Liferay DXP as the administrative user (using the previous admin email address). Go to the Control Panel and click \emph{Configuration} → \emph{Instance Settings} → \emph{Security} → \emph{SSO}. Then choose \emph{OpenSSO} in the list on the left. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/opensso-configuration.png}} \caption{OpenSSO Configuration.} \end{figure} Modify the three URL fields (Login URL, Logout URL, and Service URL) so they point to your OpenAM server (in other words, only modify the host name portion of the URLs), check the \emph{Enabled} box, and click \emph{Save}. Liferay DXP then redirects users to OpenAM when they request the \texttt{/c/portal/login} URL \emph{for example, when they click on the }Sign In* link). Liferay DXP's OpenAM configuration can be applied at either the system scope or at the instance scope. To configure the OpenAM SSO module at the system scope, navigate to the Control Panel, click on \emph{Configuration} → \emph{System Settings} → \emph{Security} → \emph{SSO} → \emph{OpenSSO}. Click on it and you'll find these settings to configure. The values configured here provide the default values for all portal instances. Enter them in the same format as you would when initializing a Java primitive type with a literal value. Property Label \textbar{} Property Key \textbar{} Description \textbar{} Type \textbf{Version} \textbar{} \texttt{version} \textbar{} OpenAM version to use (12 and below or 13) \textbar{} \texttt{String} \textbf{Enabled} \textbar{} \texttt{enabled} \textbar{} Check this box to enable OpenAM authentication. Note that OpenAM works only if LDAP authentication is also enabled and Liferay DXP's authentication type is set to screen name. \textbar{} \texttt{boolean} \textbf{Import from LDAP} \textbar{} \texttt{importFromLDAP} \textbar{} If this is checked, users authenticated from OpenAM that do not exist in Liferay DXP are imported from LDAP. LDAP must be enabled. \textbar{} \texttt{boolean} \textbf{Login URL} \textbar{} \texttt{loginURL} \textbar{} The URL to the login page of the OpenAM server \textbar{} \texttt{String} \textbf{Logout URL} \textbar{} \texttt{logoutURL} \textbar{} The URL to the logout page of the OpenAM server \textbar{} \texttt{String} \textbf{Service URL} \textbar{} \texttt{serviceURL} \textbar{} The URL by which OpenAM can be accessed to use the authenticated web services. If you are using OpenAM Express 8 or higher, you need to have the server running Java 6. \textbar{} \texttt{String} \textbf{Screen Name Attribute} \textbar{} \texttt{screenNameAttr} \textbar{} The name of the attribute on the OpenAM representing the user's screen name \textbar{} \texttt{String} \textbf{Email Address Attribute} \textbar{} \texttt{emailAddressAttr} \textbar{} The name of the attribute on the OpenAM representing the user's email address \textbar{} \texttt{String} \textbf{First Name Attribute} \textbar{} \texttt{firstNameAttr} \textbar{} The name of the attribute on the OpenAM representing the user's first name \textbar{} \texttt{String} \textbf{Last Name Attribute} \textbar{} \texttt{lastNameAttr} \textbar{} The name of the attribute on the OpenAM representing the user's last name \textbar{} \texttt{String} To override these default settings for a particular portal instance, navigate to the Control Panel and click \emph{Configuration} → \emph{Instance Settings} → \emph{Security} → \emph{SSO}. Then choose \emph{OpenSSO} in the list on the left. \chapter{CAS (Central Authentication Service) Single Sign On Authentication}\label{cas-central-authentication-service-single-sign-on-authentication} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} CAS is an authentication system originally created at Yale University. It is a widely used open source single sign-on solution and was the first SSO product to be supported by Liferay DXP. @product@'s CAS module includes the CAS client, so there's no need to install it separately. \noindent\hrulefill \textbf{Note:} Liferay DXP supports CAS 3.3.x. If you use a later version of CAS, it is best to use CAS's support for standards such as OpenID Connect or SAML to interface with Liferay DXP. \noindent\hrulefill The CAS Server application requires your server to have a properly configured Secure Socket Layer (SSL) certificate. To generate one yourself, use the \texttt{keytool} utility that comes with the JDK. First generate the key, then export the key into a file. Finally, import the key into your local Java key store. For public, Internet-based production environments, you must purchase a signed key from a recognized certificate authority or have your key signed by a recognized certificate authority. For Intranets, you should have your IT department pre-configure users' browsers to accept the certificate so they don't get warning messages about the certificate. To generate a key, use the following command: \begin{verbatim} keytool -genkey -alias tomcat -keypass changeit -keyalg RSA \end{verbatim} Instead of the password in the example (\texttt{changeit}), use a password you can remember. If you are not using Tomcat, you may want to use a different alias as well. For first and last names, enter \texttt{localhost} or the host name of your server. It cannot be an IP address. To export the key to a file, use the following command: \begin{verbatim} keytool -export -alias tomcat -keypass changeit -file server.cert \end{verbatim} Finally, to import the key into your Java key store, use the following command: \begin{verbatim} keytool -import -alias tomcat -file server.cert -keypass changeit -keystore $JAVA_HOME/jre/lib/security/cacerts \end{verbatim} If you are on a Windows system, replace \texttt{\$JAVA\_HOME} above with \texttt{\%JAVA\_HOME\%}. Of course, all of this must be done on the system where CAS is running. Once your CAS server is up and running, configure Liferay DXP to use it. CAS configuration can be applied either at the system scope or at the scope of a portal instance. To configure the CAS SSO module at the system or instance scope, navigate to the Control Panel, click on \emph{Configuration} → \emph{System Settings} (or \emph{Instance Settings}) → \emph{Security} → \emph{SSO}. The values configured in System Settings provide the default values for all portal instances. Enable CAS authentication and then modify the URL properties to point to your CAS server. \textbf{Enabled:} Check this box to enable CAS single sign-on. \textbf{Import from LDAP:} A user may be authenticated from CAS and not yet exist in Liferay DXP. Select this to automatically import users from LDAP if they do not exist in Liferay DXP. For this to work, LDAP must be enabled. The rest of the settings are various URLs with defaults included. Change \emph{localhost} in the default values to point to your CAS server. When you are finished, click \emph{Save}. After this, when users click the \emph{Sign In} link, they are directed to the CAS server to sign in to Liferay DXP. For some situations, it might be more convenient to specify the system configuration via files on the disk. To do so, create the following file: \begin{verbatim} {LIFERAY_HOME}/osgi/configs/com.liferay.portal.security.sso.cas.configuration.CASConfiguration.cfg \end{verbatim} The format of this file is the same as any properties file. The key to use for each property that can be configured is shown below. Enter values in the same format as you would when initializing a Java primitive type with a literal value. Property Label \textbar{} Property Key \textbar{} Description \textbar{} Type \textbf{Enabled} \textbar{} \texttt{enabled} \textbar{} Check this box to enable CAS SSO authentication. \textbar{} \texttt{boolean} \textbf{Import from LDAP} \textbar{} \texttt{importFromLDAP} \textbar{} Users authenticated from CAS that do not exist in Liferay DXP are imported from LDAP. LDAP must be enabled separately. \textbar{} \texttt{boolean} \textbf{Login URL} \textbar{} \texttt{loginURL} \textbar{} Set the CAS server login URL. \textbar{} \texttt{String} \textbf{Logout on session expiration} \textbar{} \texttt{logoutOnSessionExpiration} \textbar{} If checked, browsers with expired sessions are redirected to the CAS logout URL. \textbar{} \texttt{boolean} \textbf{Logout URL} \textbar{} \texttt{logoutURL} \textbar{} The CAS server logout URL. Set this if you want Liferay DXP's logout function to trigger a CAS logout \textbar{} \texttt{String} \textbf{Server Name} \textbar{} \texttt{serverName} \textbar{} The name of the Liferay DXP instance (e.g., \texttt{liferay.com}). If the provided name includes the protocol (\texttt{https://}, for example) then this will be used together with the path \texttt{/c/portal/login} to construct the URL to which the CAS server will provide tickets. If no scheme is provided, the scheme normally used to access the @product@ login page will be used. \textbar{} \texttt{String} \textbf{Server URL} \textbar{} \texttt{serviceURL} \textbar{} If provided, this will be used as the URL to which the CAS server provides tickets. This overrides any URL constructed based on the Server Name as above. \textbar{} \texttt{String} \textbf{No Such User Redirect URL} \textbar{} \texttt{noSuchUserRedirectURL} \textbar{} Set the URL to which to redirect the user if the user can authenticate with CAS but cannot be found in Liferay DXP. If import from LDAP is enabled, the user is redirected if the user could not be found or could not be imported from LDAP. \textbar{} \texttt{String} To override system defaults for a particular portal instance, navigate to the Control Panel, click on \emph{Configuration} → \emph{Instance Settings}, click on \emph{Authentication} on the right and then on \emph{CAS} at the top. \chapter{Authenticating Using SAML}\label{authenticating-using-saml} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The SAML (Security Assertion Markup Language) adapter provides Single Sign On (SSO) and Single Log Off (SLO) in your deployment. Each Liferay DXP instance serves as either the Service Provider (SP) or the Identity Provider (IdP). An identity provider is a trusted provider that provides single sign-on for users to access other websites. A service provider is a website that hosts applications and grants access only to identified users with proper credentials. \noindent\hrulefill \textbf{Note:} A single Liferay DXP instance is \emph{either} the SP or the IdP in your SSO setup; it can't be both. You can, however, use separate instances for both purposes (for example, one instance is the SP and another is the IdP). \noindent\hrulefill Below is background on how SAML works. To jump right to its configuration, see the articles on \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider}{Setting Up SAML as an Identity Provider} or \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-service-provider}{Setting Up SAML as a Service Provider} for instructions on using the \href{https://web.liferay.com/marketplace/-/mp/application/15188711}{SAML adapter}. Use the instructions to make the conceptual magic from this article come to life! \section{What's new in Liferay Connector to SAML 2.0}\label{whats-new-in-liferay-connector-to-saml-2.0} The \texttt{5.0.0} version of the application for Liferay DXP brings some long-awaited improvements: \begin{itemize} \tightlist \item Liferay DXP acting as a Service Provider (SP) can now connect to multiple Identity Providers (IdP). \item Developers have an extension point for customizing which Identity Providers to users can use to sign in. \item Support for other Signature Algorithms (like \texttt{SHA-256}) \item Signature method algorithm URL's can now be blacklisted from the metadata (for example, disabling \texttt{SHA-1}: \texttt{http://www.w3.org/2000/09/xmldsig\#rsa-sha1}) \end{itemize} \noindent\hrulefill \textbf{Note:} If you're migrating from a Liferay SAML adapter prior to version 3.1.0, your portal properties are automatically migrated to System Settings configurations. Please see the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-saml}{Configuring SAML} article for details on settings. \noindent\hrulefill \section{Important SAML Paths}\label{important-saml-paths} For reference, here are a few important SAML paths. This URL is the default location of the metadata XML file: \begin{verbatim} [host]:[port]/c/portal/saml/metadata \end{verbatim} Note that when configuring SAML, no importing of SAML certificates is required. Liferay DXP reads certificates from the SAML metadata XML file. If you want a third-party application like Salesforce to read a Liferay SAML certificate, you can export the Liferay DXP certificate from the keystore. The default keystore file is \begin{verbatim} [Liferay Home]/data/keystore.jks \end{verbatim} You can change this path in System Settings → SSO → SAML Configuration → Key Store Path. \section{Single Sign On}\label{single-sign-on} Both the IdP and the SP can initiate the Single Sign On process, and the SSO flow is different depending on each one. Regardless of how it's initiated, SSO is configured for HTTPS between the SP and IdP, so all transport-level communication is encrypted. SAML requests are signed using certificates configured in Liferay DXP, using the SAML Web Browser SSO profile as defined in the \href{http://saml.xml.org/saml-specifications}{SAML 2.0 specification}. In all cases, responses are sent using HTTP-POST or HTTP-Redirect. HTTP-POST is preferred because it reduces the risk that the URL is too long for a browser to handle. Consider IdP initiated SSO first. \section{Identity Provider Initiated SSO}\label{identity-provider-initiated-sso} Sometimes a user enters the SSO cycle by sending a request directly from the browser to the IdP. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-idp-initiated-sso.png}} \caption{Identity Provider Initiated SSO} \end{figure} \subsection{The SSO Request to the IdP}\label{the-sso-request-to-the-idp} If Liferay DXP is the IdP, the IdP initiated SSO URL \begin{itemize} \tightlist \item Must specify the path as \texttt{/c/portal/saml/sso}. \item Must include the \texttt{entityId} parameter which is the identifier to a previously configured Service Provider Connection (SPC). \item May include a \texttt{RelayState} parameter which contains a URL encoded value where the user is redirected upon successful authentication. This URL should point to a location on the desired SPC (according to the \href{https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf}{SAML 2.0 standards section 3.4.3}, this value \emph{must not} exceed 80 bytes in length). It is useful to specify a landing page after SSO has been executed. \end{itemize} For non-Liferay DXP IdPs (Siteminder, ADFS, etc.), consult the vendor's documentation on constructing IdP initiated SSO URLs. If the IdP determines that the user isn't authenticated, it prompts the user with the appropriate login screen. \subsection{The SSO Response from the IdP}\label{the-sso-response-from-the-idp} Upon successful authentication, the IdP constructs a SAML Response. It includes attribute statements configured in the designated Service Provider Connection (SPC; see the \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider}{next article} on setting up the SPC in Liferay DXP's SAML adapter). The IdP sends the response to the Assertion Consumer Service URL. The request contains two parameters: \texttt{SAMLResponse} and \texttt{RelayState}. \noindent\hrulefill \textbf{Note:} The method for sending the SAML response (for example, HTTP-POST) and the Assertion Consumer Service URL are usually imported as part of the SAML metadata XML provided by the SP. In Liferay DXP, you import the SP's metadata in the SAML Adapter's Service Provider Connections tab. \noindent\hrulefill \subsection{The SP Processes the SSO Response}\label{the-sp-processes-the-sso-response} The SP validates and processes the SAML Response. Liferay DXP's SAML solution requires signed \texttt{SAMLResponse} messages. This signature process ensures proper identification for the IdP and prevents potential SAML Response spoofing. \begin{itemize} \tightlist \item If one Liferay DXP instance is the IdP and another is the SP, make sure the SAML metadata XML file imported into the SP contains the IdP's certificate. \item If Liferay DXP is the IdP and another application is the SP, export the certificate from the IdP and import it into the SP's certificate store. \end{itemize} If a \texttt{RelayState} is included in the SAML Response, the user is redirected to it. Otherwise the home page of the SP is served. \section{Service Provider Initiated SSO}\label{service-provider-initiated-sso} Most of the time, authentication requests come from the Service Provider. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-sp-initiated-sso.png}} \caption{Service Provider Initiated SSO} \end{figure} \subsection{The SSO Request to the SP}\label{the-sso-request-to-the-sp} When the user's browser requests a protected resource or login URL on the SP, it triggers the SP initiated SSO process. When Liferay DXP is the SAML SP, SSO is initiated either by requesting \texttt{/c/portal/login} URL or a protected resource that requires authentication (for example, a document not viewable by the Guest Role). If the user requests a protected resource, its URL is recorded in the \texttt{RelayState} parameter. If the user requested \texttt{/c/portal/login}, the \texttt{RelayState} can be set by providing the \texttt{redirect} parameter. Otherwise, if the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{portal property} \texttt{auth.forward.by.last.path} is set to \texttt{true}, the last accessed path is set as the \texttt{RelayState}. For non-Liferay DXP SPs, consult the vendor documentation on initiating SSO. \subsection{The AuthnRequest to the IdP}\label{the-authnrequest-to-the-idp} The SP looks up the IdP's Single Sign On service URL and sends an \texttt{AuthnRequest}. When Liferay DXP is the SP it looks up the configured SAML Identity Provider Connection and sends a SAML \texttt{AuthnRequest} to the IdP's Single Sign On service URL as defined in the SAML metadata XML document. Liferay DXP supports sending and receiving the \texttt{AuthnRequest} using HTTP-POST or HTTP-Redirect binding. HTTP-POST is preferred. If the user doesn't have an active session or if \texttt{ForceAuthn} was requested by the SP, the user must authenticate by providing credentials. When Liferay DXP is the IdP, authentication occurs in the Login Portlet. Liferay DXP decodes and verifies the \texttt{AuthnRequest} before requesting the user to authenticate. \subsection{The SSO Response from the IdP}\label{the-sso-response-from-the-idp-1} After authentication, a SAML Response is constructed, sent to the Assertion Consumer Service URL of the SP, and verified. The IdP automatically makes this choice based on the SP metadata. When Liferay DXP is configured as the IdP, any attributes configured on the Service Provider Connection are included in the response as attribute statements. The Assertion Consumer Service URL is looked up from the SAML metadata XML of the SP. When Liferay DXP is configured as the SP, response and assertion signatures are verified. Liferay DXP requires the sender to be authenticated. This is done via whole message signature from the issuing IdP. Responses missing the signature are considered unauthenticated and the response is rejected. For non-Liferay DXP SP or IdP vendors, consult their documentation. The user is redirected to the requested resource or to the URL contained in the \texttt{RelayState} parameter (for example, the last page the user accessed before initiating SSO). \section{Single Log Off}\label{single-log-off} The Single Log Off request is sent from the user's browser to the IdP or an SP, and the SLO flow differs in each case. First consider IdP initiated SLO. \section{Identity Provider Initiated SLO}\label{identity-provider-initiated-slo} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-idp-initiated-slo.png}} \caption{Identity Provider Initiated SLO} \end{figure} \subsection{The SLO Request to the IdP}\label{the-slo-request-to-the-idp} An IdP initiated SLO request is sent directly to the IdP by the user's browser. When Liferay DXP is the IdP, the IdP initiated SSO URL must specify the URL path as \texttt{/c/portal/logout} If the user is signed on to any configured SP, the SAML plugin takes over the logout process, displaying all the signed on services. The single logout screen displays the authentication status of each SP and whether any SPs can't be logged out of (for example, if the SP is down or doesn't support SLO). For non-Liferay DXP IdPs (Siteminder, ADFS, etc.) consult the vendor's documentation on constructing IdP initiated SLO URLs. The IdP sends a SAML \texttt{LogoutRequest} to the SP. \begin{itemize} \tightlist \item When Liferay DXP is configured as the IdP, the \texttt{LogoutRequest} is sent using either HTTP-POST, HTTP-Redirect, or SOAP binding. HTTP-Post binding is preferred but in its absence, the first available SLO endpoint with supported binding is selected. \item When Liferay DXP is configured as the SP, supported bindings for \texttt{LogoutRequest} are HTTP-Post, HTTP-Redirect, or SOAP. \item For other IdPs or SPs, please consult the vendor's documentation. \end{itemize} \subsection{The SLO Response from the SP}\label{the-slo-response-from-the-sp} The SP delivers a \texttt{LogoutResponse} to the IdP. The IdP sends a SAML \texttt{LogoutRequest} to the second SP. The second SP then delivers the \texttt{LogoutResponse} to the IdP. The process is repeated for all SPs the user is logged into. When Liferay DXP is the IdP, Liferay DXP logs the user out after the last SP has delivered its \texttt{LogoutResponse} or has timed out. \section{Service Provider Initiated SLO}\label{service-provider-initiated-slo} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-sp-initiated-slo.png}} \caption{Service Provider Initiated SLO} \end{figure} \subsection{The SLO Request to the SP}\label{the-slo-request-to-the-sp} In SP initiated SLO, the user's browser sends a logout request directly to the SP. When Liferay DXP is configured as the SP, the SLO is initiated by requesting this logout URL: \begin{verbatim} /c/portal/logout \end{verbatim} For other SPs, consult the vendor's documentation on initiating SLO. A SAML \texttt{LogoutRequest} is sent to the Single Log Out service URL of the IdP. \begin{itemize} \item If Liferay DXP serves as the SP, the \texttt{LogoutRequest} is sent to the IdP configured by the IdP Connections tab of the SAML provider (see the \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider}{next article} to set up the IdP Connection) and the SLO service URL defined in the SAML metadata. \item When Liferay DXP is the IdP, if the user has logged on to other SPs, the user is presented with a single logout screen with the status of each SP logout, flagging any that can't be looged out of (some SPs might not support SLO or are currently down). If there are no other SPs to log out of, the SAML session terminates and the IdP destroys its session. \end{itemize} \subsection{The SLO Response from the SP}\label{the-slo-response-from-the-sp-1} If the user is logged in to additional SPs (beyond just the initiating SP), the IdP sends the SAML \texttt{LogoutRequest} to each one. When Liferay DXP is the IdP, the \texttt{LogoutResponse} is sent using either HTTP-Post, HTTP-Redirect, or SOAP binding. Each SP delivers its \texttt{LogoutResponse} to the IdP. When Liferay DXP is the SP, the \texttt{LogoutResponse} is sent using either HTTP-Post, HTTP-Redirect or direct response to SOAP request. After all additional SPs deliver their \texttt{LogoutResponse}s to the IdP, the IdP destroys its SSO session. When Liferay DXP is the IdP, once the last SP has delivered its \texttt{LogoutResponse} or has timed out, the IdP destroys the Liferay DXP session, logging out the user. Finally, the IdP sends a \texttt{LogoutResponse} to the SP that initiated SLO. The initiating SP terminates its SAML session and logs the user out. \section{Related Topics}\label{related-topics-6} \begin{itemize} \tightlist \item \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider}{Setting Up SAML as an Identity Provider} \item \href{/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-service-provider}{Setting Up SAML as a Service Provider} \item \href{/docs/7-2/deploy/-/knowledge_base/d/token-based-single-sign-on-authentication}{Token-Based SSO Authentication} \end{itemize} \chapter{Setting up Liferay DXP as a SAML Identity Provider}\label{setting-up-liferay-dxp-as-a-saml-identity-provider} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} An identity provider is a trusted provider that provides single sign-on for users to access other websites. A service provider is a website that hosts applications and grants access only to identified users with proper credentials. Liferay Portal 6.1 EE and later versions support SAML 2.0 integration via the \href{https://web.liferay.com/marketplace/-/mp/application/15188711}{Liferay Connector to SAML 2.0} application. It is provided from Liferay Marketplace and allows Liferay DXP to act as a SAML 2.0 identity provider or as a service provider. \noindent\hrulefill \textbf{Important:} You can set Liferay DXP up as an Identity Provider or as a Service Provider. Each single Liferay DXP instance can serve as an identity provider or as a service provider, but \textbf{not both}. Both configurations are covered in this article. \noindent\hrulefill \section{Storing Your Keystore}\label{storing-your-keystore} Your first step is to determine where to store your keystore. You have two options: \begin{itemize} \tightlist \item In the file system \item In the Documents and Media library \end{itemize} The file system keystore manager is used by default and the default location is the \texttt{{[}Liferay\ Home{]}/data} directory (you can change the location in System Settings → SSO → SAML Configuration → Key Store Path). To use Documents and Media library storage for your keystore instead of file system storage, go to \emph{Control Panel} → \emph{System Settings} → \emph{Security} → \emph{SSO} → \emph{SAML KeyStoreManager Implementation Configuration}. Select from the two options: \emph{Filesystem Keystore Manager} or \emph{Document Library Keystore Manager}. If you use Document Library storage, you can use any number of \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{back-end file stores}. These are protected not only by the system where the key is stored, but also by Liferay DXP's permissions system. \section{Configuring Liferay DXP as a SAML Identity Provider}\label{configuring-liferay-dxp-as-a-saml-identity-provider} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item To access the SAML Admin interface, click on \emph{Control Panel} → \emph{Configuration} and then on \emph{SAML Admin}. \item To begin configuring Liferay DXP to use SAML, select a SAML role for @product@ and choose an entity ID. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-initial-config.png}} \caption{Select a SAML role for Liferay and enter an entity ID.} \end{figure} Select the \emph{Identity Provider} SAML role. Enter your own entity ID. Then click \emph{Save}. A new Certificate and Private Key section appears. \item The Certificate and Private Key section lets you create a keystore for SAML. Click on \emph{Create Certificate} and enter the following information: \begin{itemize} \tightlist \item Your common name (your first and last name) \item The name of your organization \item The name of your organizational unit \item The name of your city or locality \item The name of your state or province \item The name of your country \item The length in days that your keystore will remain valid (how long before the keystore expires) \item The key algorithm (RSA is the default) \item The key length in bits (2048 is the default) \item The key password \end{itemize} Click \emph{Save}. When you create the certificate and private key, you also create a keystore if one doesn't already exist. As described above, this keystore has two storage options: file system storage (the default) and Documents and Media storage. By default, the certificate uses the \texttt{SHA256} algorithm for encryption and is fingerprinted and self-signed via RSA and \texttt{SHA256}. \item After you click \emph{Save}, you can click \emph{Replace Certificate} at any time to replace the current certificate with a new one if your old one has expired or if you want to change the key's password. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/saml-keystore-info.png}} \caption{The General tab of the SAML Admin portlet displays information about the current certificate and private key and allows administrators to download the certificate or replace the certificate.} \end{figure} Three more tabs now appear: \textbf{General:} For enabling or disabling a SAML IdP and managing the required keystore. \textbf{Identity Provider:} Contains IdP options, such as whether to enable SSL. If SSL has been enabled, then SAML requests are not approved unless they are also encrypted. \textbf{Service Provider Connections:} This tab manages any Service Providers connected to this Liferay DXP instance. See below for more information on the Identity Provider and Service Provider Connections tabs. \item After you save your certificate and private key information, check the \emph{Enabled} box at the top of the General tab and click \emph{Save}. You successfully set Liferay DXP up as a SAML Identity Provider! \end{enumerate} \section{Changing the Identity Provider Settings}\label{changing-the-identity-provider-settings} To configure Liferay DXP's SAML Identity Provider Settings, navigate to the \emph{Identity Provider} tab of the SAML Admin Control Panel entry. The \emph{Identity Provider} tab includes these options: \textbf{Sign Metadata?:} Check this box to ensure the metadata XML file that's produced is signed. \textbf{SSL Required:} Check this box to reject any SAML messages that are \emph{not} sent over SSL. This affects URLs in the generated metadata. \textbf{Require Authn Request Signature?:} When this box is checked, each Authn Request must be signed by the sending Service Provider. In most cases, this should be enabled. \textbf{Session Maximum Age:} Specify the maximum duration of the SAML SSO session in seconds. If this property is not set or is set to \texttt{0}, the SSO session has an unlimited duration. The SSO session maximum duration can be longer than the portal session maximum duration. If the portal session expires before the SSO session expires, the user is logged back in to Liferay DXP automatically. SSO session expiration does not trigger a single logout from all service providers. You can use the session maximum age, for example, to force users to sign in again after a certain period of time. \textbf{Session Timeout:} Specify the maximum idle time of the SAML SSO session. Even if the session maximum age is unlimited, the SSO session expires whenever the user's idle time reaches the limit set by the session timeout property. \section{Checkpoint}\label{checkpoint-2} Before adding a Service Provider (SP), verify you've completed these tasks: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A SAML keystore has been generated. It can be stored in one of two locations: the \texttt{data} folder or in the Documents and Media library. \item On the \emph{Identity Provider} tab, the following settings have been set: \begin{enumerate} \def\labelenumii{\alph{enumii}.} \item \textbf{Sign Metadata} has been checked. \item \textbf{SSL Required} - checked if SSL is active elsewhere. SSL is disabled by default. \item \textbf{Authn Request Signature Required:} has been checked. \item \textbf{Session Maximum Age:} has been set. If set to \texttt{0}, then the SSO has an unlimited duration. \item \textbf{Session Timeout:} Specify the maximum idle time of the SAML SSO session. \end{enumerate} \item Once the \emph{Enabled} checkbox has been checked, the IdP is live, and you can generate the required metadata. This URL is the default location of Liferay DXP's metadata XML file: \begin{verbatim} [host]:[port]/c/portal/saml/metadata \end{verbatim} \end{enumerate} If this URL does not display correctly, then the SAML instance has not been enabled. Use the URL or click \emph{Save} in the browser to generate an actual \texttt{XML} file. Your Identity Provider is now set up. Next, you must register a Service Provider. \chapter{Registering a SAML Service Provider}\label{registering-a-saml-service-provider} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Setting up Liferay DXP as a SAML Identity Provider is only useful if you can connect to one or more SAML Service Providers. Navigate to the Service Provider Connections tab of the SAML Admin Control Panel entry and click the \emph{Add Service Provider} button to add a SAML Service Provider. The New Service Provider page includes these options: \textbf{Name:} The name of the Service Provider with which to connect. The name can be anything; it's purely cosmetic. \textbf{Entity ID:} The Service Provider's entity ID. This value must match the entity ID declared in the Service Provider metadata. \textbf{Enabled:} Check this box to activate the Service Provider connection. \textbf{Assertion Lifetime:} Defines the number of seconds after which the SAML assertion issued by the Identity Provider should be considered expired. \textbf{Force Encryption:} If the SP does not provide a public key for encrypting the assertions, abort the single sign-on. \textbf{Metadata:} Provide a URL to the Service Provider metadata XML file or manually upload the Service Provider metadata XML file. If you provide a URL, the XML file is retrieved and periodically polled for updates. The update interval can be configured in System Settings with the Runtime Metadata Refresh Interval (\texttt{saml.metadata.refresh.interval} if using a \texttt{config} file) property which specifies a number of seconds. If fetching the metadata XML file by URL fails, you can't enable the Service Provider connection. If the Identity Provider server cannot access the metadata via URL, you can upload the XML file manually. In this case, the metadata XML file is not updated automatically. \textbf{Name Identifier Format:} Choose the Name Identifier Format used in the SAML Response. This should be set according to what the Service Provider expects to receive. For Liferay Service Providers, any selection other than email address indicates that the Name Identifier refers to screen name. The formats don't have any special meaning to Liferay Identity Providers. The \texttt{NameID} value is defined by the Name Identifier attribute. See the next option. \textbf{Name Identifier Attribute Name:} This specifies which attribute of the Liferay DXP \texttt{User} object to use as the \texttt{NameID} value. Possible values include \texttt{emailAddress}, \texttt{screenName} and \texttt{uuid}. Additionally, you can prefix the name with \texttt{static:} or \texttt{expando:}. If you use the prefix \texttt{static}, the value is whatever comes after \texttt{static:}. If you use the prefix \texttt{expando}, the value is whatever custom field is specified after \texttt{expando:}. For example, \texttt{expando:SSN} would look up the \texttt{User} custom field with the name \texttt{SSN}. \textbf{Attributes Enabled:} Include and resolve assertion attributes. \textbf{Attributes Namespace Enabled:} When this box is checked, the attribute names are namespaced like this: \begin{verbatim} urn:liferay:user:expando: urn:liferay:user: urn:liferay:groups: urn:liferay:organizationRole: urn:liferay:organization: urn:liferay:roles: urn:liferay:siteRole: urn:liferay:userGroupRole: urn:liferay:userGroups: \end{verbatim} \textbf{Attributes:} Enter a list of attributes to include in the assertion, one per line. Each line is an expression that gets parsed. Examples: \begin{verbatim} organizations organizationRoles roles siteRoles userGroups static:[attributeName]=[attributeValue] expando:[userCustomFieldName] \end{verbatim} Note that the full namespace depends on the attribute name. Attribute namespaces can be useful. Use them when attribute names from different namespaces might conflict. For example, \texttt{expando:user} vs \texttt{urn:liferay:roles:user}. \textbf{Keep Alive URL:} If users are logged into several Liferay DXP SP instances via a Liferay DXP IdP, their sessions can be kept alive as long as they keep a browser window open to one of them. Configure this only if the SP is Liferay DXP. The URL is \texttt{https://{[}SP\ host\ name{]}/c/portal/saml/keep\_alive}. \section{Checkpoint}\label{checkpoint-3} Verify your settings are correct by connecting the Liferay DXP-based IdP to its first SP. SPs connect to only one IdP, so if the first one doesn't work, the rest won't either. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Provide a general name for the SP. \item The \texttt{Entity\ ID} name must be identical to the one declared in the Service Provider metadata. \item Check the \emph{Enabled} checkbox. \item Set a value for the \emph{Assertion Lifetime}. \item Choose whether encryption should be required (recommended). \item Make sure the SP's metadata has been provided either as a URL or an XML file has been uploaded. \item Make sure \emph{Name Identifier Format} and \emph{Name Identifier Attribute Name} have been set. \item Make sure \emph{Attributes Namespace Enabled} has been set. \end{enumerate} If you don't have a Service Provider to add right now, that's fine. In the next section, you'll learn how to set Liferay DXP up as a SAML Service Provider. The same instance can't be both, but after you set up another Liferay DXP instance as a Service Provider, come back to this one and add the Service Provider according to the instructions above. \chapter{Setting up Liferay DXP as a SAML Service Provider}\label{setting-up-liferay-dxp-as-a-saml-service-provider} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Many of these steps are similar to configuring Liferay DXP as a SAML Identity Provider. As a reminder, a single Liferay DXP installation can be configured as a SAML Identify Provider \emph{or} as a SAML Service Provider but not as both. If your Liferay DXP installation is already a SAML Identity Provider, use a \emph{different} Liferay DXP installation as a SAML Service Provider. \noindent\hrulefill \textbf{Note:} If you have a third party IdP with Liferay DXP as the SP, all messages coming from the IdP must be signed. If they're not, an error message appears and communication between the IdP and Liferay DXP fails. \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Install the Liferay Connector to SAML 2.0 app. To confirm successful deployment, look for the \emph{SAML Admin} entry in the Configuration section of the Control Panel. \item To begin configuring Liferay DXP to use SAML, you must select a SAML role for Liferay DXP and choose an entity ID. Select the \emph{Service Provider} SAML role. Choose your own entity ID. Then click \emph{Save} and a new section entitled Certificate and Private Key appears. \item The Certificate and Private Key section is for creating a keystore for SAML. Click \emph{Create Certificate} and enter the following information: \begin{itemize} \tightlist \item Your common name (your first and last name) \item The name of your organization \item The name of your organizational unit \item The name of your city or locality \item The name of your state or province \item The name of your country \item The length in days that your keystore will remain valid (how long before the keystore expires) \item The key algorithm (RSA is the default) \item The key length in bits (2048 is the default) \item The key password \end{itemize} Remember that the keystore has two storage options: file system storage (the default) and Documents and Media storage.By default, the certificate uses the SHA256 algorithm for encryption and is fingerprinted and self-signed via MD5 and SHA1. When you enter all the required information, click \emph{Save}. \item After you clicked \emph{Save}, check that you can view information about your certificate or download your certificate. If you can, you successfully created a keystore. After you create a keystore, additional options appear. There are three tabs: \textbf{General}: Enables or disables SAML SP and manages the required keystore. \textbf{Service Provider}: This tab manages basic and advanced configurations for the SP. \textbf{Identity Provider Connection}: This tab manages connections to the IdP. There can be multiple IdP connections. \item You can also generate an encryption certificate. This is a separate key for encrypting assertions. If you want assertions encrypted, you must generate a key for this. The procedure is exactly the same as generating your certificate in step 3 above. \item Next, you must configure an Identity Provider connection. Click on the \emph{Identity Provider Connections} tab. Enter a name for the Identity Provider, enter its entity ID, and enter its metadata URL. If you have already followed the previous instructions and configured a separate Liferay DXP installation as an Identify provider, you'd enter the following information: \begin{itemize} \tightlist \item Name: \emph{Liferay IdP} \item Entity ID: {[}ID of IdP{]} \item Clock Skew: Set the tolerance in milliseconds between SP and IdP. \item Force Authn: Whether the IdP should force reauthentication regardless of context. \item Metadata URL: http://localhost:8080/c/portal/saml/metadata (test this URL first) \item Name Identifier Format: See settings. \item Attribute Mapping: See settings. \item Keep Alive URL: See settings. \end{itemize} \textbf{Important}: The Liferay Connector to SAML 2.0 app supports using \emph{either} a URL to a SAML IdP metadata file \emph{or} an actual (uploaded) SAML metadata XML file. The value entered in the \emph{Metadata URL} field is persisted to the database only when there a metadata URL and there is no specified metadata XML file. Otherwise, Liferay DXP keeps the original metadata URL in the database. This behavior ensures that once a metadata URL has been specified, there is always a metadata URL saved in the database. This way, if a portal administrator forgets the previously entered metadata URL or its format, he or she can look at the displayed metadata URL and choose to modify the displayed metadata URL or overwrite the previously saved metadata URL by specifying a metadata XML file. \item Finally, after you save your certificate and private key information and configure an Identity Provider connection, check the \emph{Enabled} box at the top of the General tab and click \emph{Save}. Liferay DXP is now a SAML Service Provider! \end{enumerate} Note that the SAML Service Provider session is tied to the normal session on the application server. Session expiration on the application server terminates the session on the Service Provider but does not initiate single logout. You can add multiple IdP connections. To add another Identity Provider, click \emph{Add Identity Provider} again and enter the details for the other provider. When users log in, they are asked to choose an identity provider, so be sure to name the providers so users can recognize them. \section{Checkpoint}\label{checkpoint-4} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A SAML keystore has been generated. \item Verify the connection to the IdP. \begin{enumerate} \def\labelenumii{\alph{enumii}.} \item \emph{Name}: generic name for the IdP. \item \emph{Entity ID}: the same name of the IdP. If the IdP is another Liferay DXP instance, then it is the same name as the above example. \item \emph{Metadata URL}: The IdP's metadata as a URL or as an XML file. \item If the IdP is another @product instance, ensure its corresponding Service Provider Connection for this SP is enabled. \end{enumerate} \item On the \emph{General} tab, the \emph{Enabled} checkbox has been checked. \item Once \emph{Enabled} checkbox has been checked, the service provider's metadata becomes available: \begin{verbatim} [host]:[port]/c/portal/saml/metadata \end{verbatim} \end{enumerate} \section{Setting Up Liferay DXP as a SAML Service Provider in a Clustered Environment}\label{setting-up-liferay-dxp-as-a-saml-service-provider-in-a-clustered-environment} You can use the Liferay Connector to SAML 2.0 app as an SSO solution for a clustered Liferay DXP environment. If your multi-node cluster is behind a load balancer, you must enable all the nodes as SPs, and they must share the same keystore manager. If using the Filesystem Keystore Manager (the default): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Configure each node of your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP cluster} as a SAML service provider as above. \item Copy the keystore file (\texttt{{[}Liferay\ Home{]}/data/keystore.jks}, by default) from the first node to the remaining nodes. This file is the Java keystore that's created by the SAML Provider app. The keystore contains the valid or self-signed certificate managed by the SAML connector app. \item Verify that the service provider metadata has been generated to be used either as a URL or an XML file. The metadata is the same for all nodes because of the same database back-end. The IdP's request goes through the load balancer. \item At this point, all nodes have the same SAML SP configuration and each of them can respond to web requests and handle the SAML protocol. To test your SSO solution, sign into Liferay DXP via your load balancer, navigate to a few pages of a few different sites, and then log out. \end{enumerate} If using the Document Library Keystore Manager, skip step 2 because the keystore file is stored in the database shared by all the nodes. Now you know how to configure Liferay DXP either as a SAML identity provider or a service provider. You also know how to configure SAML in a clustered environment. \chapter{Changing the Settings for Service Provider and Identity Provider Connections}\label{changing-the-settings-for-service-provider-and-identity-provider-connections} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} To change the SAML Service Provider Settings, navigate to the Service Provider tab. The Service Provider tab includes these options: \textbf{Require Assertion Signature?:} Check this box to require SAML assertions to be individually signed in addition to the entire SAML message. \noindent\hrulefill \textbf{Note:} Individual assertions need not be signed as long as the SAML response itself is signed. The SP and IdP should always communicate over \texttt{https} to have encryption at the transport level. If you believe man-in-the-middle attacks are possible, the SAML response can be signed. The only reason to sign the assertions is if the SAML response is not signed. In this case, assertions should not only be signed but also encrypted. \noindent\hrulefill \textbf{Clock Skew:} Clock skew is a tolerance in milliseconds used by the Service Provider for mitigating time differences between the clocks of the Identity Provider and the Service Provider. This usually only matters when assertions have been made to expire very quickly. \textbf{LDAP Import Enabled:} Check this box to import user information from the configured LDAP connection based on the resolved \texttt{NameID}. LDAP connections can be configured from Instance Settings. \textbf{Sign Authn Requests:} Check this box to sign the \texttt{AuthnRequest} even if the Identity Provider metadata indicates that it's not required. \textbf{Sign Metadata:} Check this box to sign the metadata XML file. \textbf{SSL Required:} Check this box to reject SAML messages that are not sent over HTTPS. This does not affect how URLs are generated. \section{Changing the SAML Identity Provider Connection Settings}\label{changing-the-saml-identity-provider-connection-settings} To configure Liferay DXP's SAML Identity Provider Settings, navigate to the Identity Provider Connection tab of the SAML Admin portlet and click the \emph{Edit} action button on the IdP you want to configure. \textbf{Name:} The name of the Identity Provider with which to connect. \textbf{Entity ID:} The Identity Provider's entity ID. This value must match the entity ID declared in the Identity Provider metadata. \textbf{Enabled:} Check the box to enable this IdP. \textbf{Clock Skew:} Clock skew is a tolerance in milliseconds used by the Service Provider for mitigating time differences between the clocks of the Identity Provider and the Service Provider. This usually only matters when assertions have been made to expire very quickly. \textbf{Force Authn:} Check this box to have the Service Provider ask the Identity Provider to re-authenticate the user before verifying the user. \textbf{Metadata:} You can provide a URL to the Identity Provider metadata XML file or you can manually upload it. If you provide a URL, the XML file is automatically retrieved and periodically polled for updates. You can change the update interval in System Settings by modifying the Runtime Metadata Refresh Interval property which specifies a number of seconds. If fetching the metadata XML file by URL fails, you can't enable the Identity Provider connection. If the metadata is inaccessible via URL, you can upload the XML file manually. In this case, the metadata XML file is not updated automatically. \textbf{Name Identifier Format:} Choose the Name Identifier Format used in the SAML Response. Set this according to what the Service Provider expects to receive. For Liferay Service Providers, selections other than email address indicate that the Name Identifier refers to screen name. The formats don't have any special meaning to Liferay Identity Providers. The Name Identifier attribute defines the \texttt{NameID} value. \textbf{Attribute Mapping:} Attribute mapping is done from the attribute name or friendly name in the SAML Response to the Liferay DXP attribute name. For example, if you want to map a response attribute named \texttt{mail} to the Liferay DXP attribute \texttt{emailAddress}, enter the following mapping: \begin{verbatim} mail=emailAddress \end{verbatim} Available Liferay DXP attributes are: \texttt{emailAddress}, \texttt{screenName}, \texttt{firstName}, \texttt{lastName}, \texttt{modifiedDate}, and \texttt{uuid}. \textbf{Keep Alive URL:} If users are logged into several Liferay DXP SP instances via a Liferay DXP IdP, their sessions can be kept alive as long as they keep a browser window open to one of them. Configure this only if the IdP is Liferay DXP. The URL is \texttt{https://{[}IdP\ host\ name{]}/c/portal/saml/keep\_alive}. On the Liferay DXP IdP, configure this URL the same way, but point back to this SP. Save your changes when you are finished configuring the Liferay DXP instance as a service provider. There is no need to restart the server: the changes are applied immediately. Make the above configurations through the SAML Control Panel interface and not via properties. Some features of the Liferay Connector to SAML 2.0 app are not available as properties. \noindent\hrulefill \textbf{Limitation:} The Liferay SAML app can only be used with a single virtual host. Technically, this means that in the SAML metadata for Liferay DXP, only one binding can be added in this form: \begin{verbatim} ... ... ... \noindent\hrulefill # Configuring SAML There are some ways of configuring the SAML plugin outside the UI. This is done via OSGi configuration files and by uploading metadata XML to configure how connections are negotiated. ## OSGi Configuration Properties As noted in the previous tutorials, anything related to configuring SP connections must be done through the SAML Admin UI where configurations are saved to Liferay's database. SP connections can no longer be made via properties files as they were in versions prior to 3.1.0. \noindent\hrulefill **Note:** Don't use OSGi `.config` files or Liferay DXP's System Settings Control Panel application to configure SAML providers (IdP or SP). The System Settings UI is auto-generated, and is for advanced administrators. It does not perform the enhanced validation on the fields that the SAML Admin UI performs, so it could allow administrators to create invalid configurations. \noindent\hrulefill This is a portal instance scoped configuration which can be managed via OSGi Configuration Admin. The affected properties are those in the `SAMLProviderConfiguration` metatype: - `keyStoreCredentialPassword()` - `keyStoreEncryptionCredentialPassword()` - `assertionSignatureRequired()` - `authnRequestSignatureRequired()` - `clockSkew()` - `defaultAssertionLifetime()` - `entityId()` - `enabled()` - `ldapImportEnabled` - `role()` - `sessionMaximumAge` - `sessionTimeout()` - `signAuthnRequest()` - `signMetadata()` - `sslRequired()` - `allowShowingTheLoginPortlet()` The SAML Admin UI remains the place for creating the portal instance scoped configuration instances. Note that there is also a system wide configuration, represented by the `SamlConfiguration` metatype. If you used Liferay 6.2, please note that the following system wide properties were removed: `saml.metadata.paths` (served no purpose after removal of SP connection defaults) `saml.runtime.metadata.max.refresh.delay` `saml.runtime.metadata.min.refresh.delay` The latter two properties were replaced with the single property `com.liferay.saml.runtime.configuration.SamlConfiguration.getMetadataRefreshInterval()`. Note also the introduction of the *SAML KeyStoreManager Implementation Configuration* in *Control Panel* → *System Settings* → Security → SSO. The options for this configuration are explained above in the Setting up Liferay DXP as a SAML Identity Provider section. In the latest version of the plugin, the `SHA256` algorithm is the default encryption algorithm used in the configuration and to generate keys. The default configuration tries `SHA256`, then `SHA384`, then `SHA512` before falling back to `SHA1`. Because `SHA1` is potentially vulnerable, you can blacklist it using this property: ```properties blacklisted.algorithms=["blacklisted_algorithm_url", "another_blacklisted_algorithm_url"] \end{verbatim} To blacklist \texttt{SHA1}, therefore, you'd have this configuration: \begin{verbatim} blacklisted.algorithms=["http://www.w3.org/2000/09/xmldsig#sha1"] \end{verbatim} Place these in a config file with this name: \begin{verbatim} com.liferay.saml.opensaml.integration.internal.bootstrap.SecurityConfigurationBootstrap.config \end{verbatim} There's a lot more granularity in how connections are negotiated if you configure the metadata XML. \section{Configuring Negotiation Via metadata.xml}\label{configuring-negotiation-via-metadata.xml} If the default negotiation configuration doesn't work for you, you can craft your own configuration and upload it. Before doing this, visit your host's metadata URL and save a copy of the configuration in case you need it later: \begin{verbatim} http://[hostname]/c/portal/saml/metadata \end{verbatim} For example, if you're stuck connecting to a legacy IdP that only supports \texttt{SHA1}, you can upload a configuration that disables the other algorithms: \begin{verbatim} ... omitted ... \end{verbatim} Notice that in the configuration above, the \texttt{\textless{}md:Extensions\textgreater{}} block has only one signing algorithm: \texttt{SHA1}. \noindent\hrulefill \textbf{Note:} Since the default configuration falls back to \texttt{SHA1}, you shouldn't need to do this unless your legacy system can't negotiate via the fallback mechanism. Also note that if you blacklisted \texttt{SHA1}, this won't work. Due to \href{https://en.wikipedia.org/wiki/SHA-1}{vulnerabilities in \texttt{SHA1}}, it's best to avoid using it altogether if possible. \noindent\hrulefill If you've changed your metadata configuration, you can go back to the default configuration if you saved it before making the change. If you didn't, you can provide a URL instead of an uploaded XML file to one of your peers' metadata configurations. \chapter{NTLM Single Sign On Authentication}\label{ntlm-single-sign-on-authentication} NTLM (NT LAN Manager) is a suite of Microsoft protocols that provide authentication, integrity, and confidentiality for users. Though Microsoft has adopted Kerberos in modern versions of Windows server, NTLM is still used when authenticating to a workgroup. Liferay DXP now supports NTLM v2 authentication. NTLM v2 is more secure and has a stronger authentication process than NTLMv1. \noindent\hrulefill \textbf{Note:} NTLM authentication was deprecated in 7.0 and was removed. You can still install it from Marketplace \href{https://web.liferay.com/marketplace/-/mp/application/125668266}{here} or \href{https://web.liferay.com/marketplace/-/mp/application/125668305}{here}. \noindent\hrulefill Note that in order to use NTLM SSO, Liferay DXP's portal instance authentication type must be set to screen name. \noindent\hrulefill \textbf{Note:} To USE NTLM with Liferay DXP, you must configure your browser. Consult your browser vendor's documentation for the details. \noindent\hrulefill Most importantly, all users \emph{must} be imported from an Active Directory server. NTLM (and Kerberos) works only if the users are in the AD; otherwise any SSO requests initiated by Liferay DXP fail. NTLM configuration can be applied either at the system scope or at the scope of a portal instance. To configure the NTLM SSO module at the system scope, navigate to the Control Panel, click on \emph{Configuration} → \emph{System Settings} → \emph{Security} → \emph{SSO} → NTLM. The values configured there provide the default values for all portal instances. Enter values in the same format as you would when initializing a Java primitive type with a literal value. Property Label \textbar{} Property Key \textbar{} Description \textbar{} Type \textbf{Enabled} \textbar{} \texttt{enabled} \textbar{} Check this box to enable NTLN SSO authentication. Note that NTLM will only work if Liferay DXP's authentication type is set to screen name. \textbar{} \texttt{boolean} \textbf{Domain Controller} \textbar{} \texttt{domainController} \textbar{} Enter the IP address of your domain controller. This is the server that contains the user accounts you want to use with Liferay DXP. \textbar{} \texttt{String} \textbf{Domain Controller Name} \textbar{} \texttt{domainControllerName} \textbar{} Specify the domain controller NetBIOS name. \textbar{} \texttt{String} \textbf{Domain} \textbar{} \texttt{domain} \textbar{} Enter the domain / workgroup name \textbar{} \texttt{String} \textbf{Service Account} \textbar{} \texttt{serviceAccount} \textbar{} You need to create a service account for NTLM. This account will be a computer account, not a user account. \textbar{} \texttt{String} \textbf{Service Password} \textbar{} \texttt{serviceAccount} \textbar{} Enter the password for the service account. \textbar{} \texttt{String} \textbf{Negotiate Flags} \textbar{} \texttt{negotiateFlags} \textbar{} Only available at system level. Set according to the client's requested capabilities and the server's ServerCapabilities. See \href{http://msdn.microsoft.com/en-us/library/cc717152\%28v=PROT.10\%29.aspx}{here} \textbar{} \texttt{String} Note the AD's name and IP address correspond to the \texttt{domainControllerName} and \texttt{domainController} settings. The \texttt{Service\ Account} is for the \emph{NTLM} account (registered with NTLM), not the Liferay DXP user account. To override system defaults for a particular portal instance, navigate to the Control Panel, click on \emph{Configuration} → \emph{Instance Settings}, click on \emph{Authentication} and then on \emph{NTLM}. \section{Summary}\label{summary-2} NTLM authentication is often highly desirable in Intranet scenarios where the IT department has control over what software is running on client devices and thus can ensure NTLM compatibility. In an Active Directory based network / domain, it is hard to beat the user experience that NTLM authentication can provide. Please remember that in order to use NTLM SSO, your Liferay DXP instance authentication type must be set to screen name \emph{and} that all users have been imported from your active directory. If this is not acceptable for your Liferay DXP implementation, then another SSO solution (such as CAS) can be used as a broker between your portal and the NTLM authentication process. \chapter{OpenID Single Sign On Authentication}\label{openid-single-sign-on-authentication} OpenID is a single sign-on standard implemented by multiple vendors. Users can register for an ID with the vendor they trust. The credential issued by that vendor can be used by all the web sites that support OpenID. Some high profile OpenID vendors are Google, Paypal, Amazon, and Microsoft. Please see the \href{http://www.openid.net/}{OpenID site} for a more complete list. \noindent\hrulefill \textbf{Note:} OpenID is deprecated in 7.0 and has been removed. You can still install it from Marketplace \href{https://web.liferay.com/marketplace/-/mp/application/125668346}{here} or \href{https://web.liferay.com/marketplace/-/mp/application/125668379}{here}. \noindent\hrulefill With OpenID, users don't have to register for a new account on every site which requires an account. Users register on \emph{one} site (the OpenID provider's site) and then use those credentials to authenticate to many web sites which support OpenID. Web site owners sometimes struggle to build communities because users are reluctant to register for \emph{another} account. Supporting OpenID removes that barrier, making it easier for site owners to build their communities. All the account information is kept with the OpenID provider, making it much easier to manage this information and keep it up to date. Liferay DXP can act as an OpenID consumer, allowing users to automatically register and sign in with their OpenID accounts. Internally, the product uses \href{https://github.com/jbufu/openid4java}{OpenID4Java} to implement the feature. \section{OpenID at the System Scope}\label{openid-at-the-system-scope} OpenID is enabled by default in Liferay DXP but can be disabled or enabled at either the system scope or portal instance scope. To configure the OpenID SSO module at the system level, navigate to the Control Panel and click on \emph{Configuration} → \emph{System Settings} → \emph{Security} → \emph{SSO}. There's only a single configuration setting. Check the \emph{Enabled} box to enable OpenID at the system scope (for all portal instances), uncheck it to disable it at the system scope. \section{OpenID at the Instance Scope}\label{openid-at-the-instance-scope} To configure the OpenID SSO module at the portal instance scope, navigate to the Control Panel and click on \emph{Configuration} → \emph{Instance Settings}, then on \emph{Authentication} → \emph{OpenID}. There's only a single configuration setting. Check the \emph{Enabled} box to enable OpenID for the current portal instance, or uncheck it to disable it for the current portal instance. Regardless of whether OpenID is enabled at the System or Instance scope, users can see the OpenID icon when they sign into Liferay DXP. Click \emph{Sign In}. The OpenID icon is displayed at the lower left. \chapter{Authenticating with Kerberos}\label{authenticating-with-kerberos} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can use Kerberos to authenticate Microsoft Windows ™ accounts with Liferay DXP. This is done completely through configuration by using a combination of Liferay DXP's LDAP support and a web server that supports the Kerberos protocol. Note that this configuration is preferred above \href{/docs/7-1/deploy/-/knowledge_base/d/ntlm-single-sign-on-authentication}{NTLM} because security vulnerabilities persist. While it's beyond the scope of this article to explain how to set up Kerberos and Active Directory on a Windows ™ server, we can describe the minimum prerequisites for setting up Liferay authentication: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A Windows ™ server with Active Directory and DNS set up so the AD server and Liferay DXP can resolve each other on the network. In other words, they must be able to ping each other \emph{by name}. \item An administrative user in AD Liferay DXP can use to bind to AD. \item A Kerberos keytab file exported via the \texttt{ktpass} command containing the cryptographic information the Liferay DXP server needs to bind to AD. \item A web server in front of Liferay DXP that supports Kerberos, such as Apache, NGNIX, or IIS. The web server must also support injecting a header to be used as a token in the Liferay DXP configuration (see below). \item Of course, you need a Liferay DXP installation that can also resolve by name the other servers. It should never run on the Active Directory server. \end{enumerate} When you have all of these prerequisites in place, you're ready to configure Kerberos authentication. \section{How Kerberos Authentication Works}\label{how-kerberos-authentication-works} From the prerequisites, you may be able to guess that there are several moving parts to how SSO works with Kerberos. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/kerberos.png}} \caption{Kerberos authentication requires a web server in front of your Liferay DXP server.} \end{figure} First, a properly configured web browser sends a negotiate request using encrypted Windows user data. To configure this, the browser must recognize the site as a trusted site (explained below). The web server's Kerberos module uses the keytab file to bind over the Kerberos protocol to AD and verify the user information. If all is okay, the AD server confirms the connection with a valid response. The web server you choose must support both the Kerberos protocol and the injection of a custom header into the request that Liferay DXP can later read. When the web server forwards the request to Liferay DXP, it reads the header to obtain the user data and authenticate the user. Next, you'll learn how to get all of this working. \section{Configuring Kerberos Authentication}\label{configuring-kerberos-authentication} There are four components to configure: a user keytab from Active Directory, a web server in front of your application server, Liferay DXP, and your Windows™ clients. \section{Creating the User Keytab}\label{creating-the-user-keytab} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a user so Liferay DXP can bind to Active Directory. \item Generate a Kerberos keytab file using \texttt{ktpass}: \begin{verbatim} ktpass -princ HTTP/[web server host name]@[domain] -mapuser [user name]@[domain] -crypto ALL -ptype KRB5_NT_PRINCIPAL -pass [password] -out c:\kerberos.keytab \end{verbatim} For example: \begin{verbatim} ktpass -princ HTTP/mywebserver.intdomain.local@INTDOMAIN.LOCAL -mapuser Marta@INTDOMAIN.LOCAL -crypto ALL -ptype KRB5_NT_PRINCIPAL -pass password-for-Marta -out c:\kerberos.keytab \end{verbatim} \item Ensure that the AD domain controller and the web server can see each other on the network via DNS configuration or \texttt{hosts} file. \end{enumerate} \section{Configuring Your Web Server}\label{configuring-your-web-server} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Configure Kerberos authentication. On Linux, this involves installing \texttt{krb5} and configuring it to match your realm that's already configured for Active Directory. The example domain for the user configured in step two above would look like this: \begin{verbatim} [libdefaults] default_realm = INTDOMAIN.LOCAL [domain_realm] mywebserver.intdomain.local = INTDOMAIN.LOCAL intdomain.local = INTDOMAIN.LOCAL .intdomain.local = INTDOMAIN.LOCAL [realms] INTDOMAIN.LOCAL = { admin_server = winserver.intdomain.local kdc = winserver.intdomain.local } \end{verbatim} \item Copy the keytab file you generated on your AD server to your web server. \item Configure your web server, making sure you set the correct server name, Kerberos service name, Kerberos authentication realms, and the path to the keytab file. For example, if you're using the Apache HTTP server, the configuration might look like this: \begin{verbatim} LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so LoadModule proxy_ajp_module /usr/lib/apache2/modules/mod_proxy_ajp.so LoadModule auth_kerb_module /usr/lib/apache2/modules/mod_auth_kerb.so Order deny,allow Allow from all ProxyRequests Off ProxyPreserveHost On ProxyPass / ajp://localhost:8009/ ProxyPassReverse / ajp://localhost:8009/ ServerName mywebserver.intdomain.local Order allow,deny Allow from all AuthType Kerberos KrbServiceName HTTP/mywebserver.intdomain.local@INTDOMAIN.LOCAL AuthName "Domain login" KrbAuthRealms INTDOMAIN.LOCAL Krb5KeyTab /etc/apache2/kerberos.keytab require valid-user KrbMethodNegotiate On KrbMethodK5Passwd Off #KrbLocalUserMapping On # Below directives put logon name of authenticated user into http header X-User-Global-ID RequestHeader unset X-User-Global-ID RewriteEngine On RewriteCond %{LA-U:REMOTE_USER} (.+) RewriteRule /.* - [E=RU:%1,L,NS] RequestHeader set X-User-Global-ID %{RU}e # Remove domain suffix to get the simple logon name # RequestHeader edit X-User-Global-ID "@INTDOMAIN.LOCAL$" "" Listen 10080 \end{verbatim} The last line is commented out based on user preference. If you want the domain removed from the user name when saved in Liferay DXP, uncomment it. Otherwise, leave it commented out to store the domain with the user name. \end{enumerate} \section{Connecting Liferay DXP to Active Directory over LDAP}\label{connecting-liferay-dxp-to-active-directory-over-ldap} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Finally, configure Liferay DXP to access Active Directory via the LDAP protocol. Change authentication to be by Screen Name by selecting it in Configuration → Instance Settings → Authentication → General. \item Connect Liferay DXP to AD over LDAP by going to Configuration → Instance Settings → Authentication → LDAP and adding an LDAP server. Provide the information appropriate to your installation: \textbf{Base Provider URL:} Your AD server on the proper port. \textbf{Base DN:} Your domain configuration. The example above might be \texttt{DC=INTDOMAIN.DC=LOCAL}. \textbf{Principal/Credentials:} Supply the credentials for the user exported to the keytab file. \textbf{Authentication Search Filter:} Supply the appropriate search filter to return user objects. For example, \texttt{(\&(objectCategory=person)(sAMAccountName=*))} \textbf{UUID:} Supply what uniquely identifies a user, such as \texttt{sAMAccountName}. \textbf{Screen Name:} Supply the field that should be mapped to Liferay DXP's screen name field, such as \texttt{sAMAccountName}. \textbf{Password:} Supply the field that contains the user's password, such as \texttt{userPassword}. \item Be sure to test the connection, save, and enable the configuration. \item Finally, configure the token for single sign-on at Configuration → System Settings → Security → SSO → Token Based SSO. Make sure the User Token Name matches \emph{exactly} the token you configured in your web server. Click the \emph{Enabled} and \emph{Import from LDAP} boxes and click \emph{Save}. \end{enumerate} Excellent! You've configured your servers. All that's left is to configure your clients. \section{Configuring your Clients}\label{configuring-your-clients} You must do two things: make your computer log into the domain and configure your Liferay DXP server as a trusted Internet site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Join your computer to your domain. In keeping with the example above, you'd make your computer a member of the \texttt{INTDOMAIN.LOCAL} domain. \item Log in as a user in that domain. \item Internet Explorer, Edge, and Chrome use the Windows™ settings for trusted sites. If you use these browsers, go to Internet Options → Security → Local Intranet Sites and add your Liferay DXP server's URL. For example, add \texttt{http://mywebserver.intdomain.local:10080}. \item Firefox can be configured by typing \texttt{about:config} in its address bar. Search for the below two preferences and add the Liferay DXP server's URL as the value for both: \begin{itemize} \tightlist \item \texttt{network.negotiate-auth.delegation-uris} \item \texttt{network.negotiate-auth.trusted-uris} \end{itemize} \end{enumerate} After configuring these things, test your configuration by accessing Liferay DXP through the web server's URL. Since you are already logged into your client machine, you should be automatically logged into Liferay DXP without a user/password prompt. Congratulations on configuring Kerberos with Liferay DXP! \chapter{Configuring CORS}\label{configuring-cors} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} CORS stands for Cross-Origin Resource Sharing. An Origin is a web server at a different domain, and a Resource is some asset stored on the server, like an image, PDF, or HTML file. Sometimes you must request resources stored on another origin. This is called a cross-origin request, and web servers have policies to allow or deny such requests. For example, browsers themselves don't allow cross-origin AJAX-style requests from scripts to help mitigate \href{https://en.wikipedia.org/wiki/Cross-site_scripting}{cross-site scripting} attacks. These APIs follow a \emph{same origin} policy. But for certain resources, it can be convenient to allow Liferay DXP to serve them to different origins. For example, if you manage images in Docs \& Media, you may want to allow cross-origin requests for them. You can enable CORS for matching URLs in Liferay DXP or for JAX-RS application resources. \section{Enabling CORS for Liferay DXP Services}\label{enabling-cors-for-liferay-dxp-services} You'll find the settings in Configuration → System Settings → Security → Security Tools → Portal Cross Resource Origin Sharing (CORS): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Add} to create a configuration entry. \item Fill out the fields on the form. When finished, click \emph{Save}. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/CORS-portal.png}} \caption{The CORS system settings provide a way to configure CORS headers for Liferay services.} \end{figure} \textbf{Enabled:} Check this box to enable the entry. \textbf{Name:} Give the configuration entry a name. \textbf{URL Pattern:} Use the Plus button to add as many patterns as you need. Define patterns that match URLs to the resources you want to share. For example, if you have many attachments in the Knowledge Base application, you could define this pattern: \begin{verbatim} /knowledge_base/* \end{verbatim} This would define resources stored in the Knowledge Base as applicable to the policy you set in the response header below. \textbf{CORS Response Headers:} Use the Plus button to add as many headers as you need. Define policies for any of the \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers\#CORS}{CORS headers} here. You can also use a \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{configuration file} to configure CORS. \section{Enabling CORS for JAX-RS Applications}\label{enabling-cors-for-jax-rs-applications} You'll find the settings in Configuration → System Settings → Security → Security Tools → Web Contexts Resource Origin Sharing (CORS): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Add} to create a configuration entry. \item Fill out the fields on the form. When finished, click \emph{Save}. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/CORS-jax-rs.png}} \caption{There's a separate system settings category for CORS web contexts.} \end{figure} \textbf{Dynamic Web Context OSGi Filter:} Define an LDAP-style \href{https://osgi.org/specification/osgi.cmpn/7.0.0/service.http.whiteboard.html}{filter} to define which JAX-RS whiteboard applications the CORS headers in this entry apply to. This is the default filter: \begin{verbatim} (&(!(liferay.cors=false))(osgi.jaxrs.name=*)) \end{verbatim} It applies CORS headers to all deployed JAX-RS whiteboard applications without a \texttt{liferay.cors=false} property. This helps during development, but in production you should use the narrowest configuration possible. \textbf{URL Pattern:} Use the Plus button to add as many patterns as you need. Define patterns that match URLs to the web services you want to access. \textbf{CORS Response Headers:} Use the Plus button to add as many headers as you need. Define policies for any of the \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers\#CORS}{CORS headers} here. \href{/docs/7-2/frameworks/-/knowledge_base/f/jax-rs}{JAX-RS} developers can use the \texttt{@CORS} annotation to set policies for their deployed applications. \chapter{AntiSamy}\label{antisamy} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP includes an \href{https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project}{AntiSamy} module that protects against user-entered malicious code. If your site allows users to post content, such as in message boards, blogs, or other applications, they could include malicious code either intentionally or unintentionally. The AntiSamy module filters HTML/CSS fragments and removes suspect JavaScript code from them. The module leverages the powerful \href{https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project}{OWASP AntiSamy library} to enforce a content policy that's been effective for the auction site eBay. The AntiSamy module adds an OWASP AntiSamy implementation to your portal's list of existing sanitizer implementations. Liferay DXP uses the AntiSamy sanitizer and any existing configured sanitizers to scrub user input to blogs entries, calendar events, message boards posts, wiki pages, and web content articles. AntiSamy is enabled by default. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/antisamy.png}} \caption{Liferay DXP's AntiSamy configuration options allow you to specify both a blacklist and a whitelist.} \end{figure} \section{Configuring AntiSamy}\label{configuring-antisamy} AntiSamy uses both a blacklist and a whitelist, so you can define subsets of entities that should be sanitized or not sanitized. The whitelist prevents content of that type from being filtered, while the blacklist filters content of that type. By default, everything is sanitized except for \texttt{JournalArticle}, \texttt{BlogsEntry}, and \texttt{FragmentEntry}. The assumption is that users posting these kinds of content are trusted, while users posting message boards or wiki articles may not be trusted. If this is not the configuration you want, you can change it: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Control Panel} → \emph{System Settings} → \emph{Security Tools} → \emph{AntiSamy Sanitizer}. \item Enter a package path you want to sanitize into the \emph{Blacklist} field. \item Use the plus (+) button to add further Blacklist fields if you need them. \item Use the plus (+) button to add further Whitelist fields if you need them. \item Enter a package path you don't want sanitized into a \emph{Whitelist} field. \item If you want to remove a package path from the configuration, click the trash can icon. \item When finished, click \emph{Save}. \end{enumerate} \section{Using Wildcards}\label{using-wildcards} You can use wildcards in the configuration. For example, if you only want to sanitize message board posts and nothing else, you can \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Configure the whitelist to \texttt{*} \item Configure the blacklist to \texttt{com.liferay.message.boards.*} \end{enumerate} The whitelist and the blacklist work together. Without the blacklist, the above configuration's whitelist must include every content type except \texttt{com.liferay.message.boards}, which would be a daunting task to configure. Use AntiSamy to ensure user-generated content stays safe for other users to view. \chapter{OAuth 2.0}\label{oauth-2.0} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} OAuth 2.0 is an industry-standard authorization protocol. Users can seamlessly share select credentials from your Liferay-based website with various clients. You've seen this before: any time you see a ``This site wants to access:'' button (followed by a list of things like email address, friends list, etc.) from Google or Facebook, or you authorize a third-party Twitter client, that's OAuth 2.0 in action. It works by authorizing password-less access to portions of user-owned resources (such as an email address, a user profile picture, or something else from your account) and other permissioned resources. OAuth 2.0's design encrypts all authorization transport through HTTPS, which prevents data passed between the systems from being intercepted. \section{Flow of OAuth 2.0}\label{flow-of-oauth-2.0} OAuth 2.0 takes advantage of web standards wherever possible: transport is encrypted with HTTPS; tokens are implemented as HTTP headers; data is passed via web services. Here's how OAuth 2.0 works: \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-flow.png}} \caption{OAuth 2.0 takes advantage of web standards.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A user accesses a third-party application that supports authorization via credentials from a Liferay-based website. In the application (web or mobile), the user requests authorization via OAuth, sending the browser or app to the Liferay-based website. When using PKCE (explained below), the application also generates a code verifier and sends a code challenge that is created by applying a transformation to it. \item The user authenticates and is shown the resources the application wants permission to access. When the user gives permission by clicking \emph{Allow}, Liferay generates an authorization code that's sent to the application over HTTPS. \item The application then requests a more permanent authorization token and sends the code with the request (along with the PKCE code verifier). \item If the authorization code matches (and the transformed PKCE code verifier matches the previously sent code challenge), Liferay cryptographically generates an authorization token for this user and application combination. It sends the token to the application over HTTPS. Initial authorization is now complete! \item When the application must retrieve data, it sends the token with the request to prove it's authorized to have that data. \item Provided the token matches what Liferay has for that user and application, access is granted to retrieve the data. \end{enumerate} That description throws around a lot of terms. Definitions provided below. \section{OAuth 2.0 Terminology}\label{oauth-2.0-terminology} \textbf{Authentication:} Providing credentials so a system can verify who you are by matching those credentials with what it has stored. OAuth is not an authentication protocol. \textbf{Authorization:} Granting access to resources stored on another system. OAuth is an authorization protocol. \textbf{Application:} Any client (web, mobile, etc.) that must be authorized to have access to resources. Applications must be registered by administrators before users can authorize access to their resources. \textbf{Client:} Almost synonymous with \emph{application}, except that applications can have variants, such as web and mobile. These variants are clients. \textbf{Client ID:} An identifier given to a client so it can be recognized. \textbf{Client Secret:} A previously agreed-upon text string that identifies a client as a legitimate client. \textbf{Access Token:} A cryptographically generated text string that identifies a user/client combination for access to that User's resources. \textbf{Response Type:} OAuth 2.0 supports several response types. Pictured and described above is the most common: the authorization code. Other response types are \emph{password} (logging in with a user name and password), and \emph{client credentials} (headless predefined application access). \textbf{Scope:} A list of items that define what the application wants to access. This list is sent during the initial authorization request (or otherwise defaults to scopes selected in the application registration) so users can grant or deny access to their resources. \textbf{Callback URI:} Also called a Redirection Endpoint URI. After authorization is complete, the authorization server (i.e., Liferay) sends the client to this location. \section{Creating an Application}\label{creating-an-application} When you have an application that can use OAuth 2.0 for authorization, you must register that application so Liferay DXP can recognize it. Do this by accessing \emph{Control Panel} → \emph{Configuration} → \emph{OAuth2 Administration}: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Add} (\pandocbounded{\includegraphics[keepaspectratio]{./images/icon-add.png}}) button. \item Fill out the form (description below). \item Click \emph{Save} to save the application. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-new-application.png}} \caption{Adding an application registers it so users can authorize access to their data.} \end{figure} \textbf{Name:} Give the application a recognizable title. \textbf{Website URL:} Add a link to the application's website. \textbf{Callback URIs:} Enter at least one (line-separated) URI where users should be redirected after they authorize (or refuse to authorize) access to their accounts. This should link to a handler for whichever Allowed Authorization Types you support (see below). \textbf{Client Profile:} Choose a template that filters the authorization types that are appropriate (secure) for that profile. For example, if your application is a web application, choose \emph{Web Application}, and these authorization types are available and selected automatically: Authorization Code, Client Credentials, Refresh Token, and Resource Owner Password Credentials. These are OAuth 2 ``flows'' documented in the \href{https://tools.ietf.org/html/rfc6749}{OAuth2 RFC 6749 Standards Document}. If you want to select authorization types manually, select \emph{Other}. \textbf{Allowed Authorization Types:} Select the defined OAuth 2 \href{https://tools.ietf.org/html/rfc6749\#section-1.2}{protocol flows} your application supports. Several common combinations are defined for you in the various Client Profiles above. After you save the form, it reappears with additional fields: \textbf{Client ID:} The system generates this for you; it's an identifier for your application, so that Liferay DXP knows what application is being authorized to access user data. \textbf{Client Secret:} Click the \emph{pencil} icon to generate a client secret. The secret identifies the client during the authorization process (see figure 1 above). Not all client profiles require a client secret, because some are incapable of keeping it secret! This is when the aforementioned PKCE code challenge and verifier is needed. \textbf{Icon:} Upload an icon that your application's users identify with your application. This is displayed on the authorization screen. \textbf{Privacy Policy URL:} Add a link to your application's privacy policy. \textbf{Token Introspection:} Allow your application to retrieve metadata from the token by requesting it from Liferay DXP. This implements \href{https://tools.ietf.org/html/rfc7662}{RFC 7662}. Excellent! Now you know how to add OAuth2 authorization for your application to Liferay DXP! Next, you must define scopes of user data the application can access. \chapter{OAuth2 Scopes}\label{oauth2-scopes} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} In OAuth 2.0, applications are granted access to limited subsets of user data. These are called \emph{scopes} (not to be confused with Liferay scopes). They are created in two ways: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item By administrators, by creating a Service Access Policy for the scope \item By developers, by creating a JAX-RS endpoint. By default, scopes are generated based on the HTTP verbs supported by the JAX-RS endpoint. A special annotation overrides this behavior and registers specific scopes. \end{enumerate} \section{Creating a Scope for a JSONWS Service}\label{creating-a-scope-for-a-jsonws-service} The most common way to create a scope is to create a \href{/docs/7-2/deploy/-/knowledge_base/d/service-access-policies}{Service Access Policy} prefixed with the name \texttt{OAUTH2\_}. This naming convention causes the policy to appear in the OAuth application configuration screen as a scope. For example, say the application needs access to a user's profile information to retrieve the email address. To grant the application access to this, go to \emph{Control Panel} → \emph{Configuration} → \emph{Service Access Policy}, and create the policy pictured below. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-service-access-policy.png}} \caption{A Service Access Policy defines a scope for OAuth 2.0 applications.} \end{figure} Note that the policy is not a default policy, and that it grants access only to one method in the \texttt{UserService}. This is a JSONWS web service generated by Service Builder. You can view a list of all available services in your installation at this URL: \begin{verbatim} http://[host]:[port]/api/jsonws/ \end{verbatim} Once you create a policy and name it with the \texttt{OAUTH2\_} prefix, it appears in the \emph{Scopes} tab in OAuth2 Administration. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-scopes-tab.png}} \caption{Scopes named with the proper prefix appear in the Scopes tab of your application configuration.} \end{figure} Now you can select it and save your application. \section{Creating the Authorization Page}\label{creating-the-authorization-page} This step is optional. Users need an interface to authorize access to their accounts, and one is provided automatically. If, however, you want to customize the page, you can create an authorization page in your Site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{System Settings} → \emph{Security} → \emph{OAuth2}. Click the bottom item on the left, labeled \emph{Authorize Screen}. \item Two defaults appear. The first is the URL to the authorize page. By default, it's \texttt{/group/guest/authorize-oauth2-application}. This corresponds to the default site's URL and a page on that site called \texttt{authorize-oauth2-application}. \item If you have customized the name and URL of your default site, make the appropriate change here so the URL matches the page you'll create in that site next. Click \emph{Save}. \item Go to your Site's \emph{Build} → \emph{Pages} screen. Click the \pandocbounded{\includegraphics[keepaspectratio]{./images/icon-add.png}} button and choose \emph{Private Page}. This forces users to log in. \item Choose the \emph{Full Page Application} type. \item Give the page the same name you configured in step 2. \item Uncheck the box labeled \emph{Add this Page to the following Menus:}. You don't want this page showing up in your Site navigation. \item On the page that appears next, verify the Friendly URL matches the URL you configured in step 2. \item Under \emph{Full Page Application}, choose \emph{OAuth2 Authorize Portlet}. \item Click \emph{Save}. \end{enumerate} Excellent! Users can use the default or the UI of your design to go through the authorization process. Now that you have the UI and you understand scopes, it's time to make the authorization process happen in your application. \chapter{Authorizing Account Access with OAuth2}\label{authorizing-account-access-with-oauth2} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Once you have an application registered, you can start authorizing users. To do that, you must construct the URL to the authorization server (Liferay DXP). The authorization server asks users to authorize the requested permissions to their resources, defined as you saw in the previous tutorial as scopes. \section{Authorization Code Flow}\label{authorization-code-flow} The most common OAuth flow is the Authorization Code flow, used for web applications. The URL for this requires the following request parameters: \begin{itemize} \tightlist \item \texttt{response\_type} \item \texttt{client\_id} \end{itemize} To construct a URL for this authorization, follow this pattern: \begin{verbatim} https://[hostname]/o/oauth2/authorize?response_type=code&client_id=[client ID] \end{verbatim} The client ID comes from registering the application. It's automatically generated (you can change it if you edit the application). IMPORTANT: Sometimes the phrase ``web application'' is used loosely, implying applications where the above URL is requested from the web browser directly. If this happens, you'd leak the client secret, compromising the security of the grant flow and the application. In such cases, select the ``User Agent Application'' client profile instead when registering your application. This makes a secure alternative available to your application: PKCE Extended Authorization Code flow (see below). Once the user has authorized the requested permissions to their resources, the authorization server returns an authorization code to your application at its registered callback URI (A.K.A. redirect URI) as a query string parameter. \begin{verbatim} [your callback URI]?code=[authorization server generated code] \end{verbatim} Your application must then exchange this authorization code for an access token by sending a POST request following this pattern: \begin{verbatim} http://localhost:8080/o/oauth2/token \end{verbatim} With the following parameters in the body (encoded as \texttt{application/x-www-form-urlencoded}): \begin{verbatim} client_id=[client ID] client_secret=[client secret] grant_type=authorization_code code=[authorization server generated code] redirect_uri=[registered callback URI] \end{verbatim} In the body of HTTP response to this request, you receive JSON like this: \begin{verbatim} { "access_token": "[authorization server generated access token]", "token_type": "Bearer", "expires_in": 600, "scope": "[the scopes that were authorized by the user]", "refresh_token": "[authorization server generated refresh token]" } \end{verbatim} From this you should extract and persist the access token. If you intend to use the token for an indefinite amount of time (beyond 600 seconds from the above example) you also need the refresh token. This can be used in conjunction with the Refresh Token Flow to obtain a new access token with the same permissions, without further user authorization. The authorization server only issues Refresh Tokens if your application registration is registered for this flow. \section{PKCE Extended Authorization Code Flow}\label{pkce-extended-authorization-code-flow} This flow is the same as above with the addition of the Proof Key for Code Exchange (PKCE). It requires another request parameter: \texttt{code\_challenge}. This flow is for clients like smartphone applications that may not have sole access to the URL (and thus the request parameters) redirected to by the authorization server after the user authorization. It protects against a malicious application on the same system authorizing itself by reading the response code. To do this, the client application sends a \emph{code challenge} with the authorization request: a string it has generated and which it only knows. To generate this string it must first create another secret string known as the \emph{Code Verifier}, and then apply a transformation to it. After authorization, the code verifier is sent with the authorization code, validating the client. For more detail on how to do this, please refer to the \href{https://tools.ietf.org/html/rfc7636}{PKCE specification}. To support this flow, you must have defined PKCE as an Allowed Authorization Type when you created the application. This is part of the Native Application and User Agent Application client profiles. To request an authorization code using PKCE, use a URL containing the \texttt{code\_challenge} request parameter: \begin{verbatim} https://[hostname]/o/oauth2/authorize?response_type=code&client_id=[client ID]&code_challenge=[PKCE code challenge] \end{verbatim} The rest of the process is identical to Authorization Code flow, except that when making the final request to get the access token, you must also provide the following parameter: \begin{verbatim} code_verifier=[Code Verifier that was transformed and sent as code_challenge previously] \end{verbatim} \section{Client Credentials and Resource Owner Flows}\label{client-credentials-and-resource-owner-flows} There are two other, less used flows. If you have a scenario where two servers exchange agreed upon, non user-centric data, you can bypass the Allow/Deny screen for users and authorize the client. This is called the Client Credentials flow, and you'd use this URL pattern: \begin{verbatim} https://[hostname]/o/oauth2/token?grant_type=client_credentials&client_id=[client ID]&client_secret=[client secret] \end{verbatim} A final flow, where users trust the application with their passwords is rare, but possible. This is called the Resource Owner Password flow, and its URL pattern looks like this: \begin{verbatim} https://[hostname]/o/oauth2/token?grant_type=password&client_id=[client ID]&client_secret=[client secret]&username=[user@emailaddress.com]&password= \end{verbatim} Users are prompted for their passwords, and upon successful log in, receive an authorization code. \section{Token Use}\label{token-use} All flows above result in an access token that's sent by the authorization server (Liferay DXP) to the client application. This token is sent in the response for the client application to store and send along with any future request for data. For example, say the authorization code \texttt{946856e2b5ddf0928f6fc55f657bab73} was sent to the client application. When the client requests data, this code must be sent in each request header. Using a command line HTTP client such as Curl, you could send a request like this: \begin{verbatim} curl -H 'Authorization: Bearer 946856e2b5ddf0928f6fc55f657bab73' 'https://[hostname]/o/api/sample2' \end{verbatim} OAuth 2.0 provides a convenient way for client applications to be granted access to particular services (scopes) by users without sharing credential information. \section{Revoking Access}\label{revoking-access} Once access is granted, users or administrators are free to revoke access whenever they wish. If this happens to a client, the token becomes invalid and the client must ask the user for authorization again. This puts users in control of what has access to their data, and they can exercise this control at any time. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-user-apps.png}} \caption{Users have complete control over what applications have access to their data in their account profiles.} \end{figure} In their account areas, users can click \emph{OAuth2 Connected Applications} and see a list of applications they've allowed to access their accounts. From here, they can revoke access by clicking the \emph{Remove Access} item in the Action menu or the \emph{Remove Access} button in the detail screen for the application. Administrators can view the authorizations in the Authorizations tab of any app in \emph{Control Panel} → \emph{Configuration} → \emph{OAuth2 Administration}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/oauth-revoke-access.png}} \caption{All authorizations for an app appear in the Authorizations tab for the app.} \end{figure} Clicking the \emph{Revoke} button on any listed authorization revokes that application's access to that user's account. \section{Summary}\label{summary-3} OAuth 2.0 provides a complete and secure authorization flow for users, without their having to share any credential information. Once applications are created in the system, secure tokens provide access to particular scopes of information, and this access can be revoked at any time, making OAuth 2.0 a convenient method for users and developers alike to access the information they need. \chapter{Upgrading to 7.0}\label{upgrading-to-7.0} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Upgrading to 7.0 involves migrating your installation and \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{code (your theme and custom apps)} to the new version. Here you'll learn how to upgrade your installation. Here are the installation upgrade paths: \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.6389}} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.3611}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright Upgrade Path \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Description \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Liferay Portal 5.x and 6.0.x → Liferay Portal 6.2 → Liferay DXP 7.2 & Support life ended for Liferay Portal 5.0, 5.1, 5.2, and 6.0 \\ Liferay Portal 6.1.x → Liferay DXP 7.1 → @product@ 7.2 & Support life ended for Liferay Portal 6.1 \\ Liferay Portal 6.2+ → Liferay DXP 7.2 & \\ Liferay DXP 7.0+ → @product@ 7.2 & \\ \end{longtable} \noindent\hrulefill \noindent\hrulefill \textbf{Note:} Themes and custom apps from Liferay Portal 6.0 through Liferay DXP 7.1 can be upgraded directly to Liferay DXP 7.2. See the \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{code upgrade instructions} for details. \noindent\hrulefill Here are the upgrade steps: \noindent\hrulefill \textbf{Note:} You can \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database}{prepare a new Liferay server for data upgrade} in parallel with the steps up to and including the step to \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{upgrading the Liferay DXP data}. \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \{.root\}\href{/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay}{If You're Upgrading to Liferay Portal 6.2, Follow the Liferay Portal 6.2 Upgrade Instructions First} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-a-sharded-environment}{If You're Upgrading a Sharded Environment, Follow the Instructions for Upgrading It} Upgrading a sharded installation to 7.0 requires migrating it to as many non-sharded Liferay DXP installations (servers) as you have shards.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/updating-a-cluster}{If You're a Upgrading a Cluster, Read Those Instructions First} If you're updating a cluster, read those instructions first and apply them to your upgrade.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/planning-for-deprecated-applications}{Plan for Handling the Deprecated Applications} Every application deprecation has different ramifications. Learn how the deprecations might affect your site and decide how to replace the functionality you use from those applications.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy}{Test Upgrading a Liferay DXP Backup Copy} Here you'll prune a backup copy of your database and upgrade the data. You'll learn how to use the upgrade tool and resolve upgrade issues. The notes and scripts you assemble as you prune and upgrade the database copy are invaluable for correctly and efficiently upgrading the Liferay DXP database you'll use with 7.0.\{.summary\} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy\#copy-the-production-installation-to-a-test-server}{Copy the Production Installation to a Test Server} You'll use the installation copy to test data changes.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy\#copy-the-production-backup-to-the-test-database}{Copy the Production Database Backup} Copy the production backup to the test database and save the copy logs for analysis.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database}{Remove Duplicate Web Content and Structure Field Names} \item \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database}{Find and Remove Unused Objects} You may have intermediate versions of objects (e.g., \texttt{JournalArticle} objects) that you don't need. Remove them and objects that only reference them.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database\#test-with-the-pruned-database-copy}{Test Liferay DXP with its Pruned Database Copy} Make sure Liferay DXP continues to work successfully. If it's broken, start over with a fresh database backup and prune it more carefully.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-your-test-server-and-database}{Install the New Liferay DXP Version on a Test Server} Install the Liferay DXP version you're upgrading to, to use its upgrade tool.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade}{Tune Your Database for the Upgrade} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{Upgrade the Liferay Data, then Return Here} \item \href{/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy\#copy-the-production-backup-to-the-test-database}{If the Upgrade Took too Long, Prune a Fresh Database Backup More and Upgrade Its Data} \item \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database}{Test the Upgraded Instance} Make sure Liferay DXP continues to work successfully. If it's broken, start over with a fresh database backup and prune it more carefully.\{.summary\} \item Checkpoint: You've pruned and upgraded your production database copy. You're ready to prepare for upgrading the production database. \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database}{Prepare to Upgrade the Liferay DXP Database} Preparing for the production database upgrade involves pruning and testing it, upgrading your Marketplace apps, publishing staged changes, and synchronizing a complete data and configuration backup.\{.summary\} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#remove-all-unused-objects-you-identified-earlier}{Remove All Noted Unused Objects} Remove all unused objects you noted from pruning your test database.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#test-using-the-pruned-database}{Test Liferay DXP} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#upgrade-your-marketplace-apps}{Upgrade Your Marketplace Apps} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#publish-all-staged-changes-to-production}{Publish All Staged Changes} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#synchronize-a-complete-backup}{Synchronize a Complete Liferay DXP Backup} Synchronize a complete backup of your production Liferay DXP server installation and pruned production database.\{.summary\} \item Checkpoint: You're ready to prepare a 7.0 server for upgrading a production database. \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade}{Prepare a New Liferay DXP Server for Data Upgrade} Set up a production server with 7.0, configured to use your document repository and Liferay DXP database. You'll migrate your portal and system properties too. (Note, this step can be done in parallel with any of the previous steps.)\{.summary\} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#synchronize-a-complete-backup}{Request an Upgrade Patch From Liferay Support (Liferay DXP Only)} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#install-liferay}{Install the Liferay DXP Version You're Upgrading To} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#install-the-latest-upgrade-patch-or-fix-pack-liferay-dxp-only}{Install the Latest Upgrade Patch or Fix Pack (Liferay DXP Only)} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#migrate-your-osgi-configurations-70}{Migrate Your OSGi Configurations (Liferay DXP 7.0+)} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#migrate-your-portal-properties}{Migrate Your Portal Properties} Migrate your portal properties to your new 7.0 server.\{.summary\} \begin{enumerate} \def\labelenumiii{\arabic{enumiii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#update-your-portal-properties}{Update Your Portal Properties} Some of the portal properties have new values or have been removed or replaced. Update your properties for 7.0.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#convert-applicable-properties-to-osgi-configurations}{Convert Applicable Properties to OSGi Configurations} Many applications are configured using OSGi Configuration (Config Admin) instead of portal properties. Convert your existing properties to their OSGi Configuration replacements.\{.summary\} \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#configure-your-documents-and-media-file-store}{Configure Your Documents and Media File Store} The upgrade tool upgrades your Documents and Media file store too. Update your Documents and Media file store configuration and specify it for the upgrade tool.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade\#disable-indexing}{Disable Indexing} Improve the data upgrade performance by disabling indexing.\{.summary\} \item Checkpoint: You've prepared a new Liferay DXP server for executing the data upgrade \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{Upgrade the Liferay DXP data} This section explains the data upgrade options, upgrade configuration, and the upgrade process.\{.summary\} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade}{Tune Your Database for the Upgrade} \item \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade}{Configure the Data Upgrade} Configure the data upgrade, including the data store and whether to automatically upgrade the modules.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool}{Upgrade the Core} \begin{enumerate} \def\labelenumiii{\arabic{enumiii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool\#upgrade-tool-usage}{Run the Data Upgrade Tool} Run the data upgrade tool. Resolve any core upgrade issues.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database\#synchronize-a-complete-backup}{Issues Upgrading to 7.0 or Lower? Restore the Database Backup} If the issues were with upgrades to Liferay 7.0 or lower, get a clean start by restoring the pruned production database backup.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool}{Upgrade Your Resolved Issues} If there were issues upgrading to 7.2, resolve them and restart the data upgrade tool; continue if there were no issues.\{.summary\} \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell}{Upgrade the Liferay Modules} Learn how to use Gogo Shell to upgrade the Liferay modules, if you didn't upgrade them automatically with the core.\{.summary\} \begin{enumerate} \def\labelenumiii{\arabic{enumiii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell\#command-usage}{Upgrade Modules that are Ready for Upgrade} Discover which modules are ready for upgrade and upgrade them.\{.summary\} \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell\#checking-upgrade-status}{Check Module Upgrade Status and Resolve Any Module Upgrade Issues} \item Checkpoint: You've completed upgrading the Liferay data. It's time to get your server ready for production.\{.summary\} \end{enumerate} \end{enumerate} \item \href{/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks}{Execute the Post-Upgrade Tasks} Now that your database is upgraded, clean up remnants of upgrading by restoring your database optimizations, enabling and regenerating your search indexes, and more.\{.summary\} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \item \href{/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks\#tuning-your-database-for-production}{Remove the Database Tuning} \item \href{/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks\#re-enabling-search-indexing-and-re-indexing-search-indexes}{Re-enable and Re-Index the Search Indexes} \item \href{/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks\#enabling-web-content-view-permissions}{Update Web Content Permissions (7.0 and lower)} \item \href{/docs/7-2/deploy/-/knowledge_base/d/planning-for-deprecated-applications}{Address Any Deprecated Apps That Still Need Handling} \end{enumerate} \item Checkpoint: You've completed the upgrade and post-upgrade tasks \end{enumerate} Follow the steps above to upgrade your Liferay DXP installation to 7.0. They link upgrade topic details to help complete a safe, successful upgrade. \chapter{Planning for Deprecated Applications}\label{planning-for-deprecated-applications} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} As Liferay DXP evolves so do Liferay applications. They may, for example, be deprecated in favor of newer and better Liferay applications. The deprecations might call for migrating application data to a new application or completely removing the application and data. Before upgrading, examine the deprecations: \begin{itemize} \item \href{/docs/7-2/deploy/-/knowledge_base/d/deprecated-apps-in-7-2-what-to-do}{7.2 deprecations} \item \href{/docs/7-1/deploy/-/knowledge_base/d/deprecated-apps-in-7-1-what-to-do}{7.1 deprecations} \end{itemize} Determine how and when to address the deprecations in your upgrade plan. \chapter{Test Upgrading a Liferay DXP Backup Copy}\label{test-upgrading-a-liferay-dxp-backup-copy} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Before upgrading your production Liferay instance, you should do a trial run (even multiple runs) to make sure that you upgrade successfully and efficiently. Here's the process: \begin{itemize} \item \hyperref[preparing-a-test-server-and-database]{Preparing a test server and database}: This involves copying your current production installation to a test server and copying your production data backup to a test database. After you prune data from the test database (next step) you'll test against it. \item \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database}{Pruning the database}: Free your database of duplicate and unused objects. By removing them you can reduce upgrade time and improve your server's performance. \item \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-your-test-server-and-database}{Upgrading your test server and database}: First you'll optimize your database for the data upgrade. Taking time to do this can save upgrade time. Then you'll do an upgrade test run (or several test runs) on a the pruned database copy. After going through the upgrade process, resolving any issues, and testing the upgraded server successfully, you can confidently upgrade your production database. \end{itemize} \noindent\hrulefill \textbf{Tip:} These steps and \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade}{preparing a new Liferay DXP server} can be done in parallel to save time. \noindent\hrulefill Now prepare your test environment. \section{Preparing a Test Server and Database}\label{preparing-a-test-server-and-database} Using a new separate server and database let's you safely test upgrading. \section{Copy the Production Installation to a Test Server}\label{copy-the-production-installation-to-a-test-server} Prepare a test server to use a copy of your production installation. Your test server must use the same Liferay version you're using on production. Configure your server to use a new empty database for testing data upgrades. \section{Copy the Production Backup to the Test Database}\label{copy-the-production-backup-to-the-test-database} Import data from your \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{production database backup} to the new empty database. \noindent\hrulefill \textbf{Important:} Make sure to save the data import log---you'll examine it in the next steps. \noindent\hrulefill Next you'll prune your database of unneeded data. \chapter{Pruning the Database}\label{pruning-the-database} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Accumulating unneeded site data is common. For example, you may have many unused versions of Web Content articles or Documents and Media files. If you're done revising them and don't need the intermediate revisions, you can remove them. This saves you space and upgrade time. Here you'll remove unneeded data and then test your server. \section{Remove Duplicate Web Content Structure Field Names}\label{remove-duplicate-web-content-structure-field-names} If you've used Web Content Management extensively, you might have structures without unique field names. Find and remove duplicate field names before upgrading. If you upgraded to Liferay Portal 6.2 previously and skipped doing this, you'll encounter this error: \begin{verbatim} 19:29:35,298 ERROR [main][VerifyProcessTrackerOSGiCommands:221] com.liferay.portal.verify.VerifyException: com.liferay.dynamic.data.mapping.validator.DDMFormValidationException$MustNotDuplicateFieldName: The field name page cannot be defined more than once com.liferay.portal.verify.VerifyException: com.liferay.dynamic.data.mapping.validator.DDMFormValidationException$MustNotDuplicateFieldName: The field name page cannot be defined more than once \end{verbatim} If this is the case, roll back to your previous backup of Liferay Portal 6.2 and find and remove duplicate field names. \section{Find and Remove Unused Objects}\label{find-and-remove-unused-objects} In the UI or using database queries, identify unused objects. Then remove them via Liferay's UI or using Liferay's API through the \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{script console} or a portlet you create. \noindent\hrulefill \textbf{Important}: You should only use Liferay's UI or API because they account for relationships between Liferay DXP objects. Never use SQL directly on your database to remove records. Your SQL might miss object relationships, orphaning objects and causing performance problems. \noindent\hrulefill Here are some common places to check for unused objects. \section{Objects From the Large/Populated Tables}\label{objects-from-the-largepopulated-tables} Table rows are mapped to Liferay DXP objects. Large tables with many records might contain lots of unused objects. The greater the table size and the records per table, the longer upgrading takes. Finding and removing unused objects associated with such tables reduces upgrade times. Your data import log (from the previous step) can provide valuable table information. Database engines show this information in different ways. Your database import log might look like this: \begin{verbatim} Processing object type SCHEMA\_EXPORT/TABLE/TABLE\_DATA imported "LIFERAY"."JOURNALARTICLE" 13.33 GB 126687 rows imported "LIFERAY"."RESOURCEPERMISSION" 160.9 MB 1907698 rows imported "LIFERAY"."PORTLETPREFERENCES" 78.13 MB 432285 rows imported "LIFERAY"."LAYOUT" 52.05 MB 124507 rows imported "LIFERAY"."ASSETENTRY" 29.11 MB 198809 rows imported "LIFERAY"."MBMESSAGE" 24.80 MB 126185 rows imported "LIFERAY"."PORTALPREFERENCES" 4.091 MB 62202 rows imported "LIFERAY"."USER\_" 17.32 MB 62214 rows ... \end{verbatim} Several items stand out in the example database import: \begin{itemize} \tightlist \item The \texttt{JOURNALARTICLE} table makes up 98\% of the database size. \item There are many \texttt{RESOURCEPERMISSION} records. \item There are many \texttt{PORTLETPREFERENCES} records. \end{itemize} Search for unused objects associated with the tables that stand out and use Liferay's API (e.g., the UI or \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{script console}) to delete the objects. \section{Common Object Types Worth Checking}\label{common-object-types-worth-checking} Some object types should be checked for unused objects. Here are some reasons for checking them: \begin{itemize} \tightlist \item Removing them frees related unused objects for removal \item They're version objects that aren't worth keeping \end{itemize} Check these object types: \begin{itemize} \item \textbf{Sites}: Remove sites you don't need. When you remove a site, remove its related objects: \begin{itemize} \item Layouts \item Portlet preferences \item File entries (document library objects) \item Asset Entries \item Tags \item Vocabularies and categories \item Expando fields and their values \item \texttt{ResourcePermission} objects \item (and everything else) \end{itemize} \item \textbf{Instances}: Unused instances are rare, but since they are the highest object in the hierarchy, removing their objects can optimize upgrades considerably: \begin{itemize} \item Sites (and all their related content) \item Users \item Roles \item Organizations \item Global \texttt{ResourcePermission} objects \end{itemize} \item \textbf{Intermediate web content versions:} Liferay DXP generates a new web content version after any modification (including translations). Consider removing versions you don't need. Removing a Journal Article, for example, also removes related objects such as image files (\texttt{JournalArticleImage}) that are part of the content. Removing unneeded image files frees space in your database and file system. For more details, see \href{/docs/7-2/deploy/-/knowledge_base/d/example-removing-intermediate-journal-article-versions}{Example: Removing Intermediate Journal Article Versions}. \item \textbf{Document versions}: As with Journal Articles, if you don't need intermediate document versions, delete them. This saves space both in the database and on the file system, space that no longer needs to be upgraded. \item \textbf{Layouts:} Layouts are site pages, and they affect upgrade performance because they relate to other entities such as portlet preferences, permissions, assets, ratings, and more. Remove unneeded layouts. \item \textbf{Roles}: Remove any Roles you don't need. Deleting them also deletes related \texttt{ResourceBlockPermission} and \texttt{ResourcePermission} objects. \item \textbf{Users:} If you have Users that aren't active anymore, remove them. \item \textbf{Vocabularies}: Remove any unused vocabularies. Note that removing a vocabulary also removes its categories. \item \textbf{Orphaned data}: Check for unused objects that are not connected to anything. Here are some examples: \begin{itemize} \item \texttt{DLFileEntries} with no file system data. \item \texttt{ResourcePermission} objects associated to a Role, Layout, User, portlet instance, etc. that no longer exists. \item \texttt{PortletPreference} objects associated with a portlet or layout that no longer exists. This is common in environments with many embedded portlets. These portlet instances have a different lifecycle and aren't deleted when the portlet is removed from a template. \end{itemize} \end{itemize} If you want to see an example of removing intermediate object versions, read \href{/docs/7-2/deploy/-/knowledge_base/d/example-removing-intermediate-journal-article-versions}{Example: Removing Intermediate Journal Article Versions} and then return here. Next, you'll test Liferay DXP with its pruned database. \section{Test with the Pruned Database Copy}\label{test-with-the-pruned-database-copy} Find and resolve any issues related to the objects you removed. You can always restart pruning a new copy of your production database if you can't resolve an issue. \noindent\hrulefill \textbf{Warning:} the upgrade to Liferay DXP 7.2 moves Web Content images to the Document Library and then deletes their former table \texttt{JournalArticleImage}. Make sure the images show in the upgraded Web Content articles. \noindent\hrulefill Once you've successfully tested Liferay DXP with its pruned database copy, you can upgrade the database to 7.0. \chapter{Example: Removing Intermediate Journal Article Versions}\label{example-removing-intermediate-journal-article-versions} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} These instructions and code samples demonstrate removing intermediate Journal Article versions. In the \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{script console}, you can remove unneeded object versions by executing Java or Groovy code. Here are example steps for removing intermediate Journal Article versions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Decide how many of the latest versions to keep. You must keep the original version and the most recent version, but you may keep older recent versions too. For example, you may want to keep the two latest versions or just the latest. \item Find a method for deleting the entity versions. Liferay DXP \href{@app-ref@/apps/}{app APIs} and \href{@platform-ref@/7.2-latest/javadocs/portal-kernel/}{com.lifieray.portal.kernel API} are available at \href{@platform-ref@}{@platform-ref@}. If it's a \href{/docs/7-2/appdev/-/knowledge_base/a/service-builder}{Service Builder} entity, examine the \texttt{delete*} methods in the entity's \texttt{*LocalServiceUtil} class. For example, this \texttt{deleteArticle} in \href{@app-ref@/web-experience/latest/javadocs/com/liferay/journal/service/JournalArticleLocalServiceUtil.html\#deleteArticle-long-java.lang.String-double-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-}{\texttt{JournalArticleLocalServiceUtil}} deletes a Journal Article version: \begin{verbatim} deleteArticle(long groupId, java.lang.String articleId, double version, java.lang.String articleURL, com.liferay.portal.kernel.service.ServiceContext serviceContext) \end{verbatim} \item Aggregate the entity versions to delete and the information required to delete them. For example, get all the Journal Article versions in range that match your removal criteria and associate their entity IDs and group IDs with them---the \texttt{deleteArticle} method requires the entity ID and group ID. The entity object (e.g., \texttt{JournalArticle}) typically has a version field. \texttt{JournalArticleResource} has each Journal Article's article ID (the entity's ID) and group ID. \texttt{JournalArticleResource} is our key to getting each \texttt{JournalArticle}, which can have multiple versions. Here are steps for identifying the Journal Article versions to delete: \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \tightlist \item Get all the \texttt{JournalArticleResource} objects. \end{enumerate} \begin{verbatim} List journalArticleResources = JournalArticleLocalServiceUtil.getJournalArticleResources(start, end); \end{verbatim} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \setcounter{enumii}{1} \item Get each Journal Article version's workflow status via the \texttt{JournalArticle} object associated with each \texttt{JournalArticleResource}. Dynamic Query is an efficient way to get exactly the data you want (and nothing more) from each object. \end{enumerate} \begin{verbatim} for (JournalArticleResource journalArticeResource : journalArticleResources) { List journalArticlesVersionsToDelete = new ArrayList(); DynamicQuery dq = DynamicQueryFactoryUtil.forClass(JournalArticle.class) .setProjection(ProjectionFactoryUtil.projectionList() .add(ProjectionFactoryUtil.property("id")) .add(ProjectionFactoryUtil.property("version")) .add(ProjectionFactoryUtil.property("status"))) .add(PropertyFactoryUtil.forName("groupId") .eq(journalArticeResource.getGroupId())) .add(PropertyFactoryUtil.forName("articleId") .eq(journalArticeResource.getArticleId())) .addOrder(OrderFactoryUtil.asc("version")); List result = JournalArticleLocalServiceUtil.dynamicQuery(dq); // See the next step for the sample code that goes here } \end{verbatim} \begin{enumerate} \def\labelenumii{\arabic{enumii}.} \setcounter{enumii}{2} \tightlist \item For each \texttt{JournalArticleResource} (there's one for each Journal Article entity), build a list of intermediate versions in range of the first or latest versions you want to keep and whose status qualifies them for deletion. For example, you may want to delete intermediate article versions that are approved or expired (i.e., \href{@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/workflow/WorkflowConstants.html}{WorkflowConstants.STATUS\_APPROVED or WorkflowConstants.STATUS\_EXPIRED}). The \texttt{MIN\_NUMBER\_FIRST\_VERSIONS\_KEPT} and \texttt{MIN\_NUMBER\_LATEST\_VERSIONS\_KEPT} variables here mark the minimum and maximum number of first (oldest) and latest (newest) versions to keep. \end{enumerate} \begin{verbatim} List journalArticlesVersionsToDelete = new ArrayList(); for (int i=0; i < result.size(); i++) { long id = (long) result.get(i)[0]; double version = (double) result.get(i)[1]; int status = (int) result.get(i)[2]; if ((status == WorkflowConstants.STATUS_APPROVED) || (status == WorkflowConstants.STATUS_EXPIRED) { if (i < MIN_NUMBER_FIRST_VERSIONS_KEPT) { continue; } if (i >= (result.size() - MIN_NUMBER_LATEST_VERSIONS_KEPT)) { continue; } journalArticlesVersionsToDelete.add(version); } } // See the next step for the sample code that goes here \end{verbatim} \item Lastly, delete each Journal Article matching the versions you aggregated. \begin{verbatim} for (double version : journalArticlesVersionsToDelete) { { JournalArticleLocalServiceUtil.deleteArticle(journalArticeResource.getGroupId(), journalArticeResource.getArticleId(), journalArticlesVersionsToDelete(i), null, null); } \end{verbatim} \end{enumerate} You can write similar code to remove intermediate versions of other entities. \noindent\hrulefill \textbf{Tip:} Print the version (and any other information of interest) of each object you're removing. You can also comment out the object deletion call and read the printout of versions to be removed as a test before committing to deleting them. \noindent\hrulefill After you've pruned your database, test it with Liferay DXP. \chapter{Upgrading Your Test Server and Database}\label{upgrading-your-test-server-and-database} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} After you've \href{/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database}{pruned your database and tested it successfully}, it's ready for upgrade. Here you'll install 7.0 and migrate your current installation files to it and upgrade them. Then you'll optimize your database for the upgrade and upgrade your data. Lastly, you'll test this upgraded test environment. You may run into issues that require you to start again with backup of your pruned database. After you're satisfied with the test upgrade, you can prepare for upgrading production. Start with preparing 7.0 on a test server. \section{Install Liferay on a Test Server and Configure It to Use the Pruned Database}\label{install-liferay-on-a-test-server-and-configure-it-to-use-the-pruned-database} \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade}{Prepare a new test server with 7.0}. Configure it to use the pruned database copy---keep the original backup in case you want to restart test upgrades on a copy of it. You'll use the new test server's Liferay upgrade tool next. \section{Tune Your Database for the Upgrade}\label{tune-your-database-for-the-upgrade} \href{/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade}{Tune your database for the upgrade}. \section{Upgrade the Database}\label{upgrade-the-database} Upgrade the database to 7.0 (see \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{Upgrade the Database}); then return here. If the upgrade took too long, search the upgrade log to identify more unused objects. Then retry these steps with a fresh copy of the production database. \section{Test the Upgraded Portal and Resolve Any Issues}\label{test-the-upgraded-portal-and-resolve-any-issues} Test this upgraded 7.0 instance and resolve any issues. If you can't resolve an issue, retry these steps with a fresh copy of the production database. \section{Checkpoint: You've Pruned and Upgraded a Production Database Copy}\label{checkpoint-youve-pruned-and-upgraded-a-production-database-copy} By removing unused objects from Liferay DXP in your test environment, you've made upgrading feasible to do in production. You identified unused objects, documented/scripted removing them, and successfully upgraded the Liferay DXP database copy. It's time to prepare your production environment for upgrading. \chapter{Preparing to Upgrade the Liferay DXP Database}\label{preparing-to-upgrade-the-liferay-dxp-database} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} After testing the upgrade on a copy of your production database, you can apply what you learned to your production database. \noindent\hrulefill \textbf{Tip:} This step and \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade}{preparing a new Liferay DXP server} can be done in parallel to save time. \noindent\hrulefill \section{Remove All Unused Objects You Identified Earlier}\label{remove-all-unused-objects-you-identified-earlier} Previously you identified and removed unused objects from a copy of your Liferay DXP production database backup. In the same way (in the script console or UI) you removed the unused objects from the backup, remove them from your pre-upgrade production database. \section{Test Using the Pruned Database}\label{test-using-the-pruned-database} Find and resolve any issues related to the objects you removed. By removing the objects from production and testing your changes before upgrading, you can more easily troubleshoot issues, knowing that they're not related to upgrade processes. \section{Upgrade Your Marketplace Apps}\label{upgrade-your-marketplace-apps} Upgrade each Marketplace app (Kaleo, Calendar, Notifications, etc.) that you're using to its latest version for your Liferay DXP installation. Before proceeding with the upgrade, troubleshoot any issues regarding these apps. \section{Publish all Staged Changes to Production}\label{publish-all-staged-changes-to-production} If you have \href{/docs/7-2/user/-/knowledge_base/u/enabling-staging}{local/remote staging enabled} and have content or data saved on the staged site, \href{/docs/7-2/user/-/knowledge_base/u/publishing-staged-content-efficiently}{publish} it to the live site. If you skip this step, you must run a full publish (or manually publish changes) after the upgrade, since the system won't know what content changed since the last publishing date. \section{Synchronize a Complete Backup}\label{synchronize-a-complete-backup} \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{Completely back up your Liferay DXP installation, pruned production database, and document repository}. It's time to prepare a new Liferay DXP server. \chapter{Preparing a New Liferay DXP Server for Data Upgrade}\label{preparing-a-new-liferay-dxp-server-for-data-upgrade} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} To upgrade your Liferay DXP database, prepare a new server for hosting 7.0. You'll use this server to run the database upgrade and run 7.0. Then you can run your production server while you're configuring a new server to host 7.0 exactly the way you want. \noindent\hrulefill \textbf{Note:} these steps can be done in parallel with any of the upgrade preparation steps: planning for deprecated apps, testing upgrades on a Liferay DXP backup copy, or preparing to upgrade the @product@ database. \noindent\hrulefill Get the latest fixes for 7.0 by requesting an upgrade patch. \section{Request an Upgrade Patch from Liferay Support (Liferay DXP only)}\label{request-an-upgrade-patch-from-liferay-support-liferay-dxp-only} An \emph{upgrade patch} contains the latest fix pack and hot fixes planned for the next service pack. Upgrade patches provide the latest fixes available for your data upgrade. \section{Install Liferay}\label{install-liferay} \href{/docs/7-2/deploy/-/knowledge_base/d/deploying-product}{Install Liferay DXP on your application server} or \href{/docs/7-2/deploy/-/knowledge_base/d/installing-product}{use Liferay DXP bundled with your application server of choice}. \noindent\hrulefill \textbf{Important:} Do not start your application server. It's not ready to start until after the Liferay DXP database upgrade. \noindent\hrulefill \section{Install the Latest Upgrade Patch or Fix Pack (Liferay DXP only)}\label{install-the-latest-upgrade-patch-or-fix-pack-liferay-dxp-only} Install the upgrade patch (if you requested it from Liferay Support) or the \href{https://help.liferay.com/hc/en-us/articles/360028810452-Patching-Liferay-DXP}{latest Fix Pack}. \section{Migrate Your OSGi Configurations (7.0+)}\label{migrate-your-osgi-configurations-7.0} Copy your \href{/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files}{OSGi configuration files} (i.e., \texttt{.config} files) to your new server's \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. \section{Migrate Your Portal Properties}\label{migrate-your-portal-properties} It is likely that you have overridden portal properties to customize your installation. If so, you must update the properties files (e.g., \texttt{portal-setup-wizard.properties} and \texttt{portal-ext.properties}) to 7.0. For features that use OSGi Config Admin, you must convert your properties to OSGi configurations. As you do this, you must account for property changes in all versions of Liferay DXP since your current version up to and including 7.0. Start with updating your portal properties. \section{Update Your Portal Properties}\label{update-your-portal-properties} If you're coming from a version prior to Liferay Portal 6.2, start with these property-related updates: \begin{itemize} \item If you're on Liferay Portal 6.1, \href{/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay\#review-the-liferay-6}{adapt your properties to the new defaults that Liferay Portal 6.2 introduced}. \item If you're on Liferay 6.0.12, \href{/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay\#migrate-your-image-gallery-images}{migrate the Image Gallery}. \item If you have a sharded environment, \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-a-sharded-environment}{configure your upgrade to generate a non-sharded environment}. \item Liferay's image sprite framework is deprecated as of 7.2 and is disabled by default. The framework requires scanning plugins for image sprites. If you don't use the framework, there's no need for it to scan for images sprites. If you use the framework, enable it by overriding the default \texttt{sprite.enabled} portal property (new in 7.2) value with the following setting in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file: \begin{verbatim} sprite.enabled=true \end{verbatim} \end{itemize} \noindent\hrulefill \textbf{Note:} You can build image sprites using any framework you like and deploy them in your plugins. \noindent\hrulefill When a new version of Liferay DXP is released, there are often changes to default settings, and this release is no different. If you rely on the defaults from your old version, you should review the changes and decide to keep the defaults from your old version or accept the defaults of the new. Because no existing properties changed from 7.1 to 7.2, here's a list of the 6.2 properties that have changed in 7.2: \begin{verbatim} users.image.check.token=false organizations.types=regular-organization,location organizations.rootable[regular-organization]=true organizations.children.types[regular-organization]=regular-organization,location organizations.country.enabled[regular-organization]=false organizations.country.required[regular-organization]=false organizations.rootable[location]=false organizations.country.enabled[location]=true organizations.country.required[location]=true layout.set.prototype.propagate.logo=true editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=simple web.server.servlet.check.image.gallery=true blogs.trackback.enabled=true discussion.comments.format=bbcode discussion.max.comments=0 dl.file.entry.thumbnail.max.height=128 dl.file.entry.thumbnail.max.width=128 \end{verbatim} This property was removed: \begin{verbatim} organizations.children.types[location] \end{verbatim} The latest \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{portal properties reference} provides property details and examples. Some properties are replaced by OSGi configurations. \section{Convert Applicable Properties to OSGi Configurations}\label{convert-applicable-properties-to-osgi-configurations} Properties in modularized features have changed and must now be deployed separately in \href{/docs/7-2/user/-/knowledge_base/u/system-settings\#exporting-and-importing-configurations}{OSGi configuration files} (OSGi Config Admin). Use the \href{/docs/7-2/reference/-/knowledge_base/r/blade-cli}{\texttt{blade\ upgradeProps}} command to scan your \texttt{portal-ext.properties} file to discover which properties are now set via OSGi Config Admin. You can also check the upgrade log from previous attempts for traces like these: \begin{verbatim} 2019-03-09 17:05:17.678 ERROR [main][VerifyProperties:161] Portal property "layout.first.pageable[link_to_layout]" is obsolete 2019-03-09 17:05:17.679 ERROR [main][VerifyProperties:136] Portal property "journal.article.check.interval" was modularized to com.liferay.journal.web as "check.interval" \end{verbatim} \noindent\hrulefill \textbf{Tip:} The Control Panel's \emph{Configuration → System Settings} screens are the most accurate way to create \texttt{.config} files. Use them to \href{/docs/7-2/user/-/knowledge_base/u/system-settings\#exporting-and-importing-configurations}{export a screen's configuration} to a \texttt{.config} file. \noindent\hrulefill \section{Update Your Database Driver}\label{update-your-database-driver} Install the recommended database driver and update your database connection driver specified in your \texttt{portal-ext.properties}. See the \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database Templates}. \section{Configure Your Documents and Media File Store}\label{configure-your-documents-and-media-file-store} General document store configuration (e.g., \texttt{dl.store.impl={[}File\ Store\ Impl\ Class{]}}) continues to be done using \texttt{portal-ext.properties}. But here's what's changed for document storage: \begin{itemize} \item Store implementation class package names changed from \texttt{com.liferay.portlet.documentlibrary.store.*} in Liferay Portal 6.2 to \texttt{com.liferay.portal.store.*} in Liferay DXP 7.0+. Make sure your \texttt{portal-ext.properties} file sets \texttt{dl.store.impl} in one of these ways: \begin{verbatim} dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore dl.store.impl=com.liferay.portal.store.db.DBStore dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore dl.store.impl=com.liferay.portal.store.s3.S3Store \end{verbatim} \item JCR Store was deprecated in Liferay DXP 7.0. The \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Configuration} documentation describes other store options. \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{Migrate to a supported document store} before upgrading your data. \item CMIS Store was deprecated since 7.0.10 Fix Pack 14 and was removed in Liferay DXP 7.2. The \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Configuration} documentation describes other store options. \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{Migrate to a supported document store} before upgrading your data. \item Since Liferay DXP 7.0, document store type-specific configuration (e.g., specific to Simple File Store, Advanced File Store, S3, etc.) is done in the Control Panel at \emph{Configuration → System Settings → File Storage} or using OSGi configuration files (\texttt{.config} files). Type specific configuration is no longer done using \texttt{portal-ext.properties}. \end{itemize} For example, these steps to create a \texttt{.config} file specifying a root file location for a Simple File Store or Advanced File Store: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \texttt{.config} file named after your store implementation class. Simple File Store: \texttt{com.liferay.portal.store.file.system.configuration.FileSystemStoreConfiguration.config} Advanced File Store: \texttt{com.liferay.portal.store.file.system.configuration.AdvancedFileSystemStoreConfiguration.config} \item Set the following \texttt{rootDir} property and replace \texttt{\{document\_library\_path\}} with your file store's path. \begin{verbatim} rootDir="{document_library_path}" \end{verbatim} \item Copy the \texttt{.config} file to your \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. \end{enumerate} The \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Configuration} provides more document store configuration details. \section{Configure Kerberos in place of NTLM}\label{configure-kerberos-in-place-of-ntlm} If you're using NTLM to authenticate Microsoft Windows ™ accounts with Liferay DXP, switch to using \href{/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-kerberos}{Kerberos}. Security vulnerabilities persist with NTLM. NTLM has been deprecated and removed from the bundle, but you can still \href{https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-security-sso-ntlm}{build and deploy the module}. \section{Disable Indexing}\label{disable-indexing} Before starting the upgrade process in your new installation, you must disable indexing to prevent upgrade process performance issues that arise when the indexer attempts to re-index content. To disable indexing, create a file called \texttt{com.liferay.portal.search.configuration.IndexStatusManagerConfiguration.config} in your \texttt{{[}Liferay\ Home{]}/osgi/configs} folder and add the following content: \begin{verbatim} indexReadOnly="true" \end{verbatim} After you complete the upgrade, re-enable indexing by removing the \texttt{.config} file or setting \texttt{indexReadOnly="false"}. Your new 7.0 server is ready for upgrading your database. \chapter{Upgrading the Liferay DXP Data}\label{upgrading-the-liferay-dxp-data} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Now you're ready to upgrade the Liferay DXP data. The upgrade processes update the database schema for the core and your installed modules. Verification processes test the upgrade. Configured verifications for the core and modules run afterwards, but can be run manually too. Here are the ways to upgrade: \begin{itemize} \item \textbf{Upgrade everything in one shot}: Use the upgrade tool to upgrade the core and all the modules. \item \textbf{Upgrade the core and the modules separately}: Use the upgrade tool (recommended) or \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell}{Gogo shell} to upgrade the core. Then use Gogo shell to upgrade each module. \end{itemize} If you are upgrading from Liferay Portal 6.2 or earlier, use the upgrade tool to upgrade everything. It's the easiest, most comprehensive way to upgrade from those versions. Since version 7.0, however, Liferay DXP's modular framework lets you upgrade modules---even the core---individually. A helpful practice for large databases is to focus first on upgrading the core and your most important modules; then back up your database before continuing upgrades. Upgrading is a flexible process that adjusts to your preferences. \noindent\hrulefill \textbf{Note:} Liferay enterprise subscribers can use the upgrade tool to execute upgrades for fix packs. Since Liferay DXP 7.1, a fix pack's micro upgrade processes in the core (database schema micro version changes) are not mandatory. This means you can install a fix pack (i.e., core code) without having to execute the database schema micro version changes. You can execute micro version changes when you want, even outside of major or minor version upgrades. Before using the upgrade tool to execute a fix pack's micro upgrade process, however, you must shut down the server, install the fix pack, and \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{back up the Liferay DXP database, installation, and Document Library store}. Module micro database schema version changes in fix packs execute automatically on server startup unless the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade}{\texttt{autoUpgrade} setting} is \texttt{false} (the default is \texttt{true}). \chapter{Tuning for the Data Upgrade}\label{tuning-for-the-data-upgrade} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Upgrading impacts the database differently from daily running in production. Because of this, you should tune your database for the upgrade process before you run it, and then re-apply your production settings after the upgrade completes. \begin{itemize} \item Data upgrades execute many more update statements (\texttt{INSERT}, \texttt{UPDATE}, and \texttt{DELETE}) and less \texttt{SELECT} statements than production instances. When upgrading, tune your database for executing updates. \item Data upgrades should be done in safe environments completely separate from production servers and should use database backup copies. If upgrade errors occur or you make mistakes, they don't impact production, and you can always restart using your database backup copy. \end{itemize} The data upgrade tuning instructions given here are a starting point for tuning your Liferay DXP data upgrade. They account for data upgrade activities and a safe data upgrade environment: \begin{itemize} \item Deactivate data integrity measures that impact performance. Restore the backup if failures occur. \item Make commit-related transaction I/O operations asynchronous. \item Increase the interval to flush commits to disk. \item Minimize transaction logging. \end{itemize} \noindent\hrulefill \textbf{Note:} These options worked well for us on specific versions of each database. Please consult your database vendor's documentation for information on how to optimize executing updates on your specific database version. \noindent\hrulefill \noindent\hrulefill \textbf{Important:} Test your database configuration to determine tuning that's best for your system, and consult your DBA as appropriate. \textbf{Never} use database upgrade configurations in production. Always restore your production database settings before starting your Liferay DXP server for production use with the database. \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} Some database properties and configurations are global and affect schemas in the same database. \noindent\hrulefill These configurations were optimal for upgrading data in a Liferay 6.2 EE installation that had these characteristics: \begin{itemize} \item 3.2 GB database \item 15 GB Document Library \item Content translated in 3 languages \item Record count for most populated entities: \begin{itemize} \tightlist \item 1,694,000 rating entries \item 1,605,000 permissions (\texttt{ResourcePermission} objects) \item 871,000 assets (\texttt{AssetEntry} objects) \item 400,000 users \item 400,000 sites (\texttt{Group} objects) \item 402,000 images \item 259,000 message forum threads and posts \item 200,000 documents \item 193,000 portlet preferences \item 103,000 web content pieces (\texttt{JournalArticle} objects) \item 50,600 pages \item 3,276 journal article images \item 3,100 document folders \end{itemize} \end{itemize} Start with configuring the database upgrade tool's Java process. \section{Tuning the Database Upgrade Java Process}\label{tuning-the-database-upgrade-java-process} Make sure to provide adequate memory for the database upgrade tool's Java process. 15GB was appropriate for the test scenario. Also make sure to set the file encoding to UTF-8 and the time zone to GMT. Here are the Java process settings: \begin{itemize} \tightlist \item Xmx 15 GB RAM \item File encoding UTF-8 \item User time zone GMT \end{itemize} Here is the \texttt{db\_upgrade.sh} command: \begin{verbatim} db_upgrade.sh -j "-Xmx15000m -Dfile.encoding=UTF-8 -Duser.timezone=GMT" \end{verbatim} It's time to tune your database transaction engine. \section{Tuning the Database Transaction Engine for Executing Updates}\label{tuning-the-database-transaction-engine-for-executing-updates} Many more update statements are executed during data upgrade than in production. Here's how to optimize each database's transaction engine for the updates. \section{IBM DB2}\label{ibm-db2} Please consult IBM's official DB2 documentation. \section{MariaDB}\label{mariadb} In addition to the default database configuration, turn off InnoDB double-write. \section{Microsoft SQL Server}\label{microsoft-sql-server} In addition to the default database configuration, set \href{https://docs.microsoft.com/en-us/sql/relational-databases/logs/control-transaction-durability}{transaction durability} to \texttt{FORCED}. \section{MySQL}\label{mysql} In addition to the default database configuration, turn off \href{https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html\#sysvar_innodb_doublewrite}{InnoDB double-write}. \section{Oracle Database}\label{oracle-database} The default configuration works well. It configures \href{https://docs.oracle.com/database/121/REFRN/GUID-FD8D1BD2-0F85-4844-ABE7-57B4F77D1608.htm\#REFRN10048}{asynchronous I/O to disk} automatically. \section{PostgreSQL}\label{postgresql} In addition to the default database configuration, turn off \href{https://www.postgresql.org/docs/10/wal-async-commit.html}{synchronous commits}. \section{Tuning the Database Transaction Log}\label{tuning-the-database-transaction-log} In production, transaction logs mark safe states to roll back to. In data upgrades, however, the safe state is the original data backup. Since transaction logging is insignificant for data upgrades, it should be disabled or minimized. Here are log tuning instructions for each database. \section{IBM DB2}\label{ibm-db2-1} Please consult IBM's official DB2 documentation. \section{MariaDB}\label{mariadb-1} In addition to the default database configuration, set the InnoDB flush log at transaction commit to \texttt{0}. \section{Microsoft SQL Server}\label{microsoft-sql-server-1} Use the default database configuration. \section{MySQL}\label{mysql-1} In addition to the default database configuration, set the \href{https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html\#sysvar_innodb_flush_log_at_trx_commit}{InnoDB flush log at transaction commit} to \texttt{0}. \section{Oracle Database}\label{oracle-database-1} Use the default database configuration. \section{PostgreSQL}\label{postgresql-1} In addition to the default database configuration, Set the \href{https://www.postgresql.org/docs/10/wal-async-commit.html}{write ahead log writer delay} to \texttt{1000} milliseconds. Congratulations! You have a starting point to plan your own Liferay DXP data upgrade project. Remember, optimal tuning depends on your data, infrastructure conditions, and database vendor. Analyze your data, tune for upgrade, and time your test upgrades. Use this information to determine the best database and Java process configuration for your Liferay DXP data upgrade. \chapter{Configuring the Data Upgrade}\label{configuring-the-data-upgrade} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The upgrade tool provides the easiest way to upgrade the core and installed modules. You can use text files or the tool's command line interface to configure your upgrade. The upgrade tool can upgrade everything---the core and all the modules---together or separately. 7.0 bundles include the upgrade tool. If you installed @product-ver@ manually, you can download the upgrade tool separately. \begin{itemize} \item \emph{Liferay DXP 7.2}: Go to the \href{https://customer.liferay.com/group/customer/downloads}{\emph{Downloads} page} and select the \emph{DXP 7.2} product and the \emph{Product/Service Packs} file type. In the listing that appears, click \emph{Download} for the \emph{Liferay DXP Upgrade Client}. \item \emph{Liferay Portal CE 7.2}: Go to the \href{https://www.liferay.com/downloads-community}{\emph{Downloads} page} and select \emph{Download} for \emph{Liferay Portal Tools for 7.2}. \end{itemize} Before starting the data upgrade process, configure the upgrade tool for the core upgrade and specify whether the upgrade tool should upgrade non-core module data automatically. \section{Configuring the Core Upgrade}\label{configuring-the-core-upgrade} The core upgrade requires configuration. You can configure it at runtime via the command line interface or pre-configure it in these files in \texttt{{[}Liferay\ Home{]}/tools/portal-tools-db-upgrade-client/}: \begin{itemize} \tightlist \item \texttt{app-server.properties}: Specifies the server's location and libraries. \item \texttt{portal-upgrade-database.properties}: Configures the database connection. \item \texttt{portal-upgrade-ext.properties}: Sets the rest of the portal properties that the upgrade requires. You might want to copy your current portal properties (except your database properties) into this file. Before copying your current properties, make sure you've \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database}{updated the portal properties for 7.0}. \end{itemize} Each file's properties are described next. \section{Configuring app-server.properties}\label{configuring-app-server.properties} Specify the following information to configure 7.0's app server: \texttt{dir:} the absolute path of the application server folder. \emph{(required)} \texttt{extra.lib.dirs:} a comma delimited list of extra directories containing any binaries or resources to add to the class path. Use all absolute paths OR all paths relative to \texttt{dir}. \emph{(required)} \texttt{global.lib.dir:} the application server's global library directory. Use the absolute path or a path relative to \texttt{dir}. \emph{(required)} \texttt{portal.dir:} the directory where portal is installed in your app server. Use the absolute path or a path relative to \texttt{dir}. \emph{(required)} \texttt{server.detector.server.id:} ID of a supported application server. (\emph{required}) Here are the IDs: \begin{itemize} \tightlist \item \texttt{jboss} \item \texttt{jonas} \item \texttt{resin} \item \texttt{tomcat} \item \texttt{weblogic} \item \texttt{websphere} \item \texttt{wildfly} \end{itemize} Relative paths must use Unix style format. The following properties, for example, are for Windows and use relative paths: \begin{verbatim} dir=D:\ extra.lib.dirs=Liferay/liferay-portal-master/tomcat-9.0.10/bin global.lib.dir=Liferay/liferay-portal-master/tomcat-9.0.10/lib portal.dir=Liferay/liferay-portal-master/tomcat-9.0.10/webapps/ROOT server.detector.server.id=tomcat \end{verbatim} These properties, for example, are for Linux and use all absolute paths: \begin{verbatim} dir=/ extra.lib.dirs=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/bin global.lib.dir=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/lib portal.dir=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/webapps/ROOT server.detector.server.id=tomcat \end{verbatim} \section{Configuring portal-upgrade-database.properties}\label{configuring-portal-upgrade-database.properties} Specify the following information to configure the database you're upgrading. Note that these properties correspond exactly to the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#JDBC}{JDBC portal properties} you'd use in a \texttt{portal-ext.properties} file. \texttt{jdbc.default.driverClassName} \emph{(required)} \texttt{jdbc.default.url} \emph{(required)} \texttt{jdbc.default.username} \emph{(required)} \texttt{jdbc.default.password} \emph{(required)} \section{Configuring portal-upgrade-ext.properties}\label{configuring-portal-upgrade-ext.properties} Specify the following information to configure the upgrade: \texttt{liferay.home:} The \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay home folder} \emph{(required)} \texttt{dl.store.impl:} The implementation for persisting documents to the document library store. This property is mandatory if you're using a \texttt{*FileSystemStore} implementation. If you \href{/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade}{updated this property in your \texttt{portal-ext.properties}}, copy it here. Otherwise, set the property one of these ways: \begin{verbatim} dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore dl.store.impl=com.liferay.portal.store.db.DBStore dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore dl.store.impl=com.liferay.portal.store.s3.S3Store \end{verbatim} \texttt{hibernate.jdbc.batch\_size:} The JDBC batch size used to improve performance; set to \emph{250} by default \emph{(optional)} \section{Example Upgrade Configuration}\label{example-upgrade-configuration} Here's an example interaction with the upgrade tool's command line interface: \begin{verbatim} Please enter your application server (tomcat): tomcat Please enter your application server directory (../../tomcat-8.0.32): Please enter your extra library directories (../../tomcat-8.0.32/bin): Please enter your global library directory (../../tomcat-8.0.32/lib): Please enter your portal directory (../../tomcat-8.0.32/webapps/ROOT): [ db2 mariadb mysql oracle postgresql sqlserver sybase ] Please enter your database (mysql): mariadb Please enter your database host (localhost): (etc.) \end{verbatim} The command line interface creates the configuration files based on your input. You can put this information into configuration files to configure the tool manually. Here are example upgrade configuration files that you can customize and copy into \texttt{{[}Liferay\ Home{]}/tools/portal-tools-db-upgrade-client/}: \begin{itemize} \item \texttt{app-server.properties}: \begin{verbatim} dir=../../tomcat-8.0.32 global.lib.dir=/lib portal.dir=/webapps/ROOT server.detector.server.id=tomcat extra.lib.dirs=/bin \end{verbatim} \item \texttt{portal-upgrade-database.properties}: \begin{verbatim} jdbc.default.url=jdbc:mysql://lportal62?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.username=root jdbc.default.password= \end{verbatim} \item \texttt{portal-upgrade-ext.properties}: \begin{verbatim} liferay.home=/home/user/servers/liferay7 module.framework.base.dir=/home/user/servers/liferay7/osgi dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore \end{verbatim} \end{itemize} Next, decide if the upgrade tool should upgrade non-core modules automatically. \section{Configuring Non-Core Module Data Upgrades}\label{configuring-non-core-module-data-upgrades} You can configure the upgrade tool to upgrade all installed modules automatically or to open a Gogo shell (after core upgrade completes) for you to execute module upgrades manually. If the upgrade tool's \texttt{autoUpgrade} property is set to \texttt{true} (the default setting), upgrade processes for all installed modules are run too. If you set \texttt{autoUpgrade="false"} in a file called \texttt{com.liferay.portal.upgrade.internal.configuration.ReleaseManagerConfiguration.config} and copy the file into the \texttt{{[}Liferay\ Home{]}/osgi/configs} folder, the upgrade tool opens Gogo shell after the core upgrade. In the Gogo shell, you can \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell}{administer module upgrades}. It's time to run the upgrade tool. \chapter{Upgrading the Core Using the Upgrade Tool}\label{upgrading-the-core-using-the-upgrade-tool} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The upgrade tool provides the easiest way to upgrade the core and installed modules. Here's how to use it. \section{Upgrade Tool Usage}\label{upgrade-tool-usage} The \texttt{db\_upgrade.sh} script in the \texttt{{[}Liferay\ Home{]}/tools/portal-tools-db-upgrade-client} folder (\texttt{db\_upgrade.bat} on Windows) invokes the upgrade tool. This command prints the upgrade tool usage: \begin{verbatim} db_upgrade.sh --help \end{verbatim} This configuration prevents automatic module upgrade, but causes the upgrade tool to open a Gogo shell for \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell}{upgrading modules} after finishing the core upgrade. Here are the tool's default Java parameters: \begin{verbatim} -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.timezone=GMT -Xmx2048m \end{verbatim} The \texttt{-j} option overrides the JVM parameters. For example, these options set the JVM memory to 10GB, which is a good starting point for this process type: \begin{verbatim} db_upgrade.sh -j "-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.timezone=GMT -Xmx10240m" \end{verbatim} The \texttt{-l} option specifies the tool's log file name: \begin{verbatim} db_upgrade.sh -l "output.log" \end{verbatim} Here are all the upgrade tool command line options: \textbf{--help} or \textbf{-h}: Prints the tool's help message. \textbf{--jvm-opts} or \textbf{-j} + \textbf{{[}arg{]}}: Sets any JVM options for the upgrade process. \textbf{--log-file} or \textbf{-l} + \textbf{{[}arg{]}}: Specifies the tool's log file name---the default name is \texttt{upgrade.log}. \textbf{--shell} or \textbf{-s}: Automatically connects you to the Gogo shell after finishing the upgrade process. \noindent\hrulefill \textbf{Note:} Only execute the upgrade process on a server with ideal memory, CPU, and database connection configurations. If executing an upgrade remotely using \texttt{ssh}, make sure to guard against interruptions: \begin{itemize} \tightlist \item If you're executing the upgrade using \texttt{ssh}, ignore hangups (connection loss) by using \texttt{nohup} or something similar. \item On the machine you're connecting from, disable settings that shutdown or sleep that machine. \end{itemize} The upgrade process continues on the server even if you lose connection to it. If you lose connection, reconnect and monitor upgrade status via the log (default log file is \texttt{upgrade.log}). If you're using an earlier version of 7.0 and upgrade execution is interrupted, check your log file for where execution stopped. \begin{itemize} \tightlist \item If execution stopped during an upgrade process for Core 7.1 or higher, or any module upgrade process, restart the upgrade tool to continue the upgrade from that point. You can also use Gogo shell to \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell\#checking-upgrade-status}{check module upgrade status} and continue upgrading modules. \item If execution stopped during an upgrade process for Core 7.0 or lower, you must \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{restore the data from a backup} and start the upgrade again. \end{itemize} \noindent\hrulefill \noindent\hrulefill \textbf{Warning:} To prevent the tool's expanded command from growing too large for Windows, execute the upgrade tool script from the \texttt{{[}Liferay\ \ Home{]}/tools/portal-tools-db-upgrade-client} folder. \noindent\hrulefill It's time to upgrade your core data using the upgrade tool. \section{Running and Managing the Core Upgrade}\label{running-and-managing-the-core-upgrade} Start the upgrade tool, as the previous section explains. Here are the core upgrade stages: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Show the upgrade patch level \item Execute the core upgrade processes \item Execute the core verifiers \end{enumerate} Monitor the upgrade via the upgrade tool log file (default file is \texttt{upgrade.log}). If a core upgrade process fails, analyze the failure and resolve it. If a core upgrade step for Liferay DXP 7.1 (or newer) fails, executing the upgrade tool again starts it from that step. If you configured the upgrade tool to upgrade non-core modules, the tool opens a Gogo shell and starts upgrading them. The Gogo shell lets you upgrade modules, check module upgrade status, verify upgrades, and restart module upgrades. Read on to learn how to use Gogo shell commands to complete Liferay DXP upgrades. \chapter{Upgrading Modules Using Gogo Shell}\label{upgrading-modules-using-gogo-shell} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay's Gogo shell can upgrade and verify individual modules. It's a fine-grained approach to upgrading the core and non-core modules. If you haven't already upgraded your non-core modules using the upgrade tool or if there are modules you need to revisit upgrading, you can upgrade them using Gogo Shell. \noindent\hrulefill \textbf{Note}: You must \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade}{Configure the core upgrade} before using Gogo shell commands to upgrade the core. \noindent\hrulefill Below is a list of commands. \section{Command Usage}\label{command-usage} If you ran the upgrade tool and it opened Gogo shell, you're already connected. Otherwise, you can execute commands using the \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo Shell portlet}. Here are the commands: \texttt{exit} or \texttt{quit:} Exits the Gogo shell \texttt{upgrade:help:} Displays upgrade commands \texttt{upgrade:check:} Lists upgrades pending execution because they failed in the past or the module hasn't reached its final version \texttt{upgrade:execute\ \{module\_name\}:} Executes upgrades for that module \texttt{upgrade:executeAll:} Executes all pending module upgrade processes \texttt{upgrade:list:} Lists all registered upgrades \texttt{upgrade:list\ \{module\_name\}:} Lists the module's required upgrade steps \texttt{upgrade:list\ \textbar{}\ grep\ Registered:} Lists registered upgrades and their versions \texttt{verify:help:} Displays verify commands \texttt{verify:check\ \{module\_name\}:} Lists the latest execution result for the module's verify process \texttt{verify:checkAll:} Lists the latest execution results for all verify processes \texttt{verify:execute\ \{module\_name\}:} Executes the module's verifier \texttt{verify:executeAll:} Executes all verifiers \texttt{verify:list:} Lists all registered verifiers There are many useful \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Liferay commands and standard commands available in Gogo shell}. The following sections describe Liferay upgrade commands. \section{Listing module upgrade processes}\label{listing-module-upgrade-processes} Before upgrading modules, you should find which have unresolved dependencies, which are resolved and available to upgrade, and examine the module upgrade processes. Executing \texttt{upgrade:list} in the Gogo shell lists the modules whose upgrade dependencies are satisfied. These modules can be upgraded. If a module is active but not listed, its dependencies must be upgraded. The Gogo shell command \texttt{scr:info\ {[}upgrade\_step\_class\_qualified\_name{]}} shows the upgrade step class's unsatisfied dependencies. Here's an example \texttt{scr:info} command: \begin{verbatim} scr:info com.liferay.journal.upgrade.JournalServiceUpgrade \end{verbatim} Invoking \texttt{upgrade:list\ {[}module\_name{]}} lists the module's upgrade processes, in no particular order. For example, executing \texttt{upgrade:list\ com.liferay.bookmarks.service} (for the Bookmarks Service module), lists this: \begin{verbatim} Registered upgrade processes for com.liferay.bookmarks.service 1.0.0 {fromSchemaVersionString=0.0.0, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextExtender$ModuleApplicationContextExtension$1@6e9691da} {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0-step-3, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradePortletId@5f41b7ee} {fromSchemaVersionString=1.0.0-step-1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradePortletSettings@53929b1d} {fromSchemaVersionString=1.0.0-step-2, toSchemaVersionString=1.0.0-step-1, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradeLastPublishDate@3e05b7c8} {fromSchemaVersionString=1.0.0-step-3, toSchemaVersionString=1.0.0-step-2, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradeClassNames@6964cb47} \end{verbatim} An application's upgrade step class names typically reveal their intention. For example, the example's \texttt{com.liferay.bookmarks.upgrade.v1\_0\_0.UpgradePortletId} upgrade step class updates the app's portlet ID. The other example upgrade step classes update class names, the \texttt{LastPublishDate}, and \texttt{PortletSettings}. The example's step from \texttt{0.0.0} to \texttt{1.0.0} upgrades the module from an empty database. To examine a module's upgrade process better, you can sort the listed upgrade steps mentally or in a text editor. Here's the upgrade step order for a Bookmarks Service module to be upgraded from Liferay Portal 6.2 (the module's database exists) to schema version \texttt{1.0.0}: \begin{itemize} \tightlist \item \texttt{0.0.1} to \texttt{1.0.0-step-3} \item \texttt{0.0.1-step-3} to \texttt{1.0.0-step-2} \item \texttt{0.0.1-step-2} to \texttt{1.0.0-step-1} \item \texttt{0.0.1-step-1} to \texttt{1.0.0} \end{itemize} The overall module upgrade process starts at version \texttt{0.0.1} and finishes at version \texttt{1.0.0}. The first step starts on the initial version (\texttt{0.0.1}) and finishes on the target version's highest step (\texttt{step-3}). The last step starts on the target version's lowest step (\texttt{step-1}) and finishes on the target version (\texttt{1.0.0}). Once you understand the module's upgrade process, you can execute it with confidence. \section{Executing module upgrades}\label{executing-module-upgrades} Executing \texttt{upgrade:execute\ {[}module\_name{]}} upgrades the module. You might run into upgrade errors that you must resolve. Executing the command again starts the upgrade from the last successful step. You can check upgrade status by executing \texttt{upgrade:list\ {[}module\_name{]}}. For example, entering \texttt{upgrade:list\ com.liferay.iframe.web} outputs this: \begin{verbatim} Registered upgrade processes for com.liferay.iframe.web 0.0.1 {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.iframe.web.upgrade.IFrameWebUpgrade$1@1537752d} \end{verbatim} The first line lists the module's name and current version. The example module's current version is \texttt{0.0.1}. The \texttt{toSchemaVersionString} value is the target version. Executing \texttt{upgrade:list\ {[}module\_name{]}} on the module after successfully upgrading it shows the module's name followed by the version you targeted. For example, if you successfully upgraded \texttt{com.liferay.iframe.web} to version \texttt{1.0.0}, executing \texttt{upgrade:list\ com.liferay.iframe.web} shows the module's version is \texttt{1.0.0}: \begin{verbatim} Registered upgrade processes for com.liferay.iframe.web 1.0.0 {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.iframe.web.upgrade.IFrameWebUpgrade$1@1537752d} \end{verbatim} For module upgrades that don't complete, you can check their status and resolve their issues. \section{Checking upgrade status}\label{checking-upgrade-status} The command \texttt{upgrade:check} lists modules that have impending upgrades. For example, if module \texttt{com.liferay.dynamic.data.mapping.service} failed in a step labeled \texttt{1.0.0-step-2}, executing \texttt{upgrade:check} shows this: \begin{verbatim} Would upgrade com.liferay.dynamic.data.mapping.service from 1.0.0-step-2 to 1.0.0 and its dependent modules \end{verbatim} Modules often depend on other modules to complete upgrading. Executing \texttt{scr:info\ {[}upgrade\_step\_class\_qualified\_name{]}} shows the upgrade step class's dependencies. You must upgrade modules on which your module depends. To resolve and activate a module, its upgrade must complete. The \href{http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager/tutorials/leveraging-the-shell.html}{Apache Felix Dependency Manager} Gogo shell command \texttt{dm\ wtf} reveals unresolved dependencies. If your module requires a certain data schema version (e.g., its \texttt{bnd.bnd} specifies \texttt{Liferay-Require-SchemaVersion:\ 1.0.2}) but the module hasn't completed upgrade to that version, \texttt{dm\ wtf} shows that the schema version is not registered. \begin{verbatim} 1 missing dependencies found. ------------------------------------- The following service(s) are missing: * com.liferay.portal.kernel.model.Release (&(release.bundle.symbolic.name=com.liferay.journal.service)(release.schema.version=1.0.2)) is not found in the service registry \end{verbatim} The \texttt{dm\ wtf} command can also help detect errors in portlet definitions and custom portlet \texttt{schemaVersion} fields. Browsing the Liferay DXP database \texttt{Release\_} table can help you determine a module's upgrade status too. The core's \texttt{servletContextName} field value is \texttt{portal}. If the core's \texttt{schemaVersion} field matches your new Liferay DXP version (e.g., \texttt{7.2.1} for Liferay Portal CE GA2) and the \texttt{verified} field is \texttt{1} (true), the core upgrade completed successfully. Each module has one \texttt{Release\_} table record, and the value for its \texttt{schemaVersion} field must be \texttt{1.0.0} or greater (\texttt{1.0.0} is the initial version for 7.0 modules, except for those that were previously traditional plugins intended for Liferay Portal version 6.2 or earlier). \section{Executing verify processes}\label{executing-verify-processes} Some modules have verify processes. These make sure the upgrade executed successfully. Verify processes in the core are automatically executed after upgrading Liferay DXP. You can also execute them by configuring the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Verify}{\texttt{verify.*} portal properties} and restarting your server. To check for available verify processes, enter the Gogo shell command \texttt{verify:list}. To run a verify process, enter \texttt{verify:execute\ {[}verify\_qualified\_name{]}}. \chapter{Executing Post-Upgrade Tasks}\label{executing-post-upgrade-tasks} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Since you optimized your system for upgrading, after the upgrade is complete you must re-optimize it for production. \section{Tuning Your Database for Production}\label{tuning-your-database-for-production} Prior to upgrading your Liferay DXP database, you tuned it for upgrade. Now that upgrade is complete, restore the production database tuning you used previously. \section{Re-enabling Search Indexing and Re-indexing Search Indexes}\label{re-enabling-search-indexing-and-re-indexing-search-indexes} Make sure to re-enable search indexing by removing the \texttt{com.liferay.portal.search.configuration.IndexStatusManagerConfiguration.config} file from your \texttt{{[}Liferay\ Home{]}/osgi/configs} folder or setting this property in it: \begin{verbatim} indexReadOnly="false" \end{verbatim} Then re-index Liferay DXP's search indexes. Don't just do this blindly, however. By default, Liferay DXP ships with an embedded configuration for Elasticsearch. This configuration works great for demo purposes, but is not supported in production. Make sure to \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{install and configure a standalone Elasticsearch instance to run in production}. \section{Enabling Web Content View Permissions}\label{enabling-web-content-view-permissions} Prior to Liferay DXP 7.1, all users could view web content articles by default. Now view permissions are checked by default. Here are options for opening view permissions: Option 1: Edit view permissions per web content article per role. Option 2: Open view permissions for all web content articles by going to \emph{System Settings → Web Experience → Web Content} and deselecting \emph{Article view permissions check enabled}. Once you've configured search, re-indexed your search index, and set web content view permissions, your upgraded system is ready for action! Congratulations! \chapter{Upgrading a Sharded Environment}\label{upgrading-a-sharded-environment} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Since Liferay DXP 7.0, Liferay removed its own physical partitioning implementation (also known as sharding) in favor of the capabilities provided natively by database vendors. Upgrading a sharded installation to DXP 7.0 or higher requires migrating it to as many non-sharded Liferay DXP installations (servers) as you have shards. These steps guide you through configuring the new Liferay DXP servers to use your formerly sharded data. \noindent\hrulefill \textbf{Note:} Liferay continues to support its logical partitioning capabilities (also known as \href{/docs/7-2/user/-/knowledge_base/u/setting-up-a-virtual-instance}{virtual instances}) for the foreseeable future. \noindent\hrulefill \noindent\hrulefill For any further assistance with sharding contact your Liferay account manager or Liferay Support. \noindent\hrulefill \section{Add Configurations Before the Data Upgrade}\label{add-configurations-before-the-data-upgrade} In addition to other configurations, you will need to set more properties to migrate your shards to virtual instances for your data upgrade. Here is how to configure the upgrade to migrate from sharding: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Copy all of the shard JDBC connection properties from \texttt{portal-ext.properties} to\texttt{portal-upgrade-database.properties}. For example, JDBC connections for a default shard and two non-default shards might look like this: \begin{verbatim} jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to the default database shard] jdbc.default.username=[the user name] jdbc.default.password=[the password] jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] jdbc.two.driverClassName=[the database driver class name] jdbc.two.url=[the URL to database shard two] jdbc.two.username=[the user name] jdbc.two.password=[the password] \end{verbatim} \item Set the JDBC \emph{default} connection properties in each server's \texttt{portal-upgrade-database.properties} to specify the associated shard. \begin{itemize} \tightlist \item Add the original JDBC properties for the respective non-default shard database. For example, shard \texttt{one}'s original properties might start with \texttt{jdbc.one}: \end{itemize} \begin{verbatim} jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] \end{verbatim} \begin{itemize} \tightlist \item Rename the properties to start with \texttt{jdbc.default}. For example: \end{itemize} \begin{verbatim} jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to database shard one] jdbc.default.username=[the user name] jdbc.default.password=[the password] \end{verbatim} \end{enumerate} \section{Upgrade and Update Properties}\label{upgrade-and-update-properties} When you perform the database upgrade, upgrade the default shard first, and then each of the non-default shards. See \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{Using the Database Upgrade Tool} for more information on running the database upgrade. After the database upgrade has been completed, make the following configuration changes to your application servers: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In each server's \texttt{portal-ext.properties}, use the JDBC \emph{default} properties you specified in the \texttt{portal-upgrade-database.properties} (see the \emph{default} properties above). \item Remove the non-default shard JDBC properties from the default shard server's \texttt{portal-ext.properties} file, leaving only the default shard database \texttt{jdbc.default} properties. For example: Old JDBC properties: \begin{verbatim} jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to the default database shard] jdbc.default.username=[the user name] jdbc.default.password=[the password] jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] jdbc.two.driverClassName=[the database driver class name] jdbc.two.url=[the URL to database shard two] jdbc.two.username=[the user name] jdbc.two.password=v[the password] \end{verbatim} New JDBC properties: \begin{verbatim} jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to your database] jdbc.default.username=[the user name] jdbc.default.password=[the password] \end{verbatim} \end{enumerate} Once you have completed all of these steps, you have migrated off of a sharded environment to virtual instances on separate Liferay DXP servers together with your DXP upgrade. Congratulations! You have migrated off of a sharded environment to virtual instances on separate Liferay DXP servers. You have also upgraded to 7.0. Your virtual instances are ready for action. \chapter{Migrating From Audience Targeting to Segmentation and Personalization}\label{migrating-from-audience-targeting-to-segmentation-and-personalization} 7.0 integrates all the Audience Targeting app's features into Liferay's core as Segmentation and Personalization. This enables better integration with other applications and provides developers with easier access to Segmentation and Personalization features. Audience Targeting users must migrate their user segments into the new Segments application. There are three steps to the migration process: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Upgrade to 7.0. \item Migrate custom rules. \item Migrate behavior-based features. \end{enumerate} First, to upgrade to the latest version of Liferay DXP, follow the \href{/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver}{upgrade guide}. Most of your Audience Targeting configuration is automatically transferred into the new engine. Next, any custom rules that were created in Audience Targeting must be re-evaluated. Some custom rules may have an out-of-the-box equivalent now, while others must be migrated. If a rule must be re-implemented, follow the \href{/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization}{Segmentation and Personalization development guide}. You can check \href{/docs/7-2/deploy/-/knowledge_base/d/migrating-user-segments}{the list of rules that are automatically migrated} to see how much additional work you have in store. You must also \href{/docs/7-2/deploy/-/knowledge_base/d/manually-migrating-from-audience-targeting}{migrate display widgets} since the new Personalization features use different tools. Finally, you must migrate behavior-based features, but since Audience Targeting's analytics features are now part of Analytics Cloud, there isn't a direct path to upgrade. See the \href{https://help.liferay.com/hc/en-us/articles/360006947671-Creating-Segments}{Analytics Cloud documentation}. \chapter{Migrating User Segments}\label{migrating-user-segments} In Audience Targeting, a user segment represents a subset of users. A user segment is defined by one or more rules that users must match to belong to that user segment. In 7.0, segments work in a similar way, but they are defined by criteria instead of rules. Segment criteria are sets of fields defined by different user actions or properties (profile information, organization information, session information) that can be combined through operations (like equals, not equals, contains, not contains, greater than, and less than) and conjunctions (AND, OR) to define complex filters. Due to the similarities between Audience Targeting user segments and 7.0 Segments, certain data can be migrated automatically as part of the upgrade process. \section{Upgrade Process}\label{upgrade-process} As a result of the upgrade process, \begin{itemize} \tightlist \item All Audience Targeting User Segments appear under the new Segments administration in 7.2, with the same name. \item For every segment, those Audience Targeting rules with an equivalent in Liferay DXP 7.2 have been migrated into the corresponding criteria fields (see Table below). \item Audience Targeting tables have been removed from your Liferay DXP Database. \end{itemize} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.3333}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.3333}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.3333}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright Audience Targeting Rule \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Liferay DXP 7.2. Segment Criteria Field \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Upgrade Path \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Browser & Browser & Automated. Use user agent field with \texttt{contains} operation as an alternative \\ Custom Field & Custom Field & Automated \\ Language & Language & Automated \\ Last Login Date & Last Sign In Date & Automated \\ Organization Member & Organization & Automated \\ OS & User Agent & Automated \\ Previous Visited Site & Not Available & Automated \\ Regular Role & Role & Automated \\ Site Member & Site & Automated \\ User Group Member & User Group & Automated \\ Age & Not Available & Suggested: custom field \\ Facebook (various) & Not Available & Suggested: custom field \\ Gender & Not Available & Suggested: custom field \\ Score Points & Not Available & Suggested: cookie \\ Visited Page/Content & Not Available & Suggested: cookie \\ \end{longtable} \noindent\hrulefill Here's an example user segment as it would appear in Audience Targeting for Liferay DXP 7.1: \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/migrating-audience-targeting-segment.png}} \caption{A Liferay DXP 7.1 Audience Targeting Segment.} \end{figure} And here is the same segment migrated to Liferay 7.2: \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/migrating-new-segment.png}} \caption{A Liferay DXP 7.2 Segment} \end{figure} For those Audience Targeting rules without a direct equivalent, a manual migration is required. If you have any these rules, you can learn about your next steps in \href{/docs/7-2/deploy/-/knowledge_base/d/manually-migrating-from-audience-targeting}{Manual Migration}. \chapter{Manually Migrating from Audience Targeting}\label{manually-migrating-from-audience-targeting} As explained in the previous article, some Audience Targeting rules do not have a direct equivalent in Liferay DXP 7.2 and, therefore, they cannot be migrated automatically. Here are the recommended solutions for each rule type. \section{User Attribute Rules}\label{user-attribute-rules} Some User Attributes, like Gender or Age, do not have a direct equivalent in 7.0. User Attributes retrieved from external sources like Facebook also do not have a replacement. To replace these, you must create a \href{/docs/7-2/user/-/knowledge_base/u/creating-segments-with-custom-fields-and-session-data}{custom user field} and use that to define your new Segment. \section{Session Rules}\label{session-rules} For Session attributes that do not have a direct equivalent, the recommended solution is to use a URL field for the current URL or a previously visited URL on your site as criteria, or to use a Cookie for more advanced session tracking needs. \section{Behavior Rules}\label{behavior-rules} In 7.0 analytics is now managed through Analytics Cloud. You can learn more about creating behavior based rules in the \href{https://help.liferay.com/hc/en-us/articles/360006947671-Creating-Segments}{Analytics Cloud documentation}. \section{Migrating Custom Rules}\label{migrating-custom-rules} Audience Targeting segmentation features could be extended using custom rules. As part of the upgrade planning process, the function of any such rules should be re-evaluated with the new Segmentation features of 7.0 in mind. First, check the \href{/docs/7-2/reference/-/knowledge_base/r/defining-segmentation-criteria}{Segmentation reference} if any new criteria fields can replace their function. In particular, custom fields, URL fields, and cookies might help you migrate your custom rules with little to no additional development. If none of them cover your requirements, follow the development guide for instructions on \href{/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization}{how to add new criteria fields and contributors}. \section{Migrating Display Portlets}\label{migrating-display-portlets} With Audience Targeting, you could display personalized content with the User Segment Display Content portlet or by using Asset Publisher with the \href{https://help.liferay.com/hc/en-us/articles/360018174271-Using-the-Audience-Targeting-Widgets-}{Segments filter enabled}. In 7.0, you must choose the most appropriate personalization option for your use cases. \section{User Segment Content Display}\label{user-segment-content-display} The User Segment Content Display portlet was used to display existing content based on segment membership rules. In 7.0, you can cover the same use case by defining manual content sets with variations for your different audiences and applying it to an asset publisher. See the documentation for \href{/docs/7-2/user/-/knowledge_base/u/content-set-personalization}{creating personalized Content Sets}. With this feature, you can assign any number of assets to the Content List for the given audience, and then use the Asset Publisher to define how content is displayed on the page. \section{Asset Publisher Personalization}\label{asset-publisher-personalization} Finally, if you want to display a dynamic list of content for your different audiences based on a filter in the same way you did with in Audience Targeting with the Segments filter in the Asset Publisher, you can create a dynamic content set with variations for your audiences and apply it to an asset publisher. In addition, the new \href{/docs/7-2/user/-/knowledge_base/u/content-page-personalization}{Experience-based Content Page personalization} feature may fulfill a use case that you were previously solving with one of the methods previously available. \chapter{Deprecated Apps in 7.2: What to Do}\label{deprecated-apps-in-7.2-what-to-do} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} During the development of any software product, it's sometimes necessary to stop development on or remove outdated or unpopular features. 7.0 is no different. In 7.0, Liferay has deprecated several apps and features. There are three types of deprecated apps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Deprecated apps that remain in Liferay DXP, but will be removed in a future release. (Availability: \emph{Bundled}) \item Deprecated apps that have been removed from Liferay DXP, yet are still available for download via \href{https://web.liferay.com/marketplace}{Liferay Marketplace} (Availability: \emph{Marketplace}) \item Deprecated apps that have been removed from Liferay DXP and aren't available for download. (Availability: \emph{Removed}) \end{enumerate} \noindent\hrulefill \textbf{Note:} All apps deprecated by Liferay are no longer in active development. You should therefore plan to stop using these apps. Such apps, however, may still be available for download. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} For information on apps deprecated in Liferay DXP 7.1, please see \href{/docs/7-1/deploy/-/knowledge_base/d/deprecated-apps-in-7-1-what-to-do}{Deprecated Apps in 7.1: What to Do} \noindent\hrulefill Here are the apps deprecated in 7.0. \section{Foundation}\label{foundation} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.1364}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.5909}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.2727}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright App \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Availability \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Notes \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot AlloyUI & Bundled & Replaced by \href{https://metaljs.com/}{MetalJS} (temporary) exposed as \href{/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs}{ClayUI tag} equivalents. \\ CMIS Store & Removed & Migrate to another \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Store option}. Before \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{upgrading to 7.0}, migrate your document store data using \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{Data Migration in Server Administration}. \\ JCRStore & Removed & Migrate to another \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Store option}. Before \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{upgrading to 7.0}, migrate your document store data using \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{Data Migration in Server Administration}. \\ Legacy Search Portlet & Bundled & Will be removed in a future release. Replaced by the \href{/docs/7-2/user/-/knowledge_base/u/search}{Search widgets}. \\ Liferay Mobile Device Detection Enterprise & Removed & Contact 51Degrees for up-to-date definitions. \\ Sprite framework & Bundled & Liferay's image sprite framework is deprecated and is disabled by default via the \texttt{sprite.enabled} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal property}. You can still build image sprites using any framework you like and deploy them in your plugins. \\ \end{longtable} \noindent\hrulefill \section{Personalization}\label{personalization} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.1364}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.5909}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.2727}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright App \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Availability \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Notes \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Audience Targeting & Removed & Replaced by \href{/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization}{Personalization}. \\ \end{longtable} \noindent\hrulefill \section{Web Experience}\label{web-experience} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.1364}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.5909}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.2727}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright App \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Availability \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Notes \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot RSS Publisher & Bundled & See \href{/docs/7-1/user/-/knowledge_base/u/the-rss-publisher-widget}{the article} on enabling and using this widget. \\ User Group Pages (Copy Mode) & Bundled & See the \href{/docs/7-1/user/-/knowledge_base/u/user-group-sites\#legacy-user-group-sites-behavior}{Legacy User Group Sites Beahavior} instructions on how to enable it. \\ Resources Importer & Bundled & Deprecated as of 7.1 with no direct replacement \\ \end{longtable} \noindent\hrulefill \section{Forms}\label{forms} \noindent\hrulefill \begin{longtable}[]{@{}lll@{}} \toprule\noalign{} App & Availability & Notes \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Web Form & Removed & Final version released for 7.0. \\ \end{longtable} \noindent\hrulefill \section{Security}\label{security-1} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.0938}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.5625}} >{\raggedright\arraybackslash}p{(\linewidth - 4\tabcolsep) * \real{0.3438}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright App \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Availability \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Notes \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Central Authentication Service (CAS) & Bundled & Migrate to \href{https://help.liferay.com/hc/en-us/articles/360028711032-Introduction-to-Authenticating-Using-SAML}{SAML based authentication}. \\ Google Login & Marketplace & Replaced by \href{/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-openid-connect}{OpenID Connect}. \\ NTLM & Marketplace & Replaced by \href{/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-kerberos}{Kerberos}. \\ OpenAM / OpenSSO & Bundled & Migrate to \href{https://help.liferay.com/hc/en-us/articles/360028711032-Introduction-to-Authenticating-Using-SAML}{SAML based authentication}. \\ OpenID & Marketplace & Replaced by \href{/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-openid-connect}{OpenID Connect}. \\ \end{longtable} \noindent\hrulefill \section{User and System Management}\label{user-and-system-management} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.1429}} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.8571}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright App \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Availability \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Live Users & Enabled through the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html}{\texttt{live.users.enabled}} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal property}. \\ \end{longtable} \noindent\hrulefill \section{Related Topics}\label{related-topics-7} \href{/docs/7-2/deploy/-/knowledge_base/d/apps-in-maintenance-mode}{Apps in Maintenance Mode} \chapter{Apps in Maintenance Mode}\label{apps-in-maintenance-mode} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} At a designated time, Liferay may cease enhancing a product or capability. This is called \emph{maintenance mode}. During this mode, Liferay actively supports and provides bug fixes for the product or capability in accordance with the subscribers' subscription level and the end of service life policies of the compatible Liferay DXP version. Maintenance mode does not necessarily mean that deprecation in a future Liferay DXP version is planned for the product or capability; it only means that enhancements aren't being made for the current Liferay DXP development cycle. As of Liferay DXP 7.2, these products and capabilities have transitioned into maintenance mode: \begin{itemize} \tightlist \item Liferay Connected Services \item Liferay Connector to OAuth 1.0a \item Liferay Drools \item Liferay Mobile Experience (Liferay Screens, Liferay Mobile SDK, Liferay Push) \item Liferay Reports \item Liferay Sync \item Staging \end{itemize} \section{Related Topics}\label{related-topics-8} \href{/docs/7-2/deploy/-/knowledge_base/d/deprecated-apps-in-7-2-what-to-do}{Deprecated Apps in 7.2: What to do?} \chapter{Maintaining Liferay DXP}\label{maintaining-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Once you have a Liferay DXP installation, there are some things you must do to keep it running smoothly. Backing up your installation in case of a hardware failure protects your data and helps you get your system back in working order quickly. And if you're a DXP customer, patching your system regularly brings the latest bug fixes to your running instance. \noindent\hrulefill Upgrading 7.0 to a new GA version can require \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{data upgrade}. Until you perform all required data upgrades (if any), Liferay DXP startup fails with messages like these: \begin{verbatim} 2019-03-06 17:22:35.025 INFO [main][StartupHelper:72] There are no patches installed You must first upgrade to Liferay DXP 7210 2019-03-06 17:22:35.098 ERROR [main][MainServlet:277] java.lang.RuntimeException: You must first upgrade to Liferay DXP 7201 \end{verbatim} \noindent\hrulefill Read on to learn about how to keep your system running well. \chapter{Patching Liferay DXP}\label{patching-liferay-dxp} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} While we strive for perfection with every Liferay DXP release, the reality of the human condition dictates that releases may not be as perfect as originally intended. But we've planned for that. Included with every Liferay DXP bundle is a Patching Tool that handles installing two types of patches: fix packs and hotfixes. \noindent\hrulefill \textbf{Important:} Make sure to \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{back up your Liferay DXP installation and database} regularly, especially before patching. The patching tool installs code changes and some of these make data changes (if necessary) automatically on startup. Certain fix packs (service packs) can include data/schema micro changes---they're optional and revertible. Module upgrades and any micro changes they include are applied at server startup by default, or can be applied manually by \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade\#configuring-non-core-module-data-upgrades}{disabling the \texttt{autoUpgrade} property}. Server startup skips all Core micro changes. Instead, you can apply them using the \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{upgrade tool} before server startup. \noindent\hrulefill \noindent\hrulefill \textbf{Important:} Installing the latest service pack on top of Liferay DXP 7.2 GA1/FP1 requires running the \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data}{data upgrade tool}. Examine the \href{/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver}{Liferay DXP upgrade instructions} to determine preparations, testing, and post upgrade steps that are appropriate for you. To eliminate system down time during upgrade, consider using the \href{/docs/7-2/deploy/-/knowledge_base/d/other-cluster-update-techniques}{Blue-green deployment technique}. Liferay DXP 7.2 FP2/SP1's data schema change adds version columns to several tables. \href{https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html\#d0e2225}{Hibernate's optimistic locking system} uses the columns to preserve data integrity during concurrent data modifications. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} \href{/docs/7-2/deploy/-/knowledge_base/d/updating-a-cluster}{Patching a cluster} requires additional considerations. \chapter{Patching Basics}\label{patching-basics} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay ships 7.0 fixes through three different channels: \begin{itemize} \tightlist \item Fix packs \item Hotfixes \item Service Packs \end{itemize} \section{Fix Packs}\label{fix-packs} The latest fixes that patch the core are bundled together weekly into fix packs that are provided to all of Liferay's customers. Fix packs include fixes for both the core and the applications and modules that ship with Liferay DXP. The fixes address regressions or obvious bugs and don't require you to make additional changes. Each fix pack contains all previous fix packs since the last service pack. \href{https://help.liferay.com/hc/en-us/articles/360035038331}{Security Fix Packs} are a special fix pack type for deploying critical security fixes quickly without changing fix pack levels. Fixes that don't fit these requirements are considered for service packs or hot fixes. \section{Hotfixes}\label{hotfixes} A hotfix is provided to customers when they contact Liferay about an emergency situation, and Liferay's support team---working with the customer---determines that the problem is a product issue that must be fixed very quickly. Support fixes the bug and provides a hotfix to the customer immediately. This is a short-term fix. Hotfixes can patch the core, the applications, and modules. \section{Service Packs}\label{service-packs} Service packs are built on top of the original Liferay DXP release and repackaged with the latest fix pack, Patching Tool, and modules. Since a service pack is a fix pack, it contains all previous fix packs since the last service pack. Each one includes the most recent patches and updates. Service packs can also include changes that have these characteristics: \begin{itemize} \tightlist \item Require more extensive testing. \item Require some of your attention, such as updating your documentation. \item Improve the product. \end{itemize} Rather than updating existing Liferay DXP systems with service packs, you should \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Keep systems up-to-date with fix packs (according to your own deployment schedule). \item Install the latest Marketplace updates frequently. \item Update the Patching Tool when necessary. \end{enumerate} This method updates the installation to the service pack levels, while allowing scheduled deployments and avoiding full environment rebuilds. \section{How Patches are Tested}\label{how-patches-are-tested} Liferay extensively tests service packs, fix packs, and hotfixes to ensure high quality. Fixes in fix packs go through both automated regression testing and manual testing. Hotfixes receive similar automated testing, and the support engineer who fixes a reported issue tests it. Before releasing a service pack, Liferay runs test suites on the packaged service pack. \chapter{Using the Patching Tool}\label{using-the-patching-tool} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The Patching Tool installs, removes, compares, and prepares Liferay DXP patches. It is pre-installed in Liferay DXP bundles, easy to install into @product@ manual installations, and easy to update. The Patching Tool's executable scripts facilitate patching. Here are the essentials to get started using the Patching Tool: \begin{itemize} \tightlist \item \hyperref[installing-the-patching-tool]{Installing the Patching Tool} (for manual installations only) \item \hyperref[executables]{Executables} \end{itemize} \section{Installing the Patching Tool}\label{installing-the-patching-tool} Liferay DXP bundles come with the Patching Tool pre-installed (in \texttt{{[}Liferay\ Home{]}/patching-tool}) and pre-configured with the default settings. Skip this section if you're using a bundle. If you installed Liferay DXP manually, however, you must also install the Patching Tool manually. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Download the Patching Tool from the \href{https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066}{Customer Portal}. \item Unzip the Patching Tool to your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder (recommended) or to another folder. \end{enumerate} After installing the Patching Tool, you must \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-patching-tool}{configure it to use your Liferay DXP installation}. The \texttt{patching-tool} folder you extracted from the Patching Tool ZIP file contains the Patching Tool, including its executable scripts. \section{Executables}\label{executables} The Unix shell and Windows batch scripts distributed with the Patching Tool make it easier to use. On Unix systems, run \begin{verbatim} ./patching-tool.sh parameters \end{verbatim} On Windows, run \begin{verbatim} patching-tool parameters \end{verbatim} The Windows command \texttt{patching-tool} is used in the examples that follow. On Unix, replace the name of the executable before running the commands. Installing patches is next! \chapter{Installing Patches}\label{installing-patches} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Before installing any patches, you must shut down your server. On Windows operating systems, files in use are locked by the OS, and can't be patched. On Unix-style systems, you can usually replace files that are running, but the old ones reside in memory. For these reasons, it is best to shut down Liferay DXP before installing patches. Liferay distributes all patches (fix packs and hotfixes) as ZIP files. When you download a patch, either from a \href{https://help.liferay.com/hc}{Help Center} ticket (hotfix) or from the \href{https://customer.liferay.com/downloads}{Customer Portal} (fix pack), place it in the Patching Tool's \texttt{patches} folder (e.g., \texttt{{[}Liferay\ Home{]}/patching-tool/patches}) without unzipping it. To list your installed patches and available local patches, execute this command: \begin{verbatim} patching-tool info \end{verbatim} This displays a list of patches you've already installed, along with a list of patches that \emph{can} be installed from what's in the \texttt{patches} folder. To install the available patches, use the following steps. First, issue the following command: \begin{verbatim} patching-tool install \end{verbatim} To make sure the all changed OSGi bundles replace the existing ones, delete the \texttt{osgi/state} folder from the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home folder}. \noindent\hrulefill \textbf{Note}: The \texttt{osgi/state} folder contains OSGi bundle state information. If an OSGi bundle's changes in a hot fix or fix pack are internal only, they are invisible to the OSGi framework, that OSGi bundle stays installed, and its state information stays unchanged. Hot fixes, for example, may contain in-place changes that do not use the API. The framework cannot detect such changes. A fix pack's changes may also be transparent to the framework. For these reasons, deleting the \texttt{osgi/state} folder after applying fix packs and hot fixes is recommended. \noindent\hrulefill \noindent\hrulefill \textbf{Important}: The \texttt{osgi/state} folder should ONLY be deleted when working in a development environment or when applying a fix pack or hot fix. \noindent\hrulefill If there are new database indexes created by the patch, the Patching Tool tells you to update them. To get the list, run this command: \begin{verbatim} patching-tool index-info \end{verbatim} Since there's no database connection at patching time, the indexes must be created at portal startup. If the server has permissions to modify the database indexes, instruct Liferay DXP to create the indexes automatically at startup by adding this setting to your \texttt{portal-ext.properties} file: \begin{verbatim} database.indexes.update.on.startup=true \end{verbatim} Otherwise, you must create the indexes manually. Check the \texttt{patching-tool\ index-info} command output for more details. After installing patches, you can execute the \texttt{patching-tool\ info} command to verify them. \noindent\hrulefill \textbf{Note:} If there are any issues with the installed patches, verify that there aren't any remaining files from the previous patch installation of a fix pack or hotfix within the application server cache. \noindent\hrulefill During the installation, \texttt{patching-backup-deps.zip} and \texttt{patching-backup.zip} files are created and stored in the web application's \texttt{WEB-INF} folder. These files are required to restore the Liferay DXP's original state; removing them disables patching. \noindent\hrulefill \textbf{Note:} When installing patches, Liferay DXP's \texttt{web.xml} is always overwritten by the one contained in the patch. If you've customized \texttt{web.xml}, you must re-implement your customizations after installing a patch. \noindent\hrulefill The \texttt{patching-backup.zip} file is necessary for installing future patches, because the Patching Tool reverts the installed fix pack before installing a new one. To revert the installed fix pack, it examines the contents of the \texttt{patching-backup.zip} to determine the changes that it needs to revert. \section{Handling Hotfixes and Patches}\label{handling-hotfixes-and-patches} As stated previously, hotfixes are short term fixes provided as quickly as possible, and fix packs are larger bundles of hotfixes provided to all customers at regular intervals. If you already have a hotfix installed and the fix pack that contains that hotfix is released, the Patching Tool can manage this for you. Fix packs always supersede hotfixes; so when you install your fix pack, the hotfix it contains is uninstalled and the fix pack version is installed in its place. The Patching Tool applies fixes to fix packs automatically. If a new (fixed) version of a fix pack is released, install it with the Patching Tool. The Patching Tool uninstalls the old fix pack and installs the new version in its place. \section{Fix Pack Dependencies}\label{fix-pack-dependencies} Some hotfixes depend on fix packs. If you attempt to install a hotfix that depends on a fix pack, the Patching Tool notifies you. Go to the \href{hhttps://customer.liferay.com/downloads}{Customer Portal} and obtain the hotfix dependency. Once all the necessary patches are available in the \texttt{patches} folder, the Patching Tool installs them. \section{Updating the Patching Tool}\label{updating-the-patching-tool} When a patch you're trying to install requires a Patching Tool update, the Patching Tool tells you. To update the Patching Tool, download the latest one from the \href{https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066}{Customer Portal}. Overwrite the existing Patching Tool by unzipping the new one to the \texttt{patching-tool} folder's parent folder. \section{Cleaning Up}\label{cleaning-up} After you've performed your patching procedure (whether you've installed or \href{/docs/7-2/deploy/-/knowledge_base/d/working-with-patches\#uninstalling-patches}{removed patches}), it's important to clean up Liferay DXP's cache of deployed code. This ensures that you're using the revision you've just installed the patches for when you start the server. This is really easy to do. To clear out the cached code, remove the contents of the \texttt{{[}Liferay\ Home{]}/work} folder. Now you're ready to start your server. \chapter{Working with Patches}\label{working-with-patches} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Here are some things you might need to do with patches: \begin{itemize} \tightlist \item Report Patch Levels to Liferay Support \item Uninstall Patches \item Show collisions between patches and deployed plugins \item Separate Patches from your Installation \end{itemize} Start with reporting patch levels to Liferay Support. \section{Including support-info in Support Tickets}\label{including-support-info-in-support-tickets} Providing your environment's information (e.g., hardware architecture) and patch level to Liferay Support is critical for reproducing your issues. Write your support information (including your patch level) to a file by executing this command: \begin{verbatim} patching-tool support-info \end{verbatim} The support information is written to file \texttt{patching-tool-support-info-actual-timestamp.txt} in your \texttt{patching-tool} folder. Please upload this file to the \href{https://help.liferay.com/hc}{Help Center} ticket. \section{Uninstalling Patches}\label{uninstalling-patches} Have you noticed that the Patching Tool only seems to have an \texttt{install} command? This is because patches are managed not by the command, but by what appears in the \texttt{patches} folder. You manage the patches you have installed by adding or removing patches from this folder. Here's how to uninstall (remove) a patch: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Remove the patch from your \texttt{patches} folder. \item Run the \texttt{patching-tool\ install} command. \end{enumerate} To revert ALL patches, run this command: \begin{verbatim} patching-tool revert \end{verbatim} Now you know how to remove and revert patches. You can also compare patch levels. See the \href{/docs/7-2/deploy/-/knowledge_base/d/comparing-patch-levels}{reference guide} for a list of the available commands. \section{Showing collisions between patches and deployed plugins}\label{showing-collisions-between-patches-and-deployed-plugins} Some patches update files you might have customized via a plugin. The \texttt{patching-tool\ list-collisions} command lists differences (collisions) between installed patch files and your plugin's version of them. Here's the command: \begin{verbatim} patching-tool list-collisions \end{verbatim} It is an alias for the following diff command: \begin{verbatim} patching-tool diff collisions files _base \end{verbatim} \texttt{\_base} is the literal patch level name. Collisions are only listed for installed patches that contain source code files. \noindent\hrulefill \textbf{Note:} As of Patching Tool 2.0.9, \texttt{patching-tool\ list-collisions} lists only JSP file collisions in fragment bundles. \noindent\hrulefill \section{Separating Patches from the Installation}\label{separating-patches-from-the-installation} The Patching Tool's \texttt{separate} command helps reduce the patched Liferay DXP installation size. If the installation has been patched, you can make it smaller by moving the restore files out of it. Patched installations are large because the restore files are stored inside the web application's \texttt{WEB-INF} folder by default. These files are required for patching the installation again. If these files are removed, subsequent patching processes fail. Because of this, Liferay added an option to separate the patching files from the installation while still preserving and restoring them safely when new patches arrive. To do this, use this command: \begin{verbatim} patching-tool separate [separation_name] \end{verbatim} This command produces a \texttt{liferay-patching-files-{[}separation-name{]}.zip} file in the Patching Tool's \texttt{patches} folder. It contains the necessary files and metadata for patching, verification, and validation. Once you create this file, the patch files are removed from their default location and are now only available in this file. You can store this file elsewhere to reduce your installation's size. \textbf{WARNING:} If the product is separated from its patches in this way, you cannot run most of the Patching Tool commands until the patches are restored. After the separation process only the following commands can be used: \begin{itemize} \tightlist \item \texttt{auto-discovery} \item \texttt{info} \item \texttt{setup} \end{itemize} Any other command returns this: \begin{verbatim} This installation does not include data for patching. Please copy the liferay-patching-files-[separation-name].zip file into the 'patches' directory and run patching-tool setup. \end{verbatim} This is how you restore the patch files to your system. Details below. \section{Restoring the Separated Patch Files}\label{restoring-the-separated-patch-files} When you need to patch Liferay DXP again, you must restore the separated patch artifact. To do this, copy the \texttt{liferay-patching-files-{[}separation-name{]}.zip} back to the Patching Tool's \texttt{patches} folder and run \texttt{patching-tool\ setup} command. The command finds the necessary patching artifact and restores the patch files to the installation. After that, the Patching Tool works like it did prior to separating the patches. \chapter{Configuring the Patching Tool}\label{configuring-the-patching-tool} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The Patching Tool installs Liferay DXP patches. It ships with prepackaged Liferay DXP bundles. If any of the following scenarios describes your @product@ installation, however, you must configure the Patching Tool manually: \begin{itemize} \tightlist \item Installed Liferay DXP manually on an existing application server \item Customized your Liferay DXP folder structure \item Running in a cluster \end{itemize} If none of the above scenarios describe your installation, you can skip this section. If you installed Liferay DXP manually, you must also install the Patching Tool manually. Download it from the \href{https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066}{Customer Portal}. Unzipping it to your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder is the easiest way to use it. Read on to configure the Patching Tool for your environment. \chapter{Patching Tool Basic configuration}\label{patching-tool-basic-configuration} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} There are two ways to configure the Patching Tool: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Automatically by executing the \texttt{auto-discovery} command \item Manually by editing the configuration file (see \href{/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration}{Patching Tool Advanced Configuration}) \end{enumerate} Automatic configuration generates the configuration files by looking for Liferay DXP files in the local file system. By default the Patching Tool looks for them in its parent folder. To start the process, run this command in your Patching Tool folder (\texttt{patching-tool}): \begin{verbatim} patching-tool auto-discovery \end{verbatim} If Liferay DXP is not installed in the parent folder, specify its location: \begin{verbatim} patching-tool auto-discovery /opt/liferay-dxp \end{verbatim} If you specified the wrong location of Liferay DXP or it is not in the parent folder, the Patching Tool can't find the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} and reports an error like this: \begin{verbatim} The .liferay-home has not been detected in the given directory tree. Configuration: patching.mode=binary war.path=../tomcat-9.0.17/webapps/ROOT/ global.lib.path=../tomcat-9.0.17/lib/ext/ liferay.home=**[please enter manually]** The configuration hasn't been saved. Please save this to the default.properties file. \end{verbatim} Here are ways to resolve the Liferay Home issue: \begin{itemize} \tightlist \item Specify the Liferay Home path in the \texttt{default.properties} file. \item If the Liferay Home is in the Patching Tool's tree, create a \texttt{.liferay-home} file in the Liferay Home folder and re-run the auto-discovery process. \end{itemize} When the Patching Tool is configured, running \texttt{patching-tool\ info} reports product version information. That's it! Now that you've installed and configured the Patching Tool, you're ready to download and install patches. \chapter{Patching Tool Advanced Configuration}\label{patching-tool-advanced-configuration} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} By default, the Patching Tool's configuration file called \texttt{default.properties} is in the tool's folder. A Patching Tool configuration file typically looks like this: \begin{verbatim} patching.mode=binary war.path=../tomcat-9.0.17/webapps/ROOT/ global.lib.path=../tomcat-9.0.17/lib/ext/ liferay.home=../ \end{verbatim} The properties above (described fully in \href{/docs/7-2/deploy/-/knowledge_base/d/patching-tool-configuration-properties}{Patching Tool Configuration Properties}) define the location of \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}, the patching mode (binary or source), the path to where WAR files are deployed in the app server, and the global library path. The tool's auto-discovery bases the OSGi module framework paths on the Liferay Home. If, however, you changed the OSGi module framework paths to something different than those under the default folder \texttt{{[}Liferay\ Home{]}/osgi}, you must manually specify the following properties: \begin{verbatim} module.framework.core.path=path_to_modules_core_dir module.framework.marketplace.path=path_to_modules_marketplace_dir module.framework.modules.path=path_to_modules_modules_dir module.framework.portal.path=path_to_modules_portal_dir module.framework.static.path=path_to_modules_static_dir \end{verbatim} Using auto-discovery and working with the default profile \texttt{default.properties} is the easiest way to use the Patching Tool, and is great for smaller, single server installations. But many Liferay DXP installations serve millions of pages per day, and the Patching Tool has been designed for this as well. So if you're running a small, medium, or large cluster of Liferay DXP machines, you can use the Patching Tool profiles to manage patching for all of them. \section{Using Profiles with the Patching Tool}\label{using-profiles-with-the-patching-tool} You can create profiles for multiple runtimes by running auto-discovery or creating them manually. To auto-discover other runtimes, run the Patching Tool with parameters like this: \begin{verbatim} ./patching-tool.sh [name of profile] auto-discovery [path/to/Liferay Home] \end{verbatim} This runs the same discovery process, but on the path you specify. It writes the profile information to a file called \texttt{{[}name\ of\ profile{]}.properties}. Alternatively, you can manually create profile property files in your \texttt{patching-tool} folder. See \href{/docs/7-2/deploy/-/knowledge_base/d/patching-tool-configuration-properties}{Patching Tool configuration properties} (profile properties) for a complete list of the available configuration properties. You can have as many profiles as you want and use the same Patching Tool to patch all of them. This helps to keep all your installations in sync. \chapter{Installing patches on the 7.0 WAR}\label{installing-patches-on-the-7.0-war} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} If you \href{/docs/7-1/deploy/-/knowledge_base/d/installing-product-on-tomcat}{installed Liferay DXP manually} as a WAR file on a supported application server, you must apply patches to the WAR file and supporting files and re-deploy them. This article shows you how to do that. \section{Prerequisites}\label{prerequisites-1} Download the necessary artifacts from the \href{https://customer.liferay.com/downloads}{Customer Portal:} \begin{itemize} \tightlist \item Liferay DXP WAR file (\texttt{liferay-dxp-{[}version{]}.war}) \item Dependencies ZIP file (\texttt{liferay-dxp-dependencies-{[}version{]}.zip}) \item OSGi JARs ZIP file (\texttt{liferay-dxp-osgi-{[}version{]}.zip}) \item \href{https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066}{Latest Patching Tool} \end{itemize} \section{Install the patch on the Liferay DXP WAR and artifacts}\label{install-the-patch-on-the-liferay-dxp-war-and-artifacts} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create an arbitrary folder. Unzip the dependency artifacts and the Patching Tool into it. The folder contents should look like this: \begin{itemize} \tightlist \item \texttt{{[}patching-home{]}/} \begin{itemize} \tightlist \item \texttt{liferay-dxp-dependencies-{[}version{]}/} ← Unzipped Dependencies \item \texttt{osgi/} ← Unzipped OSGi JARs \item \texttt{patching-tool/} ← Unzipped Patching Tool \item \texttt{liferay-dxp-{[}version{]}.war/} ← Liferay DXP WAR File \end{itemize} \end{itemize} \item Create the default profile configuration file in the Patching Tool folder: \texttt{patching-home/patching-tool/default.properties}. The contents should look like this: \end{enumerate} \begin{verbatim} patching.mode=binary war.path=../../patching-home/liferay-dxp-[version].war global.lib.path=../../patching-home/liferay-dxp-dependencies-[version] liferay.home=../../patching-home \end{verbatim} \begin{verbatim} If you're using a different OSGi folder structure, you can specify it as the [Patching Tool Advanced Configuration](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration) documentation describes: \end{verbatim} \begin{verbatim} module.framework.core.path=/osgi-home/osgi/core module.framework.marketplace.path=/osgi-home/osgi/marketplace module.framework.modules.path=/osgi-home/osgi/modules module.framework.portal.path=/osgi-home/osgi/portal module.framework.static.path=/osgi-home/osgi/static \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item Download the patch (fix pack or hotfix) to install and put it in a folder called \texttt{patches} in your Patching Tool folder (i.e.~\texttt{{[}patching-home{]}/patching-tool/patches}). \item Execute the Patching Tool's \texttt{info} command: \end{enumerate} \begin{verbatim} /patching-home/patching-tool> patching-tool info Loading product and patch information... Product information: * installation type: binary * build number: 7210 * service pack version: - available SP version: Not available - installable SP version: Not available * patching-tool version: 2.0.12 * time: 2019-06-03 18:30Z * host: 91WRQ72 (8 cores) * plugins: no plugins detected Currently installed patches: - ... \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{4} \tightlist \item Install the patch. \end{enumerate} \begin{verbatim} /patching-home/patching-tool> patching-tool.sh install One patch is ready to be installed. Applying dxp... Cleaning up: [1%..10%..20%..30%..40%..50%..60%..70%..80%..90%..100%] Installing patches: [1%..10%..20%..30%..40%..50%..60%..70%..80%..90%...100%] The installation was successful. One patch is installed on the system. \end{verbatim} Great! You have successfully patched the artifacts, and they are ready to be deployed on any supported Application Server. \section{Related Topics}\label{related-topics-9} \href{/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration}{Patching Tool Advanced Configuration} \href{/docs/7-2/deploy/-/knowledge_base/d/deploying-product}{Deploying Liferay DXP} \chapter{Keeping up with Fix packs and Service Packs}\label{keeping-up-with-fix-packs-and-service-packs} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The \emph{Announcements} section on \href{https://help.liferay.com/hc}{Liferay's Help Center page} lists all fix pack updates, security alerts, product releases, and system updates. The approximate frequency of fix pack and service pack releases is explained \href{/docs/7-2/deploy/-/knowledge_base/d/patching-basics}{here}. The \emph{Receive Notifications} sidebar lets you subscribe to the latest updates on products, patches, and system improvements. Click \emph{Downloads} on the Liferay Digital Experience Platform page to access: \begin{itemize} \tightlist \item Latest Release \item Fix Packs \item Service Packs Archive \item Security Advisories \item Patching Tool \end{itemize} Click \emph{Support Information} to access the compatibility matrix, support FAQs, and more. \chapter{Backing up a Liferay DXP Installation}\label{backing-up-a-liferay-dxp-installation} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Once you have an installation of Liferay DXP running, you should implement a comprehensive backup plan. In case some kind of catastrophic hardware failure occurs, you'll be thankful to have backups and procedures for restoring Liferay DXP from one of them. @product@ isn't very different from other Java web applications that might be running on your application server. Nevertheless, there are some specific components you should include in your backup plan. The recommended backup plan includes backing up these things: \begin{itemize} \tightlist \item Source code \item Liferay DXP's file System \item Liferay DXP's database \end{itemize} \section{Backing up Source Code}\label{backing-up-source-code} If you have extended Liferay DXP or have written any plugins, they should be stored in a source code repository such as Git, Subversion, or CVS, unless you're Linus Torvalds, and then tarballs are okay too (that's a joke). You should back up your source code repository on a regular basis to preserve your ongoing work. This probably goes without saying in your organization since nobody wants to lose source code that's taken months to produce. Thus you should include source code in your Liferay DXP backup plan. Next, let's examine the Liferay DXP installation items you should back up. \section{Backing up Liferay DXP's File System}\label{backing-up-liferay-dxps-file-system} The \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home folder} stores Liferay DXP's properties configuration files, such as \texttt{portal-setup-\ wizard.properties} and \texttt{portal-ext.properties}. You should absolutely back them up. In fact, it's best to back up your entire application server and Liferay Home folder contents. Liferay DXP stores configuration files, search indexes, and cache information in Liferay Home's \texttt{/data} folder. If you're using the File System store or the Advanced File System store, the documents and media repository is also stored here by default. It's always important to back up your \texttt{/data} folder. The files that comprise Liferay DXP's OSGi runtime are stored in Liferay Home's \texttt{/osgi} folder. It contains all of the app and module JAR files deployed to Liferay DXP. The \texttt{/osgi} folder also contains other required JAR files, configuration files, and log files. It's also important to back up your \texttt{/osgi} folder. Liferay Home's \texttt{/logs} folder contains Liferay DXP's log files. If a problem occurs on Liferay DXP, the @product@ log files often provide valuable information for determining what went wrong. The \texttt{/data}, \texttt{/osgi}, and \texttt{/logs} folders are all contained in the Liferay Home folder. Thus, if you're backing up both your application server folder and your Liferay Home folder, you're in good shape. Remember that if you've configured the document library to store files to a location other than the default location, you should also back up that location. That covers the Liferay DXP file system locations you should back up. Next, let's discuss how to back up Liferay DXP's database. \section{Backing up Liferay DXP's Database}\label{backing-up-liferay-dxps-database} Liferay DXP's database is the central repository for all of the portal's information. It's the most important component to back up. You can back up the database live (if your database allows this) or by exporting (dumping) the database into a file and then backing up the exported file. For example, MySQL ships with a \texttt{mysqldump} utility which lets you export the entire database and data into a large SQL file. This file can then be backed up. On restoring the database you can import this file into the database to recreate the database state to that of the time you exported the database. If you're storing Liferay DXP's Documents and Media Library files to a Jackrabbit JSR-170 repository database, you should back it up. If you've placed your search index into a database (not recommended; see the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering}{Liferay DXP Clustering} article for information on using Cluster Link or Solr), you should back up that database too. If you wish to avoid re-indexing your content after restoring your database, back up your search indexes. This is easiest to do if you have a separate Elastic or Solr environment on which your index is stored. If you're in a clustered configuration and you're replicating indexes, you'll need to back up each index replica. Restoring your application server, your Liferay Home folder, the locations of any file system-based media repositories, and your database from a backup system should give you a functioning portal. Restoring search indexes should avoid the need to re-index when you bring your site back up after a catastrophic failure. Good, consistent backup procedures are key to recovering successfully from a hardware failure. \chapter{Monitoring Liferay DXP}\label{monitoring-liferay-dxp} These articles show you how to monitor Liferay DXP. Monitoring vital statistics such as Java memory heaps, garbage collection, database connection pools, and the application server helps you optimize performance. Better monitoring means better tuning and thus avoids dangerous runtime scenarios like out of memory errors and wasted heap space. You'll learn basic monitoring techniques, such as \begin{itemize} \tightlist \item Using the Visual VM tool and the JMX Console \item Garbage Collection \end{itemize} Read on to learn more about monitoring Liferay DXP! \chapter{Monitoring Garbage Collection and the JVM}\label{monitoring-garbage-collection-and-the-jvm} Although the \href{/docs/7-2/deploy/-/knowledge_base/d/tuning-guidelines}{tuning parameters} give you a good start to JVM tuning, you must monitor GC performance to ensure you have the best settings to meet your needs. There are several tools to help you monitor Oracle JVM performance. \section{VisualVM}\label{visualvm} \href{https://visualvm.github.io/}{VisualVM} provides a centralized console for viewing Oracle JVM performance information and its Visual GC plugin shows garbage collector activities. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/visual-vm-gc.png}} \caption{VisualVM's Visual GC plugin shows the garbage collector in real-time.} \end{figure} \noindent\hrulefill \textbf{Note:} Oracle's JDK has VisualVM bundled (\texttt{\$JAVA\_HOME/bin/jvisualvm}). However, always download and use the latest version from VisualVM's \href{https://visualvm.github.io/}{official website}. \noindent\hrulefill \section{JMX Console}\label{jmx-console} This tool helps display various statistics like Liferay DXP's distributed cache performance, application server thread performance, JDBC connection pool usage, and more. \noindent\hrulefill \textbf{Note:} The JMX Console is the preferred tool for monitoring application server performance. \noindent\hrulefill To enable JMX connections, add these JVM arguments: \begin{verbatim} -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false \end{verbatim} If you're running JMX Console from a another machine, add these JVM arguments too: \begin{verbatim} -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.rmi.port=5000 -Djava.rmi.server.hostname=[place IP address here] \end{verbatim} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/visual-vm-jmx.png}} \caption{VisualVM monitors the JVM using Java Management Extensions.} \end{figure} \section{Garbage Collector Verbose Logging}\label{garbage-collector-verbose-logging} Add these JVM arguments to activate verbose logging for the JVM garbage collector. \begin{verbatim} -verbose:gc -Xloggc:/tmp/liferaygc1.log -XX:+PrintGCDetails -XX:+PrintGCCause -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime \end{verbatim} Examining these logs helps you tune the JVM properly. \noindent\hrulefill \textbf{Note:} Adding these JVM arguments generates a heap dump if an \texttt{OutOfMemoryError} occurs. The dump is written to the heap dump path specified. Specify the path to use: \texttt{-XX:+HeapDumpOnOutOfMemoryError\ -XX:HeapDumpPath=/heap/dump/path/} \noindent\hrulefill Garbage collector log files can grow huge. You can use additional arguments like the following ones to rotate the log to a new log file when the current log file reaches a maximum size: \begin{verbatim} -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=50M \end{verbatim} These arguments rotate the logs to up to \texttt{10} log files with a maximum size of \texttt{50M} each. Now you can monitor garbage collection in the JVM and tune it for top performance. \chapter{Managing Liferay DXP with Liferay Connected Services}\label{managing-liferay-dxp-with-liferay-connected-services} Liferay Connected Services (LCS) is a set of tools and services for managing and monitoring your Liferay DXP instances. LCS can help you install fix packs, monitor your instances' performance, activate your instances, and help you manage your subscriptions. In other words, LCS is like a butler for the mansion that is Liferay DXP. It's like having a single butler that can serve several mansions at once! \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Before going any further, you should take note of a few key terms used throughout this guide: \textbf{Project:} Represents a group of users belonging to a company or organization. For example, a project can consist of all the users from a project team or business unit, or it can include the entire company. \textbf{Environment}: Represents a physical cluster of servers or a virtual or logical aggregation of servers. \textbf{Server}: Describes a concrete Liferay DXP instance. It can be a standalone server or a cluster node. As you go through this guide, you'll cover the following topics: \begin{itemize} \tightlist \item \href{/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs}{Getting Started} \item \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration}{LCS Preconfiguration} \item \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{Registering Your Liferay DXP Server with LCS} \item \href{/docs/7-2/deploy/-/knowledge_base/d/using-lcs}{Using LCS} \item \href{/docs/7-2/deploy/-/knowledge_base/d/troubleshooting-your-lcs-connection}{Troubleshooting Your LCS Connection} \end{itemize} You'll get started with the configuration steps required to use LCS with Liferay DXP. \chapter{Getting Started with LCS}\label{getting-started-with-lcs} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill To use LCS, you must register a server in an LCS environment. An LCS environment represents a physical cluster of servers or a virtual or logical aggregation of servers. Each environment is part of an LCS project. An LCS project represents a group of users belonging to a company or organization. For example, a project can consist of all the users from a project team or business unit, or it can include the entire company. LCS projects don't initially contain any environments. You must therefore create one before you can register any servers in LCS. The first time you log in to \href{https://lcs.liferay.com}{lcs.liferay.com}, LCS presents you with a wizard that walks you through the environment creation process. Click \emph{Get Started} to begin. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-00.png}} \caption{Click \emph{Get Started} to begin the wizard.} \end{figure} Each of these steps corresponds to a step in the wizard: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the LCS project for your new environment. You can select any of your available LCS projects. Note that each project lists its available subscriptions and whether it supports elastic subscriptions. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-01.png}} \caption{Select the LCS project you want to create the environment in, and click \emph{Next}.} \end{figure} \item Name and describe the environment. The name is mandatory, but the description is optional. Although you can enter anything you wish in these fields, it's best to choose a name and description that accurately identify the environment (e.g., Development, Production, Test, etc.). Note that you can change these values after creating the environment. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-02.png}} \caption{Name and describe the environment, then click \emph{Next}.} \end{figure} \item Select the environment's subscription type from the project's available subscriptions. Even if you won't use LCS to activate the servers defined for this environment, you must still select a subscription type. Also note that you can't change this selection after creating the environment. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-03.png}} \caption{Select the environment's subscription type, then click \emph{Next}.} \end{figure} \item Select whether servers that connect to this environment are part of a cluster. LCS provides additional tools in clustered environments that help you manage the cluster. For example, clustered environments show cluster-specific metrics, and fix packs apply to all cluster nodes. There are a few things to keep in mind if you set the environment as clustered: \begin{itemize} \tightlist \item You can't change this setting after creating the environment. \item Each clustered environment can only support nodes that belong to a single cluster. To connect a different cluster's nodes, you must create a separate clustered environment exclusively for those nodes. \item You must set the portal property \texttt{cluster.link.enabled} to \texttt{true} in any servers that connect to a clustered environment. \end{itemize} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-04.png}} \caption{Select whether this is a clustered environment, then click \emph{Next}.} \end{figure} \item Select whether the environment allows elastic subscriptions. Elastic subscriptions let you register an unlimited amount of servers. This is critical for auto-scaling situations in which servers are created and destroyed automatically in response to demand. Elastic environments are also useful for bringing additional servers online on a temporary basis for any other purpose, such as business continuity planning. For more information, see \href{/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions\#elastic-subscriptions}{the documentation on elastic subscriptions}. Also note that you can't change this selection after creating the environment. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-05.png}} \caption{Select whether this is an elastic environment, then click \emph{Next}.} \end{figure} \item Enable the LCS service you want to use with servers that connect to this environment. The following service is available: \textbf{Liferay Instance Activation:} Enabling this lets LCS activate any Liferay DXP instances that connect to the environment. If you disable this service, you must activate via an XML file from Liferay support, and such instances must run version 5.0.0 or newer of the LCS client app. Note that you \textbf{must} use LCS for activation of Elastic subscriptions. Otherwise, you don't have to use LCS for activation. Portal Analytics, Fix Pack Management and Portal Properties Analysis have been removed from the list of available services. For more information about this change, please read \href{https://help.liferay.com/hc/en-us/articles/360037317691-Liferay-Connected-Services-Feature-Deprecation-Update-March-2020}{this article} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-06.png}} \caption{Enable or disable the LCS services you want to use for servers that connect to the environment, then click \emph{Next}.} \end{figure} \item A completed form presents your selections. Review them and make any changes that you want. When you're finished, click \emph{Create Environment}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-onboarding-07.png}} \caption{This form contains each of your selections from the previous steps. Make any changes you want, then click \emph{Create Environment}.} \end{figure} \end{enumerate} After creating your environment, the wizard shows a screen that lets you download the LCS client app, download the environment's token file, and go to your project's dashboard in LCS. Before registering a server in your new environment, however, you must complete the \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration}{preconfiguration steps} for that server. \chapter{LCS Preconfiguration}\label{lcs-preconfiguration} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Before registering your server with LCS, there are a few things you must configure. The sections in this guide walk you through these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item \hyperref[downloading-the-lcs-client-app]{Downloading the LCS Client App} \item \hyperref[preconfiguring-lcs-to-connect-through-a-proxy]{Preconfiguring LCS to Connect Through a Proxy} \item \hyperref[ensuring-access-to-lcs]{Ensuring Access to LCS} \item \hyperref[ntp-server-synchronization]{NTP Server Synchronization} \item \hyperref[configuring-websphere]{Configuring WebSphere}: This is only necessary if you're running Liferay DXP on the WebSphere application server. \item \hyperref[installing-the-lcs-client-app]{Installing the LCS Client App} \end{enumerate} \hyperref[upgrading-the-lcs-client-app]{The last section} in this guide shows you how to upgrade the LCS client app once your server is registered with LCS. We highly recommend that you upgrade the app whenever Liferay releases a new version of it. \noindent\hrulefill \textbf{Note:} You must use LCS for activation of Elastic subscriptions. Otherwise, you don't have to use LCS for activation. You can instead request an XML activation key from Liferay Support. \noindent\hrulefill \section{Downloading the LCS Client App}\label{downloading-the-lcs-client-app} The LCS client app is included in each Liferay DXP bundle and autodeploys when the bundle starts. The included version of the app, however, may be outdated. To get the latest version of the LCS client app, you must first download it via Liferay Marketplace. \noindent\hrulefill \textbf{Note:} Even though Liferay Marketplace and this guide use the term \emph{purchase}, the LCS client app is free of charge. The purchase process for a free app in Liferay Marketplace adds the app to your Liferay project, much like downloading a free app in a mobile app store adds the app to your account. \noindent\hrulefill Use these steps to purchase and download the app (if you've already purchased the app, you can skip to step 3 to download it): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \href{https://web.liferay.com/marketplace/-/mp/application/71774947}{the LCS client app in Liferay Marketplace}. Sign in to Marketplace, then click the LCS client app's \emph{Free} button. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-client-app-marketplace.png}} \caption{Click the app's \emph{Free} button to begin the purchase process.} \end{figure} \item Select your project, accept the license agreement, and then click the \emph{Purchase} button. Marketplace then displays your receipt. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-client-app-receipt.png}} \caption{Liferay Marketplace displays your receipt for the LCS client app.} \end{figure} \item On the receipt, click \emph{See Purchased}. This shows where you can download the LCS client app. To download the app, click the \emph{App} button next to the latest version of the app. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If you must download the LCS client app later, such as when [upgrading it](#upgrading-the-lcs-client-app), select *Purchased Apps* from the User menu at the top-right of Liferay Marketplace. On the Purchased Apps screen, select the project you associated with the LCS client app and then select the app. This takes you to the same downloads page shown in the screenshot. \end{verbatim} \noindent\hrulefill \begin{verbatim} ![ Click the *App* button next to the version of the app you want to download.](./images/lcs-client-download-page.png){./images/lcs-client-download-page.png) \end{verbatim} Great! You've successfully downloaded the LCS client app. Before installing it, however, there are a few additional pre-configuration steps you should complete. These appear next; then you'll learn how to install the app. \noindent\hrulefill \textbf{Note:} If your server connects to the Internet through a proxy, you must configure your server or the LCS client app \textbf{before} deploying the app. The following section contains instructions on this. If your server doesn't connect through a proxy, skip this section. \noindent\hrulefill \section{Preconfiguring LCS to Connect Through a Proxy}\label{preconfiguring-lcs-to-connect-through-a-proxy} If your server connects to the Internet through a proxy, you must set some properties \textbf{before} deploying the LCS client app. There are two ways to do so---chose only one: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \hyperref[jvm-app-server-arguments]{As JVM app server arguments}. \item \hyperref[lcs-client-app-properties]{As LCS client app properties}. \end{enumerate} \noindent\hrulefill \textbf{Note:} Use only one of these methods to configure your server to connect through a proxy. \noindent\hrulefill \section{JVM App Server Arguments}\label{jvm-app-server-arguments} To set the proxy properties in your server, set them as JVM app server arguments. Set these properties to the appropriate values for your proxy: \begin{verbatim} -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttp.proxyUser= -Dhttp.proxyPassword= -Dhttps.proxyHost= -Dhttps.proxyPort= \end{verbatim} Note that the \texttt{user}, \texttt{password}, and \texttt{https} properties are only needed if your proxy requires authentication. \section{LCS Client App Properties}\label{lcs-client-app-properties} To set the proxy properties via the LCS client app, you must create and deploy a config file containing the properties. Follow these steps to do so: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create the config file \texttt{com.liferay.lcs.client.configuration.LCSConfiguration.config}. In the steps that follow, you'll set the proxy properties in this file. \item Set these \texttt{proxy*} properties to the appropriate values for your proxy: \begin{verbatim} proxyHostName="" proxyHostPort="" \end{verbatim} \item If your proxy requires authentication, pass the credentials via these properties: \begin{verbatim} proxyHostLogin="" proxyHostPassword="" \end{verbatim} \item If your proxy requires NTLM authentication, you must also populate these properties: \begin{verbatim} proxyAuthType="ntlm" proxyDomain="" proxyWorkstation="" \end{verbatim} Be sure to set \texttt{proxyDomain} and \texttt{proxyWorkstation} to the appropriate values for your proxy. Note that you can leave \texttt{proxyWorkstation} blank if you don't need it. \item Deploy the config file to \texttt{osgi/configs}. \end{enumerate} \section{Ensuring Access to LCS}\label{ensuring-access-to-lcs} For the LCS client app to work, it must be able to access the following DNS names. If your server is behind a proxy and/or a firewall, then you must open access to these: \begin{itemize} \tightlist \item \texttt{lcs.liferay.com} \item \texttt{lcs-gateway.liferay.com} \end{itemize} As an added security measure, you can also restrict traffic to HTTPS. The next section discusses NTP server synchronization. \section{NTP Server Synchronization}\label{ntp-server-synchronization} For LCS to work properly, the application server running Liferay DXP should be synchronized with a time server. If it's not, you may get log errors similar to these: \begin{verbatim} ERROR [pool-6-thread-3][HandshakeTask:68] java.lang.RuntimeException: Handshake expired. Check that the server is synchronized with an NTP server. WARN [liferay/hot_deploy-1][LCSHotDeployMessageListener:186] LCS portlet is not connected java.lang.RuntimeException: com.liferay.jsonwebserviceclient.JSONWebServiceInvocationException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'oauth_problem': was expecting ('true', 'false' or 'null')_ at [Source: oauth_problem=timestamp_refused&oauth_acceptable_timestamps=1477311475-1477312075; line: 1, column: 14] [Sanitized] \end{verbatim} For information on how to synchronize your application server with a time server, see your application server's documentation. \section{Configuring WebSphere}\label{configuring-websphere} IBM ® WebSphere ® is a trademark of International Business Machines Corporation, registered in many jurisdictions worldwide. If you're running the WebSphere application server, then there are some additional configuration steps you must take before deploying the LCS client app: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Shut down the application server. \item Add these properties in a \texttt{portal-ext.properties} file: \begin{verbatim} module.framework.properties.org.osgi.framework.bootdelegation=\ __redirected,\ com.sun.ccpp,\ com.sun.ccpp.*,\ com.liferay.aspectj,\ com.liferay.aspectj.*,\ com.liferay.portal.servlet.delegate,\ com.liferay.portal.servlet.delegate*,\ com.sun.crypto.*,\ com.sun.image.*,\ com.sun.jmx.*,\ com.sun.jna,\ com.sun.jndi.*,\ com.sun.mail.*,\ com.sun.management.*,\ com.sun.media.*,\ com.sun.msv.*,\ com.sun.org.*,\ com.sun.syndication,\ com.sun.tools.*,\ com.sun.xml.*,\ com.yourkit.*,\ com.ibm.crypto.*,\ sun.*,\ javax.validation,\ javax.validation.*,\ jdk.*,\ weblogic.jndi,\ weblogic.jndi.*\ \end{verbatim} \item In your Liferay DXP installation, delete the \texttt{osgi/state} folder. \item Start the application server. \item Navigate to the WebSphere console in a browser. \item Select your server and navigate to \emph{Java and Process Management} → \emph{Process Definition} → \emph{Additional Properties}. \item Select \emph{Java Virtual Machine} → \emph{Custom Properties}. \item Click \emph{New}, and enter the following: \begin{itemize} \tightlist \item Name: \texttt{com.ibm.crypto.provider.DoRSATypeChecking} \item Value: \texttt{false} \end{itemize} \item Click \emph{Save}, then \emph{OK} to apply changes to the master configuration. \end{enumerate} Note that for LCS client app versions prior to 5.0.0, you must also change the value of the \texttt{digital.signature.algorithm.provider} property in the app's \texttt{portlet.properties} file to \texttt{IBMJCE}: \begin{verbatim} digital.signature.algorithm.provider=IBMJCE \end{verbatim} \section{Installing the LCS Client App}\label{installing-the-lcs-client-app} Once you've addressed the above pre-configuration steps, you're ready to install the LCS client app. Follow these steps to install the app: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder (usually the parent folder of the application server's folder), delete this file: \begin{verbatim} osgi/marketplace/Liferay Connected Services Client.lpkg \end{verbatim} \item Place the new \texttt{Liferay\ Connected\ Services\ Client.lpkg} in \texttt{osgi/marketplace}. \end{enumerate} Great! Now you're all set to \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{register your server with LCS}. The next section shows you how to upgrade the LCS client app. We highly recommend that you do this whenever Liferay releases a new version of the app. \section{Upgrading the LCS Client App}\label{upgrading-the-lcs-client-app} Your server should always be running the latest version of the LCS client app. There are two ways to upgrade the app, depending on the exact LCS pre-configuration steps you followed: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Via Liferay Marketplace \emph{inside} Liferay DXP. Use this method if you don't need to configure the LCS client app (e.g., to connect through a proxy) before it deploys. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If you choose this method and have a clustered environment, you must perform the upgrade separately on each node in your cluster. Therefore, you may prefer to upgrade manually as detailed in the next step to ensure that all your nodes are running the exact same version of the LCS client app. \end{verbatim} \noindent\hrulefill \begin{verbatim} To perform the upgrade, first navigate to *Control Panel* → *Apps* → *Purchased*. Apps needing an update are listed first. Click *Update* next to the LCS client app. Note that you may need to restart your server for the upgrade to complete. \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item Manually, after downloading the LCS client app's LPKG file to your machine. Use this method if you must pre-configure the LCS client app to connect through a proxy. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** If you used JVM app server arguments to configure your server to connect through a proxy, then you don't need to pre-configure the LCS client app to connect through the same proxy. \end{verbatim} \noindent\hrulefill \begin{verbatim} To update the LCS client app manually, follow the previous sections in this guide for downloading and pre-configuring the app. Then deploy it to `[Liferay Home]/deploy` as you would any other app. \end{verbatim} Contact Liferay Support if you need additional assistance with the upgrade process. \chapter{Registering Your Liferay DXP Server with LCS}\label{registering-your-liferay-dxp-server-with-lcs} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Follow these steps to register your Liferay DXP server with LCS: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Ensure that you've completed the \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration}{LCS preconfiguration steps}. \item Log in to \href{https://lcs.liferay.com}{lcs.liferay.com}. This takes you to your company's LCS project. If your company has multiple projects, from the menu to the right of the Dashboard tab select the project that's getting a new server. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-select-project.png}} \caption{Select your LCS project from the menu highlighted by the red box in this screenshot.} \end{figure} \item Select or create the environment in which to register this server. If you're using LCS for activation, upon connection to LCS your server consumes an activation key from the subscription type assigned to the environment. Note that a subscription type can only be assigned to an environment when creating the environment. If you have sufficient permissions in your company's project, you can \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments\#creating-environments}{create a new environment} by selecting \emph{Add Environment}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-registration-select-environment.png}} \caption{You must register your server in an LCS environment. The red box in this screenshot highlights environments.} \end{figure} \item Select the environment's \emph{Registration} tab. This is where you manage and download the \href{/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens}{environment's token file}, that registers servers in the environment. In the Registration tab's \emph{Services} section, change the Liferay Instance Activation setting, if needed. Note that if you change this option and there are servers already registered in the environment, you must regenerate the token file and use it to reconnect those servers to LCS. You'll regenerate and/or download the token in the next step. Additionally, If you disable this service, you must activate via an XML file from Liferay support, and such instances must run version 5.0.0 or newer of the LCS client app. Liferay Instance Activation is either enabled or disabled for all servers that connect to this environment. If Portal Property Analysis is selected, you can prevent LCS environment. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-registration.png}} \caption{An environment's Registration tab lets you manage the token file used to register your server in the environment.} \end{figure} \item What you do now depends on what you did in the previous step: \textbf{Changes to Liferay Instance Activation:} Regenerate and download the token. Regenerating a token causes all servers using the old token to disconnect from LCS. You must reconnect them using the new token. \textbf{No changes to LCS service selections:} Download the token. \item Place the token file in your server's \texttt{{[}Liferay\ Home{]}/data} folder. Note that \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} is usually the parent folder of the application server's folder. If your server is running, it should connect to LCS in about one minute. If your server isn't running, it connects to LCS on startup. \item Celebrate! Your Liferay DXP server is registered in LCS. If for some reason it isn't, see the \href{/docs/7-2/deploy/-/knowledge_base/d/troubleshooting-your-lcs-connection}{LCS troubleshooting article}. \end{enumerate} \noindent\hrulefill \textbf{Note:} You may be wondering what happens if LCS goes offline. Don't worry, this doesn't cause a rift in the space-time continuum. LCS is deployed on a global cloud infrastructure set up for automatic failure recovery. The potential for non-availability is very low. In the event of an outage, however, registered servers maintain a local copy of their uptime information to transmit to LCS when it comes back online. If you use LCS for activation, active subscriptions have a 30-day grace period to re-establish connectivity and remain valid. This is ample time for LCS to come back online. \noindent\hrulefill \section{Determining Your Server's LCS Connection Status}\label{determining-your-servers-lcs-connection-status} In Liferay DXP, you can view your LCS connection status in the LCS client app. Access the client by clicking \emph{Control Panel} → \emph{Configuration} → \emph{Liferay Connected Services}. Here's a full description of what a connected LCS client app displays: \textbf{Connection Uptime:} The duration of the client's connection with LCS. \textbf{Last Message Received:} The time the LCS client received the latest connection message from LCS. These messages occur only upon connection/reconnection and are unrelated to server metrics. It's therefore common for a long period of time to pass before the client receives another such message for a reconnection event. \textbf{Services:} The LCS services enabled for this server. Note that all servers in an environment use the same set of LCS services. LCS services can't be controlled on a server-by-server basis. Note: Portal Analytics, Fix Pack Management and Portal Properties Analysis have been removed from the list of available services. For more information about this change, please read \href{https://help.liferay.com/hc/en-us/articles/360037317691-Liferay-Connected-Services-Feature-Deprecation-Update-March-2020}{this article} \textbf{Project Home:} A link to this server's LCS project. \textbf{Environment:} A link to this server's LCS environment. \textbf{Server Dashboard:} A link to the server on LCS. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-connected.png}} \caption{The server is connected to LCS.} \end{figure} \chapter{Using LCS}\label{using-lcs} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Once your Liferay DXP server is connected to LCS, you can get down to the business that LCS is designed for---managing your servers. If you're not already there, log in with your account on \href{https://lcs.liferay.com}{lcs.liferay.com}. This is where you'll manage environments, register servers and more. This articles in this section each detail one or more of LCS's features: \begin{itemize} \item \href{/docs/7-2/deploy/-/knowledge_base/d/what-lcs-stores-about-your-liferay-dxp-servers}{\textbf{What LCS Stores About Your Liferay DXP Servers:}} For LCS to work, the LCS servers must store certain information about your servers. Sensitive data, however, isn't stored on the LCS servers. This article describes the data that LCS does and doesn't store. \item \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-users-in-your-project}{\textbf{Managing LCS Users in Your Project:}} Learn how to manage your LCS project's users by assigning them roles. \item \href{/docs/7-2/deploy/-/knowledge_base/d/using-the-dashboard}{\textbf{Using the Dashboard:}} Learn how to manage your LCS projects and access your environments and servers in LCS. \item \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments}{\textbf{Managing LCS Environments:}} Learn how to create and manage your LCS project's environments. This includes instructions on generating tokens for an environment's servers. \item \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-servers}{\textbf{Managing LCS Servers:}} Learn how to manage your servers in LCS. This includes viewing server status and editing server settings. \item \href{/docs/7-2/deploy/-/knowledge_base/d/managing-your-lcs-account}{\textbf{Managing Your LCS Account:}} Learn how to manage your LCS account. This includes setting general account preferences, managing LCS web notifications, and configuring LCS to send you notification emails when specific events occur in your LCS projects. \item \href{/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions}{\textbf{Managing Liferay DXP Subscriptions:}} Learn how to view and manage your Liferay DXP subscriptions for the servers in your LCS project. \item \href{/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens}{\textbf{Understanding Environment Tokens:}} Learn about the environment tokens that you use to connect your servers to LCS. \end{itemize} \chapter{What LCS Stores About Your Liferay DXP Servers}\label{what-lcs-stores-about-your-liferay-dxp-servers} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill To protect your users' privacy, LCS only stores system-specific data. LCS doesn't gather or store data on your users. By default, LCS stores the following information about your server: \begin{itemize} \tightlist \item Portal build number and edition \item Patching Tool Version \item LCS Client Build Number \item Application Server Name \item Database Name \item File Encoding \item OS Name and Version \item Timezone \item IP Address \item Java Version and Java Options \item Number of Processor Cores \item File System Usage \item Memory Usage \end{itemize} The other data LCS stores depends on the services you enabled in your environment token, and whether your server was connected before certain services were removed. For more information on this, see \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{Registering Servers with LCS}. If you enabled the following services, LCS gathered and stored the data listed for each: \begin{itemize} \item \textbf{Portal analytics:} \begin{itemize} \tightlist \item Portal and portlet metrics \item JVM metrics \item Cache and server metrics \end{itemize} \item \textbf{Fix pack management:} \begin{itemize} \tightlist \item Patches installed on the server \end{itemize} \item \textbf{Portal properties analysis:} \begin{itemize} \tightlist \item \texttt{portal.properties} (except sensitive data) \end{itemize} \end{itemize} Sensitive data is any key-value pair that contains user names or passwords. For example, LCS did not store the following properties because they contain sensitive data: \begin{verbatim} omniadmin.users ldap.security.credentials.0, ldap.security.credentials.1, ldap.security.credentials.2 ... facebook.connect.app.secret auth.token.shared.secret auth.mac.shared.key captcha.engine.recaptcha.key.private amazon.secret.access.key tunneling.servlet.shared.secret microsoft.translator.client.secret dl.store.s3.secret.key auto.deploy.glassfish.jee.dm.passwd \end{verbatim} LCS also did not store properties that end in \texttt{.password}, besides the following non-sensitive properties: \begin{verbatim} portal.jaas.plain.password portal.jaas.strict.password login.create.account.allow.custom.password \end{verbatim} LCS also allowed you to prevent it from analyzing specific properties of your choosing, by defining blacklisted properties. LCS is no longer gathering or storing the data listed above, that was associated with enabled services. \chapter{Managing LCS Users in Your Project}\label{managing-lcs-users-in-your-project} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill The Users section of LCS is where you manage the LCS users that are part of your project. It's here that you can grant or revoke LCS Roles. To manage users, first click the \emph{Users} tab just below the Dashboard tab on the upper-left of your screen. \noindent\hrulefill \textbf{Note:} You can't add users to your project via the LCS UI or the LCS client app. To add users to your project, you must contact Liferay support. \noindent\hrulefill \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-users.png}} \caption{The Users tab lets you manage the LCS users in your project.} \end{figure} The \emph{Users} tab displays a list of the users in your project. This list includes each user's name, email, image, LCS Roles, and a \emph{Manage Roles} button. Each LCS user must have an assigned Role. The following Roles are available: \textbf{LCS Administrator:} All LCS functionality is available to administrators. This is the only Role that can manage other users' Roles. \textbf{LCS Environment Manager:} All LCS functionality is available in the scope of an environment, with the exception of managing other users. \textbf{LCS Environment Viewer:} Has read-only access in the scope of an environment. You should note that each of these LCS Roles assume users already have the LCS User Role in their Liferay.com accounts. The LCS User Role is granted automatically the first time a user logs into LCS. The actions that can be performed by each of the LCS Roles are detailed in the below permissions matrix. \textbf{LCS Permissions Matrix} Action \textbar{} ~LCS Administrator \textbar{} ~LCS Environment Manager \textbar{} ~LCS Environment Viewer \textbar{} Access LCS \textbar{} true \textbar{} true \textbar{} true \textbar{} Access Any Environment \textbar{} true \textbar{} false \textbar{} false \textbar{} Access a Particular Environment \textbar{} true \textbar{} true \textbar{} true \textbar{} Manage Users \textbar{} true \textbar{} false \textbar{} false \textbar{} Create and Delete Environments \textbar{} true \textbar{} false \textbar{} false \textbar{} Edit Any Environment \textbar{} true \textbar{} false \textbar{} false \textbar{} Edit a Particular Environment \textbar{} true \textbar{} true \textbar{} false \textbar{} Server Registration in Any Environment \textbar{} true \textbar{} false \textbar{} false \textbar{} Server Registration in a Particular Environment \textbar{} true \textbar{} true \textbar{} false \textbar{} Install Fix Packs in Any Environment \textbar{} true \textbar{} false \textbar{} false \textbar{} Install Fix Packs in a Particular Environment \textbar{} true \textbar{} true \textbar{} false \textbar{} Now that you know what Roles are available in an LCS project and what they do, you're ready to learn how to manage them. \section{Managing LCS Roles}\label{managing-lcs-roles} Follow these steps to manage a user's LCS Roles: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the user's \emph{Manage Roles} button. \item To revoke a Role, click \emph{Revoke Role} for that Role. \item To assign a Role, choose the Role (and environment, if applicable) and click \emph{Assign}. \end{enumerate} \noindent\hrulefill \textbf{Note:} A user can't have an environment Role (e.g., LCS Environment Manager, LCS Environment Viewer) and the LCS Administrator Role at the same time. \noindent\hrulefill \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-user-roles.png}} \caption{You can assign or revoke a user's LCS Roles.} \end{figure} \chapter{Using the Dashboard}\label{using-the-dashboard} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill The LCS Dashboard shows a project's environments and servers. If you're not already at the Dashboard, click it near the upper left-hand corner of your LCS site. Clicking \emph{Dashboard} takes you to the project view. From there, you can get to the environment view and the server view. Each of these views gives you a different look into certain aspects of your LCS project. You'll start with the project view. \section{Using the Project View}\label{using-the-project-view} You can get to the project view at any time by clicking the \emph{Dashboard} tab near the upper left-hand corner of your LCS site. The project appears to the right of this tab, with a drop-down arrow for switching between projects if you have more than one. You can also switch between projects from the user menu at the top right of the Dockbar. The project view contains a Status table that lists status messages for each server in your project. For example, a status message appears for a server when the server is offline. Status messages also appear for servers when fix packs are available, monitoring is unavailable, the patching tool is unavailable, or other events occur that relate to LCS. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-project-view.png}} \caption{The LCS project view shows an overview of your LCS project.} \end{figure} LCS lists the environments in your project on the left side of the screen. You can also create new environments here by clicking the \emph{Add Environment} tab (more on this shortly). To view an environment's settings, click the environment's gear icon. Clicking an environment shows more information about it. This takes you to the environment view. Also note that each environment's icon indicates the environment's type and status: \textbf{Red icon:} There's a problem with one or more of the environment's servers. \textbf{Green icon:} The environment's servers are operating properly. \textbf{Icon with a circle:} The environment's servers are clustered. \chapter{Managing LCS Environments}\label{managing-lcs-environments} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Environments are the key components of your LCS project. When you register a server in LCS, you do so in an environment. An environment is therefore the gateway to managing and monitoring your servers in LCS. \section{Creating Environments}\label{creating-environments} The first time you log in to LCS, a wizard walks you through each step required to create your project's first environment. The \href{/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs}{getting started article} explains this in detail. You can create additional environments via the same wizard or a simple form. To create an environment, click the \emph{Add Environment} button from the Dashboard. This opens the New Environment form. Each section in this form corresponds to a step in the wizard. If you want to use the wizard instead, click the \emph{Open Wizard} link at the top of the form. See the \href{/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs}{getting started article} for a description of each setting in the form and wizard. \noindent\hrulefill \textbf{Note:} When creating an environment, make your selections carefully for the \emph{Subscription Type}, \emph{Cluster}, and \emph{Elastic} fields. You can't change them after creating the environment. \noindent\hrulefill \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-new-environment.png}} \caption{The New Environment form lets you create environments.} \end{figure} \section{Working with Environments}\label{working-with-environments} Clicking an environment on the left-hand side of the Dashboard takes you to the environment view, which lets you manage an environment in your LCS project. The UI is segmented into three tabs: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Fix Packs:} View and apply fix packs for the environment's servers. This tab only appears if a server is registered in the environment. A table displays the following information for each fix pack: \begin{itemize} \tightlist \item \textbf{Name:} The fix pack's name. \item \textbf{Status:} The fix pack's status. \item \textbf{Server:} The server the fix pack can be applied to. \item \textbf{Size:} The fix pack's size. This only appears if the server is running. \item \textbf{Download:} A button to download the fix pack to the server. This only appears if the server is running. \end{itemize} Once a fix pack downloads, LCS prompts you to restart your server, which installs any downloaded fix packs. Note that you must start your server with the privileges required to write to the disk location where patches are stored and processed (the \texttt{patching-tool} folder). To use LCS to install fix packs across a cluster, follow the same procedure. LCS downloads and installs fix packs simultaneously across all nodes---you don't have to handle each separately. \item \textbf{Registration:} Generate and download \href{/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens}{\emph{environment tokens}} that connect your servers to LCS. \item \textbf{Environment Settings:} Change the environment's name, location, and description. You can also see if the environment allows clustered servers and view the environment's subscription type. Click the \emph{Save} button to save any changes you make in the Environment Settings tab. You can also delete the environment by clicking \emph{Delete Environment}. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-environment-view.png}} \caption{The LCS environment view shows an overview of an LCS environment.} \end{figure} \chapter{Managing LCS Servers}\label{managing-lcs-servers} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill Clicking a server in the Dashboard or environment view takes you to the server view. Server view provides detailed information about a server. To protect your users' privacy, LCS doesn't gather, store, or analyze user data. Server view is segmented into six tabs: \textbf{Page Analytics:} This service has been disabled, if you enabled it earlier you can see here the past history for metrics on page views and load times. \textbf{Snapshot Metrics:} This service has been disabled, if you enabled it earlier you can see here the past history for application, JVM, and server metrics. \textbf{Fix Packs:} This service has been disabled, if you enabled it earlier you can see here the past history for the server's available and installed fix packs. \textbf{Portal Properties:} This service has been disabled, if you enabled it earlier you can see here the past history for your portal's properties and their settings. \textbf{Details:} Displays general information about your Liferay DXP installation, Java version, and hardware. \textbf{Server Settings:} View or change your server's name, location, and description. You can also unregister the server from LCS. \noindent\hrulefill \textbf{Note:} LCS only supported Snapshot Metrics for servers running on Tomcat or WebLogic. On other application servers you may see a console message indicating that LCS doesn't support server metrics for your application server. You may also see a benign \texttt{NullPointerException} for the LCS \texttt{TaskSchedulerServiceImpl} and \texttt{ScheduleTasksCommand}. \noindent\hrulefill \section{Details}\label{details} The \emph{Details} tab shows general information about your server. There are three tabs under Details: \emph{Software}, \emph{Java}, and \emph{Hardware}. Each shows information, respectively, about your Liferay DXP installation, Java installation, and hardware. This information is useful to the Liferay Support team in the event you need their assistance. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-details.png}} \caption{The Details tab shows information about your server.} \end{figure} \section{Server Settings}\label{server-settings} Finally, the \emph{Server Settings} tab lets you view and edit your server's name, location, and description. You can also unregister your server from LCS. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-settings.png}} \caption{You can use the Server Settings tab to give your server a fun name.} \end{figure} \section{Page Analytics}\label{page-analytics} Page Analytics appears by default when you enter server view. Page Analytics shows page views and load times for the selected site and time period. By default, all sites are selected. You can select a specific site from the \emph{Site} selector menu. You can also select a different time period in the \emph{Period} and \emph{Ending At} fields. The arrows next to the \emph{Ending At} field move the selected time period up or down, respectively, by one period. For example, if you select \emph{One Hour} in the \emph{Period} field, pressing the right arrow next to \emph{Ending At} moves the selected time period up by one hour. Note that at the beginning of the current time period, it can take up to 15 minutes for data to become available. Also note that data is available for three months from the time LCS collected it. By default, load times and page views for all pages are plotted against time in separate graphs. Below these graphs, a table displays summary statistics of data over the same time period, for each page. If you click a page in the table, the graphs plot the data for just that page. If you can't find the page you're looking for, you can search for it in the \emph{Search} box at the top of the table. To plot data for all pages again, click the \emph{All Pages} row at the bottom of the table. Load times are also color coded to indicate speed. The \emph{Load Times} graph's background is red for values above 3,000 ms, orange for values from 2,000 to 3,000 ms, and green for values less than 2,000 ms. Likewise, the table displays all load times greater than 3,000 ms in red text. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-page-analytics-01.png}} \caption{The Page Analytics interface in the LCS Server view.} \end{figure} \section{Snapshot Metrics}\label{snapshot-metrics} To view other metrics and statistics of your server's performance, click the \emph{Snapshot Metrics} tab near the top of the page. These metrics are broken down into three main categories: \emph{Application}, \emph{JVM}, and \emph{Server}. Application is selected by default when you click the Snapshot Metrics button. The Application category also has three other categories: \emph{Pages}, \emph{Portlets}, and \emph{Cache}. Pages lists the frequency that specific pages load, along with their average load times. Portlets lists the same statistics, but for specific portlets in your server. The Cache category lists Liferay Single VM metrics and Hibernate metrics. The following screenshot shows the statistics in the Portlets category. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-metrics-application-portlets.png}} \caption{The LCS application metrics show portlet performance statistics, like frequency of use and average load time.} \end{figure} The JVM category, as its name indicates, shows statistics about the JVM running on your server. This includes data on the garbage collector and memory. The number of runs, total time, and average time are listed for each garbage collector item. The memory metrics are presented in a bar chart that shows the usage of the PS Eden Space, Code Cache, Compressed Class Space, PS Old Gen, PS Survivor Space, and Metaspace. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-metrics-jvm.png}} \caption{The LCS JVM metrics show performance data for memory and the garbage collector.} \end{figure} Server is the third category in Snapshot Metrics. The Server category shows additional information about how your server is running. For example, horizontal bar graphs show the number of current threads running on your server, as well as the JDBC connection pools. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-metrics-server.png}} \caption{The LCS server metrics show current threads and JDBC connection pools.} \end{figure} Note that in Snapshot Metrics, the application and garbage collector metrics are based on data collected by LCS from server registration to the present. Memory and server metrics, however, show only the current state. \section{Fix Packs}\label{fix-packs-1} To view your server's fix packs, click the Fix Packs tab near the top of the page. The available and installed fix packs appear in separate tables. The available fix packs table functions exactly like the Fix Packs table in environment view for downloading and installing fix packs. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-fix-packs.png}} \caption{The Fix Packs tab displays your server's available and installed fix packs.} \end{figure} \section{Portal Properties}\label{portal-properties} The \emph{Portal Properties} tab lets you view your portal's property values in a searchable table. This gives you a convenient display for your portal property settings. The properties in this table are organized into the following categories: \textbf{Default Values:} The default values for your portal's properties. \textbf{Custom Values:} Any custom values you've set for your portal's properties. This includes any property values you change via a \texttt{portal-ext.properties} file. \textbf{Dynamic Properties:} Any property values set at runtime. For example, the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder's location depends on your configuration. To specify this folder when setting any properties that require it, use \texttt{\$\{liferay.home\}} instead of an absolute directory path. You can display any combination of these categories by selecting the corresponding checkboxes from the gear icon next to the search box at the top-right of the table. For example, by checking the \emph{Show Default Values} and \emph{Show Custom Values} checkboxes, the table shows your portal's default and custom property values. To show only the custom values, select only \emph{Show Custom Values}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-server-portal-properties.png}} \caption{Click the gear icon to select the type of portal properties to show in the table.} \end{figure} \chapter{Managing Your LCS Account}\label{managing-your-lcs-account} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill To manage your LCS account, select \emph{My Account} from the user menu in the Dockbar. This takes you to a UI with four tabs: \textbf{Projects:} Displays your LCS projects in a searchable table that lists the administrator's email address for each project. \textbf{Email Notifications:} Configure LCS to send you emails when specific events occur in your LCS projects by adding rules that define what events trigger a notification. There are no rules by default. Click the \emph{Add Rule} button to define one. First specify the project, environment, and server for the notification. Note that you have the option of selecting all environments and servers in a project. Then check the checkbox for each event that you want to trigger an email notification. For example, if you create a notification rule with \emph{The server unexpectedly shuts down} selected for all servers and environments in your project, then LCS sends you an email if any server in your project goes offline without a normal shut down event. Click \emph{Save} when you're done defining the notification rule. It then appears in a table along with other existing rules. Each has Edit and Delete Action buttons. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-add-notification-rule.png}} \caption{You can add rules to determine the events that trigger notifications.} \end{figure} \textbf{Notification History:} Displays your web notification history in a searchable table. You can also select the date range from which to display notifications. \textbf{Preferences:} Manage your LCS account's preferences. You can change your account's language, time zone, and default LCS project. Your default LCS project is the one shown each time you log in to LCS. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-account-preferences.png}} \caption{You can change your LCS account's general preferences.} \end{figure} \section{Using Web Notifications}\label{using-web-notifications} LCS also displays web notifications under the bell icon in the Dockbar. A red badge on this icon shows your unread notification count. LCS and Liferay Support send these notifications. For example, LCS generates notifications when a server shuts down or some other event requiring your attention occurs. To mark a notification as read, click the small \emph{x} icon next to it. To mark all notifications as read, click the \emph{Mark All as Read} button. To mark notifications as unread again, click the \emph{Undo} button that appears. To see your notification history, click the \emph{Notifications History} button. You can also access your notification history by selecting \emph{My Account} from the user menu in the Dockbar. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-user-web-notifications.png}} \caption{Web notifications let you know what's happening in your LCS projects.} \end{figure} \chapter{Managing Liferay DXP Subscriptions}\label{managing-liferay-dxp-subscriptions} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill LCS lets you use and view your Liferay DXP subscriptions. Recall that when you \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments\#creating-environments}{create an environment}, you assign its subscription type and choose whether LCS activates servers that connect to that environment. If you use LCS for activation, registering a server in that environment consumes an activation key from the environment's subscription type. You can also view your project's available activation keys and see how they're being used. Depending on your subscription agreement, LCS also lets you register servers via \emph{elastic subscriptions}. Elastic subscriptions let you register an unlimited number servers. This is invaluable in auto-scaling environments, where servers are automatically created and destroyed in response to load. Note that to use elastic subscriptions, you must set the environment as elastic when you create it. Also note that LCS only uses elastic subscriptions for servers that exceed the number that the environment's subscription type allows. In other words, LCS uses the environment's regular subscriptions before any elastic subscriptions. You can access these features from the \emph{Subscriptions} tab on the upper-left of the LCS site. This tab contains two other tabs: \emph{Details} and \emph{Elastic Subscriptions}. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-subscriptions.png}} \caption{LCS lets you view and manage your subscriptions.} \end{figure} There are four tables in the \emph{Details} tab: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Subscriptions:} Shows a list of the available subscriptions in your LCS project. For each subscription, this table shows the following information: \begin{itemize} \tightlist \item Subscription Type \item Start Date \item Expiration Date \item Support End Date \item Platform \item Product \item Processor Cores Allowed \item Activation Keys \item Used Activation Keys \end{itemize} Note that \emph{Processor Cores Allowed} shows the number of processor cores that the subscription allows for each server. \item \textbf{Subscriptions Summary:} Shows how your subscriptions are currently used in your project. For each subscription type, this table shows the number of activation keys allowed, used, and available. \item \textbf{Project Environments:} Shows your project's environments and their assigned subscription types. Each environment must have a subscription type. \item \textbf{Project Servers:} Shows the environment and subscription type for each server in your LCS project. \end{enumerate} If any of the information in these tables is missing or incorrect, contact Liferay Support. \noindent\hrulefill \textbf{Note:} If you don't use LCS for activating your servers, then you can register as many servers as you want in LCS. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} If you try to activate a server that exceeds the number of processor cores that your subscription allows per server, the activation fails and the server is locked down. A console error also indicates the server's core count. You can compare this with your subscription's processor cores allowed in LCS's Subscriptions table. To activate the server, you can either reduce the number of cores it uses (e.g., by deploying to different server hardware, or reducing the number of virtual processors in a VM or container), or contact Liferay Sales to increase the number of processor cores that your subscription allows per server. \noindent\hrulefill \section{Decommissioning Servers}\label{decommissioning-servers} To decommission a server and free its activation key for reuse, select the server's environment on the left and then select the server. In the server's \emph{Server Settings} tab, select \emph{Unregister}. Also note that when you shut down a server normally, its activation key is immediately freed for reuse. If the server crashes or its shutdown is forced (e.g., kill), its activation key is freed for reuse within six minutes. \section{Elastic Subscriptions}\label{elastic-subscriptions} Elastic subscriptions let you register an unlimited number of servers. This is crucial for auto-scaling environments where servers are created and destroyed automatically. You can view data on your elastic servers from the \emph{Subscriptions} tab's \emph{Elastic Subscriptions} tab. \noindent\hrulefill \textbf{Note:} To register elastic servers in an environment, that environment must be set as elastic when it's created. For more information, see the \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments\#creating-environments}{documentation on creating environments}. \noindent\hrulefill \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-elastic-subscriptions.png}} \caption{The \emph{Elastic Subscriptions} tab shows details about your project's elastic servers.} \end{figure} The \emph{Elastic Subscriptions} tab displays the number of elastic servers online and the uptime details for each. A graph shows the number of elastic servers online each day, while a table lists each elastic server's start time, end time, and duration. The total duration for servers is below the table. To download a report of the table's data, click \emph{Download Report}. Also, you can use the \emph{Environment} and \emph{Month} selectors above the graph to select the environment and month to show data from, respectively. The data in both the graph and the table reflect your selections here. \chapter{Understanding Environment Tokens}\label{understanding-environment-tokens} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill To register a server in an environment, you must use that environment's token file. LCS Administrators and Environment Managers can generate and distribute this file. It contains all the information the LCS client app needs to register the server in the environment. When the server starts up, it uses the token to connect to LCS. If you use LCS for activation, the server automatically consumes an activation key from the environment's subscription upon connection. This makes it possible to activate servers automatically on startup with no interaction required. \noindent\hrulefill \textbf{Note:} For instructions on using and managing your environment tokens, see the instructions on \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{registering your server with LCS}. \noindent\hrulefill There are a few things to keep in mind when using environment tokens: \begin{itemize} \item Each environment can have only one token file. If you regenerate the token, servers using the old file are disconnected from LCS and can't reconnect until receiving the new file. If the server disconnects due to token regeneration and is running version 4.0.2 or later of the LCS client app, the server enters a 30-day grace period during which it functions normally. This gives the administrator time to use the new token file to reconnect to LCS. Servers running earlier versions of the LCS client app present users with an error page until the administrator reconnects with the new token. \item Use caution when distributing the token file, as anyone can use it to connect to your environment (and consume an activation key in your subscription if you're using LCS for activation). \item Minimal information (server name, location, etc\ldots) is used to register a server with LCS. You can change this information from \href{/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-servers}{the server view in LCS} at any time. \item Environment tokens connect using OAuth. Using an environment token overrides the OAuth authorization cycle. If LCS Administrators or Environment Managers have never registered servers in LCS, the first time they do so an OAuth authorization entry is created in LCS. If they've previously registered servers in LCS, their existing credentials are used when they create a token file. \item If the credentials of the LCS user who generated the token become invalid, you must generate a new token and use it to reconnect to LCS. An LCS user's credentials become invalid if the user leaves the LCS project or becomes an LCS Environment Manager or LCS Environment Viewer in a different environment. \end{itemize} So why bother with environment tokens at all? Besides simplifying the LCS connection process, environment tokens are valuable in auto-scaling environments where algorithms create and destroy servers automatically. In this situation, having clients that activate and configure themselves is crucial. \noindent\hrulefill \textbf{Note}: If your auto-scaling environment creates new server nodes from a server in a system image, that server can't require human interaction during setup. When creating such an image, you must change any portal property settings that prevent automatic setup. By default, Liferay DXP's setup wizard requires human interaction. You must therefore set the \texttt{setup.wizard.enabled} property to \texttt{false} if you want your auto-scaling environment to create new nodes from the server. \chapter{Troubleshooting Your LCS Connection}\label{troubleshooting-your-lcs-connection} \noindent\hrulefill \textbf{Note:} LCS is deprecated and will be shut down on December 31, 2021. Customers who activate LCS are advised to replace it with our latest activation key type which is suitable for virtualized environments. For further information, please see \href{https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation}{Changes to Liferay Product Activation}. \noindent\hrulefill If you use LCS to activate Liferay DXP, your server must maintain its connection to LCS at all times. If this connection is interrupted, your server enters a grace period to allow for reconnection. Lengthy interruptions, however, can affect your server's uptime. \noindent\hrulefill \textbf{Note:} You must use LCS for activation of Elastic subscriptions. Otherwise, you don't have to use LCS for activation. You can instead request an XML activation key from Liferay Support. \noindent\hrulefill The following sections in this document provide some background information and help you troubleshoot problems with your server's LCS connection: \hyperref[lcs-grace-periods]{\textbf{LCS Grace Periods:}} Describes how grace periods work in LCS. You should read this section before attempting any troubleshooting steps. \hyperref[troubleshooting]{\textbf{Troubleshooting:}} Presents troubleshooting steps for specific problems. \hyperref[increasing-log-levels]{\textbf{Increasing Log Levels:}} If you contact Liferay Support, you'll be asked to increase your server's log levels and then provide your log files. This section shows you how to do this. \noindent\hrulefill \textbf{Note:} The odds of LCS being unavailable are low. LCS is deployed on a global cloud infrastructure set up for automatic failure recovery. Notifications also let the LCS team react quickly to any downtime. During LCS updates and new version releases, however, LCS is unavailable for a few minutes while changes are applied. \noindent\hrulefill \section{LCS Grace Periods}\label{lcs-grace-periods} There are 2 grace period types in LCS: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Connection Grace Period:} Occurs when your activated LCS connection is interrupted. This gives you time to re-establish the connection. \item \textbf{Subscription Grace Period:} Occurs when your subscription is about to expire. This gives you time to renew the subscription. \end{enumerate} \noindent\hrulefill \textbf{Note:} These grace periods only apply to servers previously connected and activated in LCS. If the subscription check or connection fails when a server attempts to connect to LCS for the first time, that server doesn't enter a grace period. It's therefore important to verify that an active subscription is available before connecting a new server to LCS. To do this, check the \href{/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions}{Subscriptions tab} in LCS. \noindent\hrulefill \section{Connection Grace Period}\label{connection-grace-period} If your server's LCS connection is interrupted, the server continues to run and enters a grace period that lasts for up to 30 days to allow for reconnection. During this grace period, Liferay DXP displays a warning message to administrators. Upon seeing this message, administrators should contact Liferay Support and follow the troubleshooting steps below. LCS automatically restores your server's activation upon reconnection (you shouldn't need to restart the server). If for some reason the connection can't be restored, Liferay Support will provide an alternative way to activate your server. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-grace-period.png}} \caption{A warning message is displayed to administrators if the server can't connect to LCS to validate the subscription.} \end{figure} While disconnected from LCS, the LCS client app continually attempts to reconnect. If reconnection continues to fail, ensure that your server can access \texttt{lcs.liferay.com} and \texttt{lcs-gateway.liferay.com}. If the LCS client app stops attempting to reconnect, there will be no activity in the logs. In this case, you can force reconnection by redeploying the app. Follow these steps to do so: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your server's \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder (usually the parent folder of the application server's folder), remove this file: \begin{verbatim} osgi/marketplace/Liferay Connected Services Client.lpkg \end{verbatim} \item Place \texttt{Liferay\ Connected\ Services\ Client.lpkg} in \texttt{{[}Liferay\ Home{]}/deploy}. If you \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration\#preconfiguring-lcs-to-connect-through-a-proxy}{connect to LCS through a proxy}, and configured this inside the LCS client app, make sure the app you deploy is also configured to do so. \end{enumerate} You should also ensure that you've enabled email notifications in LCS for server disconnection events. To do this, you must create a notification rule that sends an email whenever the server shuts down unexpectedly. The documentation on \href{/docs/7-2/deploy/-/knowledge_base/d/managing-your-lcs-account}{managing your LCS account} explains how to do this. \section{Subscription Grace Period}\label{subscription-grace-period} At least 90 days before the subscription expires, Liferay will reach out to begin the renewal process. 30 days before expiration, Liferay Support sends warning messages through the Help Center, \href{https://lcs.liferay.com}{the LCS site}, and \href{https://www.liferay.com/group/customer}{the Customer Portal}. After the expiration date, your servers may be placed in an additional grace period, which is communicated through the same support channels. If the renewal isn't completed during this grace period, then the subscription becomes inactive and the Liferay DXP instance enters the 30-day grace period. As soon as the renewal is processed, the instance activates and any error or warning messages disappear within 24 hours. Note that by using XML activation keys (provided by Liferay Support upon request), you can continue to use your Liferay DXP instances even after a subscription has expired. \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/lcs-support-expiration.png}} \caption{LCS sends you a notification prior to the expiration of your subscription.} \end{figure} \section{Troubleshooting}\label{troubleshooting} If you encounter issues with LCS, the Liferay Support team is here to help. If you need support, open a \href{https://help.liferay.com/hc}{Help Center} ticket. You can begin troubleshooting the following scenarios, which the Liferay Support team can also assist you with. \noindent\hrulefill \textbf{Note:} Before troubleshooting specific issues or contacting Liferay Support, make sure that you've followed the LCS \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration}{preconfiguration} and \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{registration} steps correctly. \noindent\hrulefill \section{Server Can't Reach LCS}\label{server-cant-reach-lcs} If your server can't reach LCS, verify that you can access the public sites required by LCS: \begin{itemize} \item \href{https://lcs.liferay.com/}{\texttt{lcs.liferay.com}} should be viewable in a browser. \item \texttt{lcs-gateway.liferay.com} should respond on port 443: \begin{verbatim} curl -vk -I "https://lcs-gateway.liferay.com" telnet lcs-gateway.liferay.com 443 \end{verbatim} \end{itemize} \section{Subscription Issues}\label{subscription-issues} For issues related to your subscription, first review the documentation on \href{/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions}{managing your subscription}. Subscription errors usually involve one of these problems: \begin{itemize} \tightlist \item Your server can reach LCS, but can't locate a subscription. \item Your server can reach LCS and locate a subscription, but activating your server would exceed the subscription's number of activation keys or cores. \end{itemize} In either case, you must verify that a subscription is available and that you're not exceeding its number of activation keys or cores. You can find this information on the LCS site's Subscriptions page, as described in \href{/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions}{the documentation on managing subscriptions}. If the environment in which you're trying to activate a server isn't assigned the subscription you want to use, then you must create a new environment and assign it the correct subscription. Once assigned, you can't change an environment's subscription. Follow \href{/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs}{the initial registration steps} for instructions on creating a new environment and activating a new server. \noindent\hrulefill \textbf{Note:} When shutting down servers, you must ensure that the LCS site receives the server shutdown commands. Otherwise, LCS may not release that server's activation key for reuse and attempts to activate additional servers may exceed the subscription's number of activation keys. There's a higher likelihood of this happening in rolling deployments and/or when using containers. For more information, see the \href{https://help.liferay.com/hc/en-us/articles/360018261011}{KB article on properly unregistering subscriptions}. \noindent\hrulefill \section{Invalid Token}\label{invalid-token} If the token is invalid, first review the documentation on \href{/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens}{environment tokens}. The following table lists causes and solutions for invalid tokens. \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.4400}} >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.5600}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright ~Cause \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright ~Solution \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot The LCS user who generated the token no longer has permissions. This happens when the user leaves the LCS project or becomes an LCS Environment Manager or LCS Environment Viewer in a different environment. & Regenerate the token. \\ The token's file name is changed after download. & Download the token again from LCS. \\ The token is regenerated. & Use the regenerated token. \\ \end{longtable} \noindent\hrulefill \section{Increasing Log Levels}\label{increasing-log-levels} If you contact Liferay Support, you're asked to increase your server's log levels and then provide your log files. You can find these log files in \texttt{{[}Liferay\ Home{]}/logs} (\href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} is usually the parent folder of the application server's folder). There are 2 types of log files in this folder: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Liferay log files:} The files \texttt{liferay.{[}date{]}.log} and \texttt{liferay.{[}date{]}.xml} are the logs for your Liferay DXP installation. Note that LOG and XML files for the same date contain the same information--the only difference is the file format. \item \textbf{LCS log files:} The \texttt{lcs-portlet-{[}date{]}.log} files are the LCS client app's logs. Note that if there's only a single LCS log file, it may appear without a date as \texttt{lcs-portlet.log}. When you increase the log levels as described in the following sections, the additional log messages are written to these LCS log files. \end{enumerate} There are 2 ways to increase the log levels: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{In your Liferay DXP instance's Control Panel:} This is a temporary configuration that resets upon shutting down the server. Note that if the server isn't activated, you can't access the Control Panel. In that case, Liferay Support can provide an XML activation key. \item \textbf{In a Log4j configuration:} This is a permanent configuration that persists through server shutdown and restart. \end{enumerate} The following sections cover both options. \section{Control Panel}\label{control-panel} Follow these steps to increase the log levels via the Control Panel: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration}. \item Click the \emph{Log Levels} tab. \item Search for ``lcs''. \item Change the log level for each matching entry to DEBUG. \item While in the Control Panel, you should also navigate to \emph{Configuration} → \emph{Liferay Connected Services} and take a screenshot of what you see there. This is useful to Liferay Support. \end{enumerate} \section{Log4j}\label{log4j} Follow these steps to increase the log levels via Log4j: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Download the latest LCS client as instructed in the \href{/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration}{LCS preconfiguration article}. The app downloads as \texttt{Liferay\ Connected\ Services\ Client.lpkg}. If you don't want to download the latest client, you can use the one already installed in your server: it's in \texttt{{[}Liferay\ Home{]}/osgi/marketplace} (just make sure to shut down your server before following the rest of the steps in this section). Recall that the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder is usually the parent folder of the application server's folder. \item Expand the LPKG file, then expand the \texttt{lcs-portlet-{[}version{]}.war} file inside it. \item Inside the \texttt{WAR} file, replace the contents of \texttt{WEB-INF\textbackslash{}classes\textbackslash{}META-INF\textbackslash{}portal-log4j.xml} with the following configuration: \begin{verbatim} \end{verbatim} \item Save the file and repackage the WAR and LPKG (make sure not to change the names of these files). \item Make sure your server is shut down. \item In your installation's \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder, delete the existing LCS client app: \begin{verbatim} osgi/marketplace/Liferay Connected Services Client.lpkg \end{verbatim} \item Place the \texttt{Liferay\ Connected\ Services\ Client.lpkg} that you repackaged in step 4 in \texttt{osgi/marketplace}. \item Start your server. \end{enumerate} If you need assistance with the issues in this guide, or any other issues with LCS, contact Liferay Support. \chapter{Deployment Reference}\label{deployment-reference} Here you'll find definitions, default settings, templates, and more. Here are some of the topics: \begin{itemize} \item \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}: From this location, Liferay DXP launches applications, applies configurations, loads JAR files, and generates logs. \item \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{Portal properties}: Use a \texttt{portal-ext.properties} file (or another qualified properties file) to configure Liferay DXP and override features. \item \href{/docs/7-2/deploy/-/knowledge_base/d/system-properties}{System properties}: Examine Liferay DXP default system configuration. \item \href{/docs/7-2/deploy/-/knowledge_base/d/database-templates}{Database templates}: Use these portal properties templates to specify the Liferay DXP database. \item \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch-connector-settings-reference}{Elasticsearch settings}: Examine the configuration settings for Liferay's default Elasticsearch adapter. \end{itemize} \chapter{Liferay Home}\label{liferay-home} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} \emph{Liferay Home} is the location from which Liferay DXP launches applications, applies configurations, loads JAR files, and generates logs. \begin{itemize} \item \emph{In a Liferay DXP bundle,} Liferay Home is the installation's top-level folder and it contains the application server. \item \emph{In a manual installation,} the Liferay Home folder varies by application server. If you're doing a manual installation, please refer to the article covering that app server (e.g., \emph{Installing Liferay DXP on {[}app server{]}}) for the Liferay Home location. \end{itemize} Bundles contain this folder structure regardless of application server: \begin{itemize} \tightlist \item \textbf{\hyperref[liferay-home]{Liferay Home}} \begin{itemize} \item \textbf{{[}Application Server{]}}: This folder is named after the application server where Liferay DXP is installed. \item \texttt{data} (if HSQL database is selected): Stores an embedded HSQL database, Liferay DXP's file repository, and search indexes. The embedded HSQL database is configured by default, but it's intended for demonstration and trial purposes only. The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#JDBC}{Portal property \texttt{jdbc.default.url}} sets the Hypersonic embedded HSQL database location. \item \texttt{deploy}: To auto-deploy plugins, copy them to this folder. It supports application \texttt{.lpkg} files from Liferay Marketplace, plugin \texttt{.war} files, and plugin \texttt{.jar} files. The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Auto\%20Deploy}{Portal property \texttt{auto.deploy.deploy.dir}} sets the auto-deploy location. \item \texttt{license}: Liferay DXP's copyright and version files are here. \item \texttt{logs}: Log files go here. Examine them as you diagnose problems. \texttt{portal-impl.jar}'s \texttt{portal-impl/src/META-INF/portal-log4j.xml} file sets the log file location. To override the log file location, you must \href{/docs/7-0/tutorials/-/knowledge_base/t/advanced-customization-with-ext-plugins\#using-advanced-configuration-files}{use an \texttt{ext-impl/src/META-INF/portal-log4j-ext.xml} file in an Ext plugin}. \item \texttt{osgi}: All the JAR files and a few configuration files for the OSGi runtime belong in this folder. The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Module\%20Framework}{Portal property \texttt{module.framework.base.dir}} sets the OSGi folder location. Here are its subfolders: \begin{itemize} \tightlist \item \texttt{configs}: Component configuration files. \item \texttt{core}: Liferay DXP's core modules. \item \texttt{marketplace}: Marketplace applications and application suites. \item \texttt{modules}: Modules you've deployed. \item \texttt{portal}: Liferay DXP's non-core modules. \item \texttt{state}: Contains OSGi internal state files for such things as OSGi bundle installation, bundle storage, and more. \item \texttt{target-platform}: Target platform index. \item \texttt{test}: Modules that support test integration. \item \texttt{war}: WAR plugins you've deployed. \end{itemize} \item \texttt{patching-tool}: (Liferay DXP only) This folder contains patches and a utility for installing the patches. \item \texttt{tools}: For Liferay DXP upgrade and target platform indexer. \item \texttt{work}: Module Jasper work files. \end{itemize} \end{itemize} \noindent\hrulefill \textbf{Note:} If Liferay DXP cannot create resources in the Liferay Home folder or if it finds itself running on certain application servers, it creates a folder called \texttt{liferay} in the home folder of the operating system user that is running Liferay DXP. In this case, that \texttt{liferay} folder becomes Liferay Home. For example, if the operating system user's name is jbloggs, the \texttt{liferay} folder path is \texttt{/home/jbloggs/liferay} or \texttt{C:\textbackslash{}Users\textbackslash{}jbloggs\textbackslash{}liferay}. \chapter{Portal Properties}\label{portal-properties-1} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can set portal properties to configure and override Liferay DXP features. Your installation's \texttt{portal-impl.jar} embeds the default properties file: { portal.properties { (Opens New Window)} } Overriding a portal property requires creating an \emph{extension} portal properties file that specifies the properties you're overriding. \noindent\hrulefill \textbf{Note:} In a portal properties extension file, specify only the properties you're overriding. \noindent\hrulefill Here's an example of setting Portal's data source to a MySQL database by adding override properties in a \texttt{{[}Liferay\ Home{]}/portal-ext.properties} file: \begin{verbatim} jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.username=jbloggs jdbc.default.password=pass123 \end{verbatim} The \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#Properties\%20Override}{\texttt{include-and-override}} property specifies portal property files that override the defaults. It specifies the order the files are read---the last file read takes highest priority. Properties file prioritization (highest to lowest): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item \texttt{{[}Liferay\ Home{]}/portal-setup-wizard.properties} \item \texttt{{[}user\ home{]}/portal-setup-wizard.properties} \item \texttt{{[}Liferay\ Home{]}/portal-ext.properties} \item \texttt{{[}user\ home{]}/portal-ext.properties} \item \texttt{{[}Liferay\ Home{]}/portal-bundle.properties} \item \texttt{{[}user\ home{]}/portal-bundle.properties} \item \texttt{{[}Liferay\ Home{]}/portal.properties} \item \texttt{portal-impl.jar/portal.properties} \end{enumerate} \texttt{{[}Liferay\ Home{]}/portal-ext.properties} is the most commonly used file for overriding the defaults. \chapter{System Properties}\label{system-properties} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} System properties configure the Liferay DXP system. Your installation's \texttt{portal-impl.jar} embeds the default properties file: { system.properties { (Opens New Window)} } \chapter{Database Templates}\label{database-templates} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Below are templates (example \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties}) for configuring various databases as a built-in data source for Liferay DXP. \noindent\hrulefill \textbf{Note:} The \href{https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?}{Liferay DXP Compatibility Matrix} specifies supported databases and environments. \noindent\hrulefill \section{MariaDB}\label{mariadb-2} \begin{verbatim} jdbc.default.driverClassName=org.mariadb.jdbc.Driver jdbc.default.url=jdbc:mariadb://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.default.username= jdbc.default.password= \end{verbatim} \section{MySQL}\label{mysql-2} \noindent\hrulefill \textbf{Note:} MySQL Connector/J 8.0 is highly recommended for use with MySQL Server 8.0 and 5.7. \noindent\hrulefill \begin{verbatim} jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.username= jdbc.default.password= \end{verbatim} \section{PostgreSQL}\label{postgresql-2} \begin{verbatim} jdbc.default.driverClassName=org.postgresql.Driver jdbc.default.url=jdbc:postgresql://localhost:5432/lportal jdbc.default.username=sa jdbc.default.password \end{verbatim} See the \href{@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html\#JDBC}{default portal properties} for more database templates. \chapter{Comparing Patch Levels}\label{comparing-patch-levels} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} If you're a developer, the Patching Tool can show you what changed between different Liferay DXP patches and versions. These commands show you information about the different patch levels: \texttt{patching-tool\ diff}: Prints the differences between two patch levels. At least one stored patch level must be available. This command accepts options for filtering the output: \begin{itemize} \tightlist \item \texttt{source}: Shows the source differences between the two patch levels. \item \texttt{files}: Shows a list of the modified files. \item \texttt{fixed-issues}: Shows a list of LPS/LPE issues from our issue tracking system. \item \texttt{html}: Specify this along with one of the filtering options (\texttt{source}, \texttt{files}, or \texttt{fixed-issues}) and after the patch levels, to write the differences to an HTML file (\texttt{\textless{}stored-name-1\textgreater{}-\textless{}stored-name-2\textgreater{}-{[}type{]}-diff.html}) in the \texttt{diffs} folder. Additions are colored green and deletions are colored red. \item \texttt{collisions}: Shows a list of modified files which collide with deployed plugins. \end{itemize} For detailed usage information, run \texttt{patching-tool\ help\ diff}. \texttt{patching-tool\ store}: Manages patching level information for the \texttt{diff} command. Your patches must contain source code to store the patch level and to prepare usable information for the \texttt{diff} command. Here are the \texttt{store} command options: \begin{itemize} \tightlist \item \texttt{info}: Prints the list of patches which make up the stored patch level. \item \texttt{add}: Stores the patch level that can be found in the patches directory. \item \texttt{update}: Adds or updates patch level information. \item \texttt{rm}: Removes previously stored patch level information. \end{itemize} For detailed usage information, run \texttt{patching-tool\ help\ store}. \chapter{Patching Tool Configuration Properties}\label{patching-tool-configuration-properties} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Here are the Patching Tool configuration properties. See \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-patching-tool}{Configuring the Patching Tool} for more information on configuring the Patching Tool. \textbf{patching.mode:} This can be \texttt{binary} (the default) or \texttt{source} if you're patching a source tree. Patches contain both binary and source patches. If your development team extends Liferay DXP, have them patch their source tree. \textbf{patches.folder:} Specify where to store patches. The default location is \texttt{./patches}. \textbf{war.path:} Specify the location of the Liferay DXP installation inside your application server. Alternatively, you can specify a \texttt{.war} file here, and you can patch a Liferay DXP \texttt{.war} for installation to your application server. \textbf{global.lib.path:} Specify the location for storing \texttt{.jar} files on the global classpath. If you're not sure, search for \texttt{portal-kernel.jar}; it's on the global classpath. This property is only valid if your \texttt{patching.mode} is \texttt{binary}. \textbf{liferay.home:} Specify the default location for the \texttt{data}, \texttt{osgi}, and \texttt{tools} folders. \textbf{source.path:} Specify the location of your Liferay DXP source tree. This property is only valid if your \texttt{patching.mode} is \texttt{source}. Service Pack detection is available behind a proxy server. To configure your proxy, use the following settings, making sure to replace \texttt{{[}PROXY\_IP\_ADDRESS{]}} with your proxy server's IP address and replace the port numbers with yours: \begin{verbatim} ## Proxy settings # HTTP Proxy #proxy.http.host=[PROXY_IP_ADDRESS] #proxy.http.port=80 #proxy.http.user=user #proxy.http.password=password # HTTPS Proxy proxy.https.host=[PROXY_IP_ADDRESS] proxy.https.port=80 proxy.https.user=user proxy.https.password=password # SOCKS Proxy #proxy.socks.host=[PROXY_IP_ADDRESS] #proxy.socks.port=1080 #proxy.socks.user=user #proxy.socks.password=password \end{verbatim} \chapter{Troubleshooting Deployments}\label{troubleshooting-deployments} 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 did the entity sort order change when I migrated to a new database type?~{}} \begin{verbatim}

    Your new database uses a different default query result order--you should be able to configure a different order.

    \end{verbatim} {How can I use files to configure components?~{}} \begin{verbatim}

    See Using Files to Configure Module Components.

    \end{verbatim} {The application server and database started, but Liferay DXP failed to connect to the database. What happened and how can I fix this?~{}} \begin{verbatim}

    Liferay DXP initialization can fail while attempting to connect to a database server that isn't ready. Configuring startup to retry JDBC connections facilitates connecting @product@ to databases.

    \end{verbatim} \chapter{Liferay DXP Failed to Initialize Because the Database Wasn't Ready}\label{liferay-dxp-failed-to-initialize-because-the-database-wasnt-ready} If you start your database server and application server at the same time, Liferay DXP might try connecting to the data source before the database is ready. By default, Liferay DXP doesn't retry connecting to the database; it just fails. But there is a way to avoid this situation: database connection retries. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a \texttt{portal-ext.properties} file in your \href{/docs/7-1/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder. \item Set the property \texttt{retry.jdbc.on.startup.max.retries} equal to the number of times to retry connecting to the data source. \item Set property \texttt{retry.jdbc.on.startup.delay} equal to the number of seconds to wait before retrying connection. \end{enumerate} If at first the connection doesn't succeed, Liferay DXP uses the retry settings to try again. \section{Related Topics}\label{related-topics-10} \href{/docs/7-2/appdev/-/knowledge_base/a/connecting-to-data-sources-using-jndi}{Connecting to JNDI Data Sources} \chapter{Sort Order Changed with a Different Database}\label{sort-order-changed-with-a-different-database} If you've been using Liferay DXP, but are switching it to use a different database type, consult your database vendor documentation to understand your old and new database's default query result order. The default order is either case-sensitive or case-insensitive. This affects entity sort order in Liferay DXP. Here are some examples of ascending alphabetical sort order. Case-sensitive: \begin{verbatim} 111 222 AAA BBB aaa bbb \end{verbatim} Case-insensitive: \begin{verbatim} 111 222 AAA aaa BBB bbb \end{verbatim} Your new database's default query result order might differ from your current database's order. Consult your vendor's documentation to configure the order the way you want. \chapter{Using Files to Configure Module Components}\label{using-files-to-configure-module-components} Liferay DXP uses \href{http://felix.apache.org/documentation/subprojects/apache-felix-file-install.html}{Felix File Install} to monitor file system folders for new/updated configuration files, and the \href{http://felix.apache.org/}{Felix OSGi implementation} of \href{http://felix.apache.org/documentation/subprojects/apache-felix-config-admin.html}{Configuration Admin} to let you use files to configure module service components. To learn how to work with configuration files, first review \href{/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files}{Understanding System Configuration Files}. \section{Configuration File Formats}\label{configuration-file-formats} There are two different configuration file formats: \begin{itemize} \tightlist \item \texttt{.cfg}: An older, simple format that only supports \texttt{String} values as properties. \item \texttt{.config}: A format that supports strings, type information, and other non-string values in its properties. \end{itemize} Although Liferay DXP supports both formats, use \texttt{.config} files for their flexibility and ability to use type information. Since \texttt{.cfg} files lack type information, if you want to store anything but a \texttt{String}, you must use properties utility classes to cast \texttt{String}s to intended types (and you must carefully document properties that aren't \texttt{String}s). \texttt{.config} files eliminate this need by allowing type information. The articles below explain the file formats: \begin{itemize} \tightlist \item \href{/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files}{Understanding System Configuration Files} \item \href{https://sling.apache.org/documentation/bundles/configuration-installer-factory.html\#configuration-files-config}{Configuration file (\texttt{.config}) syntax} \item \href{https://sling.apache.org/documentation/bundles/configuration-installer-factory.html\#property-files-cfg}{Properties file(\texttt{.cfg}) syntax} \end{itemize} \section{Naming Configuration Files}\label{naming-configuration-files} Before you \href{/docs/7-1/user/-/knowledge_base/u/creating-configuration-files}{create a configuration file}, follow these steps to determine whether multiple instances of the component can be created or if the component is intended to be a singleton: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Deploy the component's module if you haven't done so already. \item In Liferay DXP's UI, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item Find the component's settings by searching or browsing for the component. \item If the component's settings page has a section called \emph{Configuration Entries}, you can create multiple instances of the component configured however you like. Otherwise, you should treat the component as a singleton. \end{enumerate} \begin{figure} \centering \pandocbounded{\includegraphics[keepaspectratio]{./images/system-settings-page-lists-configuration-entries.png}} \caption{You can create multiple instances of components whose System Settings page has a \emph{Configuration Entries} section.} \end{figure} \emph{All} configuration file names must start with the component's PID (PID stands for \emph{persistent identity}) and end with \texttt{.config} or \texttt{.cfg}. For example, this class uses \href{/docs/7-2/frameworks/-/knowledge_base/f/declarative-services}{Declarative Services} to define a component: \begin{verbatim} package com; @Component class Foo {} \end{verbatim} The component's PID is \texttt{com.Foo}. All the component's configuration files must start with the PID \texttt{com.Foo}. For each non-singleton component instance you want to create or update with a configuration, you must use a uniquely named configuration file that starts with the component's PID and ends with \texttt{.config} or \texttt{.cfg}. Creating configurations for multiple component instances requires that the configuration files use different \emph{subnames}. A subname is the part of a configuration file name after the PID and before the suffix \texttt{.config} or \texttt{.cfg}. Here's the configuration file name pattern for non-singleton components: \begin{itemize} \tightlist \item \texttt{{[}PID{]}-{[}subname1{]}.config} \item \texttt{{[}PID{]}-{[}subname2{]}.config} \item etc. \end{itemize} For example, you could configure two different instances of the component \texttt{com.Foo} by using configuration files with these names: \begin{itemize} \tightlist \item \texttt{com.Foo-one.config} \item \texttt{com.Foo-two.config} \end{itemize} Each configuration file creates and/or updates an instance of the component that matches the PID. The subname is arbitrary---it doesn't have to match a specific component instance. This means you can use whatever subname you like. For example, these configuration files are just as valid as the two above: \begin{itemize} \tightlist \item \texttt{com.Foo-puppies.config} \item \texttt{com.Foo-kitties.config} \end{itemize} Using the subname \texttt{default}, however, is Liferay DXP's convention for configuring a component's first instance. The file name pattern is therefore \begin{verbatim} [PID]-default.config \end{verbatim} A singleton component's configuration file must also start with \texttt{{[}PID{]}} and end with \texttt{.config} or \texttt{.cfg}. Here's the common pattern used for singleton component configuration file names: \begin{verbatim} [PID].config \end{verbatim} When you're done creating a configuration file, you can \href{/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files\#deploying-a-configuration-file}{deploy it}. \section{Resolving Configuration File Deployment Failures}\label{resolving-configuration-file-deployment-failures} The following \texttt{IOException} hints that the configuration file has a syntax issue: \begin{verbatim} Failed to install artifact: [path to .config or .cfg file] java.io.IOException: Unexpected token 78; expected: 61 (line=0, pos=107) \end{verbatim} To resolve this, fix the \hyperref[configuration-file-formats]{configuration file's syntax}. Great! Now you know how to configure module components using configuration files. \section{Related Articles}\label{related-articles} \href{/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files}{Understanding System Configuration Files} ================================================ FILE: book/user/user.aux ================================================ \relax \providecommand{\transparent@use}[1]{} \providecommand\zref@newlabel[2]{} \providecommand\hyper@newdestlabel[2]{} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {1}The Liferay Distinction}{1}{chapter.1}\protected@file@percent } \newlabel{the-liferay-distinction}{{1}{1}{The Liferay Distinction}{chapter.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {2}What's New in Liferay 7.2!}{3}{chapter.2}\protected@file@percent } \newlabel{whats-new-in-liferay-7.2}{{2}{3}{What's New in Liferay 7.2!}{chapter.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.1}Key New Features}{3}{section.2.1}\protected@file@percent } \newlabel{key-new-features}{{2.1}{3}{Key New Features}{section.2.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.2}Experience Creation}{3}{section.2.2}\protected@file@percent } \newlabel{experience-creation}{{2.2}{3}{Experience Creation}{section.2.2}{}} \@writefile{toc}{\contentsline {subsection}{Content Authoring}{3}{section.2.2}\protected@file@percent } \newlabel{content-authoring}{{2.2}{3}{Content Authoring}{section.2.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {2.1}{\ignorespaces Content authors can build pages out of building blocks called \emph {Fragments}.}}{4}{figure.2.1}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Site Building}{4}{figure.2.1}\protected@file@percent } \newlabel{site-building}{{2.2}{4}{Site Building}{figure.2.1}{}} \@writefile{toc}{\contentsline {subsection}{Fragments}{4}{figure.2.1}\protected@file@percent } \newlabel{fragments}{{2.2}{4}{Fragments}{figure.2.1}{}} \@writefile{toc}{\contentsline {subsection}{Fragments Toolkit/CLI}{4}{figure.2.2}\protected@file@percent } \newlabel{fragments-toolkitcli}{{2.2}{4}{Fragments Toolkit/CLI}{figure.2.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {2.2}{\ignorespaces Fragments make it easy to build pages.}}{5}{figure.2.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {2.3}{\ignorespaces Use the Fragments Editor or download the toolkit and use your own tools.}}{5}{figure.2.3}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{In-Context Editing and Content Previews}{6}{figure.2.3}\protected@file@percent } \newlabel{in-context-editing-and-content-previews}{{2.2}{6}{In-Context Editing and Content Previews}{figure.2.3}{}} \@writefile{toc}{\contentsline {subsection}{Content Usages}{6}{figure.2.3}\protected@file@percent } \newlabel{content-usages}{{2.2}{6}{Content Usages}{figure.2.3}{}} \@writefile{toc}{\contentsline {subsection}{A/B Testing (DXP only)}{6}{figure.2.3}\protected@file@percent } \newlabel{ab-testing-dxp-only}{{2.2}{6}{A/B Testing (DXP only)}{figure.2.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.3}Personalization}{6}{section.2.3}\protected@file@percent } \newlabel{personalization}{{2.3}{6}{Personalization}{section.2.3}{}} \@writefile{toc}{\contentsline {subsection}{Session Rules}{6}{section.2.3}\protected@file@percent } \newlabel{session-rules}{{2.3}{6}{Session Rules}{section.2.3}{}} \@writefile{toc}{\contentsline {subsection}{Rule Builder}{6}{section.2.3}\protected@file@percent } \newlabel{rule-builder}{{2.3}{6}{Rule Builder}{section.2.3}{}} \@writefile{toc}{\contentsline {subsection}{Content Sets}{6}{figure.2.4}\protected@file@percent } \newlabel{content-sets}{{2.3}{6}{Content Sets}{figure.2.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {2.4}{\ignorespaces The Rule Builder provides a drag-and-drop interface that helps you build exactly the criteria you need to target the right information to the right users.}}{7}{figure.2.4}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Experiences}{7}{figure.2.4}\protected@file@percent } \newlabel{experiences}{{2.3}{7}{Experiences}{figure.2.4}{}} \@writefile{toc}{\contentsline {subsection}{Content Recommendations (DXP only)}{7}{figure.2.4}\protected@file@percent } \newlabel{content-recommendations-dxp-only}{{2.3}{7}{Content Recommendations (DXP only)}{figure.2.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.4}Bulk Management}{8}{section.2.4}\protected@file@percent } \newlabel{bulk-management}{{2.4}{8}{Bulk Management}{section.2.4}{}} \@writefile{toc}{\contentsline {subsection}{Auto-Tagging}{8}{section.2.4}\protected@file@percent } \newlabel{auto-tagging}{{2.4}{8}{Auto-Tagging}{section.2.4}{}} \@writefile{toc}{\contentsline {subsection}{Bulk Operations}{8}{section.2.4}\protected@file@percent } \newlabel{bulk-operations}{{2.4}{8}{Bulk Operations}{section.2.4}{}} \@writefile{toc}{\contentsline {subsection}{Automatic Document Versioning Policies}{8}{section.2.4}\protected@file@percent } \newlabel{automatic-document-versioning-policies}{{2.4}{8}{Automatic Document Versioning Policies}{section.2.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.5}Business Operations}{8}{section.2.5}\protected@file@percent } \newlabel{business-operations}{{2.5}{8}{Business Operations}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{Forms API}{8}{section.2.5}\protected@file@percent } \newlabel{forms-api}{{2.5}{8}{Forms API}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{Workflow Metrics}{9}{section.2.5}\protected@file@percent } \newlabel{workflow-metrics}{{2.5}{9}{Workflow Metrics}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{Workflow Reports}{9}{section.2.5}\protected@file@percent } \newlabel{workflow-reports}{{2.5}{9}{Workflow Reports}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{Online Document Editing}{9}{section.2.5}\protected@file@percent } \newlabel{online-document-editing}{{2.5}{9}{Online Document Editing}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{P2P Asset Sharing}{9}{section.2.5}\protected@file@percent } \newlabel{p2p-asset-sharing}{{2.5}{9}{P2P Asset Sharing}{section.2.5}{}} \@writefile{toc}{\contentsline {subsection}{Improved Identity Management Tools}{9}{figure.2.5}\protected@file@percent } \newlabel{improved-identity-management-tools}{{2.5}{9}{Improved Identity Management Tools}{figure.2.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.6}Headless Capabilities}{9}{section.2.6}\protected@file@percent } \newlabel{headless-capabilities}{{2.6}{9}{Headless Capabilities}{section.2.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {2.5}{\ignorespaces You can share assets with other users easily.}}{10}{figure.2.5}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{OpenAPI}{10}{section.2.6}\protected@file@percent } \newlabel{openapi}{{2.6}{10}{OpenAPI}{section.2.6}{}} \@writefile{toc}{\contentsline {subsection}{Headless CMS}{10}{section.2.6}\protected@file@percent } \newlabel{headless-cms}{{2.6}{10}{Headless CMS}{section.2.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.7}Developer Experience}{11}{section.2.7}\protected@file@percent } \newlabel{developer-experience}{{2.7}{11}{Developer Experience}{section.2.7}{}} \@writefile{toc}{\contentsline {subsection}{Upgrade Tool}{11}{section.2.7}\protected@file@percent } \newlabel{upgrade-tool}{{2.7}{11}{Upgrade Tool}{section.2.7}{}} \@writefile{toc}{\contentsline {subsection}{Front-end Toolkits}{11}{section.2.7}\protected@file@percent } \newlabel{front-end-toolkits}{{2.7}{11}{Front-end Toolkits}{section.2.7}{}} \@writefile{toc}{\contentsline {subsection}{Struts Removed}{11}{section.2.7}\protected@file@percent } \newlabel{struts-removed}{{2.7}{11}{Struts Removed}{section.2.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {2.8}What's Next}{11}{section.2.8}\protected@file@percent } \newlabel{whats-next}{{2.8}{11}{What's Next}{section.2.8}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {3}Setting Up}{13}{chapter.3}\protected@file@percent } \newlabel{setting-up}{{3}{13}{Setting Up}{chapter.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {4}Where Configuration Happens}{15}{chapter.4}\protected@file@percent } \newlabel{where-configuration-happens}{{4}{15}{Where Configuration Happens}{chapter.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {5}Configuration Scope}{17}{chapter.5}\protected@file@percent } \newlabel{configuration-scope}{{5}{17}{Configuration Scope}{chapter.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {6}System Wide Settings}{19}{chapter.6}\protected@file@percent } \newlabel{system-wide-settings}{{6}{19}{System Wide Settings}{chapter.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {7}System Settings}{21}{chapter.7}\protected@file@percent } \newlabel{system-settings}{{7}{21}{System Settings}{chapter.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {7.1}Editing System Configurations}{21}{section.7.1}\protected@file@percent } \newlabel{editing-system-configurations}{{7.1}{21}{Editing System Configurations}{section.7.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {7.1}{\ignorespaces System Settings are accessed through the Control Panel.}}{22}{figure.7.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {7.2}{\ignorespaces System Settings are organized by section and category.}}{23}{figure.7.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {7.3}{\ignorespaces After saving changes to a configuration, the actions \emph {Reset Default Values} and \emph {Export} are available for it.}}{23}{figure.7.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {7.2}Configuration Scope}{23}{section.7.2}\protected@file@percent } \newlabel{configuration-scope-1}{{7.2}{23}{Configuration Scope}{section.7.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {7.4}{\ignorespaces Some System Settings entries are system scoped.}}{24}{figure.7.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {7.5}{\ignorespaces Some System Settings are virtual instance scoped.}}{24}{figure.7.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {7.6}{\ignorespaces Some System Settings are site scoped.}}{24}{figure.7.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {7.7}{\ignorespaces Some System Settings entries are widget scoped.}}{25}{figure.7.7}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {7.3}Exporting and Importing Configurations}{25}{section.7.3}\protected@file@percent } \newlabel{exporting-and-importing-configurations}{{7.3}{25}{Exporting and Importing Configurations}{section.7.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {8}Understanding System Configuration Files}{27}{chapter.8}\protected@file@percent } \newlabel{understanding-system-configuration-files}{{8}{27}{Understanding System Configuration Files}{chapter.8}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {9}Creating Configuration Files}{29}{chapter.9}\protected@file@percent } \newlabel{creating-configuration-files}{{9}{29}{Creating Configuration Files}{chapter.9}{}} \@writefile{lof}{\contentsline {figure}{\numberline {9.1}{\ignorespaces The Web Content System Settings entry has the back-end ID \texttt {com.liferay.journal.configuration.JournalServiceConfiguration}.}}{29}{figure.9.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {9.1}Key/Value Syntax}{29}{section.9.1}\protected@file@percent } \newlabel{keyvalue-syntax}{{9.1}{29}{Key/Value Syntax}{section.9.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {9.2}Multi-Value Settings}{30}{section.9.2}\protected@file@percent } \newlabel{multi-value-settings}{{9.2}{30}{Multi-Value Settings}{section.9.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {9.2}{\ignorespaces The Web Content System Settings entry has many \emph {Characters Blacklist} fields.}}{30}{figure.9.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {9.3}Escaping Characters}{31}{section.9.3}\protected@file@percent } \newlabel{escaping-characters}{{9.3}{31}{Escaping Characters}{section.9.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {9.4}Typed Values}{31}{section.9.4}\protected@file@percent } \newlabel{typed-values}{{9.4}{31}{Typed Values}{section.9.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {9.5}Deploying a Configuration File}{31}{section.9.5}\protected@file@percent } \newlabel{deploying-a-configuration-file}{{9.5}{31}{Deploying a Configuration File}{section.9.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {9.6}Configuration Files and Clustering}{32}{section.9.6}\protected@file@percent } \newlabel{configuration-files-and-clustering}{{9.6}{32}{Configuration Files and Clustering}{section.9.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {10}Factory Configurations}{33}{chapter.10}\protected@file@percent } \newlabel{factory-configurations}{{10}{33}{Factory Configurations}{chapter.10}{}} \@writefile{lof}{\contentsline {figure}{\numberline {10.1}{\ignorespaces If a System Settings entry has an ADD button, it's suitable for factory configurations.}}{33}{figure.10.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {11}Server Administration}{37}{chapter.11}\protected@file@percent } \newlabel{server-administration}{{11}{37}{Server Administration}{chapter.11}{}} \@writefile{lof}{\contentsline {figure}{\numberline {11.1}{\ignorespaces The Resources tab of Server Administration shows a graph of your server's memory usage.}}{37}{figure.11.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {12}Server Administration: Resources}{39}{chapter.12}\protected@file@percent } \newlabel{server-administration-resources}{{12}{39}{Server Administration: Resources}{chapter.12}{}} \@writefile{lof}{\contentsline {figure}{\numberline {12.1}{\ignorespaces The Resources tab of Server Administration lets you execute several server maintenance tasks.}}{40}{figure.12.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {13}Server Administration: External Services}{41}{chapter.13}\protected@file@percent } \newlabel{server-administration-external-services}{{13}{41}{Server Administration: External Services}{chapter.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {13.1}ImageMagick Configuration}{42}{section.13.1}\protected@file@percent } \newlabel{imagemagick-configuration}{{13.1}{42}{ImageMagick Configuration}{section.13.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {13.1}{\ignorespaces Enable ImageMagick and Ghostscript, and verify that the paths are correct.}}{42}{figure.13.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {13.2}Xuggler Configuration}{42}{section.13.2}\protected@file@percent } \newlabel{xuggler-configuration}{{13.2}{42}{Xuggler Configuration}{section.13.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {13.2}{\ignorespaces Install Xuggler.}}{43}{figure.13.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {14}Setting Up a Virtual Instance}{45}{chapter.14}\protected@file@percent } \newlabel{setting-up-a-virtual-instance}{{14}{45}{Setting Up a Virtual Instance}{chapter.14}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {15}Virtual Instances}{47}{chapter.15}\protected@file@percent } \newlabel{virtual-instances}{{15}{47}{Virtual Instances}{chapter.15}{}} \@writefile{lof}{\contentsline {figure}{\numberline {15.1}{\ignorespaces Add and manage virtual instances of Liferay in the Control Panel's \emph {Configuration} → \emph {Virtual Instances} section.}}{47}{figure.15.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {16}Configuring Virtual Instances}{49}{chapter.16}\protected@file@percent } \newlabel{configuring-virtual-instances}{{16}{49}{Configuring Virtual Instances}{chapter.16}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {17}Email Instance Settings}{51}{chapter.17}\protected@file@percent } \newlabel{email-instance-settings}{{17}{51}{Email Instance Settings}{chapter.17}{}} \@writefile{toc}{\contentsline {section}{\numberline {17.1}Account Created Notification}{51}{section.17.1}\protected@file@percent } \newlabel{account-created-notification}{{17.1}{51}{Account Created Notification}{section.17.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {17.2}Email Sender}{52}{section.17.2}\protected@file@percent } \newlabel{email-sender}{{17.2}{52}{Email Sender}{section.17.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {17.1}{\ignorespaces Customize the email template for the email messages sent to new Users.}}{52}{figure.17.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {17.3}Email Verification Notification}{52}{section.17.3}\protected@file@percent } \newlabel{email-verification-notification}{{17.3}{52}{Email Verification Notification}{section.17.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {17.4}Mail Host Names}{52}{section.17.4}\protected@file@percent } \newlabel{mail-host-names}{{17.4}{52}{Mail Host Names}{section.17.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {17.5}Password Changed Notification}{53}{section.17.5}\protected@file@percent } \newlabel{password-changed-notification}{{17.5}{53}{Password Changed Notification}{section.17.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {17.6}Password Reset Notification}{53}{section.17.6}\protected@file@percent } \newlabel{password-reset-notification}{{17.6}{53}{Password Reset Notification}{section.17.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {17.2}{\ignorespaces There are some handy variables available for use in email templates.}}{53}{figure.17.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {18}Instance Configuration Instance Settings}{55}{chapter.18}\protected@file@percent } \newlabel{instance-configuration-instance-settings}{{18}{55}{Instance Configuration Instance Settings}{chapter.18}{}} \@writefile{toc}{\contentsline {section}{\numberline {18.1}Appearance}{55}{section.18.1}\protected@file@percent } \newlabel{appearance}{{18.1}{55}{Appearance}{section.18.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {18.2}Contact Information}{56}{section.18.2}\protected@file@percent } \newlabel{contact-information}{{18.2}{56}{Contact Information}{section.18.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {18.3}General}{56}{section.18.3}\protected@file@percent } \newlabel{general}{{18.3}{56}{General}{section.18.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {18.4}Terms of Use}{57}{section.18.4}\protected@file@percent } \newlabel{terms-of-use}{{18.4}{57}{Terms of Use}{section.18.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {18.1}{\ignorespaces The Site ID in Site Settings is the Group ID in the terms of Use configuration.}}{57}{figure.18.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {18.2}{\ignorespaces The Web Content Article ID is displayed in the edit screen.}}{57}{figure.18.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {18.3}{\ignorespaces Turn a Web Content Article into the Site's Terms of Use page.}}{58}{figure.18.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {19}User Authentication Instance Settings}{59}{chapter.19}\protected@file@percent } \newlabel{user-authentication-instance-settings}{{19}{59}{User Authentication Instance Settings}{chapter.19}{}} \@writefile{toc}{\contentsline {section}{\numberline {19.1}General}{59}{section.19.1}\protected@file@percent } \newlabel{general-1}{{19.1}{59}{General}{section.19.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {19.1}{\ignorespaces Configure general authentication behavior and settings for external authentication systems.}}{60}{figure.19.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {19.2}Reserved Credentials}{61}{section.19.2}\protected@file@percent } \newlabel{reserved-credentials}{{19.2}{61}{Reserved Credentials}{section.19.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {20}Users Instance Settings}{63}{chapter.20}\protected@file@percent } \newlabel{users-instance-settings}{{20}{63}{Users Instance Settings}{chapter.20}{}} \@writefile{toc}{\contentsline {section}{\numberline {20.1}Personal Menu}{63}{section.20.1}\protected@file@percent } \newlabel{personal-menu}{{20.1}{63}{Personal Menu}{section.20.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {20.2}Default User Associations}{63}{section.20.2}\protected@file@percent } \newlabel{default-user-associations}{{20.2}{63}{Default User Associations}{section.20.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {20.3}Fields}{64}{section.20.3}\protected@file@percent } \newlabel{fields}{{20.3}{64}{Fields}{section.20.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {21}More Platform Section Instance Settings}{65}{chapter.21}\protected@file@percent } \newlabel{more-platform-section-instance-settings}{{21}{65}{More Platform Section Instance Settings}{chapter.21}{}} \@writefile{toc}{\contentsline {section}{\numberline {21.1}Infrastructure}{65}{section.21.1}\protected@file@percent } \newlabel{infrastructure}{{21.1}{65}{Infrastructure}{section.21.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {21.2}Localization}{65}{section.21.2}\protected@file@percent } \newlabel{localization}{{21.2}{65}{Localization}{section.21.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {21.3}Analytics}{66}{section.21.3}\protected@file@percent } \newlabel{analytics}{{21.3}{66}{Analytics}{section.21.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {21.4}Third Party}{66}{section.21.4}\protected@file@percent } \newlabel{third-party}{{21.4}{66}{Third Party}{section.21.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {22}Using Liferay's Script Engine}{67}{chapter.22}\protected@file@percent } \newlabel{using-liferays-script-engine}{{22}{67}{Using Liferay's Script Engine}{chapter.22}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {23}Invoking Liferay Services From Scripts}{69}{chapter.23}\protected@file@percent } \newlabel{invoking-liferay-services-from-scripts}{{23}{69}{Invoking Liferay Services From Scripts}{chapter.23}{}} \@writefile{toc}{\contentsline {section}{\numberline {23.1}Related Topics}{70}{section.23.1}\protected@file@percent } \newlabel{related-topics}{{23.1}{70}{Related Topics}{section.23.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {24}Running Scripts From the Script Console}{71}{chapter.24}\protected@file@percent } \newlabel{running-scripts-from-the-script-console}{{24}{71}{Running Scripts From the Script Console}{chapter.24}{}} \@writefile{toc}{\contentsline {section}{\numberline {24.1}Running the Sample Script}{71}{section.24.1}\protected@file@percent } \newlabel{running-the-sample-script}{{24.1}{71}{Running the Sample Script}{section.24.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {24.1}{\ignorespaces The script console's sample Groovy script prints the User count to the console's \emph {Output} section.}}{72}{figure.24.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {24.2}Predefined Variables}{72}{section.24.2}\protected@file@percent } \newlabel{predefined-variables}{{24.2}{72}{Predefined Variables}{section.24.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {24.2}{\ignorespaces Here's an example of invoking a Groovy script that uses the predefined \texttt {out}, \texttt {actionRequest}, and \texttt {userInfo} variables to print information about the company and current user.}}{73}{figure.24.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {24.3}Tips}{73}{section.24.3}\protected@file@percent } \newlabel{tips}{{24.3}{73}{Tips}{section.24.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {25}Leveraging the Script Engine in Workflow}{75}{chapter.25}\protected@file@percent } \newlabel{leveraging-the-script-engine-in-workflow}{{25}{75}{Leveraging the Script Engine in Workflow}{chapter.25}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.1}Injected Variables}{75}{section.25.1}\protected@file@percent } \newlabel{injected-variables}{{25.1}{75}{Injected Variables}{section.25.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.2}Variables that are Always Available}{75}{section.25.2}\protected@file@percent } \newlabel{variables-that-are-always-available}{{25.2}{75}{Variables that are Always Available}{section.25.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.3}Variables Injected into Task Nodes}{76}{section.25.3}\protected@file@percent } \newlabel{variables-injected-into-task-nodes}{{25.3}{76}{Variables Injected into Task Nodes}{section.25.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.4}Scripting Examples}{76}{section.25.4}\protected@file@percent } \newlabel{scripting-examples}{{25.4}{76}{Scripting Examples}{section.25.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.5}Calling OSGi Services}{78}{section.25.5}\protected@file@percent } \newlabel{calling-osgi-services}{{25.5}{78}{Calling OSGi Services}{section.25.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {25.6}Related Topics}{79}{section.25.6}\protected@file@percent } \newlabel{related-topics-1}{{25.6}{79}{Related Topics}{section.25.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {26}Script Examples}{81}{chapter.26}\protected@file@percent } \newlabel{script-examples}{{26}{81}{Script Examples}{chapter.26}{}} \@writefile{toc}{\contentsline {section}{\numberline {26.1}Example 1: Presenting New Terms of Use to Users}{81}{section.26.1}\protected@file@percent } \newlabel{example-1-presenting-new-terms-of-use-to-users}{{26.1}{81}{Example 1: Presenting New Terms of Use to Users}{section.26.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {26.2}Example 2: Embedding HTML Markup in Script Outputs}{82}{section.26.2}\protected@file@percent } \newlabel{example-2-embedding-html-markup-in-script-outputs}{{26.2}{82}{Example 2: Embedding HTML Markup in Script Outputs}{section.26.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {26.1}{\ignorespaces Here's an example of invoking a Groovy script that embeds HTML markup in the output of the script.}}{83}{figure.26.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {26.3}Example 3: Show Exceptions in the Script Console}{83}{section.26.3}\protected@file@percent } \newlabel{example-3-show-exceptions-in-the-script-console}{{26.3}{83}{Example 3: Show Exceptions in the Script Console}{section.26.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {26.2}{\ignorespaces Here's an example of a Groovy script that catches exceptions and prints exception information to the script console.}}{83}{figure.26.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {26.4}Example 4: Implement a Preview Mode}{84}{section.26.4}\protected@file@percent } \newlabel{example-4-implement-a-preview-mode}{{26.4}{84}{Example 4: Implement a Preview Mode}{section.26.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {26.5}Example 5: Plan a File Output for Long-Running Scripts}{84}{section.26.5}\protected@file@percent } \newlabel{example-5-plan-a-file-output-for-long-running-scripts}{{26.5}{84}{Example 5: Plan a File Output for Long-Running Scripts}{section.26.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {26.6}Related Topics}{85}{section.26.6}\protected@file@percent } \newlabel{related-topics-2}{{26.6}{85}{Related Topics}{section.26.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {27}Custom Fields}{87}{chapter.27}\protected@file@percent } \newlabel{custom-fields}{{27}{87}{Custom Fields}{chapter.27}{}} \@writefile{toc}{\contentsline {section}{\numberline {27.1}Adding Custom Fields}{88}{section.27.1}\protected@file@percent } \newlabel{adding-custom-fields}{{27.1}{88}{Adding Custom Fields}{section.27.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {27.1}{\ignorespaces At The Lunar Resort, a Head Circumference field is necessary for all users.}}{88}{figure.27.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {27.2}{\ignorespaces The Custom Fields panel is found at the bottom of the Edit User form.}}{89}{figure.27.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {27.2}Editing a Custom Field}{89}{section.27.2}\protected@file@percent } \newlabel{editing-a-custom-field}{{27.2}{89}{Editing a Custom Field}{section.27.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {27.3}{\ignorespaces The exact Custom Fields configuration options you use depend on the field type you choose.}}{90}{figure.27.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {27.4}{\ignorespaces You can delete a custom field, edit it, or configure its permissions.}}{91}{figure.27.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {28}Managing Users}{93}{chapter.28}\protected@file@percent } \newlabel{managing-users}{{28}{93}{Managing Users}{chapter.28}{}} \@writefile{lof}{\contentsline {figure}{\numberline {28.1}{\ignorespaces Administrators can access the Control Panel from the Product Menu.}}{94}{figure.28.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {29}Users and Organizations}{95}{chapter.29}\protected@file@percent } \newlabel{users-and-organizations}{{29}{95}{Users and Organizations}{chapter.29}{}} \@writefile{toc}{\contentsline {section}{\numberline {29.1}What are Users?}{95}{section.29.1}\protected@file@percent } \newlabel{what-are-users}{{29.1}{95}{What are Users?}{section.29.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {30}Adding, Editing, and Deleting Users}{97}{chapter.30}\protected@file@percent } \newlabel{adding-editing-and-deleting-users}{{30}{97}{Adding, Editing, and Deleting Users}{chapter.30}{}} \@writefile{toc}{\contentsline {section}{\numberline {30.1}Adding Users}{97}{section.30.1}\protected@file@percent } \newlabel{adding-users}{{30.1}{97}{Adding Users}{section.30.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {30.1}{\ignorespaces Add Users from the Users and Organizations section of the Control Panel.}}{98}{figure.30.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {30.2}Editing Users}{98}{section.30.2}\protected@file@percent } \newlabel{editing-users}{{30.2}{98}{Editing Users}{section.30.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {30.2}{\ignorespaces At a minimum, enter a screen name, email address, and first name to create a new user account. Then you'll be taken to the Information form and can continue configuring the user.}}{99}{figure.30.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {30.3}{\ignorespaces Enter the password twice to manually set the password for a user. If the Password Policy you're using is configured to allow it, select whether to require the user to reset their password the first time they sign in to the portal.}}{100}{figure.30.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {30.3}Deleting Users}{100}{section.30.3}\protected@file@percent } \newlabel{deleting-users}{{30.3}{100}{Deleting Users}{section.30.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {30.4}{\ignorespaces You can choose whether to view active or inactive (deactivated) portal users in the users list found at \emph {Product Menu} → \emph {Control Panel} → \emph {Users} → \emph {Users and Organizations}.}}{101}{figure.30.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {31}User Management: Additional Topics}{103}{chapter.31}\protected@file@percent } \newlabel{user-management-additional-topics}{{31}{103}{User Management: Additional Topics}{chapter.31}{}} \@writefile{toc}{\contentsline {section}{\numberline {31.1}Password Resets}{103}{section.31.1}\protected@file@percent } \newlabel{password-resets}{{31.1}{103}{Password Resets}{section.31.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {31.2}Adding an Administrative User}{103}{section.31.2}\protected@file@percent } \newlabel{adding-an-administrative-user}{{31.2}{103}{Adding an Administrative User}{section.31.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {31.3}Gender}{104}{section.31.3}\protected@file@percent } \newlabel{gender}{{31.3}{104}{Gender}{section.31.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {31.4}User Profile Pictures}{104}{section.31.4}\protected@file@percent } \newlabel{user-profile-pictures}{{31.4}{104}{User Profile Pictures}{section.31.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {31.1}{\ignorespaces Upload images for user avatars in the Edit User form.}}{105}{figure.31.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {31.2}{\ignorespaces The default user profile picture is an icon with the user initials over a randomly colored bubble.}}{105}{figure.31.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {31.3}{\ignorespaces If you disable the default initials-based profile picture, this icon is used instead.}}{105}{figure.31.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {31.5}Numeric Screen Names}{106}{section.31.5}\protected@file@percent } \newlabel{numeric-screen-names}{{31.5}{106}{Numeric Screen Names}{section.31.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {32}Organizations}{109}{chapter.32}\protected@file@percent } \newlabel{organizations}{{32}{109}{Organizations}{chapter.32}{}} \@writefile{toc}{\contentsline {section}{\numberline {32.1}When to Use Organizations}{109}{section.32.1}\protected@file@percent } \newlabel{when-to-use-organizations}{{32.1}{109}{When to Use Organizations}{section.32.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {32.2}What can Organization Administrators Do?}{110}{section.32.2}\protected@file@percent } \newlabel{what-can-organization-administrators-do}{{32.2}{110}{What can Organization Administrators Do?}{section.32.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {32.1}{\ignorespaces The My Organizations application lets Organization Administrators manage their organizations in their personal site.}}{111}{figure.32.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {32.3}Organization Roles and Permissions}{112}{section.32.3}\protected@file@percent } \newlabel{organization-roles-and-permissions}{{32.3}{112}{Organization Roles and Permissions}{section.32.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {32.4}Organization Sites}{112}{section.32.4}\protected@file@percent } \newlabel{organization-sites}{{32.4}{112}{Organization Sites}{section.32.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {33}Managing Organizations}{113}{chapter.33}\protected@file@percent } \newlabel{managing-organizations}{{33}{113}{Managing Organizations}{chapter.33}{}} \@writefile{toc}{\contentsline {section}{\numberline {33.1}Adding Organizations}{113}{section.33.1}\protected@file@percent } \newlabel{adding-organizations}{{33.1}{113}{Adding Organizations}{section.33.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {33.2}Editing Organizations}{114}{section.33.2}\protected@file@percent } \newlabel{editing-organizations}{{33.2}{114}{Editing Organizations}{section.33.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {33.3}Organization Types}{114}{section.33.3}\protected@file@percent } \newlabel{organization-types}{{33.3}{114}{Organization Types}{section.33.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {33.1}{\ignorespaces Create new organization types through the System Settings entry called Organization Types.}}{115}{figure.33.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {33.2}{\ignorespaces Custom configuration types are available in the Add Organization form.}}{116}{figure.33.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {34}Roles and Permissions}{117}{chapter.34}\protected@file@percent } \newlabel{roles-and-permissions}{{34}{117}{Roles and Permissions}{chapter.34}{}} \@writefile{lof}{\contentsline {figure}{\numberline {34.1}{\ignorespaces Assign Users to a role, directly or by their association with a Site, Organization, or User Group.}}{117}{figure.34.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {34.1}Deleting Asset Containers}{118}{section.34.1}\protected@file@percent } \newlabel{deleting-asset-containers}{{34.1}{118}{Deleting Asset Containers}{section.34.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {34.2}Default Liferay Roles}{118}{section.34.2}\protected@file@percent } \newlabel{default-liferay-roles}{{34.2}{118}{Default Liferay Roles}{section.34.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {35}Managing Roles}{121}{chapter.35}\protected@file@percent } \newlabel{managing-roles}{{35}{121}{Managing Roles}{chapter.35}{}} \@writefile{lof}{\contentsline {figure}{\numberline {35.1}{\ignorespaces The Roles application lets you add and manage roles for the global (Regular), Site, or Organization scope.}}{121}{figure.35.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {35.1}Creating Roles}{122}{section.35.1}\protected@file@percent } \newlabel{creating-roles}{{35.1}{122}{Creating Roles}{section.35.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {35.2}Assigning Users to a Role}{122}{section.35.2}\protected@file@percent } \newlabel{assigning-users-to-a-role}{{35.2}{122}{Assigning Users to a Role}{section.35.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {36}Defining Role Permissions}{123}{chapter.36}\protected@file@percent } \newlabel{defining-role-permissions}{{36}{123}{Defining Role Permissions}{chapter.36}{}} \@writefile{lof}{\contentsline {figure}{\numberline {36.1}{\ignorespaces When defining permissions on a Role, the Summary view provides a list of permissions that have already been defined for the role. The area on the left side of the screen lets you drill down through various categories of permissions.}}{124}{figure.36.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {36.2}{\ignorespaces Users assigned to the User Group Manager Role can't find any users to add unless they have view permissions on the User resource.}}{124}{figure.36.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {36.1}Delegating Social Activities Configuration}{125}{section.36.1}\protected@file@percent } \newlabel{delegating-social-activities-configuration}{{36.1}{125}{Delegating Social Activities Configuration}{section.36.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {36.3}{\ignorespaces You can fine-tune which actions are defined for a role within a specific application like the Message Boards.}}{126}{figure.36.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {37}Managing User Data}{127}{chapter.37}\protected@file@percent } \newlabel{managing-user-data}{{37}{127}{Managing User Data}{chapter.37}{}} \@writefile{toc}{\contentsline {section}{\numberline {37.1}Anonymizing Data}{128}{section.37.1}\protected@file@percent } \newlabel{anonymizing-data}{{37.1}{128}{Anonymizing Data}{section.37.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {37.1}{\ignorespaces Anonymized content is presented with the User Anonymous Anonymous's identifying information.}}{128}{figure.37.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {37.2}{\ignorespaces Assign your own Anonymous User from Control Panel → Configuration → System Settings → Users → Anonymous User.}}{129}{figure.37.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {37.2}Manual Anonymization}{129}{section.37.2}\protected@file@percent } \newlabel{manual-anonymization}{{37.2}{129}{Manual Anonymization}{section.37.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {37.3}{\ignorespaces Even though this Message Boards Message (a comment on a blog post in this case) is anonymized, it should be edited to remove User Associated Data from the content of the message.}}{131}{figure.37.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {38}Sanitizing User Data}{133}{chapter.38}\protected@file@percent } \newlabel{sanitizing-user-data}{{38}{133}{Sanitizing User Data}{chapter.38}{}} \@writefile{toc}{\contentsline {section}{\numberline {38.1}The Personal Data Erasure Screen}{134}{section.38.1}\protected@file@percent } \newlabel{the-personal-data-erasure-screen}{{38.1}{134}{The Personal Data Erasure Screen}{section.38.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {38.1}{\ignorespaces From here, you can browse all data the user posted on his or her personal Site.}}{134}{figure.38.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {38.2}{\ignorespaces Choose Regular Sites to browse all data posted by the user on administratively-created Sites.}}{135}{figure.38.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {38.3}{\ignorespaces Pepper's blog entry might need review.}}{135}{figure.38.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {38.2}Delete the User}{136}{section.38.2}\protected@file@percent } \newlabel{delete-the-user}{{38.2}{136}{Delete the User}{section.38.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {38.4}{\ignorespaces To finish the data erasure process, delete the User.}}{136}{figure.38.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {39}Exporting User Data}{137}{chapter.39}\protected@file@percent } \newlabel{exporting-user-data}{{39}{137}{Exporting User Data}{chapter.39}{}} \@writefile{toc}{\contentsline {section}{\numberline {39.1}Exporting and Downloading}{137}{section.39.1}\protected@file@percent } \newlabel{exporting-and-downloading}{{39.1}{137}{Exporting and Downloading}{section.39.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {39.1}{\ignorespaces The Export Personal Data tool lets you export all or some of the User's data.}}{138}{figure.39.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {39.2}{\ignorespaces Once User data is successfully exported, the export process is displayed in the User's Export Personal Data list.}}{139}{figure.39.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {39.2}Examining Exported Data}{139}{section.39.2}\protected@file@percent } \newlabel{examining-exported-data}{{39.2}{139}{Examining Exported Data}{section.39.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {39.3}{\ignorespaces A Comment on a blog post is User Associated Data.}}{141}{figure.39.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {40}User Groups}{143}{chapter.40}\protected@file@percent } \newlabel{user-groups}{{40}{143}{User Groups}{chapter.40}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {41}Creating a User Group}{145}{chapter.41}\protected@file@percent } \newlabel{creating-a-user-group}{{41}{145}{Creating a User Group}{chapter.41}{}} \@writefile{toc}{\contentsline {section}{\numberline {41.1}Assigning Members to a User Group}{145}{section.41.1}\protected@file@percent } \newlabel{assigning-members-to-a-user-group}{{41.1}{145}{Assigning Members to a User Group}{section.41.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {41.1}{\ignorespaces The New User Group form.}}{146}{figure.41.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {41.2}{\ignorespaces The user group you just created now appears in the table.}}{146}{figure.41.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {41.3}{\ignorespaces Select the users to add to the user group.}}{147}{figure.41.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {42}User Groups and Site Membership}{149}{chapter.42}\protected@file@percent } \newlabel{user-groups-and-site-membership}{{42}{149}{User Groups and Site Membership}{chapter.42}{}} \@writefile{lof}{\contentsline {figure}{\numberline {42.1}{\ignorespaces Select \emph {Memberships} from the People menu.}}{150}{figure.42.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {42.2}{\ignorespaces The User Groups tab in Memberships shows the User Groups currently assigned to the Site.}}{150}{figure.42.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {43}User Group Sites}{151}{chapter.43}\protected@file@percent } \newlabel{user-group-sites}{{43}{151}{User Group Sites}{chapter.43}{}} \@writefile{toc}{\contentsline {section}{\numberline {43.1}Creating User Group Sites From Site Templates}{151}{section.43.1}\protected@file@percent } \newlabel{creating-user-group-sites-from-site-templates}{{43.1}{151}{Creating User Group Sites From Site Templates}{section.43.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {43.2}Creating User Group Sites Manually}{152}{section.43.2}\protected@file@percent } \newlabel{creating-user-group-sites-manually}{{43.2}{152}{Creating User Group Sites Manually}{section.43.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {43.3}Legacy User Group Sites Behavior}{153}{section.43.3}\protected@file@percent } \newlabel{legacy-user-group-sites-behavior}{{43.3}{153}{Legacy User Group Sites Behavior}{section.43.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {44}Configuring User Group Permissions}{155}{chapter.44}\protected@file@percent } \newlabel{configuring-user-group-permissions}{{44}{155}{Configuring User Group Permissions}{chapter.44}{}} \@writefile{lof}{\contentsline {figure}{\numberline {44.1}{\ignorespaces Select \emph {Memberships} from the Site Administration menu.}}{156}{figure.44.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {44.2}{\ignorespaces Select \emph {Assign Site Roles} for the user group.}}{156}{figure.44.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {45}Editing User Groups}{157}{chapter.45}\protected@file@percent } \newlabel{editing-user-groups}{{45}{157}{Editing User Groups}{chapter.45}{}} \@writefile{lof}{\contentsline {figure}{\numberline {45.1}{\ignorespaces The user groups appear in a table.}}{157}{figure.45.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {45.2}{\ignorespaces The list of Users lets you manage the User Group's membership.}}{158}{figure.45.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {45.3}{\ignorespaces The Actions menu for a user group.}}{158}{figure.45.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {46}Password Policies}{159}{chapter.46}\protected@file@percent } \newlabel{password-policies}{{46}{159}{Password Policies}{chapter.46}{}} \@writefile{toc}{\contentsline {section}{\numberline {46.1}Adding and Configuring Password Policies}{159}{section.46.1}\protected@file@percent } \newlabel{adding-and-configuring-password-policies}{{46.1}{159}{Adding and Configuring Password Policies}{section.46.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {46.1}{\ignorespaces You can create new password policies to suit your needs.}}{160}{figure.46.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {46.2}Assigning Users to a Password Policy}{160}{section.46.2}\protected@file@percent } \newlabel{assigning-users-to-a-password-policy}{{46.2}{160}{Assigning Users to a Password Policy}{section.46.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {46.2}{\ignorespaces Assign members to new password policies to make them take effect.}}{161}{figure.46.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {46.3}Default Policy Properties}{161}{section.46.3}\protected@file@percent } \newlabel{default-policy-properties}{{46.3}{161}{Default Policy Properties}{section.46.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {47}Auditing Users}{163}{chapter.47}\protected@file@percent } \newlabel{auditing-users}{{47}{163}{Auditing Users}{chapter.47}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {48}Viewing Audit Events}{165}{chapter.48}\protected@file@percent } \newlabel{viewing-audit-events}{{48}{165}{Viewing Audit Events}{chapter.48}{}} \@writefile{lof}{\contentsline {figure}{\numberline {48.1}{\ignorespaces The Audit app displays the events it captures in a searchable list.}}{165}{figure.48.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {48.2}{\ignorespaces Click an event in the list to show its details. The details for this event show that John Watson updated his user account's \texttt {prefixId} from \texttt {1} to \texttt {4}. The \texttt {prefixId} represents a name prefix like Dr., Mr., Mrs., or Ms.}}{166}{figure.48.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {48.1}Finding Audit Events}{166}{section.48.1}\protected@file@percent } \newlabel{finding-audit-events}{{48.1}{166}{Finding Audit Events}{section.48.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {48.3}{\ignorespaces Searching for audit events is easy with the Audit app's advanced search form. You can specify various search criteria to find the types of events you want.}}{168}{figure.48.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {48.4}{\ignorespaces This record shows that the default administrative user removed the Power User Role from the User Test Test.}}{169}{figure.48.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {49}Configuring Audits}{171}{chapter.49}\protected@file@percent } \newlabel{configuring-audits}{{49}{171}{Configuring Audits}{chapter.49}{}} \@writefile{toc}{\contentsline {section}{\numberline {49.1}Reporting Audit Events in Liferay's Logs and Console}{171}{section.49.1}\protected@file@percent } \newlabel{reporting-audit-events-in-liferays-logs-and-console}{{49.1}{171}{Reporting Audit Events in Liferay's Logs and Console}{section.49.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {49.2}Configuring Audit Events for Scheduled Liferay Jobs}{172}{section.49.2}\protected@file@percent } \newlabel{configuring-audit-events-for-scheduled-liferay-jobs}{{49.2}{172}{Configuring Audit Events for Scheduled Liferay Jobs}{section.49.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {49.3}Enabling or Disabling Audit Events Entirely}{173}{section.49.3}\protected@file@percent } \newlabel{enabling-or-disabling-audit-events-entirely}{{49.3}{173}{Enabling or Disabling Audit Events Entirely}{section.49.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {49.4}The End of the Story}{173}{section.49.4}\protected@file@percent } \newlabel{the-end-of-the-story}{{49.4}{173}{The End of the Story}{section.49.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {50}Web Experience Management}{175}{chapter.50}\protected@file@percent } \newlabel{web-experience-management}{{50}{175}{Web Experience Management}{chapter.50}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {51}Authoring Content: Structured and Inline Content}{177}{chapter.51}\protected@file@percent } \newlabel{authoring-content-structured-and-inline-content}{{51}{177}{Authoring Content: Structured and Inline Content}{chapter.51}{}} \@writefile{toc}{\contentsline {section}{\numberline {51.1}Structured Web Content}{177}{section.51.1}\protected@file@percent } \newlabel{structured-web-content}{{51.1}{177}{Structured Web Content}{section.51.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {51.2}Inline Content}{178}{section.51.2}\protected@file@percent } \newlabel{inline-content}{{51.2}{178}{Inline Content}{section.51.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {51.3}What's Best for Your Use Case?}{178}{section.51.3}\protected@file@percent } \newlabel{whats-best-for-your-use-case}{{51.3}{178}{What's Best for Your Use Case?}{section.51.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {52}Building a Site}{181}{chapter.52}\protected@file@percent } \newlabel{building-a-site}{{52}{181}{Building a Site}{chapter.52}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {53}Site Management}{183}{chapter.53}\protected@file@percent } \newlabel{site-management}{{53}{183}{Site Management}{chapter.53}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {54}Understanding Site Management}{185}{chapter.54}\protected@file@percent } \newlabel{understanding-site-management}{{54}{185}{Understanding Site Management}{chapter.54}{}} \@writefile{toc}{\contentsline {section}{\numberline {54.1}Site Scope}{185}{section.54.1}\protected@file@percent } \newlabel{site-scope}{{54.1}{185}{Site Scope}{section.54.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {54.1}{\ignorespaces Your Site's content resides in the Site Administration menu.}}{186}{figure.54.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {54.2}Site Hierarchies}{186}{section.54.2}\protected@file@percent } \newlabel{site-hierarchies}{{54.2}{186}{Site Hierarchies}{section.54.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {54.2}{\ignorespaces The Site Map application lets users navigate among pages of a Site organized hierarchically.}}{187}{figure.54.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {54.3}Site Members}{188}{section.54.3}\protected@file@percent } \newlabel{site-members}{{54.3}{188}{Site Members}{section.54.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {54.4}Page Sets}{188}{section.54.4}\protected@file@percent } \newlabel{page-sets}{{54.4}{188}{Page Sets}{section.54.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {55}Adding Sites}{189}{chapter.55}\protected@file@percent } \newlabel{adding-sites}{{55}{189}{Adding Sites}{chapter.55}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {56}Adding Pages to Sites}{193}{chapter.56}\protected@file@percent } \newlabel{adding-pages-to-sites}{{56}{193}{Adding Pages to Sites}{chapter.56}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {57}Creating Pages}{195}{chapter.57}\protected@file@percent } \newlabel{creating-pages}{{57}{195}{Creating Pages}{chapter.57}{}} \@writefile{lof}{\contentsline {figure}{\numberline {57.1}{\ignorespaces The Pages screen lets you edit your Site pages as a whole.}}{195}{figure.57.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {57.2}{\ignorespaces Understanding the options on Site Pages.}}{196}{figure.57.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {57.3}{\ignorespaces You must select a page type when adding pages.}}{197}{figure.57.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {57.4}{\ignorespaces Here are three different page types as they're displayed in the heading.}}{198}{figure.57.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {58}Creating Content Pages}{199}{chapter.58}\protected@file@percent } \newlabel{creating-content-pages}{{58}{199}{Creating Content Pages}{chapter.58}{}} \@writefile{toc}{\contentsline {section}{\numberline {58.1}Creating Page Fragments}{199}{section.58.1}\protected@file@percent } \newlabel{creating-page-fragments}{{58.1}{199}{Creating Page Fragments}{section.58.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {58.2}Building Content Pages}{200}{section.58.2}\protected@file@percent } \newlabel{building-content-pages}{{58.2}{200}{Building Content Pages}{section.58.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {58.3}The Content Page Interface}{200}{section.58.3}\protected@file@percent } \newlabel{the-content-page-interface}{{58.3}{200}{The Content Page Interface}{section.58.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {58.4}Personalizing Content Pages}{200}{section.58.4}\protected@file@percent } \newlabel{personalizing-content-pages}{{58.4}{200}{Personalizing Content Pages}{section.58.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {59}Content Page Elements}{201}{chapter.59}\protected@file@percent } \newlabel{content-page-elements}{{59}{201}{Content Page Elements}{chapter.59}{}} \@writefile{lof}{\contentsline {figure}{\numberline {59.1}{\ignorespaces A Section named \emph {Banner} being displayed while editing a Content Page.}}{201}{figure.59.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {59.2}{\ignorespaces A 3 Column and 1 Column layout stacked on top of each other.}}{202}{figure.59.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {59.3}{\ignorespaces Here are several of Liferay's out of the box components arranged in the layout you saw previously.}}{202}{figure.59.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {59.1}Editable Elements}{203}{section.59.1}\protected@file@percent } \newlabel{editable-elements}{{59.1}{203}{Editable Elements}{section.59.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {59.2}Editable Text}{203}{section.59.2}\protected@file@percent } \newlabel{editable-text}{{59.2}{203}{Editable Text}{section.59.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {59.4}{\ignorespaces The rich text editor provides a simple WYSIWYG interface with a number of formatting options.}}{203}{figure.59.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {59.3}Editable Images}{203}{section.59.3}\protected@file@percent } \newlabel{editable-images}{{59.3}{203}{Editable Images}{section.59.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {59.5}{\ignorespaces Editing an image allows you to enter a URL, select an image from Documents and Media or set a link for the image.}}{204}{figure.59.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {59.4}Editable Links}{204}{section.59.4}\protected@file@percent } \newlabel{editable-links}{{59.4}{204}{Editable Links}{section.59.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {60}Content Page Management Interface}{207}{chapter.60}\protected@file@percent } \newlabel{content-page-management-interface}{{60}{207}{Content Page Management Interface}{chapter.60}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.1}{\ignorespaces Each Content Page starts as a blank page.}}{207}{figure.60.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.1}Sections}{208}{section.60.1}\protected@file@percent } \newlabel{sections}{{60.1}{208}{Sections}{section.60.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.2}{\ignorespaces \emph {Sections} contains Fragments that fully define spaces on your page.}}{208}{figure.60.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {60.3}{\ignorespaces The Section managment tool provide powerful tools, but with the training wheels still on.}}{209}{figure.60.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.2}Section Builder}{210}{section.60.2}\protected@file@percent } \newlabel{section-builder}{{60.2}{210}{Section Builder}{section.60.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.4}{\ignorespaces \emph {Sections Builder} contains \emph {Component} Fragments which are intended to be combined to create Sections.}}{210}{figure.60.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.3}Widgets}{210}{section.60.3}\protected@file@percent } \newlabel{widgets}{{60.3}{210}{Widgets}{section.60.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.5}{\ignorespaces The Widgets section provides a list of Widgets that can be added inside of a Layout.}}{211}{figure.60.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.4}Page Structure}{212}{section.60.4}\protected@file@percent } \newlabel{page-structure}{{60.4}{212}{Page Structure}{section.60.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.6}{\ignorespaces \emph {Page Structure} shows you a hierarchy of your page.}}{212}{figure.60.6}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.5}Look and Feel}{212}{section.60.5}\protected@file@percent } \newlabel{look-and-feel}{{60.5}{212}{Look and Feel}{section.60.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {60.6}Comments}{213}{section.60.6}\protected@file@percent } \newlabel{comments}{{60.6}{213}{Comments}{section.60.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.7}{\ignorespaces Administrators can enable comments for content pages.}}{213}{figure.60.7}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {60.7}The Title Bar}{213}{section.60.7}\protected@file@percent } \newlabel{the-title-bar}{{60.7}{213}{The Title Bar}{section.60.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {60.8}{\ignorespaces When creating content pages, you and your team can comment on any fragments.}}{214}{figure.60.8}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {60.9}{\ignorespaces The title bar has several tools built into it.}}{215}{figure.60.9}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {61}Building Content Pages}{217}{chapter.61}\protected@file@percent } \newlabel{building-content-pages-1}{{61}{217}{Building Content Pages}{chapter.61}{}} \@writefile{lof}{\contentsline {figure}{\numberline {61.1}{\ignorespaces You have lots of flexibility when arranging Fragments on a page.}}{217}{figure.61.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {61.1}Creating a Content Page}{218}{section.61.1}\protected@file@percent } \newlabel{creating-a-content-page}{{61.1}{218}{Creating a Content Page}{section.61.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {61.2}{\ignorespaces You have lots of flexibility when arranging Fragments on a page.}}{219}{figure.61.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {61.3}{\ignorespaces Edit the text and formatting as you see fit.}}{219}{figure.61.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {61.4}{\ignorespaces Add some images, and the big picture comes together.}}{220}{figure.61.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {61.5}{\ignorespaces Add some images, and the big picture comes together.}}{220}{figure.61.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {61.6}{\ignorespaces You can change the background color, image, or edit spacing and padding for a section. You can also remove it.}}{220}{figure.61.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {62}Propagation of Changes}{223}{chapter.62}\protected@file@percent } \newlabel{propagation-of-changes}{{62}{223}{Propagation of Changes}{chapter.62}{}} \@writefile{lof}{\contentsline {figure}{\numberline {62.1}{\ignorespaces Viewing the Usages and Propagation page.}}{224}{figure.62.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {63}Creating Page Fragments}{225}{chapter.63}\protected@file@percent } \newlabel{creating-page-fragments-1}{{63}{225}{Creating Page Fragments}{chapter.63}{}} \@writefile{toc}{\contentsline {section}{\numberline {63.1}Creating and Managing Fragments}{225}{section.63.1}\protected@file@percent } \newlabel{creating-and-managing-fragments}{{63.1}{225}{Creating and Managing Fragments}{section.63.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {63.1}{\ignorespaces Here is the Page Fragments page with no custom Fragments or Collections created.}}{226}{figure.63.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {63.2}{\ignorespaces The Fragments editor provides an environment for creating all the parts of a Fragment.}}{227}{figure.63.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {63.3}{\ignorespaces The Resources tab can be selected from the Fragment Collection.}}{228}{figure.63.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {64}Exporting and Importing Fragments}{229}{chapter.64}\protected@file@percent } \newlabel{exporting-and-importing-fragments}{{64}{229}{Exporting and Importing Fragments}{chapter.64}{}} \@writefile{toc}{\contentsline {section}{\numberline {64.1}Exporting Fragments}{229}{section.64.1}\protected@file@percent } \newlabel{exporting-fragments}{{64.1}{229}{Exporting Fragments}{section.64.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {64.1}{\ignorespaces Select Collections to export.}}{230}{figure.64.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {64.2}{\ignorespaces Exporting all of the Fragments in a Collection.}}{230}{figure.64.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {64.2}Importing}{231}{section.64.2}\protected@file@percent } \newlabel{importing}{{64.2}{231}{Importing}{section.64.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {64.3}Importing Collections}{231}{section.64.3}\protected@file@percent } \newlabel{importing-collections}{{64.3}{231}{Importing Collections}{section.64.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {64.3}{\ignorespaces Importing and exporting Collections is accessed from a single menu.}}{231}{figure.64.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {64.4}Importing Individual Page Fragments}{232}{section.64.4}\protected@file@percent } \newlabel{importing-individual-page-fragments}{{64.4}{232}{Importing Individual Page Fragments}{section.64.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {65}Using Widget Pages}{233}{chapter.65}\protected@file@percent } \newlabel{using-widget-pages}{{65}{233}{Using Widget Pages}{chapter.65}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {66}Creating Widget Pages}{235}{chapter.66}\protected@file@percent } \newlabel{creating-widget-pages}{{66}{235}{Creating Widget Pages}{chapter.66}{}} \@writefile{toc}{\contentsline {section}{\numberline {66.1}Adding a Widget Page}{235}{section.66.1}\protected@file@percent } \newlabel{adding-a-widget-page}{{66.1}{235}{Adding a Widget Page}{section.66.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {66.1}{\ignorespaces Create a page called \emph {Community} with two columns.}}{236}{figure.66.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {66.2}{\ignorespaces Your page has been added to the navigation automatically.}}{236}{figure.66.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {66.2}Adding Widgets to a Page}{237}{section.66.2}\protected@file@percent } \newlabel{adding-widgets-to-a-page}{{66.2}{237}{Adding Widgets to a Page}{section.66.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {66.3}{\ignorespaces Your page layout options are virtually limitless with a slew of application and layout combinations.}}{237}{figure.66.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {67}Creating Widget Pages from Templates}{239}{chapter.67}\protected@file@percent } \newlabel{creating-widget-pages-from-templates}{{67}{239}{Creating Widget Pages from Templates}{chapter.67}{}} \@writefile{lof}{\contentsline {figure}{\numberline {67.1}{\ignorespaces The Blog page template is already available for use along with the Search and Wiki page templates.}}{239}{figure.67.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {67.2}{\ignorespaces You can choose whether or not to inherit changes made to the page template.}}{240}{figure.67.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {67.1}Sharing Widget Page Templates}{241}{section.67.1}\protected@file@percent } \newlabel{sharing-widget-page-templates}{{67.1}{241}{Sharing Widget Page Templates}{section.67.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {68}Building a Responsive Site}{243}{chapter.68}\protected@file@percent } \newlabel{building-a-responsive-site}{{68}{243}{Building a Responsive Site}{chapter.68}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {69}Built-in Mobile Support}{245}{chapter.69}\protected@file@percent } \newlabel{built-in-mobile-support}{{69}{245}{Built-in Mobile Support}{chapter.69}{}} \@writefile{lof}{\contentsline {figure}{\numberline {69.1}{\ignorespaces A widget adjusts its size.}}{245}{figure.69.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {69.2}{\ignorespaces The main navigation adjusts its size.}}{246}{figure.69.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {69.1}Using the Device Simulator}{246}{section.69.1}\protected@file@percent } \newlabel{using-the-device-simulator}{{69.1}{246}{Using the Device Simulator}{section.69.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {69.2}Designing Mobile Friendly Pages}{246}{section.69.2}\protected@file@percent } \newlabel{designing-mobile-friendly-pages}{{69.2}{246}{Designing Mobile Friendly Pages}{section.69.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {69.3}{\ignorespaces The Simulation panel defines multiple screen sizes.}}{247}{figure.69.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {70}Mobile Device Rules}{249}{chapter.70}\protected@file@percent } \newlabel{mobile-device-rules}{{70}{249}{Mobile Device Rules}{chapter.70}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {71}Creating Mobile Device Rules}{251}{chapter.71}\protected@file@percent } \newlabel{creating-mobile-device-rules}{{71}{251}{Creating Mobile Device Rules}{chapter.71}{}} \@writefile{lof}{\contentsline {figure}{\numberline {71.1}{\ignorespaces Create a Mobile Device Family so you can create rules.}}{252}{figure.71.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {71.2}{\ignorespaces Select the operating system and device type for your rule.}}{253}{figure.71.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {71.3}{\ignorespaces You can select a mobile device family to apply for a Site or page.}}{254}{figure.71.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {72}Mobile Device Actions}{255}{chapter.72}\protected@file@percent } \newlabel{mobile-device-actions}{{72}{255}{Mobile Device Actions}{chapter.72}{}} \@writefile{lof}{\contentsline {figure}{\numberline {72.1}{\ignorespaces Disable using public pages device rules.}}{255}{figure.72.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {72.2}{\ignorespaces Getting to the Manage Actions page.}}{256}{figure.72.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {72.1}Mobile Device Rules Example}{257}{section.72.1}\protected@file@percent } \newlabel{mobile-device-rules-example}{{72.1}{257}{Mobile Device Rules Example}{section.72.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {72.3}{\ignorespaces Create the Classification rule.}}{258}{figure.72.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {72.4}{\ignorespaces Create the Actions for Android.}}{260}{figure.72.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {73}Using Full Page Applications}{261}{chapter.73}\protected@file@percent } \newlabel{using-full-page-applications}{{73}{261}{Using Full Page Applications}{chapter.73}{}} \@writefile{toc}{\contentsline {section}{\numberline {73.1}Configuring the Page}{261}{section.73.1}\protected@file@percent } \newlabel{configuring-the-page}{{73.1}{261}{Configuring the Page}{section.73.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {73.1}{\ignorespaces The Full Page Application configuration page.}}{262}{figure.73.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {73.2}{\ignorespaces The Wiki displayed as a Full Page Application.}}{263}{figure.73.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {73.3}{\ignorespaces Configuring the scope.}}{263}{figure.73.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {74}Managing Site Navigation}{265}{chapter.74}\protected@file@percent } \newlabel{managing-site-navigation}{{74}{265}{Managing Site Navigation}{chapter.74}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {75}Page Hierarchy}{267}{chapter.75}\protected@file@percent } \newlabel{page-hierarchy}{{75}{267}{Page Hierarchy}{chapter.75}{}} \@writefile{toc}{\contentsline {section}{\numberline {75.1}Creating a Page}{267}{section.75.1}\protected@file@percent } \newlabel{creating-a-page}{{75.1}{267}{Creating a Page}{section.75.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {75.1}{\ignorespaces In the default site, initially only the \emph {Home} and the hidden \emph {Search} pages exist in the Public Pages Hierarchy.}}{267}{figure.75.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {75.2}{\ignorespaces When you create a page, by default it is added to the site hierarchy.}}{268}{figure.75.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {75.2}Organizing Pages}{268}{section.75.2}\protected@file@percent } \newlabel{organizing-pages}{{75.2}{268}{Organizing Pages}{section.75.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {75.3}{\ignorespaces You can see the order of pages in Site Administration vs.~how they appear on the site.}}{269}{figure.75.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {75.4}{\ignorespaces \emph {About Us} is now the home page, and \emph {Welcome} is second in the nav.}}{269}{figure.75.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {75.5}{\ignorespaces \emph {About Us} is now nested under \emph {Welcome} and appear when you mouse-over \emph {Welcome}.}}{269}{figure.75.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {75.3}Public and Private Pages}{270}{section.75.3}\protected@file@percent } \newlabel{public-and-private-pages}{{75.3}{270}{Public and Private Pages}{section.75.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {75.4}Page Options}{270}{section.75.4}\protected@file@percent } \newlabel{page-options}{{75.4}{270}{Page Options}{section.75.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {76}Creating and Managing Navigation Menus}{271}{chapter.76}\protected@file@percent } \newlabel{creating-and-managing-navigation-menus}{{76}{271}{Creating and Managing Navigation Menus}{chapter.76}{}} \@writefile{toc}{\contentsline {section}{\numberline {76.1}Creating a Navigation Menu}{271}{section.76.1}\protected@file@percent } \newlabel{creating-a-navigation-menu}{{76.1}{271}{Creating a Navigation Menu}{section.76.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {76.1}{\ignorespaces Menus can have a standard page, a submenu, and a URL link in the submenu.}}{272}{figure.76.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {76.2}Managing Menus}{272}{section.76.2}\protected@file@percent } \newlabel{managing-menus}{{76.2}{272}{Managing Menus}{section.76.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {76.3}Modifying Menus}{273}{section.76.3}\protected@file@percent } \newlabel{modifying-menus}{{76.3}{273}{Modifying Menus}{section.76.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {76.2}{\ignorespaces Menus with a standard page, a submenu, and a URL link in the submenu are created for different reasons.}}{273}{figure.76.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {77}Displaying Navigation Menus}{275}{chapter.77}\protected@file@percent } \newlabel{displaying-navigation-menus}{{77}{275}{Displaying Navigation Menus}{chapter.77}{}} \@writefile{toc}{\contentsline {section}{\numberline {77.1}Navigation Menu Widget}{275}{section.77.1}\protected@file@percent } \newlabel{navigation-menu-widget}{{77.1}{275}{Navigation Menu Widget}{section.77.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {77.2}Choosing a Navigation Menu}{275}{section.77.2}\protected@file@percent } \newlabel{choosing-a-navigation-menu}{{77.2}{275}{Choosing a Navigation Menu}{section.77.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {77.1}{\ignorespaces Configuring the Navigation Menu Widget.}}{276}{figure.77.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {77.3}Display Template}{276}{section.77.3}\protected@file@percent } \newlabel{display-template}{{77.3}{276}{Display Template}{section.77.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {77.4}Menu Items to Show}{277}{section.77.4}\protected@file@percent } \newlabel{menu-items-to-show}{{77.4}{277}{Menu Items to Show}{section.77.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {77.2}{\ignorespaces Navigation menus give you many ways to help users navigate your Site.}}{278}{figure.77.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {78}Building Sites from Templates}{279}{chapter.78}\protected@file@percent } \newlabel{building-sites-from-templates}{{78}{279}{Building Sites from Templates}{chapter.78}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {79}Creating a Site Template}{281}{chapter.79}\protected@file@percent } \newlabel{creating-a-site-template}{{79}{281}{Creating a Site Template}{chapter.79}{}} \@writefile{lof}{\contentsline {figure}{\numberline {79.1}{\ignorespaces You can see the name of the Site template you're currently editing.}}{282}{figure.79.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {80}Managing Site Templates}{285}{chapter.80}\protected@file@percent } \newlabel{managing-site-templates}{{80}{285}{Managing Site Templates}{chapter.80}{}} \@writefile{lof}{\contentsline {figure}{\numberline {80.1}{\ignorespaces Site templates have several configurable options including the option to allow Site administrators to modify pages associated with the Site template.}}{286}{figure.80.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {81}Propagating Changes from Site Templates to Sites}{287}{chapter.81}\protected@file@percent } \newlabel{propagating-changes-from-site-templates-to-sites}{{81}{287}{Propagating Changes from Site Templates to Sites}{chapter.81}{}} \@writefile{toc}{\contentsline {section}{\numberline {81.1}Site Template Page Behavior}{287}{section.81.1}\protected@file@percent } \newlabel{site-template-page-behavior}{{81.1}{287}{Site Template Page Behavior}{section.81.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {81.1}{\ignorespaces You can click the Information icon to view important information about your Site template.}}{288}{figure.81.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {81.2}Merging and Resetting Changes}{288}{section.81.2}\protected@file@percent } \newlabel{merging-and-resetting-changes}{{81.2}{288}{Merging and Resetting Changes}{section.81.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {81.2}{\ignorespaces This type of warning is given when there are friendly URL conflicts with Site template pages.}}{289}{figure.81.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {82}Sharing Site Templates}{291}{chapter.82}\protected@file@percent } \newlabel{sharing-site-templates}{{82}{291}{Sharing Site Templates}{chapter.82}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {83}Configuring Sites}{293}{chapter.83}\protected@file@percent } \newlabel{configuring-sites}{{83}{293}{Configuring Sites}{chapter.83}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {84}Configuring Site Settings}{295}{chapter.84}\protected@file@percent } \newlabel{configuring-site-settings}{{84}{295}{Configuring Site Settings}{chapter.84}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.1}Details}{295}{section.84.1}\protected@file@percent } \newlabel{details}{{84.1}{295}{Details}{section.84.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.2}Membership Options}{295}{section.84.2}\protected@file@percent } \newlabel{membership-options}{{84.2}{295}{Membership Options}{section.84.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.3}Site Hierarchies}{295}{section.84.3}\protected@file@percent } \newlabel{site-hierarchies-1}{{84.3}{295}{Site Hierarchies}{section.84.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.4}Pages}{296}{section.84.4}\protected@file@percent } \newlabel{pages}{{84.4}{296}{Pages}{section.84.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {84.1}{\ignorespaces Selecting a Site Template.}}{296}{figure.84.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {84.5}Categorization}{296}{section.84.5}\protected@file@percent } \newlabel{categorization}{{84.5}{296}{Categorization}{section.84.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.6}Site URL}{296}{section.84.6}\protected@file@percent } \newlabel{site-url}{{84.6}{296}{Site URL}{section.84.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {84.2}{\ignorespaces When configuring virtual hosts, the public and private pages of a site can be configured to different domains.}}{297}{figure.84.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {84.7}Documents and Media}{297}{section.84.7}\protected@file@percent } \newlabel{documents-and-media}{{84.7}{297}{Documents and Media}{section.84.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.8}Site Template}{298}{section.84.8}\protected@file@percent } \newlabel{site-template}{{84.8}{298}{Site Template}{section.84.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.9}Asset Auto Tagging}{298}{section.84.9}\protected@file@percent } \newlabel{asset-auto-tagging}{{84.9}{298}{Asset Auto Tagging}{section.84.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {84.10}Custom Fields}{298}{section.84.10}\protected@file@percent } \newlabel{custom-fields-1}{{84.10}{298}{Custom Fields}{section.84.10}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {85}Social Settings and Languages}{299}{chapter.85}\protected@file@percent } \newlabel{social-settings-and-languages}{{85}{299}{Social Settings and Languages}{chapter.85}{}} \@writefile{toc}{\contentsline {section}{\numberline {85.1}Ratings}{299}{section.85.1}\protected@file@percent } \newlabel{ratings}{{85.1}{299}{Ratings}{section.85.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {85.2}Mentions}{299}{section.85.2}\protected@file@percent } \newlabel{mentions}{{85.2}{299}{Mentions}{section.85.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {85.3}Languages}{299}{section.85.3}\protected@file@percent } \newlabel{languages}{{85.3}{299}{Languages}{section.85.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {85.1}{\ignorespaces In the Languages tab, you can configure the site to use the instance's default language or another supported language.}}{300}{figure.85.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {86}Advanced Site Settings}{301}{chapter.86}\protected@file@percent } \newlabel{advanced-site-settings}{{86}{301}{Advanced Site Settings}{chapter.86}{}} \@writefile{toc}{\contentsline {section}{\numberline {86.1}Default User Associations}{301}{section.86.1}\protected@file@percent } \newlabel{default-user-associations-1}{{86.1}{301}{Default User Associations}{section.86.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {86.2}Analytics}{301}{section.86.2}\protected@file@percent } \newlabel{analytics-1}{{86.2}{301}{Analytics}{section.86.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {86.1}{\ignorespaces To set up Google Analytics: sign up, receive an ID, and then enter it into the Google Analytics ID field.}}{302}{figure.86.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {86.3}Maps}{303}{section.86.3}\protected@file@percent } \newlabel{maps}{{86.3}{303}{Maps}{section.86.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {86.4}Recycle Bin}{303}{section.86.4}\protected@file@percent } \newlabel{recycle-bin}{{86.4}{303}{Recycle Bin}{section.86.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {86.5}Content Sharing}{303}{section.86.5}\protected@file@percent } \newlabel{content-sharing}{{86.5}{303}{Content Sharing}{section.86.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {87}Customizing Personal Sites}{305}{chapter.87}\protected@file@percent } \newlabel{customizing-personal-sites}{{87}{305}{Customizing Personal Sites}{chapter.87}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {88}Importing/Exporting Sites and Content}{307}{chapter.88}\protected@file@percent } \newlabel{importingexporting-sites-and-content}{{88}{307}{Importing/Exporting Sites and Content}{chapter.88}{}} \@writefile{toc}{\contentsline {section}{\numberline {88.1}Backing Up and Restoring Pages and Their Content}{307}{section.88.1}\protected@file@percent } \newlabel{backing-up-and-restoring-pages-and-their-content}{{88.1}{307}{Backing Up and Restoring Pages and Their Content}{section.88.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {88.2}Page Export Example}{308}{section.88.2}\protected@file@percent } \newlabel{page-export-example}{{88.2}{308}{Page Export Example}{section.88.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {88.1}{\ignorespaces You can configure your export options manually by selecting pages, content, and permissions.}}{309}{figure.88.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {88.3}Export Templates}{309}{section.88.3}\protected@file@percent } \newlabel{export-templates}{{88.3}{309}{Export Templates}{section.88.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {89}Styling Apps and Assets}{311}{chapter.89}\protected@file@percent } \newlabel{styling-apps-and-assets}{{89}{311}{Styling Apps and Assets}{chapter.89}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {90}Styling Widgets with Widget Templates}{313}{chapter.90}\protected@file@percent } \newlabel{styling-widgets-with-widget-templates}{{90}{313}{Styling Widgets with Widget Templates}{chapter.90}{}} \@writefile{toc}{\contentsline {section}{\numberline {90.1}Creating a Widget Template}{313}{section.90.1}\protected@file@percent } \newlabel{creating-a-widget-template}{{90.1}{313}{Creating a Widget Template}{section.90.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {90.1}{\ignorespaces The Site Administration dropdown menu lets you choose the context in which your widget template resides.}}{314}{figure.90.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {90.2}The Template Editor}{314}{section.90.2}\protected@file@percent } \newlabel{the-template-editor}{{90.2}{314}{The Template Editor}{section.90.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {90.2}{\ignorespaces Liferay offers a versatile script editor to customize your widget template.}}{315}{figure.90.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {90.3}Configuring Widget Templates}{316}{section.90.3}\protected@file@percent } \newlabel{configuring-widget-templates}{{90.3}{316}{Configuring Widget Templates}{section.90.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {90.3}{\ignorespaces In the \emph {Configuration} menu of an app, you can edit and manage available widget templates.}}{317}{figure.90.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {91}Widget Template Example}{319}{chapter.91}\protected@file@percent } \newlabel{widget-template-example}{{91}{319}{Widget Template Example}{chapter.91}{}} \@writefile{lof}{\contentsline {figure}{\numberline {91.1}{\ignorespaces After applying the Carousel widget template, your pictures are displayed as a carousel slideshow.}}{320}{figure.91.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {92}Setting a Default Widget Template}{321}{chapter.92}\protected@file@percent } \newlabel{setting-a-default-widget-template}{{92}{321}{Setting a Default Widget Template}{chapter.92}{}} \@writefile{lof}{\contentsline {figure}{\numberline {92.1}{\ignorespaces The widget template configuration in System Settings lets you change the display style.}}{321}{figure.92.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {92.1}Default Widget Template Example}{322}{section.92.1}\protected@file@percent } \newlabel{default-widget-template-example}{{92.1}{322}{Default Widget Template Example}{section.92.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {92.2}{\ignorespaces System Settings shows where you can find the Template Key.}}{322}{figure.92.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {92.3}{\ignorespaces You can see the new default configuration.}}{323}{figure.92.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {93}Customizing Page Options}{325}{chapter.93}\protected@file@percent } \newlabel{customizing-page-options}{{93}{325}{Customizing Page Options}{chapter.93}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {94}Configuring Page Sets}{327}{chapter.94}\protected@file@percent } \newlabel{configuring-page-sets}{{94}{327}{Configuring Page Sets}{chapter.94}{}} \@writefile{lof}{\contentsline {figure}{\numberline {94.1}{\ignorespaces Selecting the Page Set configuration option.}}{327}{figure.94.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {95}Configuring Page Sets}{329}{chapter.95}\protected@file@percent } \newlabel{configuring-page-sets-1}{{95}{329}{Configuring Page Sets}{chapter.95}{}} \@writefile{lof}{\contentsline {figure}{\numberline {95.1}{\ignorespaces The Look and Feel page set tab.}}{329}{figure.95.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {95.1}Themes}{329}{section.95.1}\protected@file@percent } \newlabel{themes}{{95.1}{329}{Themes}{section.95.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {95.2}{\ignorespaces The Look and Feel interface allows you to choose a theme for the current site.}}{330}{figure.95.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {95.3}{\ignorespaces You can define a specific look and feel for a page.}}{331}{figure.95.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {95.2}Using a Custom Logo for a Site}{331}{section.95.2}\protected@file@percent } \newlabel{using-a-custom-logo-for-a-site}{{95.2}{331}{Using a Custom Logo for a Site}{section.95.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {96}Advanced Page Set Options}{333}{chapter.96}\protected@file@percent } \newlabel{advanced-page-set-options}{{96}{333}{Advanced Page Set Options}{chapter.96}{}} \@writefile{toc}{\contentsline {section}{\numberline {96.1}Executing JavaScript in Site Pages}{333}{section.96.1}\protected@file@percent } \newlabel{executing-javascript-in-site-pages}{{96.1}{333}{Executing JavaScript in Site Pages}{section.96.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {96.2}Merge Public Pages}{333}{section.96.2}\protected@file@percent } \newlabel{merge-public-pages}{{96.2}{333}{Merge Public Pages}{section.96.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {96.3}Rendering Pages for Mobile Devices}{334}{section.96.3}\protected@file@percent } \newlabel{rendering-pages-for-mobile-devices}{{96.3}{334}{Rendering Pages for Mobile Devices}{section.96.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {96.4}Robots}{334}{section.96.4}\protected@file@percent } \newlabel{robots}{{96.4}{334}{Robots}{section.96.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {96.5}Notifying Search Engines of Site Pages}{334}{section.96.5}\protected@file@percent } \newlabel{notifying-search-engines-of-site-pages}{{96.5}{334}{Notifying Search Engines of Site Pages}{section.96.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {97}Configuring Individual Pages}{335}{chapter.97}\protected@file@percent } \newlabel{configuring-individual-pages}{{97}{335}{Configuring Individual Pages}{chapter.97}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {98}Individual Page Settings}{337}{chapter.98}\protected@file@percent } \newlabel{individual-page-settings}{{98}{337}{Individual Page Settings}{chapter.98}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.1}General}{337}{section.98.1}\protected@file@percent } \newlabel{general-2}{{98.1}{337}{General}{section.98.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.2}Name and Friendly URL}{337}{section.98.2}\protected@file@percent } \newlabel{name-and-friendly-url}{{98.2}{337}{Name and Friendly URL}{section.98.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.3}Page Layout}{338}{section.98.3}\protected@file@percent } \newlabel{page-layout}{{98.3}{338}{Page Layout}{section.98.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {98.1}{\ignorespaces Setting a layout template for your page.}}{338}{figure.98.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {98.4}Categorization and SEO}{338}{section.98.4}\protected@file@percent } \newlabel{categorization-and-seo}{{98.4}{338}{Categorization and SEO}{section.98.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.5}Categorization}{338}{section.98.5}\protected@file@percent } \newlabel{categorization-1}{{98.5}{338}{Categorization}{section.98.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.6}SEO}{339}{section.98.6}\protected@file@percent } \newlabel{seo}{{98.6}{339}{SEO}{section.98.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {98.2}{\ignorespaces Enter the custom canonical URL that you want to use for the page.}}{339}{figure.98.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {98.7}Look and Feel}{339}{section.98.7}\protected@file@percent } \newlabel{look-and-feel-1}{{98.7}{339}{Look and Feel}{section.98.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {98.3}{\ignorespaces You can also configure canonical URLs at the global and instance levels.}}{340}{figure.98.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {98.8}Advanced Settings}{340}{section.98.8}\protected@file@percent } \newlabel{advanced-settings}{{98.8}{340}{Advanced Settings}{section.98.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.9}Query String}{341}{section.98.9}\protected@file@percent } \newlabel{query-string}{{98.9}{341}{Query String}{section.98.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.10}Custom Fields}{341}{section.98.10}\protected@file@percent } \newlabel{custom-fields-2}{{98.10}{341}{Custom Fields}{section.98.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.11}Embedded Widgets}{341}{section.98.11}\protected@file@percent } \newlabel{embedded-widgets}{{98.11}{341}{Embedded Widgets}{section.98.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.12}Customization Settings}{341}{section.98.12}\protected@file@percent } \newlabel{customization-settings}{{98.12}{341}{Customization Settings}{section.98.12}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.13}JavaScript}{341}{section.98.13}\protected@file@percent } \newlabel{javascript}{{98.13}{341}{JavaScript}{section.98.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {98.14}Mobile Device Rules}{342}{section.98.14}\protected@file@percent } \newlabel{mobile-device-rules-1}{{98.14}{342}{Mobile Device Rules}{section.98.14}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {99}Personalizing Pages}{343}{chapter.99}\protected@file@percent } \newlabel{personalizing-pages}{{99}{343}{Personalizing Pages}{chapter.99}{}} \@writefile{toc}{\contentsline {section}{\numberline {99.1}Enabling Page Customizations}{343}{section.99.1}\protected@file@percent } \newlabel{enabling-page-customizations}{{99.1}{343}{Enabling Page Customizations}{section.99.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {99.1}{\ignorespaces To enable page customizations, click on the \emph {Configure Page} button next to the page, expand the \emph {Customization Settings} area, and click on the \emph {Customizable} button.}}{344}{figure.99.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {99.2}{\ignorespaces Customizable regions are colored green and non-customizable regions are colored red.}}{344}{figure.99.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {99.2}Customization Permissions}{344}{section.99.2}\protected@file@percent } \newlabel{customization-permissions}{{99.2}{344}{Customization Permissions}{section.99.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {99.3}Customizing Pages}{344}{section.99.3}\protected@file@percent } \newlabel{customizing-pages}{{99.3}{344}{Customizing Pages}{section.99.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {99.3}{\ignorespaces Customizable areas are highlighted green when organizing apps on the page.}}{345}{figure.99.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {99.4}Viewing Customized Pages}{345}{section.99.4}\protected@file@percent } \newlabel{viewing-customized-pages}{{99.4}{345}{Viewing Customized Pages}{section.99.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {99.5}Customization Example}{346}{section.99.5}\protected@file@percent } \newlabel{customization-example}{{99.5}{346}{Customization Example}{section.99.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {99.4}{\ignorespaces In this example, the user added the Language app, and changed the display style from icons to a select box.}}{346}{figure.99.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {100}Changing Page Permissions}{347}{chapter.100}\protected@file@percent } \newlabel{changing-page-permissions}{{100}{347}{Changing Page Permissions}{chapter.100}{}} \@writefile{lof}{\contentsline {figure}{\numberline {100.1}{\ignorespaces The Permissions offer a plethora of options for each role.}}{348}{figure.100.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {101}Configuring Widgets}{349}{chapter.101}\protected@file@percent } \newlabel{configuring-widgets}{{101}{349}{Configuring Widgets}{chapter.101}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {102}Look and Feel Configuration}{351}{chapter.102}\protected@file@percent } \newlabel{look-and-feel-configuration}{{102}{351}{Look and Feel Configuration}{chapter.102}{}} \@writefile{toc}{\contentsline {section}{\numberline {102.1}General Settings}{351}{section.102.1}\protected@file@percent } \newlabel{general-settings}{{102.1}{351}{General Settings}{section.102.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {102.1}{\ignorespaces The General tab of the Look and Feel Configuration menu lets you define a custom widget title and select the widget contrast option using decorators.}}{352}{figure.102.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {102.2}Text Styles}{352}{section.102.2}\protected@file@percent } \newlabel{text-styles}{{102.2}{352}{Text Styles}{section.102.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {102.3}Background Styles}{352}{section.102.3}\protected@file@percent } \newlabel{background-styles}{{102.3}{352}{Background Styles}{section.102.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {102.4}Border Styles}{352}{section.102.4}\protected@file@percent } \newlabel{border-styles}{{102.4}{352}{Border Styles}{section.102.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {102.2}{\ignorespaces The Text Styles tab lets you configure the format of the text that appears in the widget.}}{353}{figure.102.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {102.3}{\ignorespaces The Background Styles tab lets you specify the widget's background color.}}{353}{figure.102.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {102.4}{\ignorespaces The Border Styles tab lets you specify a border width, style, and color for each side of the widget.}}{354}{figure.102.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {102.5}Margin and Padding}{354}{section.102.5}\protected@file@percent } \newlabel{margin-and-padding}{{102.5}{354}{Margin and Padding}{section.102.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {102.5}{\ignorespaces The Margin and Padding tab allows you to specify margin and padding lengths for the sides of your widget.}}{355}{figure.102.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {102.6}Advanced Styling}{355}{section.102.6}\protected@file@percent } \newlabel{advanced-styling}{{102.6}{355}{Advanced Styling}{section.102.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {102.6}{\ignorespaces The Advanced Styling tab displays your widget's Liferay ID and allows you to enter CSS code to customize the look and feel of your widget.}}{356}{figure.102.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {103}Exporting/Importing Widget Data}{357}{chapter.103}\protected@file@percent } \newlabel{exportingimporting-widget-data}{{103}{357}{Exporting/Importing Widget Data}{chapter.103}{}} \@writefile{lof}{\contentsline {figure}{\numberline {103.1}{\ignorespaces You can access a widget's administrative \emph {Export/Import} feature by selecting its Options menu.}}{358}{figure.103.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {103.2}{\ignorespaces You can access a widget's \emph {Export/Import} feature by selecting its Options menu.}}{358}{figure.103.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {103.1}Exporting Widget Data}{359}{section.103.1}\protected@file@percent } \newlabel{exporting-widget-data}{{103.1}{359}{Exporting Widget Data}{section.103.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {103.2}Importing Widget Data}{359}{section.103.2}\protected@file@percent } \newlabel{importing-widget-data}{{103.2}{359}{Importing Widget Data}{section.103.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {103.3}{\ignorespaces When importing widget data, you can choose a LAR file using the file explorer or drag and drop the file between the dotted lines.}}{360}{figure.103.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {104}Communication Between Portlet Widgets}{361}{chapter.104}\protected@file@percent } \newlabel{communication-between-portlet-widgets}{{104}{361}{Communication Between Portlet Widgets}{chapter.104}{}} \@writefile{lof}{\contentsline {figure}{\numberline {104.1}{\ignorespaces You can configure portlets to communicate with each other using public render parameters.}}{361}{figure.104.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {105}Sharing Widgets with Other Sites}{363}{chapter.105}\protected@file@percent } \newlabel{sharing-widgets-with-other-sites}{{105}{363}{Sharing Widgets with Other Sites}{chapter.105}{}} \@writefile{toc}{\contentsline {section}{\numberline {105.1}Any Web Site}{363}{section.105.1}\protected@file@percent } \newlabel{any-web-site}{{105.1}{363}{Any Web Site}{section.105.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {105.2}Facebook}{363}{section.105.2}\protected@file@percent } \newlabel{facebook}{{105.2}{363}{Facebook}{section.105.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {105.1}{\ignorespaces The Sharing tab in your widget's Configuration menu lets you share your widget in a variety of ways.}}{364}{figure.105.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {105.3}OpenSocial Gadget}{364}{section.105.3}\protected@file@percent } \newlabel{opensocial-gadget}{{105.3}{364}{OpenSocial Gadget}{section.105.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {105.4}Netvibes}{365}{section.105.4}\protected@file@percent } \newlabel{netvibes}{{105.4}{365}{Netvibes}{section.105.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {106}Widget Permissions}{367}{chapter.106}\protected@file@percent } \newlabel{widget-permissions}{{106}{367}{Widget Permissions}{chapter.106}{}} \@writefile{lof}{\contentsline {figure}{\numberline {106.1}{\ignorespaces Viewing the permissions configuration for a widget.}}{367}{figure.106.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {107}Widget Scope}{369}{chapter.107}\protected@file@percent } \newlabel{widget-scope}{{107}{369}{Widget Scope}{chapter.107}{}} \@writefile{lof}{\contentsline {figure}{\numberline {107.1}{\ignorespaces You can change the scope of your widget's content by navigating to its Configuration menu.}}{370}{figure.107.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {107.2}{\ignorespaces Use the drop-down menu under Content \& Data to determine which scope to manage content for.}}{371}{figure.107.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {108}Configuration Templates}{373}{chapter.108}\protected@file@percent } \newlabel{configuration-templates}{{108}{373}{Configuration Templates}{chapter.108}{}} \@writefile{lof}{\contentsline {figure}{\numberline {108.1}{\ignorespaces Create a configuration template to save your app's configuration settings.}}{373}{figure.108.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {108.1}Summary}{374}{section.108.1}\protected@file@percent } \newlabel{summary}{{108.1}{374}{Summary}{section.108.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {109}Managing Members in Your Site}{375}{chapter.109}\protected@file@percent } \newlabel{managing-members-in-your-site}{{109}{375}{Managing Members in Your Site}{chapter.109}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {110}Adding Members to Sites}{377}{chapter.110}\protected@file@percent } \newlabel{adding-members-to-sites}{{110}{377}{Adding Members to Sites}{chapter.110}{}} \@writefile{toc}{\contentsline {section}{\numberline {110.1}Administrating Site Membership}{377}{section.110.1}\protected@file@percent } \newlabel{administrating-site-membership}{{110.1}{377}{Administrating Site Membership}{section.110.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {110.2}Adding Members to a Site}{377}{section.110.2}\protected@file@percent } \newlabel{adding-members-to-a-site}{{110.2}{377}{Adding Members to a Site}{section.110.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {110.1}{\ignorespaces The current members of the Site as displayed on the \emph {Site Memberships} page.}}{378}{figure.110.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {110.2}{\ignorespaces The list of users available to add to the current Site. Note that the current members are visible but cannot be added or removed here.}}{378}{figure.110.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {110.3}Removing User Membership from a Site}{378}{section.110.3}\protected@file@percent } \newlabel{removing-user-membership-from-a-site}{{110.3}{378}{Removing User Membership from a Site}{section.110.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {110.3}{\ignorespaces Selecting to remove a user.}}{379}{figure.110.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {110.4}Assigning Site Roles}{379}{section.110.4}\protected@file@percent } \newlabel{assigning-site-roles}{{110.4}{379}{Assigning Site Roles}{section.110.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {110.4}{\ignorespaces Assigning Site Roles.}}{380}{figure.110.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {111}Creating Teams to Empower Site Members}{381}{chapter.111}\protected@file@percent } \newlabel{creating-teams-to-empower-site-members}{{111}{381}{Creating Teams to Empower Site Members}{chapter.111}{}} \@writefile{lof}{\contentsline {figure}{\numberline {111.1}{\ignorespaces Creating teams within your site can foster teamwork and collaboration, as team permissions enable team members to access the same resources and perform the same types of tasks.}}{382}{figure.111.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {111.2}{\ignorespaces The Lunar Resort Message Board Moderators Site Team has unlimited permissions on the Message Boards application.}}{383}{figure.111.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {112}Managing Content}{385}{chapter.112}\protected@file@percent } \newlabel{managing-content}{{112}{385}{Managing Content}{chapter.112}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {113}Managing Web Content}{387}{chapter.113}\protected@file@percent } \newlabel{managing-web-content}{{113}{387}{Managing Web Content}{chapter.113}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {114}Publishing Basic Web Content}{389}{chapter.114}\protected@file@percent } \newlabel{publishing-basic-web-content}{{114}{389}{Publishing Basic Web Content}{chapter.114}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {115}Creating Web Content}{391}{chapter.115}\protected@file@percent } \newlabel{creating-web-content}{{115}{391}{Creating Web Content}{chapter.115}{}} \@writefile{lof}{\contentsline {figure}{\numberline {115.1}{\ignorespaces You can choose where to create content by navigating to the Site Administration menu and selecting your Site and page scope.}}{391}{figure.115.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {115.2}{\ignorespaces By default, \emph {Basic Web Content} is the only article type available. The next tutorial covers how to create new types.}}{392}{figure.115.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {116}Using the Web Content Editor}{393}{chapter.116}\protected@file@percent } \newlabel{using-the-web-content-editor}{{116}{393}{Using the Web Content Editor}{chapter.116}{}} \@writefile{toc}{\contentsline {section}{\numberline {116.1}Basic Editor Functions}{393}{section.116.1}\protected@file@percent } \newlabel{basic-editor-functions}{{116.1}{393}{Basic Editor Functions}{section.116.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {116.1}{\ignorespaces You can access the image editor through the item selector window.}}{394}{figure.116.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {116.2}Editing the Article Source}{394}{section.116.2}\protected@file@percent } \newlabel{editing-the-article-source}{{116.2}{394}{Editing the Article Source}{section.116.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {116.2}{\ignorespaces You can view how your HTML would render by using the preview pane.}}{395}{figure.116.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {116.3}Web Content Options}{396}{section.116.3}\protected@file@percent } \newlabel{web-content-options}{{116.3}{396}{Web Content Options}{section.116.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {116.3}{\ignorespaces New web content can be customized in various ways using the menu located to the right of the editor.}}{396}{figure.116.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {116.4}{\ignorespaces This blog entry has links to two Related Assets: an article and a message board thread.}}{397}{figure.116.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {117}Publishing Web Content}{399}{chapter.117}\protected@file@percent } \newlabel{publishing-web-content}{{117}{399}{Publishing Web Content}{chapter.117}{}} \@writefile{lof}{\contentsline {figure}{\numberline {117.1}{\ignorespaces Add the Web Content Display app to a page to begin displaying your new web content article.}}{399}{figure.117.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {117.2}{\ignorespaces Publishing web content is a snap. At a minimum, you only have to select the content you wish to publish. You can also enable lots of optional features to let your users interact with your content.}}{401}{figure.117.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {117.1}Editing Published Content}{401}{section.117.1}\protected@file@percent } \newlabel{editing-published-content}{{117.1}{401}{Editing Published Content}{section.117.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {117.3}{\ignorespaces Comparing web content articles is a great feature to use during the Workflow process.}}{402}{figure.117.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {118}Other Content Options}{403}{chapter.118}\protected@file@percent } \newlabel{other-content-options}{{118}{403}{Other Content Options}{chapter.118}{}} \@writefile{toc}{\contentsline {section}{\numberline {118.1}Localizing Content}{403}{section.118.1}\protected@file@percent } \newlabel{localizing-content}{{118.1}{403}{Localizing Content}{section.118.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {118.1}{\ignorespaces Adding a translation to an article works like adding the default translation.}}{404}{figure.118.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {118.2}Xuggler for Embedding Video}{404}{section.118.2}\protected@file@percent } \newlabel{xuggler-for-embedding-video}{{118.2}{404}{Xuggler for Embedding Video}{section.118.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {118.2}{\ignorespaces If you've installed and enabled Xuggler from the \emph {Server Administration} → \emph {External Tools} section of the Control Panel, you can add audio and video to your web content!}}{405}{figure.118.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {118.3}XML Format Downloads}{405}{section.118.3}\protected@file@percent } \newlabel{xml-format-downloads}{{118.3}{405}{XML Format Downloads}{section.118.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {118.4}Subscribing to Content}{405}{section.118.4}\protected@file@percent } \newlabel{subscribing-to-content}{{118.4}{405}{Subscribing to Content}{section.118.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {118.3}{\ignorespaces The \emph {View Source} button is available from the \emph {Options} button.}}{406}{figure.118.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {118.4}{\ignorespaces Click the Subscribe icon in the web content entity's \emph {Options} menu to begin receiving web content notifications.}}{406}{figure.118.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {118.5}Organizing Structure Names}{406}{section.118.5}\protected@file@percent } \newlabel{organizing-structure-names}{{118.5}{406}{Organizing Structure Names}{section.118.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {118.5}{\ignorespaces The default ordering for Web Content Structures can yield confusing results.}}{407}{figure.118.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {118.6}{\ignorespaces Web Content Administration will now display structures in alphabetical order.}}{408}{figure.118.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {119}Designing Structured Content}{409}{chapter.119}\protected@file@percent } \newlabel{designing-structured-content}{{119}{409}{Designing Structured Content}{chapter.119}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {120}Creating Structured Web Content}{411}{chapter.120}\protected@file@percent } \newlabel{creating-structured-web-content}{{120}{411}{Creating Structured Web Content}{chapter.120}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {121}Editing Structures}{413}{chapter.121}\protected@file@percent } \newlabel{editing-structures}{{121}{413}{Editing Structures}{chapter.121}{}} \@writefile{toc}{\contentsline {section}{\numberline {121.1}Structure Fields}{413}{section.121.1}\protected@file@percent } \newlabel{structure-fields}{{121.1}{413}{Structure Fields}{section.121.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {121.1}{\ignorespaces Structures are not pre-installed. You have to make your own.}}{414}{figure.121.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {121.2}{\ignorespaces The structure editor gives you many options to customize your Web Content.}}{415}{figure.121.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {122}Configuring Structure Fields}{417}{chapter.122}\protected@file@percent } \newlabel{configuring-structure-fields}{{122}{417}{Configuring Structure Fields}{chapter.122}{}} \@writefile{toc}{\contentsline {section}{\numberline {122.1}Structure Default Values}{418}{section.122.1}\protected@file@percent } \newlabel{structure-default-values}{{122.1}{418}{Structure Default Values}{section.122.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {122.1}{\ignorespaces You can edit default values via the \emph {Actions} button of the Manage Structures interface.}}{418}{figure.122.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {122.2}{\ignorespaces You can define values for your structure fields and the standard asset metadata fields.}}{419}{figure.122.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {123}Structure Settings}{421}{chapter.123}\protected@file@percent } \newlabel{structure-settings}{{123}{421}{Structure Settings}{chapter.123}{}} \@writefile{toc}{\contentsline {section}{\numberline {123.1}Assigning Permissions}{421}{section.123.1}\protected@file@percent } \newlabel{assigning-permissions}{{123.1}{421}{Assigning Permissions}{section.123.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {123.2}WebDAV URL}{421}{section.123.2}\protected@file@percent } \newlabel{webdav-url}{{123.2}{421}{WebDAV URL}{section.123.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {123.1}{\ignorespaces You're able to assign structure permissions via the \emph {Actions} button.}}{422}{figure.123.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {124}Designing Web Content with Templates}{423}{chapter.124}\protected@file@percent } \newlabel{designing-web-content-with-templates}{{124}{423}{Designing Web Content with Templates}{chapter.124}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {125}Adding Templates with Structures}{425}{chapter.125}\protected@file@percent } \newlabel{adding-templates-with-structures}{{125}{425}{Adding Templates with Structures}{chapter.125}{}} \@writefile{lof}{\contentsline {figure}{\numberline {125.1}{\ignorespaces The Lunar Resort News Article is shaping up!}}{426}{figure.125.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {126}Embedding Widgets in Templates}{427}{chapter.126}\protected@file@percent } \newlabel{embedding-widgets-in-templates}{{126}{427}{Embedding Widgets in Templates}{chapter.126}{}} \@writefile{lof}{\contentsline {figure}{\numberline {126.1}{\ignorespaces You can find the Template Key when view the Edit page for a template..}}{427}{figure.126.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {127}Using Taglibs in Templates}{429}{chapter.127}\protected@file@percent } \newlabel{using-taglibs-in-templates}{{127}{429}{Using Taglibs in Templates}{chapter.127}{}} \@writefile{lof}{\contentsline {figure}{\numberline {127.1}{\ignorespaces You can hover your pointer over a variable for a more detailed description.}}{429}{figure.127.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {128}Assigning Template Permissions}{431}{chapter.128}\protected@file@percent } \newlabel{assigning-template-permissions}{{128}{431}{Assigning Template Permissions}{chapter.128}{}} \@writefile{toc}{\contentsline {section}{\numberline {128.1}Assigning Permissions for Individual Templates}{431}{section.128.1}\protected@file@percent } \newlabel{assigning-permissions-for-individual-templates}{{128.1}{431}{Assigning Permissions for Individual Templates}{section.128.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {129}Display Page Templates for Web Content}{433}{chapter.129}\protected@file@percent } \newlabel{display-page-templates-for-web-content}{{129}{433}{Display Page Templates for Web Content}{chapter.129}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {130}Creating Display Page Templates}{435}{chapter.130}\protected@file@percent } \newlabel{creating-display-page-templates}{{130}{435}{Creating Display Page Templates}{chapter.130}{}} \@writefile{lof}{\contentsline {figure}{\numberline {130.1}{\ignorespaces Connecting structure fields to fragment data.}}{436}{figure.130.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {131}Display Page Template Example}{437}{chapter.131}\protected@file@percent } \newlabel{display-page-template-example}{{131}{437}{Display Page Template Example}{chapter.131}{}} \@writefile{lof}{\contentsline {figure}{\numberline {131.1}{\ignorespaces Selecting the Asset type and Subtype.}}{438}{figure.131.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {131.1}Publishing with Display Page Templates}{438}{section.131.1}\protected@file@percent } \newlabel{publishing-with-display-page-templates}{{131.1}{438}{Publishing with Display Page Templates}{section.131.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {131.2}{\ignorespaces The Display Page Template creation interface.}}{439}{figure.131.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {131.3}{\ignorespaces Editing a Display Page Template with some Fragments added.}}{439}{figure.131.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {131.4}{\ignorespaces Mapping the editable fragments to structure fields.}}{440}{figure.131.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {131.5}{\ignorespaces Selecting the Asset type and Subtype.}}{441}{figure.131.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {131.6}{\ignorespaces Selecting the Asset type and Subtype.}}{441}{figure.131.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {132}Managing Content Sets}{443}{chapter.132}\protected@file@percent } \newlabel{managing-content-sets}{{132}{443}{Managing Content Sets}{chapter.132}{}} \@writefile{toc}{\contentsline {section}{\numberline {132.1}Creating and Displaying Content Sets}{443}{section.132.1}\protected@file@percent } \newlabel{creating-and-displaying-content-sets}{{132.1}{443}{Creating and Displaying Content Sets}{section.132.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {132.2}Content Set Personalization}{444}{section.132.2}\protected@file@percent } \newlabel{content-set-personalization}{{132.2}{444}{Content Set Personalization}{section.132.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {132.3}Converting Asset Publisher Configurations to Content Sets}{444}{section.132.3}\protected@file@percent } \newlabel{converting-asset-publisher-configurations-to-content-sets}{{132.3}{444}{Converting Asset Publisher Configurations to Content Sets}{section.132.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {133}Creating Content Sets}{445}{chapter.133}\protected@file@percent } \newlabel{creating-content-sets}{{133}{445}{Creating Content Sets}{chapter.133}{}} \@writefile{toc}{\contentsline {section}{\numberline {133.1}Creating a Manual Content Set}{445}{section.133.1}\protected@file@percent } \newlabel{creating-a-manual-content-set}{{133.1}{445}{Creating a Manual Content Set}{section.133.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {133.1}{\ignorespaces Content Sets is found in the Content \& Data section of Site Administration.}}{446}{figure.133.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {133.2}{\ignorespaces You can select the type of asset to add to the Content Set.}}{446}{figure.133.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {133.2}Creating a Dynamic Content Set}{447}{section.133.2}\protected@file@percent } \newlabel{creating-a-dynamic-content-set}{{133.2}{447}{Creating a Dynamic Content Set}{section.133.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {133.3}{\ignorespaces Content Sets use the same filter system as the Asset Publisher.}}{447}{figure.133.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {134}Displaying Content Sets}{449}{chapter.134}\protected@file@percent } \newlabel{displaying-content-sets}{{134}{449}{Displaying Content Sets}{chapter.134}{}} \@writefile{toc}{\contentsline {section}{\numberline {134.1}Configuring the Asset Publisher for Content Sets}{449}{section.134.1}\protected@file@percent } \newlabel{configuring-the-asset-publisher-for-content-sets}{{134.1}{449}{Configuring the Asset Publisher for Content Sets}{section.134.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {134.1}{\ignorespaces The Asset Publisher has a number of options available for selecting its source for content.}}{450}{figure.134.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {134.2}{\ignorespaces Select the Content Set you want to use.}}{450}{figure.134.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {134.3}{\ignorespaces You can see the results as the standard Asset Publisher output. You can create Widget Templates to add more style and pizzazz here.}}{451}{figure.134.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {134.2}Adding Items to an existing Content Set}{451}{section.134.2}\protected@file@percent } \newlabel{adding-items-to-an-existing-content-set}{{134.2}{451}{Adding Items to an existing Content Set}{section.134.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {134.4}{\ignorespaces The result is dynamically added to the Content List wherever it is displayed.}}{452}{figure.134.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {135}Converting Asset Publisher Configurations to Content Sets}{455}{chapter.135}\protected@file@percent } \newlabel{converting-asset-publisher-configurations-to-content-sets-1}{{135}{455}{Converting Asset Publisher Configurations to Content Sets}{chapter.135}{}} \@writefile{lof}{\contentsline {figure}{\numberline {135.1}{\ignorespaces You can generate a Content Set directly from the Asset Publisher configuration.}}{455}{figure.135.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {135.2}{\ignorespaces The Content Set is added right alongside any existing sets.}}{456}{figure.135.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {136}Organizing Content with Tags and Categories}{457}{chapter.136}\protected@file@percent } \newlabel{organizing-content-with-tags-and-categories}{{136}{457}{Organizing Content with Tags and Categories}{chapter.136}{}} \@writefile{lof}{\contentsline {figure}{\numberline {136.1}{\ignorespaces Here is the Web Content application's metadata section.}}{457}{figure.136.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {137}Tagging Content}{459}{chapter.137}\protected@file@percent } \newlabel{tagging-content}{{137}{459}{Tagging Content}{chapter.137}{}} \@writefile{lof}{\contentsline {figure}{\numberline {137.1}{\ignorespaces The Add Tag interface is very simple, only requiring the name of your tag.}}{460}{figure.137.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {138}Defining Categories for Content}{461}{chapter.138}\protected@file@percent } \newlabel{defining-categories-for-content}{{138}{461}{Defining Categories for Content}{chapter.138}{}} \@writefile{lof}{\contentsline {figure}{\numberline {138.1}{\ignorespaces After adding new vocabularies, you'll notice your vocabularies indicate the amount of categories existing beneath them.}}{462}{figure.138.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {139}Targeted Vocabularies}{465}{chapter.139}\protected@file@percent } \newlabel{targeted-vocabularies}{{139}{465}{Targeted Vocabularies}{chapter.139}{}} \@writefile{lof}{\contentsline {figure}{\numberline {139.1}{\ignorespaces You can target vocabularies by checking the \emph {Allow Multiple Categories} selector and then selecting the Asset Types.}}{465}{figure.139.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {139.1}Single and Multi-valued Vocabularies}{466}{section.139.1}\protected@file@percent } \newlabel{single-and-multi-valued-vocabularies}{{139.1}{466}{Single and Multi-valued Vocabularies}{section.139.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {139.2}{\ignorespaces Multi-valued vocabularies allow multiple categories from the vocabulary to be applied to an asset. Single-valued vocabularies only allow one category from the vocabulary to be applied. Here, the \emph {Dining} and \emph {Nightlife} categories are selected to be applied but the \emph {Scenic Adventures} category is not.}}{466}{figure.139.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {139.2}Separated Entries}{466}{section.139.2}\protected@file@percent } \newlabel{separated-entries}{{139.2}{466}{Separated Entries}{section.139.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {139.3}{\ignorespaces Vocabularies have their own entries, making it easy to select available categories.}}{467}{figure.139.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {140}Geolocating Assets}{469}{chapter.140}\protected@file@percent } \newlabel{geolocating-assets}{{140}{469}{Geolocating Assets}{chapter.140}{}} \@writefile{toc}{\contentsline {section}{\numberline {140.1}Geolocating Web Content}{469}{section.140.1}\protected@file@percent } \newlabel{geolocating-web-content}{{140.1}{469}{Geolocating Web Content}{section.140.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {140.1}{\ignorespaces Add a geolocation field to your structure to enable geolocation in your web content.}}{470}{figure.140.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {140.2}Geolocating Data Lists}{470}{section.140.2}\protected@file@percent } \newlabel{geolocating-data-lists}{{140.2}{470}{Geolocating Data Lists}{section.140.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {140.2}{\ignorespaces Add the Content and Geolocation snippets to create your web content template quickly.}}{471}{figure.140.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {140.3}Geolocating Documents and Media}{471}{section.140.3}\protected@file@percent } \newlabel{geolocating-documents-and-media}{{140.3}{471}{Geolocating Documents and Media}{section.140.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {140.3}{\ignorespaces You can enter your location in the address bar, move the indicator to a location, or share your location with the browser.}}{472}{figure.140.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {140.4}{\ignorespaces Make sure your browser is configured to share your location.}}{472}{figure.140.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {140.5}{\ignorespaces The Asset Publisher can display your geolocated assets on a map.}}{473}{figure.140.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {141}Publishing Content Dynamically}{475}{chapter.141}\protected@file@percent } \newlabel{publishing-content-dynamically}{{141}{475}{Publishing Content Dynamically}{chapter.141}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {142}Defining Content Relationships}{477}{chapter.142}\protected@file@percent } \newlabel{defining-content-relationships}{{142}{477}{Defining Content Relationships}{chapter.142}{}} \@writefile{toc}{\contentsline {section}{\numberline {142.1}Related Assets Widget}{477}{section.142.1}\protected@file@percent } \newlabel{related-assets-widget}{{142.1}{477}{Related Assets Widget}{section.142.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {142.1}{\ignorespaces Select an asset in the Asset Publisher to see its related assets displayed in the Related Assets application.}}{478}{figure.142.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {142.2}{\ignorespaces Related Assets applications can be configured to display specific content.}}{479}{figure.142.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {143}Publishing Assets}{481}{chapter.143}\protected@file@percent } \newlabel{publishing-assets}{{143}{481}{Publishing Assets}{chapter.143}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {144}Querying for Content}{483}{chapter.144}\protected@file@percent } \newlabel{querying-for-content}{{144}{483}{Querying for Content}{chapter.144}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {145}Selecting Assets}{485}{chapter.145}\protected@file@percent } \newlabel{selecting-assets}{{145}{485}{Selecting Assets}{chapter.145}{}} \@writefile{toc}{\contentsline {section}{\numberline {145.1}Selecting Assets Manually}{485}{section.145.1}\protected@file@percent } \newlabel{selecting-assets-manually}{{145.1}{485}{Selecting Assets Manually}{section.145.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {145.1}{\ignorespaces Selecting assets in the Asset Publisher manually is similar to selecting assets in the Web Content Display application except that you can select assets of any type, not just web content. You can also add scopes to expand the list of assets that are available to be displayed in the Asset Publisher.}}{486}{figure.145.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {145.2}Selecting Assets Dynamically}{487}{section.145.2}\protected@file@percent } \newlabel{selecting-assets-dynamically}{{145.2}{487}{Selecting Assets Dynamically}{section.145.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {145.2}{\ignorespaces You can filter by tags and categories, and you can set up as many filter rules as you need.}}{487}{figure.145.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {145.3}Selecting a Content Set}{489}{section.145.3}\protected@file@percent } \newlabel{selecting-a-content-set}{{145.3}{489}{Selecting a Content Set}{section.145.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {146}Configuring Display Settings}{491}{chapter.146}\protected@file@percent } \newlabel{configuring-display-settings}{{146}{491}{Configuring Display Settings}{chapter.146}{}} \@writefile{lof}{\contentsline {figure}{\numberline {146.1}{\ignorespaces You can configure the Asset Publisher to display various kinds of metadata about the displayed assets.}}{493}{figure.146.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {147}Configuring Asset Publisher Subscriptions}{495}{chapter.147}\protected@file@percent } \newlabel{configuring-asset-publisher-subscriptions}{{147}{495}{Configuring Asset Publisher Subscriptions}{chapter.147}{}} \@writefile{lof}{\contentsline {figure}{\numberline {147.1}{\ignorespaces An email subscription notifies users when new assets are published.}}{495}{figure.147.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {148}Publishing RSS Feeds}{497}{chapter.148}\protected@file@percent } \newlabel{publishing-rss-feeds}{{148}{497}{Publishing RSS Feeds}{chapter.148}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {149}Configuring RSS Feeds}{499}{chapter.149}\protected@file@percent } \newlabel{configuring-rss-feeds}{{149}{499}{Configuring RSS Feeds}{chapter.149}{}} \@writefile{lof}{\contentsline {figure}{\numberline {149.1}{\ignorespaces To create a new RSS feed, you only need to specify a name, target page, and web content structure. Of course, you can also configure other features of the feed such as its permissions, web content constraints, and presentation settings.}}{500}{figure.149.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {150}The RSS Publisher Widget}{503}{chapter.150}\protected@file@percent } \newlabel{the-rss-publisher-widget}{{150}{503}{The RSS Publisher Widget}{chapter.150}{}} \@writefile{lof}{\contentsline {figure}{\numberline {150.1}{\ignorespaces The RSS Publisher widget lets you display RSS feeds of your choosing.}}{503}{figure.150.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {150.1}Using the RSS Publisher Widget}{504}{section.150.1}\protected@file@percent } \newlabel{using-the-rss-publisher-widget}{{150.1}{504}{Using the RSS Publisher Widget}{section.150.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {150.2}{\ignorespaces The RSS Publisher widget's configuration lets you customize how the widget displays RSS feeds.}}{505}{figure.150.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {150.3}{\ignorespaces You can also use the RSS Publisher widget's configuration to specify which feeds to display.}}{506}{figure.150.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {151}Restoring Deleted Assets}{507}{chapter.151}\protected@file@percent } \newlabel{restoring-deleted-assets}{{151}{507}{Restoring Deleted Assets}{chapter.151}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {152}Configuring the Recycle Bin}{509}{chapter.152}\protected@file@percent } \newlabel{configuring-the-recycle-bin}{{152}{509}{Configuring the Recycle Bin}{chapter.152}{}} \@writefile{lof}{\contentsline {figure}{\numberline {152.1}{\ignorespaces The Recycle Bin offers several configurable options for your site.}}{509}{figure.152.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {153}Using the Recycle Bin}{511}{chapter.153}\protected@file@percent } \newlabel{using-the-recycle-bin}{{153}{511}{Using the Recycle Bin}{chapter.153}{}} \@writefile{lof}{\contentsline {figure}{\numberline {153.1}{\ignorespaces The Recycle Bin provides a seamless administrative experience for deleting and removing content.}}{511}{figure.153.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {153.2}{\ignorespaces In the Recycle Bin, you have the option of restoring or permanently deleting the content.}}{512}{figure.153.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {153.1}Drag and Drop}{513}{section.153.1}\protected@file@percent } \newlabel{drag-and-drop}{{153.1}{513}{Drag and Drop}{section.153.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {153.3}{\ignorespaces A quick and easy way of disposing your items is the drag and drop method.}}{513}{figure.153.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {154}Recycle Bin Intelligence and Support}{515}{chapter.154}\protected@file@percent } \newlabel{recycle-bin-intelligence-and-support}{{154}{515}{Recycle Bin Intelligence and Support}{chapter.154}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {155}Collaboration}{517}{chapter.155}\protected@file@percent } \newlabel{collaboration}{{155}{517}{Collaboration}{chapter.155}{}} \@writefile{lof}{\contentsline {figure}{\numberline {155.1}{\ignorespaces You can use the Documents and Media Library to manage and use documents in the portal.}}{518}{figure.155.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {155.2}{\ignorespaces You can also make your blog entries look great.}}{519}{figure.155.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {155.3}{\ignorespaces The Message Boards app is fantastic for facilitating discussions.}}{520}{figure.155.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {156}Managing Documents and Media}{521}{chapter.156}\protected@file@percent } \newlabel{managing-documents-and-media}{{156}{521}{Managing Documents and Media}{chapter.156}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {157}Publishing Files}{523}{chapter.157}\protected@file@percent } \newlabel{publishing-files}{{157}{523}{Publishing Files}{chapter.157}{}} \@writefile{lof}{\contentsline {figure}{\numberline {157.1}{\ignorespaces These documents are awesome.}}{524}{figure.157.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {157.2}{\ignorespaces This slideshow rules.}}{524}{figure.157.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {157.3}{\ignorespaces Viewing a file's details is fun.}}{525}{figure.157.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {158}Adding Files to a Document Library}{527}{chapter.158}\protected@file@percent } \newlabel{adding-files-to-a-document-library}{{158}{527}{Adding Files to a Document Library}{chapter.158}{}} \@writefile{toc}{\contentsline {section}{\numberline {158.1}Granting File Permissions and Roles}{527}{section.158.1}\protected@file@percent } \newlabel{granting-file-permissions-and-roles}{{158.1}{527}{Granting File Permissions and Roles}{section.158.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {158.1}{\ignorespaces It's often helpful to define a role for specific users to access Documents and Media from Site Administration.}}{528}{figure.158.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {158.2}Using the Add Menu}{528}{section.158.2}\protected@file@percent } \newlabel{using-the-add-menu}{{158.2}{528}{Using the Add Menu}{section.158.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {158.2}{\ignorespaces The Documents and Media's \emph {Home} folder starts empty.}}{529}{figure.158.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {158.3}{\ignorespaces The Add menu lets you upload and add all kinds of documents to the library.}}{530}{figure.158.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {159}Creating Folders}{531}{chapter.159}\protected@file@percent } \newlabel{creating-folders}{{159}{531}{Creating Folders}{chapter.159}{}} \@writefile{toc}{\contentsline {section}{\numberline {159.1}Adding a Folder}{531}{section.159.1}\protected@file@percent } \newlabel{adding-a-folder}{{159.1}{531}{Adding a Folder}{section.159.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {159.1}{\ignorespaces Select your folder's permissions.}}{532}{figure.159.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {159.2}{\ignorespaces Your new folder appears in the Document Library.}}{532}{figure.159.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {159.2}Document Type Restrictions and Workflow}{532}{section.159.2}\protected@file@percent } \newlabel{document-type-restrictions-and-workflow}{{159.2}{532}{Document Type Restrictions and Workflow}{section.159.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {159.3}{\ignorespaces You can set the document type restrictions and workflow to use for a folder's files.}}{533}{figure.159.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {159.3}Setting Folder Permissions}{533}{section.159.3}\protected@file@percent } \newlabel{setting-folder-permissions}{{159.3}{533}{Setting Folder Permissions}{section.159.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {160}Using the Documents and Media Management Bar}{535}{chapter.160}\protected@file@percent } \newlabel{using-the-documents-and-media-management-bar}{{160}{535}{Using the Documents and Media Management Bar}{chapter.160}{}} \@writefile{lof}{\contentsline {figure}{\numberline {160.1}{\ignorespaces The Management Bar is a great place to hang out if you're managing documents.}}{535}{figure.160.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {160.1}View Types}{535}{section.160.1}\protected@file@percent } \newlabel{view-types}{{160.1}{535}{View Types}{section.160.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {160.2}{\ignorespaces The Cards View type shows items in large card-like renderings.}}{536}{figure.160.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {160.2}The Info Panel}{536}{section.160.2}\protected@file@percent } \newlabel{the-info-panel}{{160.2}{536}{The Info Panel}{section.160.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {160.3}Finding and Arranging Items}{537}{section.160.3}\protected@file@percent } \newlabel{finding-and-arranging-items}{{160.3}{537}{Finding and Arranging Items}{section.160.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {160.4}Selecting Items}{537}{section.160.4}\protected@file@percent } \newlabel{selecting-items}{{160.4}{537}{Selecting Items}{section.160.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {160.3}{\ignorespaces With items selected, the Management Bar changes.}}{537}{figure.160.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {161}Viewing File Previews}{539}{chapter.161}\protected@file@percent } \newlabel{viewing-file-previews}{{161}{539}{Viewing File Previews}{chapter.161}{}} \@writefile{toc}{\contentsline {section}{\numberline {161.1}File Preview Apps}{539}{section.161.1}\protected@file@percent } \newlabel{file-preview-apps}{{161.1}{539}{File Preview Apps}{section.161.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {161.1}{\ignorespaces File previews let you view and manage a file.}}{540}{figure.161.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {161.2}Managing Files}{541}{section.161.2}\protected@file@percent } \newlabel{managing-files}{{161.2}{541}{Managing Files}{section.161.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {161.3}The Info Panel}{542}{section.161.3}\protected@file@percent } \newlabel{the-info-panel-1}{{161.3}{542}{The Info Panel}{section.161.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {162}Editing Images}{543}{chapter.162}\protected@file@percent } \newlabel{editing-images}{{162}{543}{Editing Images}{chapter.162}{}} \@writefile{lof}{\contentsline {figure}{\numberline {162.1}{\ignorespaces You can access the image editor through the Documents and Media repository.}}{544}{figure.162.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {162.2}{\ignorespaces You can also access the image editor through the item selector preview window.}}{545}{figure.162.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {162.3}{\ignorespaces The image editor's UI is clear and to the point, offering only what you need.}}{545}{figure.162.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {162.4}{\ignorespaces Select from a set of preset image filters.}}{545}{figure.162.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {162.5}{\ignorespaces The history bar lets you undo, redo, and reset changes.}}{546}{figure.162.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {163}Publishing Files}{547}{chapter.163}\protected@file@percent } \newlabel{publishing-files-1}{{163}{547}{Publishing Files}{chapter.163}{}} \@writefile{toc}{\contentsline {section}{\numberline {163.1}Using the Media Gallery}{547}{section.163.1}\protected@file@percent } \newlabel{using-the-media-gallery}{{163.1}{547}{Using the Media Gallery}{section.163.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {163.1}{\ignorespaces The Media Gallery renders large thumbnail images of media files.}}{549}{figure.163.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {163.2}{\ignorespaces The Media Gallery's slideshow provides a nice way to view images.}}{549}{figure.163.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {164}Checking Out and Editing Files}{551}{chapter.164}\protected@file@percent } \newlabel{checking-out-and-editing-files}{{164}{551}{Checking Out and Editing Files}{chapter.164}{}} \@writefile{lof}{\contentsline {figure}{\numberline {164.1}{\ignorespaces The file on the right in this image is checked out.}}{551}{figure.164.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {164.2}{\ignorespaces The version history actions let you inspect, delete, and reinstate file versions.}}{553}{figure.164.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {165}Sharing Files}{555}{chapter.165}\protected@file@percent } \newlabel{sharing-files}{{165}{555}{Sharing Files}{chapter.165}{}} \@writefile{toc}{\contentsline {section}{\numberline {165.1}Sharing Files in Documents and Media}{556}{section.165.1}\protected@file@percent } \newlabel{sharing-files-in-documents-and-media}{{165.1}{556}{Sharing Files in Documents and Media}{section.165.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {165.2}Working with Shared Files}{556}{section.165.2}\protected@file@percent } \newlabel{working-with-shared-files}{{165.2}{556}{Working with Shared Files}{section.165.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {165.1}{\ignorespaces To share a file, you must fill out the Share dialog as these steps describe.}}{557}{figure.165.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {165.2}{\ignorespaces The Notifications app contains the notifications that are sent when a user shares a file with you.}}{558}{figure.165.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {165.3}Managing Shared Files}{558}{section.165.3}\protected@file@percent } \newlabel{managing-shared-files}{{165.3}{558}{Managing Shared Files}{section.165.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {165.3}{\ignorespaces The Shared Content app lists the files shared with you, and the files you shared.}}{559}{figure.165.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {165.4}{\ignorespaces Click \emph {Manage Collaborators} to open up the list of users you shared the file with.}}{560}{figure.165.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {165.5}{\ignorespaces The Collaborators dialog lets you unshare a file or change the file permissions for each user.}}{561}{figure.165.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {166}Configuring Sharing}{563}{chapter.166}\protected@file@percent } \newlabel{configuring-sharing}{{166}{563}{Configuring Sharing}{chapter.166}{}} \@writefile{toc}{\contentsline {section}{\numberline {166.1}Global Configuration}{563}{section.166.1}\protected@file@percent } \newlabel{global-configuration}{{166.1}{563}{Global Configuration}{section.166.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {166.2}Instance Configuration}{563}{section.166.2}\protected@file@percent } \newlabel{instance-configuration}{{166.2}{563}{Instance Configuration}{section.166.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {166.1}{\ignorespaces Configure sharing globally.}}{564}{figure.166.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {166.2}{\ignorespaces You can enable or disable sharing for each instance.}}{564}{figure.166.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {166.3}Site Configuration}{564}{section.166.3}\protected@file@percent } \newlabel{site-configuration}{{166.3}{564}{Site Configuration}{section.166.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {166.3}{\ignorespaces You can enable or disable sharing for each Site.}}{565}{figure.166.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {167}Desktop Access to Documents and Media}{567}{chapter.167}\protected@file@percent } \newlabel{desktop-access-to-documents-and-media}{{167}{567}{Desktop Access to Documents and Media}{chapter.167}{}} \@writefile{lof}{\contentsline {figure}{\numberline {167.1}{\ignorespaces Select \emph {Access from Desktop} to get the folder's WebDAV URL.}}{568}{figure.167.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {168}Linking to Google Drive™}{569}{chapter.168}\protected@file@percent } \newlabel{linking-to-google-drive}{{168}{569}{Linking to Google Drive™}{chapter.168}{}} \@writefile{toc}{\contentsline {section}{\numberline {168.1}Install the App}{569}{section.168.1}\protected@file@percent } \newlabel{install-the-app}{{168.1}{569}{Install the App}{section.168.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {168.2}Configure Your Google Project}{570}{section.168.2}\protected@file@percent } \newlabel{configure-your-google-project}{{168.2}{570}{Configure Your Google Project}{section.168.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {168.3}Configure Your Portal}{570}{section.168.3}\protected@file@percent } \newlabel{configure-your-portal}{{168.3}{570}{Configure Your Portal}{section.168.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {168.4}Creating Linked Files}{571}{section.168.4}\protected@file@percent } \newlabel{creating-linked-files}{{168.4}{571}{Creating Linked Files}{section.168.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {168.1}{\ignorespaces Enter your Google project's OAuth 2 client ID, OAuth 2 client secret, and Picker API key.}}{572}{figure.168.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {168.2}{\ignorespaces Select \emph {New Google Drive Shortcut} from the \emph {Add} menu in your Document Library.}}{573}{figure.168.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {168.3}{\ignorespaces You can select files from Google Drive™ or your photos.}}{573}{figure.168.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {169}Metadata Sets}{575}{chapter.169}\protected@file@percent } \newlabel{metadata-sets}{{169}{575}{Metadata Sets}{chapter.169}{}} \@writefile{toc}{\contentsline {section}{\numberline {169.1}Managing Metadata Sets}{575}{section.169.1}\protected@file@percent } \newlabel{managing-metadata-sets}{{169.1}{575}{Managing Metadata Sets}{section.169.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {169.1}{\ignorespaces The Metadata Sets management window lets you view existing sets and create new ones for applying to document types.}}{576}{figure.169.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {169.2}Creating Metadata Sets}{576}{section.169.2}\protected@file@percent } \newlabel{creating-metadata-sets}{{169.2}{576}{Creating Metadata Sets}{section.169.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {169.2}{\ignorespaces Add your metadata set's fields to the canvas.}}{578}{figure.169.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {169.3}{\ignorespaces Edit your metadata set's fields to match the metadata that you want each field to hold.}}{579}{figure.169.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {170}Document Types}{581}{chapter.170}\protected@file@percent } \newlabel{document-types}{{170}{581}{Document Types}{chapter.170}{}} \@writefile{toc}{\contentsline {section}{\numberline {170.1}Managing Document Types}{581}{section.170.1}\protected@file@percent } \newlabel{managing-document-types}{{170.1}{581}{Managing Document Types}{section.170.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {170.2}Creating Document Types}{581}{section.170.2}\protected@file@percent } \newlabel{creating-document-types}{{170.2}{581}{Creating Document Types}{section.170.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {170.1}{\ignorespaces The Document Types management window lets you view existing document types and create new ones.}}{582}{figure.170.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {170.2}{\ignorespaces Create your new document type.}}{583}{figure.170.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {171}Online File Creation and Editing with Google Docs™}{585}{chapter.171}\protected@file@percent } \newlabel{online-file-creation-and-editing-with-google-docs}{{171}{585}{Online File Creation and Editing with Google Docs™}{chapter.171}{}} \@writefile{lof}{\contentsline {figure}{\numberline {171.1}{\ignorespaces You can create new Google documents in Documents and Media.}}{586}{figure.171.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {171.2}{\ignorespaces You can also use Google's document editor to edit existing Documents and Media files.}}{587}{figure.171.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {171.3}{\ignorespaces When using Google's document editor, you can save or discard your changes via the editor's toolbar.}}{587}{figure.171.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {172}Configuring Google Docs™ Integration}{589}{chapter.172}\protected@file@percent } \newlabel{configuring-google-docs-integration}{{172}{589}{Configuring Google Docs™ Integration}{chapter.172}{}} \@writefile{toc}{\contentsline {section}{\numberline {172.1}Configure Your Google Project}{589}{section.172.1}\protected@file@percent } \newlabel{configure-your-google-project-1}{{172.1}{589}{Configure Your Google Project}{section.172.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {172.2}Configuring the Portal}{589}{section.172.2}\protected@file@percent } \newlabel{configuring-the-portal}{{172.2}{589}{Configuring the Portal}{section.172.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {172.1}{\ignorespaces Enter your Google project's OAuth 2 client ID and client secret.}}{591}{figure.172.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {173}Creating and Editing Files with Google Docs™}{593}{chapter.173}\protected@file@percent } \newlabel{creating-and-editing-files-with-google-docs}{{173}{593}{Creating and Editing Files with Google Docs™}{chapter.173}{}} \@writefile{toc}{\contentsline {section}{\numberline {173.1}Authentication}{593}{section.173.1}\protected@file@percent } \newlabel{authentication}{{173.1}{593}{Authentication}{section.173.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {173.2}Creating Files}{593}{section.173.2}\protected@file@percent } \newlabel{creating-files}{{173.2}{593}{Creating Files}{section.173.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {173.1}{\ignorespaces You can unlink your Google account from the portal.}}{594}{figure.173.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {173.2}{\ignorespaces Select the type of Google document you want to create.}}{595}{figure.173.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {173.3}{\ignorespaces Save or discard your changes by using the toolbar in the editor.}}{595}{figure.173.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {173.3}Editing Files}{595}{section.173.3}\protected@file@percent } \newlabel{editing-files}{{173.3}{595}{Editing Files}{section.173.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {173.4}Multiple Editing Sessions}{596}{section.173.4}\protected@file@percent } \newlabel{multiple-editing-sessions}{{173.4}{596}{Multiple Editing Sessions}{section.173.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {173.4}{\ignorespaces Select \emph {Edit in Google Docs} from the file's Actions menu.}}{597}{figure.173.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {174}Integration with Microsoft Office 365™}{599}{chapter.174}\protected@file@percent } \newlabel{integration-with-microsoft-office-365}{{174}{599}{Integration with Microsoft Office 365™}{chapter.174}{}} \@writefile{lof}{\contentsline {figure}{\numberline {174.1}{\ignorespaces You can create new Office 365™ documents in Documents and Media.}}{599}{figure.174.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {174.2}{\ignorespaces You can also edit existing Documents and Media files in Office 365™.}}{600}{figure.174.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {175}Configuring Office 365™ Integration}{601}{chapter.175}\protected@file@percent } \newlabel{configuring-office-365-integration}{{175}{601}{Configuring Office 365™ Integration}{chapter.175}{}} \@writefile{toc}{\contentsline {section}{\numberline {175.1}Register an Application with the Microsoft Identity Platform}{601}{section.175.1}\protected@file@percent } \newlabel{register-an-application-with-the-microsoft-identity-platform}{{175.1}{601}{Register an Application with the Microsoft Identity Platform}{section.175.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {175.2}Configuring Liferay DXP}{601}{section.175.2}\protected@file@percent } \newlabel{configuring-liferay-dxp}{{175.2}{601}{Configuring Liferay DXP}{section.175.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {175.1}{\ignorespaces Enter your application's client ID, client secret, and tenant.}}{603}{figure.175.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {176}Creating and Editing Documents and Media Files with Office 365™}{605}{chapter.176}\protected@file@percent } \newlabel{creating-and-editing-documents-and-media-files-with-office-365}{{176}{605}{Creating and Editing Documents and Media Files with Office 365™}{chapter.176}{}} \@writefile{toc}{\contentsline {section}{\numberline {176.1}Authentication}{605}{section.176.1}\protected@file@percent } \newlabel{authentication-1}{{176.1}{605}{Authentication}{section.176.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {176.2}Creating Files}{605}{section.176.2}\protected@file@percent } \newlabel{creating-files-1}{{176.2}{605}{Creating Files}{section.176.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {176.1}{\ignorespaces You can unlink your account from the portal.}}{606}{figure.176.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {176.2}{\ignorespaces Select the type of document you want to create.}}{607}{figure.176.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {176.3}{\ignorespaces Give the document a name.}}{607}{figure.176.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {176.3}Editing Files}{608}{section.176.3}\protected@file@percent } \newlabel{editing-files-1}{{176.3}{608}{Editing Files}{section.176.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {176.4}{\ignorespaces Select \emph {Edit in Office 365} from the file's Actions menu.}}{609}{figure.176.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {177}Store Types}{611}{chapter.177}\protected@file@percent } \newlabel{store-types}{{177}{611}{Store Types}{chapter.177}{}} \@writefile{toc}{\contentsline {section}{\numberline {177.1}Simple File System Store}{611}{section.177.1}\protected@file@percent } \newlabel{simple-file-system-store}{{177.1}{611}{Simple File System Store}{section.177.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {177.1}{\ignorespaces The Simple File System Store creates a folder structure based on primary keys in Liferay DXP's database.}}{612}{figure.177.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {177.2}Using the Advanced File System Store}{612}{section.177.2}\protected@file@percent } \newlabel{using-the-advanced-file-system-store}{{177.2}{612}{Using the Advanced File System Store}{section.177.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {177.2}{\ignorespaces The Advanced File System Store creates a more nested folder structure than the Simple File System Store.}}{613}{figure.177.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {177.3}Using Amazon Simple Storage Service}{613}{section.177.3}\protected@file@percent } \newlabel{using-amazon-simple-storage-service}{{177.3}{613}{Using Amazon Simple Storage Service}{section.177.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {178}Liferay Sync}{615}{chapter.178}\protected@file@percent } \newlabel{liferay-sync}{{178}{615}{Liferay Sync}{chapter.178}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {179}Administering Liferay Sync}{617}{chapter.179}\protected@file@percent } \newlabel{administering-liferay-sync}{{179}{617}{Administering Liferay Sync}{chapter.179}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {180}Installing Liferay Sync's Prerequisites}{619}{chapter.180}\protected@file@percent } \newlabel{installing-liferay-syncs-prerequisites}{{180}{619}{Installing Liferay Sync's Prerequisites}{chapter.180}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {181}Configuring Liferay Sync}{621}{chapter.181}\protected@file@percent } \newlabel{configuring-liferay-sync}{{181}{621}{Configuring Liferay Sync}{chapter.181}{}} \@writefile{lof}{\contentsline {figure}{\numberline {181.1}{\ignorespaces The Control Panel's Configuration section contains Sync Connector Admin.}}{622}{figure.181.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {181.2}{\ignorespaces Sync Connector Admin's Sites tab lets you manage Sync on a per-Site basis.}}{623}{figure.181.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {181.3}{\ignorespaces Sync Connector Admin's Devices tab lists all the devices Sync has registered.}}{624}{figure.181.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {182}Preventing Accidental File Deletion in Liferay Sync}{625}{chapter.182}\protected@file@percent } \newlabel{preventing-accidental-file-deletion-in-liferay-sync}{{182}{625}{Preventing Accidental File Deletion in Liferay Sync}{chapter.182}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {183}Ensuring Liferay Sync Security}{627}{chapter.183}\protected@file@percent } \newlabel{ensuring-liferay-sync-security}{{183}{627}{Ensuring Liferay Sync Security}{chapter.183}{}} \@writefile{toc}{\contentsline {section}{\numberline {183.1}Liferay Sync Permissions Demonstration}{627}{section.183.1}\protected@file@percent } \newlabel{liferay-sync-permissions-demonstration}{{183.1}{627}{Liferay Sync Permissions Demonstration}{section.183.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {183.1}{\ignorespaces The upload error occurs because the user only has permission to view files.}}{628}{figure.183.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {184}Using Liferay Sync on Your Desktop}{629}{chapter.184}\protected@file@percent } \newlabel{using-liferay-sync-on-your-desktop}{{184}{629}{Using Liferay Sync on Your Desktop}{chapter.184}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {185}Installing and Configuring the Desktop Liferay Sync Client}{631}{chapter.185}\protected@file@percent } \newlabel{installing-and-configuring-the-desktop-liferay-sync-client}{{185}{631}{Installing and Configuring the Desktop Liferay Sync Client}{chapter.185}{}} \@writefile{toc}{\contentsline {section}{\numberline {185.1}Installing the Liferay Sync Desktop Client}{631}{section.185.1}\protected@file@percent } \newlabel{installing-the-liferay-sync-desktop-client}{{185.1}{631}{Installing the Liferay Sync Desktop Client}{section.185.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {185.1}{\ignorespaces Drag the Liferay Sync icon to the Applications folder.}}{632}{figure.185.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {185.2}Configuring the Liferay Sync Desktop Client}{632}{section.185.2}\protected@file@percent } \newlabel{configuring-the-liferay-sync-desktop-client}{{185.2}{632}{Configuring the Liferay Sync Desktop Client}{section.185.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {185.2}{\ignorespaces The first time you run Liferay Sync, you must tell it how to communicate with your Liferay DXP server.}}{633}{figure.185.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {185.3}{\ignorespaces When connecting over HTTPS, Liferay Sync produces an error if it can't verify the security certificate. Choosing \emph {Proceed Anyway} bypasses verification and leaves the connection open to compromise.}}{633}{figure.185.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {185.4}{\ignorespaces Select the Sites you want to sync with. Clicking a Site's gear icon opens another window where you can choose to sync with only specific subfolders in that Site.}}{634}{figure.185.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {185.5}{\ignorespaces Choose the Site's subfolders that you want to sync with. The checkbox with the minus sign indicates that not all of the \emph {registration} folder's subfolders are selected.}}{634}{figure.185.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {185.6}{\ignorespaces Congratulations, you've successfully set up Liferay Sync!}}{636}{figure.185.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {186}Using the Liferay Sync Desktop Client}{637}{chapter.186}\protected@file@percent } \newlabel{using-the-liferay-sync-desktop-client}{{186}{637}{Using the Liferay Sync Desktop Client}{chapter.186}{}} \@writefile{lof}{\contentsline {figure}{\numberline {186.1}{\ignorespaces The Liferay Sync menu in the Windows task bar and Mac menu bar gives you quick access to Sync.}}{637}{figure.186.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {186.2}{\ignorespaces When you sync with more than one Liferay DXP instance, Sync shows submenus for each.}}{638}{figure.186.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {186.1}Using Sync Preferences}{638}{section.186.1}\protected@file@percent } \newlabel{using-sync-preferences}{{186.1}{638}{Using Sync Preferences}{section.186.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {186.3}{\ignorespaces The Preferences menu's \emph {Accounts} tab lets you manage syncing with Sites per account.}}{639}{figure.186.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {186.4}{\ignorespaces The Preferences menu's \emph {General} tab contains settings for Sync's general behavior.}}{640}{figure.186.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {186.5}{\ignorespaces The Preferences menu's \emph {Network} tab contains settings for Sync's data transfer behavior.}}{641}{figure.186.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {187}Using Your Local Liferay Sync Folder}{643}{chapter.187}\protected@file@percent } \newlabel{using-your-local-liferay-sync-folder}{{187}{643}{Using Your Local Liferay Sync Folder}{chapter.187}{}} \@writefile{lof}{\contentsline {figure}{\numberline {187.1}{\ignorespaces Updating a file through Liferay Sync increments the file's version number. You can view a file's version number through the web interface.}}{644}{figure.187.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {188}Using Liferay Sync on Your Mobile Device}{645}{chapter.188}\protected@file@percent } \newlabel{using-liferay-sync-on-your-mobile-device}{{188}{645}{Using Liferay Sync on Your Mobile Device}{chapter.188}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {189}Connecting Liferay Sync Mobile}{647}{chapter.189}\protected@file@percent } \newlabel{connecting-liferay-sync-mobile}{{189}{647}{Connecting Liferay Sync Mobile}{chapter.189}{}} \@writefile{lof}{\contentsline {figure}{\numberline {189.1}{\ignorespaces This panel lets you access the app's settings, as well as your Sites and documents.}}{647}{figure.189.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {189.2}{\ignorespaces Tapping the title bar at the top of My Sites or My Documents opens the main Sync panel's compact view.}}{648}{figure.189.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {189.3}{\ignorespaces The Settings screen for the Sync app lets you sign out of your Liferay DXP instance, enable Security Mode, view the app's version, and send feedback.}}{649}{figure.189.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {190}Managing Files and Folders in Liferay Sync Mobile}{651}{chapter.190}\protected@file@percent } \newlabel{managing-files-and-folders-in-liferay-sync-mobile}{{190}{651}{Managing Files and Folders in Liferay Sync Mobile}{chapter.190}{}} \@writefile{lof}{\contentsline {figure}{\numberline {190.1}{\ignorespaces Sync shows files and folders in a list.}}{652}{figure.190.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {190.2}{\ignorespaces Downloaded files appear in the list with their size in blue.}}{652}{figure.190.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {190.3}{\ignorespaces The badge on the file's icon shows the file's version in the Liferay DXP instance. You can also share files that you've downloaded.}}{653}{figure.190.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {191}Adapting Your Media Across Multiple Devices}{655}{chapter.191}\protected@file@percent } \newlabel{adapting-your-media-across-multiple-devices}{{191}{655}{Adapting Your Media Across Multiple Devices}{chapter.191}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {192}Installing Adaptive Media}{657}{chapter.192}\protected@file@percent } \newlabel{installing-adaptive-media}{{192}{657}{Installing Adaptive Media}{chapter.192}{}} \@writefile{toc}{\contentsline {section}{\numberline {192.1}Adaptive Media's Modules}{657}{section.192.1}\protected@file@percent } \newlabel{adaptive-medias-modules}{{192.1}{657}{Adaptive Media's Modules}{section.192.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {192.2}Processing Animated GIFs}{658}{section.192.2}\protected@file@percent } \newlabel{processing-animated-gifs}{{192.2}{658}{Processing Animated GIFs}{section.192.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {193}Adding Image Resolutions}{659}{chapter.193}\protected@file@percent } \newlabel{adding-image-resolutions}{{193}{659}{Adding Image Resolutions}{chapter.193}{}} \@writefile{lof}{\contentsline {figure}{\numberline {193.1}{\ignorespaces Adaptive Media's image resolutions are listed in a table.}}{659}{figure.193.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {193.1}Adding a New Image Resolution}{660}{section.193.1}\protected@file@percent } \newlabel{adding-a-new-image-resolution}{{193.1}{660}{Adding a New Image Resolution}{section.193.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {193.2}{\ignorespaces The form for adding a new Adaptive Media resolution.}}{661}{figure.193.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {194}Managing Image Resolutions}{663}{chapter.194}\protected@file@percent } \newlabel{managing-image-resolutions}{{194}{663}{Managing Image Resolutions}{chapter.194}{}} \@writefile{toc}{\contentsline {section}{\numberline {194.1}Disabling Image Resolutions}{663}{section.194.1}\protected@file@percent } \newlabel{disabling-image-resolutions}{{194.1}{663}{Disabling Image Resolutions}{section.194.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {194.2}Enabling Image Resolutions}{663}{section.194.2}\protected@file@percent } \newlabel{enabling-image-resolutions}{{194.2}{663}{Enabling Image Resolutions}{section.194.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {194.3}Editing Image Resolutions}{664}{section.194.3}\protected@file@percent } \newlabel{editing-image-resolutions}{{194.3}{664}{Editing Image Resolutions}{section.194.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {194.4}Deleting Image Resolutions}{664}{section.194.4}\protected@file@percent } \newlabel{deleting-image-resolutions}{{194.4}{664}{Deleting Image Resolutions}{section.194.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {194.5}Generating Missing Adapted Images}{664}{section.194.5}\protected@file@percent } \newlabel{generating-missing-adapted-images}{{194.5}{664}{Generating Missing Adapted Images}{section.194.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {194.1}{\ignorespaces The \emph {Adapted Images} column shows the percentage of images that are adapted for each resolution.}}{664}{figure.194.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {194.6}The Recycle Bin and Adapted Images}{665}{section.194.6}\protected@file@percent } \newlabel{the-recycle-bin-and-adapted-images}{{194.6}{665}{The Recycle Bin and Adapted Images}{section.194.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {195}Creating Content with Adapted Images}{667}{chapter.195}\protected@file@percent } \newlabel{creating-content-with-adapted-images}{{195}{667}{Creating Content with Adapted Images}{chapter.195}{}} \@writefile{toc}{\contentsline {section}{\numberline {195.1}Including Adapted Images in Content}{667}{section.195.1}\protected@file@percent } \newlabel{including-adapted-images-in-content}{{195.1}{667}{Including Adapted Images in Content}{section.195.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {195.2}Using Adapted Images in Structured Web Content}{668}{section.195.2}\protected@file@percent } \newlabel{using-adapted-images-in-structured-web-content}{{195.2}{668}{Using Adapted Images in Structured Web Content}{section.195.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {195.3}Staging Adapted Images}{669}{section.195.3}\protected@file@percent } \newlabel{staging-adapted-images}{{195.3}{669}{Staging Adapted Images}{section.195.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {196}Migrating Documents and Media Thumbnails to Adaptive Media}{671}{chapter.196}\protected@file@percent } \newlabel{migrating-documents-and-media-thumbnails-to-adaptive-media}{{196}{671}{Migrating Documents and Media Thumbnails to Adaptive Media}{chapter.196}{}} \@writefile{toc}{\contentsline {section}{\numberline {196.1}Adding the Replacement Image Resolutions}{671}{section.196.1}\protected@file@percent } \newlabel{adding-the-replacement-image-resolutions}{{196.1}{671}{Adding the Replacement Image Resolutions}{section.196.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {196.2}Creating the Adaptive Media Images}{672}{section.196.2}\protected@file@percent } \newlabel{creating-the-adaptive-media-images}{{196.2}{672}{Creating the Adaptive Media Images}{section.196.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {196.3}Running the Migration Process}{672}{section.196.3}\protected@file@percent } \newlabel{running-the-migration-process}{{196.3}{672}{Running the Migration Process}{section.196.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {197}Advanced Configuration Options}{675}{chapter.197}\protected@file@percent } \newlabel{advanced-configuration-options}{{197}{675}{Advanced Configuration Options}{chapter.197}{}} \@writefile{lof}{\contentsline {figure}{\numberline {197.1}{\ignorespaces You can configure Gifsicle and the maximum image size for Adaptive Media.}}{676}{figure.197.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {197.2}{\ignorespaces You can also configure Adaptive Media's image processing resources.}}{676}{figure.197.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {198}Collaborating}{677}{chapter.198}\protected@file@percent } \newlabel{collaborating}{{198}{677}{Collaborating}{chapter.198}{}} \@writefile{lof}{\contentsline {figure}{\numberline {198.1}{\ignorespaces This blog entry looks fascinating.}}{678}{figure.198.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {198.2}{\ignorespaces This is a great thread.}}{679}{figure.198.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {198.3}{\ignorespaces The Wiki widget displays your wiki on a Site page.}}{680}{figure.198.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {199}Publishing Blogs}{681}{chapter.199}\protected@file@percent } \newlabel{publishing-blogs}{{199}{681}{Publishing Blogs}{chapter.199}{}} \@writefile{lof}{\contentsline {figure}{\numberline {199.1}{\ignorespaces This blog entry looks fascinating.}}{682}{figure.199.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {200}Adding Blog Entries}{683}{chapter.200}\protected@file@percent } \newlabel{adding-blog-entries}{{200}{683}{Adding Blog Entries}{chapter.200}{}} \@writefile{lof}{\contentsline {figure}{\numberline {200.1}{\ignorespaces This screenshot shows some of the blog entry editor's controls.}}{684}{figure.200.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {200.2}{\ignorespaces When creating a blog entry, the Configuration panel lets you control when and where the blog entry appears, and what to use for the entry's abstract.}}{686}{figure.200.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {200.3}{\ignorespaces The Blogs app in Site Administration lists the site's blog entries.}}{687}{figure.200.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {201}Using the Blog Entry Editor}{689}{chapter.201}\protected@file@percent } \newlabel{using-the-blog-entry-editor}{{201}{689}{Using the Blog Entry Editor}{chapter.201}{}} \@writefile{lof}{\contentsline {figure}{\numberline {201.1}{\ignorespaces This screenshot shows some of the blog entry editor's controls.}}{689}{figure.201.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {201.1}Using the Editor's Text View}{690}{section.201.1}\protected@file@percent } \newlabel{using-the-editors-text-view}{{201.1}{690}{Using the Editor's Text View}{section.201.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {201.2}Using the Editor's Code View}{690}{section.201.2}\protected@file@percent } \newlabel{using-the-editors-code-view}{{201.2}{690}{Using the Editor's Code View}{section.201.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {201.2}{\ignorespaces Editing in code view lets you work with your blog entry's underlying HTML.}}{691}{figure.201.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {202}Managing Blog Entries}{693}{chapter.202}\protected@file@percent } \newlabel{managing-blog-entries}{{202}{693}{Managing Blog Entries}{chapter.202}{}} \@writefile{lof}{\contentsline {figure}{\numberline {202.1}{\ignorespaces The Blogs app in Site Administration lists the site's blog entries.}}{693}{figure.202.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {202.1}View Types}{694}{section.202.1}\protected@file@percent } \newlabel{view-types-1}{{202.1}{694}{View Types}{section.202.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {202.2}Finding and Arranging Blog Entries}{694}{section.202.2}\protected@file@percent } \newlabel{finding-and-arranging-blog-entries}{{202.2}{694}{Finding and Arranging Blog Entries}{section.202.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {202.3}Selecting Blog Entries}{694}{section.202.3}\protected@file@percent } \newlabel{selecting-blog-entries}{{202.3}{694}{Selecting Blog Entries}{section.202.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {202.2}{\ignorespaces With multiple blog entries selected, the management bar changes to reflect the actions you can take on the selected entries.}}{695}{figure.202.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {202.4}Sharing Blog Entries}{695}{section.202.4}\protected@file@percent } \newlabel{sharing-blog-entries}{{202.4}{695}{Sharing Blog Entries}{section.202.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {202.3}{\ignorespaces You can share a blog entry via its Actions menu.}}{695}{figure.202.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {203}Configuring the Blogs App}{697}{chapter.203}\protected@file@percent } \newlabel{configuring-the-blogs-app}{{203}{697}{Configuring the Blogs App}{chapter.203}{}} \@writefile{lof}{\contentsline {figure}{\numberline {203.1}{\ignorespaces You can configure the options for your site's Blogs app.}}{697}{figure.203.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {204}Displaying Blogs}{699}{chapter.204}\protected@file@percent } \newlabel{displaying-blogs}{{204}{699}{Displaying Blogs}{chapter.204}{}} \@writefile{lof}{\contentsline {figure}{\numberline {204.1}{\ignorespaces Fancy a lunar spelunking trip? This blog entry's abstract lets you know what you're getting into.}}{700}{figure.204.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {204.2}{\ignorespaces The Card display template makes your blog posts look like fun little trading cards.}}{701}{figure.204.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {205}Aggregating Blogs}{703}{chapter.205}\protected@file@percent } \newlabel{aggregating-blogs}{{205}{703}{Aggregating Blogs}{chapter.205}{}} \@writefile{lof}{\contentsline {figure}{\numberline {205.1}{\ignorespaces The Blogs Aggregator lets you display blog entries authored by multiple authors from different sites.}}{704}{figure.205.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {206}Highlighting Recent Bloggers}{705}{chapter.206}\protected@file@percent } \newlabel{highlighting-recent-bloggers}{{206}{705}{Highlighting Recent Bloggers}{chapter.206}{}} \@writefile{lof}{\contentsline {figure}{\numberline {206.1}{\ignorespaces You can show off your site or organization's most recent bloggers from the Recent Bloggers app.}}{706}{figure.206.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {207}Creating Forums with Message Boards}{707}{chapter.207}\protected@file@percent } \newlabel{creating-forums-with-message-boards}{{207}{707}{Creating Forums with Message Boards}{chapter.207}{}} \@writefile{lof}{\contentsline {figure}{\numberline {207.1}{\ignorespaces The Message Boards widget lets you explore its categories, interact with message threads, and post new messages.}}{708}{figure.207.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {207.2}{\ignorespaces A thread's view displays author information and thread content, for the thread and all replies to the thread.}}{708}{figure.207.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {208}Creating Message Boards}{709}{chapter.208}\protected@file@percent } \newlabel{creating-message-boards}{{208}{709}{Creating Message Boards}{chapter.208}{}} \@writefile{toc}{\contentsline {section}{\numberline {208.1}Site-scoped Message Boards}{709}{section.208.1}\protected@file@percent } \newlabel{site-scoped-message-boards}{{208.1}{709}{Site-scoped Message Boards}{section.208.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {208.2}Page-scoped Message Boards}{709}{section.208.2}\protected@file@percent } \newlabel{page-scoped-message-boards}{{208.2}{709}{Page-scoped Message Boards}{section.208.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {208.1}{\ignorespaces A Message Board instance starts empty, ready for you to configure for your purposes.}}{710}{figure.208.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {208.3}Globally-scoped Message Boards}{710}{section.208.3}\protected@file@percent } \newlabel{globally-scoped-message-boards}{{208.3}{710}{Globally-scoped Message Boards}{section.208.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {208.2}{\ignorespaces Select the page's scope under the \emph {Content \& Data} menu in Site Administration.}}{711}{figure.208.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {208.3}{\ignorespaces After changing to the global scope, select \emph {Message Boards} from the \emph {Content \& Data} menu in Site Administration.}}{712}{figure.208.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {209}Configuring Message Boards}{713}{chapter.209}\protected@file@percent } \newlabel{configuring-message-boards}{{209}{713}{Configuring Message Boards}{chapter.209}{}} \@writefile{toc}{\contentsline {section}{\numberline {209.1}General Setup}{713}{section.209.1}\protected@file@percent } \newlabel{general-setup}{{209.1}{713}{General Setup}{section.209.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {209.2}Email Setup}{714}{section.209.2}\protected@file@percent } \newlabel{email-setup}{{209.2}{714}{Email Setup}{section.209.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {209.3}Thread Priorities}{714}{section.209.3}\protected@file@percent } \newlabel{thread-priorities}{{209.3}{714}{Thread Priorities}{section.209.3}{}} \gdef \LT@i {\LT@entry {1}{258.95407pt}\LT@entry {1}{210.80093pt}} \@writefile{toc}{\contentsline {section}{\numberline {209.4}User Ranks}{715}{section.209.4}\protected@file@percent } \newlabel{user-ranks}{{209.4}{715}{User Ranks}{section.209.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {209.5}RSS}{715}{section.209.5}\protected@file@percent } \newlabel{rss}{{209.5}{715}{RSS}{section.209.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {210}Message Board Permissions}{717}{chapter.210}\protected@file@percent } \newlabel{message-board-permissions}{{210}{717}{Message Board Permissions}{chapter.210}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {211}Message Board Categories}{719}{chapter.211}\protected@file@percent } \newlabel{message-board-categories}{{211}{719}{Message Board Categories}{chapter.211}{}} \@writefile{toc}{\contentsline {section}{\numberline {211.1}Adding Categories}{719}{section.211.1}\protected@file@percent } \newlabel{adding-categories}{{211.1}{719}{Adding Categories}{section.211.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {211.1}{\ignorespaces You have several options to create a message board category for your needs.}}{720}{figure.211.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {211.2}{\ignorespaces Categories help you organize threads so users can find topical threads that interest them.}}{721}{figure.211.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {211.2}Adding Subcategories}{721}{section.211.2}\protected@file@percent } \newlabel{adding-subcategories}{{211.2}{721}{Adding Subcategories}{section.211.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {211.3}Moving and Merging Categories}{722}{section.211.3}\protected@file@percent } \newlabel{moving-and-merging-categories}{{211.3}{722}{Moving and Merging Categories}{section.211.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {211.3}{\ignorespaces The Move Category form lets you move and merge categories.}}{722}{figure.211.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {212}User Subscriptions and Mailing Lists}{723}{chapter.212}\protected@file@percent } \newlabel{user-subscriptions-and-mailing-lists}{{212}{723}{User Subscriptions and Mailing Lists}{chapter.212}{}} \@writefile{toc}{\contentsline {section}{\numberline {212.1}User Subscriptions}{723}{section.212.1}\protected@file@percent } \newlabel{user-subscriptions}{{212.1}{723}{User Subscriptions}{section.212.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {212.2}Mailing Lists}{724}{section.212.2}\protected@file@percent } \newlabel{mailing-lists}{{212.2}{724}{Mailing Lists}{section.212.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {213}Using the Message Boards}{727}{chapter.213}\protected@file@percent } \newlabel{using-the-message-boards}{{213}{727}{Using the Message Boards}{chapter.213}{}} \@writefile{lof}{\contentsline {figure}{\numberline {213.1}{\ignorespaces The Message Boards widget lets you explore its categories, interact with message threads, and post new messages.}}{727}{figure.213.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {213.1}Posting New Threads}{728}{section.213.1}\protected@file@percent } \newlabel{posting-new-threads}{{213.1}{728}{Posting New Threads}{section.213.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {213.2}{\ignorespaces The Add Message form lets you create a new thread.}}{728}{figure.213.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {213.2}Participating in Message Board Threads}{729}{section.213.2}\protected@file@percent } \newlabel{participating-in-message-board-threads}{{213.2}{729}{Participating in Message Board Threads}{section.213.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {213.3}{\ignorespaces A thread's view displays author information and thread content, for the thread and all replies to the thread.}}{730}{figure.213.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {214}Managing Message Boards}{731}{chapter.214}\protected@file@percent } \newlabel{managing-message-boards}{{214}{731}{Managing Message Boards}{chapter.214}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.1}Locking Threads}{731}{section.214.1}\protected@file@percent } \newlabel{locking-threads}{{214.1}{731}{Locking Threads}{section.214.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {214.1}{\ignorespaces Define the permissions you want to use for the message boards administrators.}}{732}{figure.214.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {214.2}Moving Threads}{733}{section.214.2}\protected@file@percent } \newlabel{moving-threads}{{214.2}{733}{Moving Threads}{section.214.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.3}Deleting Threads}{733}{section.214.3}\protected@file@percent } \newlabel{deleting-threads}{{214.3}{733}{Deleting Threads}{section.214.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.4}Banning Users}{733}{section.214.4}\protected@file@percent } \newlabel{banning-users}{{214.4}{733}{Banning Users}{section.214.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.5}Splitting Threads}{733}{section.214.5}\protected@file@percent } \newlabel{splitting-threads}{{214.5}{733}{Splitting Threads}{section.214.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.6}Editing Posts}{734}{section.214.6}\protected@file@percent } \newlabel{editing-posts}{{214.6}{734}{Editing Posts}{section.214.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {214.7}Post Permissions}{734}{section.214.7}\protected@file@percent } \newlabel{post-permissions}{{214.7}{734}{Post Permissions}{section.214.7}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {215}Mentioning Users}{735}{chapter.215}\protected@file@percent } \newlabel{mentioning-users}{{215}{735}{Mentioning Users}{chapter.215}{}} \@writefile{lof}{\contentsline {figure}{\numberline {215.1}{\ignorespaces As you enter a user name after the \texttt {@} character, links to users that match the text you enter are displayed. Select the user you want to mention and publish your content.}}{735}{figure.215.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {215.2}{\ignorespaces Your notifications are accessible from your user menu and appear in a list.}}{736}{figure.215.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {215.3}{\ignorespaces You can enable or disable the Mentions feature for all of the Virtual Instance's sites.}}{737}{figure.215.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {215.4}{\ignorespaces Mentions can also be enabled or disabled per site.}}{737}{figure.215.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {216}Working Together with the Wiki}{739}{chapter.216}\protected@file@percent } \newlabel{working-together-with-the-wiki}{{216}{739}{Working Together with the Wiki}{chapter.216}{}} \@writefile{lof}{\contentsline {figure}{\numberline {216.1}{\ignorespaces The Wiki widget displays your wiki on a Site page.}}{740}{figure.216.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {217}Getting Started with Wikis}{741}{chapter.217}\protected@file@percent } \newlabel{getting-started-with-wikis}{{217}{741}{Getting Started with Wikis}{chapter.217}{}} \@writefile{lof}{\contentsline {figure}{\numberline {217.1}{\ignorespaces The Wiki app instance has a wiki node named \emph {Main} with a single front page. You can build on the Main node or click the Add icon to create a new node.}}{741}{figure.217.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {217.1}Configuring Wikis}{741}{section.217.1}\protected@file@percent } \newlabel{configuring-wikis}{{217.1}{741}{Configuring Wikis}{section.217.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {217.2}Adding Wikis}{742}{section.217.2}\protected@file@percent } \newlabel{adding-wikis}{{217.2}{742}{Adding Wikis}{section.217.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {217.3}Wiki Node Options}{742}{section.217.3}\protected@file@percent } \newlabel{wiki-node-options}{{217.3}{742}{Wiki Node Options}{section.217.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {217.2}{\ignorespaces The New Wiki Node form lets you describe your new node, set view permissions, and set permissions for the Guest and Site Member roles.}}{743}{figure.217.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {217.3}{\ignorespaces Each wiki node's Actions menu lists actions you can perform.}}{743}{figure.217.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {218}Adding and Editing Wiki Pages}{745}{chapter.218}\protected@file@percent } \newlabel{adding-and-editing-wiki-pages}{{218}{745}{Adding and Editing Wiki Pages}{chapter.218}{}} \@writefile{lof}{\contentsline {figure}{\numberline {218.1}{\ignorespaces Each empty wiki page presents a default message link you can click to edit the page.}}{745}{figure.218.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {218.2}{\ignorespaces The wiki page editing form lets you create and edit your page's content.}}{746}{figure.218.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {218.3}{\ignorespaces The wiki node's view in site administration has features that help you access and learn information about a wiki node's pages.}}{747}{figure.218.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {219}Using the Wiki on Site Pages}{749}{chapter.219}\protected@file@percent } \newlabel{using-the-wiki-on-site-pages}{{219}{749}{Using the Wiki on Site Pages}{chapter.219}{}} \@writefile{lof}{\contentsline {figure}{\numberline {219.1}{\ignorespaces Users can interact with your Wiki nodes when you add the Wiki widget to a page.}}{750}{figure.219.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {219.2}{\ignorespaces Here the user has selected to create a new Wiki instance scoped to the current page named \emph {Welcome}}}{751}{figure.219.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {220}Wiki Page Details}{753}{chapter.220}\protected@file@percent } \newlabel{wiki-page-details}{{220}{753}{Wiki Page Details}{chapter.220}{}} \@writefile{lof}{\contentsline {figure}{\numberline {220.1}{\ignorespaces Click \emph {Details} to view the wiki page's details.}}{753}{figure.220.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {220.2}{\ignorespaces The wiki page's details.}}{753}{figure.220.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {220.1}Details}{754}{section.220.1}\protected@file@percent } \newlabel{details-1}{{220.1}{754}{Details}{section.220.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {220.2}History}{754}{section.220.2}\protected@file@percent } \newlabel{history}{{220.2}{754}{History}{section.220.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {220.3}{\ignorespaces The Activities tab displays the actions taken on the wiki page.}}{754}{figure.220.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {220.3}Incoming/Outgoing Links}{755}{section.220.3}\protected@file@percent } \newlabel{incomingoutgoing-links}{{220.3}{755}{Incoming/Outgoing Links}{section.220.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {220.4}Attachments}{755}{section.220.4}\protected@file@percent } \newlabel{attachments}{{220.4}{755}{Attachments}{section.220.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {221}Other Wiki Widgets}{757}{chapter.221}\protected@file@percent } \newlabel{other-wiki-widgets}{{221}{757}{Other Wiki Widgets}{chapter.221}{}} \@writefile{toc}{\contentsline {section}{\numberline {221.1}Page Menu}{757}{section.221.1}\protected@file@percent } \newlabel{page-menu}{{221.1}{757}{Page Menu}{section.221.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {221.1}{\ignorespaces The Page Menu widget displays a wiki page's outgoing links.}}{758}{figure.221.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {221.2}Tree Menu}{758}{section.221.2}\protected@file@percent } \newlabel{tree-menu}{{221.2}{758}{Tree Menu}{section.221.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {221.2}{\ignorespaces The Tree Menu widget displays a wiki node's hierarchy to the configured depth.}}{758}{figure.221.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {221.3}Wiki Display}{759}{section.221.3}\protected@file@percent } \newlabel{wiki-display}{{221.3}{759}{Wiki Display}{section.221.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {222}Sending Alerts and Announcements}{761}{chapter.222}\protected@file@percent } \newlabel{sending-alerts-and-announcements}{{222}{761}{Sending Alerts and Announcements}{chapter.222}{}} \@writefile{toc}{\contentsline {section}{\numberline {222.1}Creating Alerts and Announcements}{761}{section.222.1}\protected@file@percent } \newlabel{creating-alerts-and-announcements}{{222.1}{761}{Creating Alerts and Announcements}{section.222.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {222.1}{\ignorespaces The Alerts widget provides administrators with an easy way to communicate important information to appropriate groups of users.}}{762}{figure.222.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {222.2}{\ignorespaces Enter your alert or announcement's title and content.}}{763}{figure.222.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {222.2}User Configuration}{763}{section.222.2}\protected@file@percent } \newlabel{user-configuration}{{222.2}{763}{User Configuration}{section.222.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {222.3}{\ignorespaces Configure your new alert or announcement.}}{764}{figure.222.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {222.4}{\ignorespaces Each user can choose how they receive alerts and announcements.}}{765}{figure.222.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {222.3}Alert and Announcement Roles}{765}{section.222.3}\protected@file@percent } \newlabel{alert-and-announcement-roles}{{222.3}{765}{Alert and Announcement Roles}{section.222.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {223}Managing Notifications and Requests}{767}{chapter.223}\protected@file@percent } \newlabel{managing-notifications-and-requests}{{223}{767}{Managing Notifications and Requests}{chapter.223}{}} \@writefile{lof}{\contentsline {figure}{\numberline {223.1}{\ignorespaces The \emph {Notifications List} section displays all your notifications in a paginated list.}}{767}{figure.223.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {223.1}Managing Notifications}{767}{section.223.1}\protected@file@percent } \newlabel{managing-notifications}{{223.1}{767}{Managing Notifications}{section.223.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {223.2}Managing Multiple Notifications}{768}{section.223.2}\protected@file@percent } \newlabel{managing-multiple-notifications}{{223.2}{768}{Managing Multiple Notifications}{section.223.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {223.3}Managing Requests}{768}{section.223.3}\protected@file@percent } \newlabel{managing-requests}{{223.3}{768}{Managing Requests}{section.223.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {224}Using the Knowledge Base}{769}{chapter.224}\protected@file@percent } \newlabel{using-the-knowledge-base}{{224}{769}{Using the Knowledge Base}{chapter.224}{}} \@writefile{lof}{\contentsline {figure}{\numberline {224.1}{\ignorespaces Knowledge Base Display's navigation and viewing provide a great reading experience.}}{770}{figure.224.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {225}Creating Knowledge Base Articles}{771}{chapter.225}\protected@file@percent } \newlabel{creating-knowledge-base-articles}{{225}{771}{Creating Knowledge Base Articles}{chapter.225}{}} \@writefile{toc}{\contentsline {section}{\numberline {225.1}Authoring Articles in the Editor}{771}{section.225.1}\protected@file@percent } \newlabel{authoring-articles-in-the-editor}{{225.1}{771}{Authoring Articles in the Editor}{section.225.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {225.1}{\ignorespaces The Knowledge Base app in Site Administration lets you create Knowledge Base articles.}}{772}{figure.225.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {225.2}{\ignorespaces You can create and modify a Knowledge Base article's content using the WYSIWYG editor.}}{773}{figure.225.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {225.2}Importing Knowledge Base Articles}{773}{section.225.2}\protected@file@percent } \newlabel{importing-knowledge-base-articles}{{225.2}{773}{Importing Knowledge Base Articles}{section.225.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {225.3}{\ignorespaces You can import ZIP files that contain Knowledge Base articles in Markdown format.}}{774}{figure.225.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {226}Managing the Knowledge Base}{775}{chapter.226}\protected@file@percent } \newlabel{managing-the-knowledge-base}{{226}{775}{Managing the Knowledge Base}{chapter.226}{}} \@writefile{toc}{\contentsline {section}{\numberline {226.1}Setting the Knowledge Base's Options}{775}{section.226.1}\protected@file@percent } \newlabel{setting-the-knowledge-bases-options}{{226.1}{775}{Setting the Knowledge Base's Options}{section.226.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {226.1}{\ignorespaces You can manage Knowledge Base articles, folders, and suggestions.}}{776}{figure.226.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {226.2}{\ignorespaces The Knowledge Base App's options.}}{776}{figure.226.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {226.2}Managing Knowledge Base Articles}{777}{section.226.2}\protected@file@percent } \newlabel{managing-knowledge-base-articles}{{226.2}{777}{Managing Knowledge Base Articles}{section.226.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {226.3}{\ignorespaces This screenshot uses a red box to highlight the text that indicates the current position in the folder hierarchy.}}{777}{figure.226.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {227}Knowledge Base Templates}{779}{chapter.227}\protected@file@percent } \newlabel{knowledge-base-templates}{{227}{779}{Knowledge Base Templates}{chapter.227}{}} \@writefile{lof}{\contentsline {figure}{\numberline {227.1}{\ignorespaces The Knowledge Base app's Templates tab.}}{779}{figure.227.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {227.1}Creating Templates}{780}{section.227.1}\protected@file@percent } \newlabel{creating-templates}{{227.1}{780}{Creating Templates}{section.227.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {227.2}{\ignorespaces The New Template form.}}{780}{figure.227.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {227.2}Managing Templates}{780}{section.227.2}\protected@file@percent } \newlabel{managing-templates}{{227.2}{780}{Managing Templates}{section.227.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {228}Responding to Knowledge Base Feedback}{781}{chapter.228}\protected@file@percent } \newlabel{responding-to-knowledge-base-feedback}{{228}{781}{Responding to Knowledge Base Feedback}{chapter.228}{}} \@writefile{lof}{\contentsline {figure}{\numberline {228.1}{\ignorespaces The Suggestions tab in Knowledge Base displays each piece of feedback that users leave on Knowledge Base articles.}}{781}{figure.228.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {229}Knowledge Base Display}{783}{chapter.229}\protected@file@percent } \newlabel{knowledge-base-display}{{229}{783}{Knowledge Base Display}{chapter.229}{}} \@writefile{lof}{\contentsline {figure}{\numberline {229.1}{\ignorespaces Select the article or folder of articles that the Knowledge Base Display widget displays.}}{784}{figure.229.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {229.2}{\ignorespaces Knowledge Base Display's navigation and viewing provide a great reading experience.}}{785}{figure.229.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {229.1}Displaying Different Article Sets}{785}{section.229.1}\protected@file@percent } \newlabel{displaying-different-article-sets}{{229.1}{785}{Displaying Different Article Sets}{section.229.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {229.3}{\ignorespaces Knowledge Base Display's content folder feature lets users switch between different sets of articles.}}{786}{figure.229.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {230}Other Knowledge Base Widgets}{787}{chapter.230}\protected@file@percent } \newlabel{other-knowledge-base-widgets}{{230}{787}{Other Knowledge Base Widgets}{chapter.230}{}} \@writefile{toc}{\contentsline {section}{\numberline {230.1}Knowledge Base Article}{787}{section.230.1}\protected@file@percent } \newlabel{knowledge-base-article}{{230.1}{787}{Knowledge Base Article}{section.230.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {230.1}{\ignorespaces The Knowledge Base Article app is great at displaying individual articles.}}{788}{figure.230.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {230.2}Knowledge Base Section}{788}{section.230.2}\protected@file@percent } \newlabel{knowledge-base-section}{{230.2}{788}{Knowledge Base Section}{section.230.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {230.2}{\ignorespaces The Knowledge Base Section widget.}}{788}{figure.230.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {230.3}Knowledge Base Search}{789}{section.230.3}\protected@file@percent } \newlabel{knowledge-base-search}{{230.3}{789}{Knowledge Base Search}{section.230.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {230.3}{\ignorespaces The Knowledge Base Search widget lets you search the Knowledge Base for keywords.}}{790}{figure.230.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {231}Importing Knowledge Base Articles}{791}{chapter.231}\protected@file@percent } \newlabel{importing-knowledge-base-articles-1}{{231}{791}{Importing Knowledge Base Articles}{chapter.231}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {232}Knowledge Base ZIP File Requirements}{793}{chapter.232}\protected@file@percent } \newlabel{knowledge-base-zip-file-requirements}{{232}{793}{Knowledge Base ZIP File Requirements}{chapter.232}{}} \@writefile{lof}{\contentsline {figure}{\numberline {232.1}{\ignorespaces Selecting \emph {Add} → \emph {Import} in Knowledge Base brings up the interface for selecting a ZIP file of Markdown source files and images to produce and update articles in your Knowledge Base.}}{796}{figure.232.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {233}Knowledge Base Importer FAQs}{797}{chapter.233}\protected@file@percent } \newlabel{knowledge-base-importer-faqs}{{233}{797}{Knowledge Base Importer FAQs}{chapter.233}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {234}Knowledge Base System Settings}{799}{chapter.234}\protected@file@percent } \newlabel{knowledge-base-system-settings}{{234}{799}{Knowledge Base System Settings}{chapter.234}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.1}Source URL Settings}{799}{section.234.1}\protected@file@percent } \newlabel{source-url-settings}{{234.1}{799}{Source URL Settings}{section.234.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.2}Importer File Convention Settings}{800}{section.234.2}\protected@file@percent } \newlabel{importer-file-convention-settings}{{234.2}{800}{Importer File Convention Settings}{section.234.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {234.3}Section Names Setting}{801}{section.234.3}\protected@file@percent } \newlabel{section-names-setting}{{234.3}{801}{Section Names Setting}{section.234.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {234.1}{\ignorespaces Create the sections you want to use with the Knowledge Base Section widget.}}{801}{figure.234.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {235}Inviting Members to Your Site}{803}{chapter.235}\protected@file@percent } \newlabel{inviting-members-to-your-site}{{235}{803}{Inviting Members to Your Site}{chapter.235}{}} \@writefile{lof}{\contentsline {figure}{\numberline {235.1}{\ignorespaces You can invite users by clicking the add sign next to the user's name.}}{804}{figure.235.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {235.2}{\ignorespaces You can confirm or ignore the invitation.}}{804}{figure.235.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {236}Assets}{805}{chapter.236}\protected@file@percent } \newlabel{assets}{{236}{805}{Assets}{chapter.236}{}} \@writefile{lof}{\contentsline {figure}{\numberline {236.1}{\ignorespaces The tags \emph {freight car} and \emph {electric locomotive} were automatically applied to this image.}}{806}{figure.236.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {237}Configuring Asset Auto Tagging}{807}{chapter.237}\protected@file@percent } \newlabel{configuring-asset-auto-tagging}{{237}{807}{Configuring Asset Auto Tagging}{chapter.237}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.1}Configuration Levels}{807}{section.237.1}\protected@file@percent } \newlabel{configuration-levels}{{237.1}{807}{Configuration Levels}{section.237.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {237.2}Global Configuration}{808}{section.237.2}\protected@file@percent } \newlabel{global-configuration-1}{{237.2}{808}{Global Configuration}{section.237.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {237.1}{\ignorespaces You can configure auto tagging globally in the Assets section of System Settings.}}{808}{figure.237.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {237.3}Instance-level Configuration}{808}{section.237.3}\protected@file@percent } \newlabel{instance-level-configuration}{{237.3}{808}{Instance-level Configuration}{section.237.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {237.2}{\ignorespaces You can also configure auto tagging for each instance.}}{809}{figure.237.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {237.4}Site-level Configuration}{809}{section.237.4}\protected@file@percent } \newlabel{site-level-configuration}{{237.4}{809}{Site-level Configuration}{section.237.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {237.3}{\ignorespaces You can enable or disable auto-tagging for a site.}}{810}{figure.237.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {238}Auto Tagging Images}{811}{chapter.238}\protected@file@percent } \newlabel{auto-tagging-images}{{238}{811}{Auto Tagging Images}{chapter.238}{}} \@writefile{toc}{\contentsline {section}{\numberline {238.1}Configuring TensorFlow Image Auto Tagging}{811}{section.238.1}\protected@file@percent } \newlabel{configuring-tensorflow-image-auto-tagging}{{238.1}{811}{Configuring TensorFlow Image Auto Tagging}{section.238.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {238.1}{\ignorespaces The tags \emph {freight car} and \emph {electric locomotive} were automatically applied to this image.}}{812}{figure.238.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {238.2}{\ignorespaces Configure TensorFlow image auto-tagging for your portal instances.}}{813}{figure.238.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {238.2}Configuring Google Cloud Vision}{813}{section.238.2}\protected@file@percent } \newlabel{configuring-google-cloud-vision}{{238.2}{813}{Configuring Google Cloud Vision}{section.238.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {238.3}{\ignorespaces You can fine tune the process that runs the TensorFlow image auto tagging in the portal.}}{814}{figure.238.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {238.3}Configuring Microsoft Cognitive Services}{814}{section.238.3}\protected@file@percent } \newlabel{configuring-microsoft-cognitive-services}{{238.3}{814}{Configuring Microsoft Cognitive Services}{section.238.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {238.4}{\ignorespaces The Google Cloud Vision provider requires an API key.}}{815}{figure.238.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {238.5}{\ignorespaces The Microsoft Cognitive Services provider requires an API key and an endpoint.}}{816}{figure.238.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {239}Auto Tagging Text}{817}{chapter.239}\protected@file@percent } \newlabel{auto-tagging-text}{{239}{817}{Auto Tagging Text}{chapter.239}{}} \@writefile{toc}{\contentsline {section}{\numberline {239.1}Configuring Google Cloud Natural Language Text Auto Tagging}{818}{section.239.1}\protected@file@percent } \newlabel{configuring-google-cloud-natural-language-text-auto-tagging}{{239.1}{818}{Configuring Google Cloud Natural Language Text Auto Tagging}{section.239.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {239.2}Configuring OpenNLP Text Auto Tagging}{818}{section.239.2}\protected@file@percent } \newlabel{configuring-opennlp-text-auto-tagging}{{239.2}{818}{Configuring OpenNLP Text Auto Tagging}{section.239.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {239.1}{\ignorespaces Configure Google Cloud Natural Language text auto tagging for your portal instances.}}{819}{figure.239.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {239.2}{\ignorespaces Configure OpenNLP text auto tagging for your portal instances.}}{820}{figure.239.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {240}Creating A Social Network}{821}{chapter.240}\protected@file@percent } \newlabel{creating-a-social-network}{{240}{821}{Creating A Social Network}{chapter.240}{}} \@writefile{lof}{\contentsline {figure}{\numberline {240.1}{\ignorespaces The Activities widget shows information about asset-related user activity in the current Site.}}{821}{figure.240.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {240.2}{\ignorespaces Users get a notification that lets them respond to connection requests.}}{822}{figure.240.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {240.3}{\ignorespaces The Contacts Center widget lets users make connections.}}{822}{figure.240.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {241}Using the Activities Widget}{823}{chapter.241}\protected@file@percent } \newlabel{using-the-activities-widget}{{241}{823}{Using the Activities Widget}{chapter.241}{}} \@writefile{lof}{\contentsline {figure}{\numberline {241.1}{\ignorespaces The Activities widget shows information about asset-related user activity in the current Site.}}{823}{figure.241.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {242}Connecting Users}{825}{chapter.242}\protected@file@percent } \newlabel{connecting-users}{{242}{825}{Connecting Users}{chapter.242}{}} \@writefile{lof}{\contentsline {figure}{\numberline {242.1}{\ignorespaces The Contacts Center widget lets users make connections.}}{825}{figure.242.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {242.2}{\ignorespaces Users get a notification that lets them respond to connection requests.}}{826}{figure.242.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {243}Exporting Widgets To Other Websites}{827}{chapter.243}\protected@file@percent } \newlabel{exporting-widgets-to-other-websites}{{243}{827}{Exporting Widgets To Other Websites}{chapter.243}{}} \@writefile{toc}{\contentsline {section}{\numberline {243.1}Sharing OpenSocial Gadgets}{827}{section.243.1}\protected@file@percent } \newlabel{sharing-opensocial-gadgets}{{243.1}{827}{Sharing OpenSocial Gadgets}{section.243.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {243.1}{\ignorespaces You can share widgets via OpenSocial.}}{828}{figure.243.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {244}Integrating with Facebook}{829}{chapter.244}\protected@file@percent } \newlabel{integrating-with-facebook}{{244}{829}{Integrating with Facebook}{chapter.244}{}} \@writefile{toc}{\contentsline {section}{\numberline {244.1}Facebook Sign On}{829}{section.244.1}\protected@file@percent } \newlabel{facebook-sign-on}{{244.1}{829}{Facebook Sign On}{section.244.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {244.2}Using Your Widget as Facebook Applications}{829}{section.244.2}\protected@file@percent } \newlabel{using-your-widget-as-facebook-applications}{{244.2}{829}{Using Your Widget as Facebook Applications}{section.244.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {245}Using Social Bookmarks}{831}{chapter.245}\protected@file@percent } \newlabel{using-social-bookmarks}{{245}{831}{Using Social Bookmarks}{chapter.245}{}} \@writefile{lof}{\contentsline {figure}{\numberline {245.1}{\ignorespaces The default social bookmarks appear in a menu below content.}}{831}{figure.245.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {246}Search}{833}{chapter.246}\protected@file@percent } \newlabel{search}{{246}{833}{Search}{chapter.246}{}} \@writefile{lof}{\contentsline {figure}{\numberline {246.1}{\ignorespaces The Type Facet configuration lists the searchable out-of-the-box asset types.}}{833}{figure.246.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {246.1}Elasticsearch}{833}{section.246.1}\protected@file@percent } \newlabel{elasticsearch}{{246.1}{833}{Elasticsearch}{section.246.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {246.2}Search Features}{834}{section.246.2}\protected@file@percent } \newlabel{search-features}{{246.2}{834}{Search Features}{section.246.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {246.2}{\ignorespaces There's a search bar embedded on all pages by default.}}{834}{figure.246.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {246.3}{\ignorespaces Results are displayed in the Search Results portlet.}}{834}{figure.246.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {246.3}Search Bar}{835}{section.246.3}\protected@file@percent } \newlabel{search-bar}{{246.3}{835}{Search Bar}{section.246.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {246.4}Search Results and Relevance}{835}{section.246.4}\protected@file@percent } \newlabel{search-results-and-relevance}{{246.4}{835}{Search Results and Relevance}{section.246.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {246.5}Search Facets}{835}{section.246.5}\protected@file@percent } \newlabel{search-facets}{{246.5}{835}{Search Facets}{section.246.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {246.4}{\ignorespaces \emph {Site} and \emph {Type} are two of the facet sets you'll encounter. They let you drill down to results that contain the search terms you entered.}}{836}{figure.246.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {247}What's New in Search}{837}{chapter.247}\protected@file@percent } \newlabel{whats-new-in-search}{{247}{837}{What's New in Search}{chapter.247}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.1}New and Improved Widgets}{837}{section.247.1}\protected@file@percent } \newlabel{new-and-improved-widgets}{{247.1}{837}{New and Improved Widgets}{section.247.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.2}Custom Filter}{837}{section.247.2}\protected@file@percent } \newlabel{custom-filter}{{247.2}{837}{Custom Filter}{section.247.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.3}Sort}{837}{section.247.3}\protected@file@percent } \newlabel{sort}{{247.3}{837}{Sort}{section.247.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.4}Search Insights}{838}{section.247.4}\protected@file@percent } \newlabel{search-insights}{{247.4}{838}{Search Insights}{section.247.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.5}New Search Admin Functionality}{838}{section.247.5}\protected@file@percent } \newlabel{new-search-admin-functionality}{{247.5}{838}{New Search Admin Functionality}{section.247.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.6}Search Engine Info}{838}{section.247.6}\protected@file@percent } \newlabel{search-engine-info}{{247.6}{838}{Search Engine Info}{section.247.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.7}Field Mappings}{838}{section.247.7}\protected@file@percent } \newlabel{field-mappings}{{247.7}{838}{Field Mappings}{section.247.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.8}Indexing Progress}{839}{section.247.8}\protected@file@percent } \newlabel{indexing-progress}{{247.8}{839}{Indexing Progress}{section.247.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.9}New System Settings}{839}{section.247.9}\protected@file@percent } \newlabel{new-system-settings}{{247.9}{839}{New System Settings}{section.247.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.10}New Infrastructure}{839}{section.247.10}\protected@file@percent } \newlabel{new-infrastructure}{{247.10}{839}{New Infrastructure}{section.247.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.11}Elasticsearch Support}{839}{section.247.11}\protected@file@percent } \newlabel{elasticsearch-support}{{247.11}{839}{Elasticsearch Support}{section.247.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.12}Application-Specific Indexes}{839}{section.247.12}\protected@file@percent } \newlabel{application-specific-indexes}{{247.12}{839}{Application-Specific Indexes}{section.247.12}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.13}API Enhancements}{840}{section.247.13}\protected@file@percent } \newlabel{api-enhancements}{{247.13}{840}{API Enhancements}{section.247.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {247.14}Multi-Language Search}{840}{section.247.14}\protected@file@percent } \newlabel{multi-language-search}{{247.14}{840}{Multi-Language Search}{section.247.14}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {248}Configuring Search Pages}{841}{chapter.248}\protected@file@percent } \newlabel{configuring-search-pages}{{248}{841}{Configuring Search Pages}{chapter.248}{}} \@writefile{toc}{\contentsline {section}{\numberline {248.1}Search Page Templates}{841}{section.248.1}\protected@file@percent } \newlabel{search-page-templates}{{248.1}{841}{Search Page Templates}{section.248.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {248.1}{\ignorespaces At first glance, not much is happening on the search page. But, there's more than meets the eye.}}{842}{figure.248.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {248.2}Default Search Pages}{842}{section.248.2}\protected@file@percent } \newlabel{default-search-pages}{{248.2}{842}{Default Search Pages}{section.248.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {248.2}{\ignorespaces By default, the embedded search bar points to the pre-configured \texttt {/search} destination page.}}{843}{figure.248.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {248.3}{\ignorespaces The default page is pre-configured with the Search Results widget and the various Facet widgets to provide a full search experience.}}{843}{figure.248.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {248.4}{\ignorespaces Configure the Search page. By default, it doesn't inherit changes from the page template.}}{844}{figure.248.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {248.3}Manual Search Page Configuration}{845}{section.248.3}\protected@file@percent } \newlabel{manual-search-page-configuration}{{248.3}{845}{Manual Search Page Configuration}{section.248.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {248.5}{\ignorespaces The search bar is only visible if it points to an existent page.}}{845}{figure.248.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {248.6}{\ignorespaces There's a handy page template for creating search pages.}}{845}{figure.248.6}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {248.4}Legacy Search Experience}{846}{section.248.4}\protected@file@percent } \newlabel{legacy-search-experience}{{248.4}{846}{Legacy Search Experience}{section.248.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {249}Searching for Assets}{847}{chapter.249}\protected@file@percent } \newlabel{searching-for-assets}{{249}{847}{Searching for Assets}{chapter.249}{}} \@writefile{toc}{\contentsline {section}{\numberline {249.1}Search Bar}{847}{section.249.1}\protected@file@percent } \newlabel{search-bar-1}{{249.1}{847}{Search Bar}{section.249.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {249.1}{\ignorespaces The default search configuration displays a search bar in its default view, beckoning users to enter the search context.}}{847}{figure.249.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {249.2}Entering Search Terms}{848}{section.249.2}\protected@file@percent } \newlabel{entering-search-terms}{{249.2}{848}{Entering Search Terms}{section.249.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {249.2}{\ignorespaces Search for text in a specific field using Elasticsearch's Query String syntax.}}{848}{figure.249.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {249.3}Matching Exact Phrases: Quoted Search}{848}{section.249.3}\protected@file@percent } \newlabel{matching-exact-phrases-quoted-search}{{249.3}{848}{Matching Exact Phrases: Quoted Search}{section.249.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {249.4}Prefix Searching}{848}{section.249.4}\protected@file@percent } \newlabel{prefix-searching}{{249.4}{848}{Prefix Searching}{section.249.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {249.3}{\ignorespaces Search for exact phrase matches by enclosing search terms in quotes. If a user searched for \emph {``agile frameworks''}, this result would not be returned.}}{849}{figure.249.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {249.4}{\ignorespaces Searching for \emph {lever} also returns \emph {leverage} and \emph {leveraging}.}}{849}{figure.249.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {249.5}Configuring the Search Bar}{849}{section.249.5}\protected@file@percent } \newlabel{configuring-the-search-bar}{{249.5}{849}{Configuring the Search Bar}{section.249.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {249.5}{\ignorespaces Configure the search bar behavior in its configuration screen.}}{850}{figure.249.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {249.6}{\ignorespaces Let the user choose which scope the search is executed for.}}{851}{figure.249.6}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {249.6}Search Suggestions}{851}{section.249.6}\protected@file@percent } \newlabel{search-suggestions}{{249.6}{851}{Search Suggestions}{section.249.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {249.7}{\ignorespaces Configure the suggestion settings to allow for user input mistakes and help lead users to results.}}{852}{figure.249.7}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {250}Facets}{853}{chapter.250}\protected@file@percent } \newlabel{facets}{{250}{853}{Facets}{chapter.250}{}} \@writefile{lof}{\contentsline {figure}{\numberline {250.1}{\ignorespaces \emph {Site} and \emph {Type} are two of the facet sets you'll encounter.}}{853}{figure.250.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {250.1}Using Facets}{854}{section.250.1}\protected@file@percent } \newlabel{using-facets}{{250.1}{854}{Using Facets}{section.250.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {250.2}{\ignorespaces When presented lots of search results, facets narrow down the results list so users can find relevant content.}}{854}{figure.250.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {250.2}Multiple Facet Selection}{855}{section.250.2}\protected@file@percent } \newlabel{multiple-facet-selection}{{250.2}{855}{Multiple Facet Selection}{section.250.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {250.3}{\ignorespaces Facet terms are additive when applied in the same facet. Any Blogs Entry OR Web Content article matching the keyword is shown here.}}{855}{figure.250.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {250.4}{\ignorespaces Facet terms selected from different facets are exclusive. These results must be of type Blogs Entry AND be from the User Marvin Smart.}}{856}{figure.250.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {250.3}Facets and Friendly URLs}{856}{section.250.3}\protected@file@percent } \newlabel{facets-and-friendly-urls}{{250.3}{856}{Facets and Friendly URLs}{section.250.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {250.5}{\ignorespaces Both intra-facet and inter-facet selection is possible.}}{857}{figure.250.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {251}Site Facet}{859}{chapter.251}\protected@file@percent } \newlabel{site-facet}{{251}{859}{Site Facet}{chapter.251}{}} \@writefile{lof}{\contentsline {figure}{\numberline {251.1}{\ignorespaces Each Site with matching content is a facet term.}}{859}{figure.251.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {251.2}{\ignorespaces The Site Facet is configurable.}}{860}{figure.251.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {252}Type Facet}{861}{chapter.252}\protected@file@percent } \newlabel{type-facet}{{252}{861}{Type Facet}{chapter.252}{}} \@writefile{lof}{\contentsline {figure}{\numberline {252.1}{\ignorespaces Each Asset Type with matching content is a Type Facet term.}}{861}{figure.252.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {252.2}{\ignorespaces The Type Facet is configurable.}}{862}{figure.252.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {253}Tag and Category Facets}{863}{chapter.253}\protected@file@percent } \newlabel{tag-and-category-facets}{{253}{863}{Tag and Category Facets}{chapter.253}{}} \@writefile{lof}{\contentsline {figure}{\numberline {253.1}{\ignorespaces Each Tag or Category with matching content is a facet term.}}{863}{figure.253.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {253.2}{\ignorespaces Tag and Category Facets are configurable.}}{864}{figure.253.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {254}Folder Facet}{865}{chapter.254}\protected@file@percent } \newlabel{folder-facet}{{254}{865}{Folder Facet}{chapter.254}{}} \@writefile{lof}{\contentsline {figure}{\numberline {254.1}{\ignorespaces Each Folder with matching content is a facet term.}}{865}{figure.254.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {254.2}{\ignorespaces The Folder Facet is configurable.}}{866}{figure.254.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {255}User Facet}{867}{chapter.255}\protected@file@percent } \newlabel{user-facet}{{255}{867}{User Facet}{chapter.255}{}} \@writefile{lof}{\contentsline {figure}{\numberline {255.1}{\ignorespaces Each User with matching content is a facet term.}}{867}{figure.255.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {255.2}{\ignorespaces The User Facet is configurable.}}{868}{figure.255.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {256}Modified Facet}{869}{chapter.256}\protected@file@percent } \newlabel{modified-facet}{{256}{869}{Modified Facet}{chapter.256}{}} \@writefile{lof}{\contentsline {figure}{\numberline {256.1}{\ignorespaces Each time period with matching content is a facet term.}}{869}{figure.256.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {256.2}{\ignorespaces Users can include a Custom Range in the Modified Facet.}}{870}{figure.256.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {256.3}{\ignorespaces The time ranges are set in the facet's configuration.}}{870}{figure.256.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {257}Custom Facet}{873}{chapter.257}\protected@file@percent } \newlabel{custom-facet}{{257}{873}{Custom Facet}{chapter.257}{}} \@writefile{lof}{\contentsline {figure}{\numberline {257.1}{\ignorespaces Custom Facets must be configured first.}}{873}{figure.257.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {257.2}{\ignorespaces Configure a Custom Facet in no time.}}{874}{figure.257.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {257.1}Finding Indexed Fields}{874}{section.257.1}\protected@file@percent } \newlabel{finding-indexed-fields}{{257.1}{874}{Finding Indexed Fields}{section.257.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {258}Search Results}{877}{chapter.258}\protected@file@percent } \newlabel{search-results}{{258}{877}{Search Results}{chapter.258}{}} \@writefile{lof}{\contentsline {figure}{\numberline {258.1}{\ignorespaces The goal is to return the perfect results to Users searching your site.}}{877}{figure.258.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {259}Display Settings}{879}{chapter.259}\protected@file@percent } \newlabel{display-settings}{{259}{879}{Display Settings}{chapter.259}{}} \@writefile{lof}{\contentsline {figure}{\numberline {259.1}{\ignorespaces Viewing a results document lets you inspect exactly what's being indexed for a particular asset. This is just a small portion of one document.}}{880}{figure.259.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {259.2}{\ignorespaces The number of results per page and the URL parameter names used to control pagination behavior are configurable.}}{881}{figure.259.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {260}Filtering Search Results with the Custom Filter Widget}{883}{chapter.260}\protected@file@percent } \newlabel{filtering-search-results-with-the-custom-filter-widget}{{260}{883}{Filtering Search Results with the Custom Filter Widget}{chapter.260}{}} \@writefile{toc}{\contentsline {section}{\numberline {260.1}Adding and Configuring Custom Filters}{883}{section.260.1}\protected@file@percent } \newlabel{adding-and-configuring-custom-filters}{{260.1}{883}{Adding and Configuring Custom Filters}{section.260.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {260.1}{\ignorespaces A custom filter has no impact until it's configured.}}{884}{figure.260.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {260.2}Custom Filter Configuration Options}{884}{section.260.2}\protected@file@percent } \newlabel{custom-filter-configuration-options}{{260.2}{884}{Custom Filter Configuration Options}{section.260.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {260.2}{\ignorespaces Once the Custom Filter is added to the page, mold it like soft clay into the beautiful sculpture you've envisioned.}}{885}{figure.260.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {261}Sorting Search Results with the Sort Widget}{887}{chapter.261}\protected@file@percent } \newlabel{sorting-search-results-with-the-sort-widget}{{261}{887}{Sorting Search Results with the Sort Widget}{chapter.261}{}} \@writefile{toc}{\contentsline {section}{\numberline {261.1}Adding and Configuring the Sort Widget}{887}{section.261.1}\protected@file@percent } \newlabel{adding-and-configuring-the-sort-widget}{{261.1}{887}{Adding and Configuring the Sort Widget}{section.261.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {261.1}{\ignorespaces Users can re-order search results with the Sort widget.}}{888}{figure.261.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {261.2}Configuring the Sort Widget}{888}{section.261.2}\protected@file@percent } \newlabel{configuring-the-sort-widget}{{261.2}{888}{Configuring the Sort Widget}{section.261.2}{}} \@writefile{toc}{\contentsline {subsection}{Finding Sortable Fields}{888}{figure.261.2}\protected@file@percent } \newlabel{finding-sortable-fields}{{261.2}{888}{Finding Sortable Fields}{figure.261.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {261.2}{\ignorespaces From the Sort widget's configuration, add, edit, or remove Sort options.}}{889}{figure.261.2}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Adding New Sort Options}{890}{figure.261.2}\protected@file@percent } \newlabel{adding-new-sort-options}{{261.2}{890}{Adding New Sort Options}{figure.261.2}{}} \@writefile{toc}{\contentsline {subsection}{Editing and Deleting Sort Options}{890}{figure.261.2}\protected@file@percent } \newlabel{editing-and-deleting-sort-options}{{261.2}{890}{Editing and Deleting Sort Options}{figure.261.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {261.3}Controlling the Sort Order}{890}{section.261.3}\protected@file@percent } \newlabel{controlling-the-sort-order}{{261.3}{890}{Controlling the Sort Order}{section.261.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {262}Search Results Behavior}{891}{chapter.262}\protected@file@percent } \newlabel{search-results-behavior}{{262}{891}{Search Results Behavior}{chapter.262}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.1}Filtering Results with Facets}{891}{section.262.1}\protected@file@percent } \newlabel{filtering-results-with-facets}{{262.1}{891}{Filtering Results with Facets}{section.262.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.2}Search Results Relevance}{891}{section.262.2}\protected@file@percent } \newlabel{search-results-relevance}{{262.2}{891}{Search Results Relevance}{section.262.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.3}Permissions and Search Results}{892}{section.262.3}\protected@file@percent } \newlabel{permissions-and-search-results}{{262.3}{892}{Permissions and Search Results}{section.262.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.4}Initial Permissions Checking}{892}{section.262.4}\protected@file@percent } \newlabel{initial-permissions-checking}{{262.4}{892}{Initial Permissions Checking}{section.262.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.5}Final Permissions Checking}{892}{section.262.5}\protected@file@percent } \newlabel{final-permissions-checking}{{262.5}{892}{Final Permissions Checking}{section.262.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.6}Search and Staging}{893}{section.262.6}\protected@file@percent } \newlabel{search-and-staging}{{262.6}{893}{Search and Staging}{section.262.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {262.7}Result Summaries}{893}{section.262.7}\protected@file@percent } \newlabel{result-summaries}{{262.7}{893}{Result Summaries}{section.262.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {262.1}{\ignorespaces User summaries contain only the User's full name.}}{894}{figure.262.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {262.2}{\ignorespaces Documents and Media and Web Content folders include titles and descriptions in their summaries.}}{894}{figure.262.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {262.8}Highlighting}{894}{section.262.8}\protected@file@percent } \newlabel{highlighting}{{262.8}{894}{Highlighting}{section.262.8}{}} \@writefile{lof}{\contentsline {figure}{\numberline {262.3}{\ignorespaces Some document summaries have lots of highlights if the search term matches text that appears in the summary.}}{894}{figure.262.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {262.4}{\ignorespaces Results that match the search term won't always have highlights.}}{895}{figure.262.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {263}Search Insights}{897}{chapter.263}\protected@file@percent } \newlabel{search-insights-1}{{263}{897}{Search Insights}{chapter.263}{}} \@writefile{toc}{\contentsline {section}{\numberline {263.1}Inspecting The Search Query String}{897}{section.263.1}\protected@file@percent } \newlabel{inspecting-the-search-query-string}{{263.1}{897}{Inspecting The Search Query String}{section.263.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {263.1}{\ignorespaces The Search Insights widget is helpful during testing and development.}}{898}{figure.263.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {263.2}{\ignorespaces The full query string isn't for the faint of heart. This example is clipped to spare the reader.}}{898}{figure.263.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {263.2}Explaining Search Results}{898}{section.263.2}\protected@file@percent } \newlabel{explaining-search-results}{{263.2}{898}{Explaining Search Results}{section.263.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {264}Searching for Localized Content}{901}{chapter.264}\protected@file@percent } \newlabel{searching-for-localized-content}{{264}{901}{Searching for Localized Content}{chapter.264}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.1}What is Localized Search?}{902}{section.264.1}\protected@file@percent } \newlabel{what-is-localized-search}{{264.1}{902}{What is Localized Search?}{section.264.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.2}Fully Localized}{902}{section.264.2}\protected@file@percent } \newlabel{fully-localized}{{264.2}{902}{Fully Localized}{section.264.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.3}Fully Localized for Search}{902}{section.264.3}\protected@file@percent } \newlabel{fully-localized-for-search}{{264.3}{902}{Fully Localized for Search}{section.264.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.4}Site-Localized Search}{902}{section.264.4}\protected@file@percent } \newlabel{site-localized-search}{{264.4}{902}{Site-Localized Search}{section.264.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.5}Assets Supporting Localized Search}{903}{section.264.5}\protected@file@percent } \newlabel{assets-supporting-localized-search}{{264.5}{903}{Assets Supporting Localized Search}{section.264.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.6}Web Content Articles:}{903}{section.264.6}\protected@file@percent } \newlabel{web-content-articles}{{264.6}{903}{Web Content Articles:}{section.264.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.7}Categories:}{903}{section.264.7}\protected@file@percent } \newlabel{categories}{{264.7}{903}{Categories:}{section.264.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.8}Documents and Media File Entries:}{904}{section.264.8}\protected@file@percent } \newlabel{documents-and-media-file-entries}{{264.8}{904}{Documents and Media File Entries:}{section.264.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.9}Dynamic Data Mapping Fields:}{904}{section.264.9}\protected@file@percent } \newlabel{dynamic-data-mapping-fields}{{264.9}{904}{Dynamic Data Mapping Fields:}{section.264.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.10}Examples}{904}{section.264.10}\protected@file@percent } \newlabel{examples}{{264.10}{904}{Examples}{section.264.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.11}Fully Localized Search for Web Content Articles}{904}{section.264.11}\protected@file@percent } \newlabel{fully-localized-search-for-web-content-articles}{{264.11}{904}{Fully Localized Search for Web Content Articles}{section.264.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {264.12}Site-Localized Search for Documents and Media}{905}{section.264.12}\protected@file@percent } \newlabel{site-localized-search-for-documents-and-media}{{264.12}{905}{Site-Localized Search for Documents and Media}{section.264.12}{}} \@writefile{lof}{\contentsline {figure}{\numberline {264.1}{\ignorespaces Even though the content of this DM File is written in Portuguese, it was appended with the \emph {en} locale, so it's searchable in an English language site.}}{905}{figure.264.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {264.2}{\ignorespaces The uploaded DM File doesn't appear when the site language is changed, because only fields with the site's locale are searched.}}{906}{figure.264.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {264.3}{\ignorespaces Once the field is reindexed with the site's locale, it can be returned as a search result in the site.}}{906}{figure.264.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {265}Configuring Search}{907}{chapter.265}\protected@file@percent } \newlabel{configuring-search}{{265}{907}{Configuring Search}{chapter.265}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.1}System Scoped Search Configuration}{907}{section.265.1}\protected@file@percent } \newlabel{system-scoped-search-configuration}{{265.1}{907}{System Scoped Search Configuration}{section.265.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.2}Default Keyword Query}{907}{section.265.2}\protected@file@percent } \newlabel{default-keyword-query}{{265.2}{907}{Default Keyword Query}{section.265.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {265.1}{\ignorespaces There are numerous system scoped entries for search in System Settings.}}{908}{figure.265.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {265.3}Default Search Result Permission Filter}{909}{section.265.3}\protected@file@percent } \newlabel{default-search-result-permission-filter}{{265.3}{909}{Default Search Result Permission Filter}{section.265.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.4}Index Status Manager}{909}{section.265.4}\protected@file@percent } \newlabel{index-status-manager}{{265.4}{909}{Index Status Manager}{section.265.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.5}Indexer Writer Helper}{909}{section.265.5}\protected@file@percent } \newlabel{indexer-writer-helper}{{265.5}{909}{Indexer Writer Helper}{section.265.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.6}Index Registry}{910}{section.265.6}\protected@file@percent } \newlabel{index-registry}{{265.6}{910}{Index Registry}{section.265.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.7}Index Query Preprocessor}{910}{section.265.7}\protected@file@percent } \newlabel{index-query-preprocessor}{{265.7}{910}{Index Query Preprocessor}{section.265.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.8}Reindex}{910}{section.265.8}\protected@file@percent } \newlabel{reindex}{{265.8}{910}{Reindex}{section.265.8}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.9}Engine Helper}{911}{section.265.9}\protected@file@percent } \newlabel{engine-helper}{{265.9}{911}{Engine Helper}{section.265.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.10}Permission Checker}{911}{section.265.10}\protected@file@percent } \newlabel{permission-checker}{{265.10}{911}{Permission Checker}{section.265.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.11}Title Field Query Builder}{911}{section.265.11}\protected@file@percent } \newlabel{title-field-query-builder}{{265.11}{911}{Title Field Query Builder}{section.265.11}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.12}Elasticsearch 6}{911}{section.265.12}\protected@file@percent } \newlabel{elasticsearch-6}{{265.12}{911}{Elasticsearch 6}{section.265.12}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.13}Search Web}{912}{section.265.13}\protected@file@percent } \newlabel{search-web}{{265.13}{912}{Search Web}{section.265.13}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.14}Search Administration}{912}{section.265.14}\protected@file@percent } \newlabel{search-administration}{{265.14}{912}{Search Administration}{section.265.14}{}} \@writefile{toc}{\contentsline {subsection}{Index Actions}{912}{section.265.14}\protected@file@percent } \newlabel{index-actions}{{265.14}{912}{Index Actions}{section.265.14}{}} \@writefile{toc}{\contentsline {subsection}{Field Mappings}{913}{section.265.14}\protected@file@percent } \newlabel{field-mappings-1}{{265.14}{913}{Field Mappings}{section.265.14}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.15}Portal Properties}{913}{section.265.15}\protected@file@percent } \newlabel{portal-properties}{{265.15}{913}{Portal Properties}{section.265.15}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.16}Site Scoped Search Configuration}{913}{section.265.16}\protected@file@percent } \newlabel{site-scoped-search-configuration}{{265.16}{913}{Site Scoped Search Configuration}{section.265.16}{}} \@writefile{toc}{\contentsline {section}{\numberline {265.17}Widget Scoped Search Configuration}{914}{section.265.17}\protected@file@percent } \newlabel{widget-scoped-search-configuration}{{265.17}{914}{Widget Scoped Search Configuration}{section.265.17}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {266}Low Level Search Options: Searching Additional or Alternate Indexes}{915}{chapter.266}\protected@file@percent } \newlabel{low-level-search-options-searching-additional-or-alternate-indexes}{{266}{915}{Low Level Search Options: Searching Additional or Alternate Indexes}{chapter.266}{}} \@writefile{toc}{\contentsline {section}{\numberline {266.1}Configuring Low Level Search}{915}{section.266.1}\protected@file@percent } \newlabel{configuring-low-level-search}{{266.1}{915}{Configuring Low Level Search}{section.266.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {266.1}{\ignorespaces The Low Level Options widget has several configuration options.}}{916}{figure.266.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {266.2}Example: Searching an Alternate Index}{917}{section.266.2}\protected@file@percent } \newlabel{example-searching-an-alternate-index}{{266.2}{917}{Example: Searching an Alternate Index}{section.266.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {266.2}{\ignorespaces Configure the search page to search a different index.}}{918}{figure.266.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {267}Search Tuning: Synonym Sets}{919}{chapter.267}\protected@file@percent } \newlabel{search-tuning-synonym-sets}{{267}{919}{Search Tuning: Synonym Sets}{chapter.267}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.1}Requirements and Limitations}{919}{section.267.1}\protected@file@percent } \newlabel{requirements-and-limitations}{{267.1}{919}{Requirements and Limitations}{section.267.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.2}Creating and Managing Synonym Sets}{920}{section.267.2}\protected@file@percent } \newlabel{creating-and-managing-synonym-sets}{{267.2}{920}{Creating and Managing Synonym Sets}{section.267.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {267.1}{\ignorespaces Add as many synonymous keywords to a set as you'd like.}}{920}{figure.267.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {267.2}{\ignorespaces Synonym sets can be managed in bulk.}}{920}{figure.267.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {267.3}Using Synonym Sets}{921}{section.267.3}\protected@file@percent } \newlabel{using-synonym-sets}{{267.3}{921}{Using Synonym Sets}{section.267.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {267.3}{\ignorespaces The Blogs Entry does not contain the word ``rover'' but it can be matched because of a synonym set mapping ``cart'' as its synonym. The synonym is even highlighted.}}{921}{figure.267.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {267.4}Known Issues}{921}{section.267.4}\protected@file@percent } \newlabel{known-issues}{{267.4}{921}{Known Issues}{section.267.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {267.5}Related Resources}{921}{section.267.5}\protected@file@percent } \newlabel{related-resources}{{267.5}{921}{Related Resources}{section.267.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {268}Search Tuning: Customizing Search Results}{923}{chapter.268}\protected@file@percent } \newlabel{search-tuning-customizing-search-results}{{268}{923}{Search Tuning: Customizing Search Results}{chapter.268}{}} \@writefile{lof}{\contentsline {figure}{\numberline {268.1}{\ignorespaces The Lunar Resort wants to tweak these results: pin the Activities page to the top, and hide the legal content entirely.}}{924}{figure.268.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {268.1}Availability}{924}{section.268.1}\protected@file@percent } \newlabel{availability}{{268.1}{924}{Availability}{section.268.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.2}Requirements and Limitations}{924}{section.268.2}\protected@file@percent } \newlabel{requirements-and-limitations-1}{{268.2}{924}{Requirements and Limitations}{section.268.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.3}Creating and Managing Result Rankings}{925}{section.268.3}\protected@file@percent } \newlabel{creating-and-managing-result-rankings}{{268.3}{925}{Creating and Managing Result Rankings}{section.268.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.4}Adding Aliases}{925}{section.268.4}\protected@file@percent } \newlabel{adding-aliases}{{268.4}{925}{Adding Aliases}{section.268.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {268.2}{\ignorespaces Apply your custom rankings to matched results of additional search terms.}}{925}{figure.268.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {268.5}Activating and Deactivating Aliases}{926}{section.268.5}\protected@file@percent } \newlabel{activating-and-deactivating-aliases}{{268.5}{926}{Activating and Deactivating Aliases}{section.268.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.6}Pinning and Hiding Results}{926}{section.268.6}\protected@file@percent } \newlabel{pinning-and-hiding-results}{{268.6}{926}{Pinning and Hiding Results}{section.268.6}{}} \@writefile{lof}{\contentsline {figure}{\numberline {268.3}{\ignorespaces Pin results to the top of the Search Results list.}}{926}{figure.268.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {268.7}Adding Results}{927}{section.268.7}\protected@file@percent } \newlabel{adding-results}{{268.7}{927}{Adding Results}{section.268.7}{}} \@writefile{lof}{\contentsline {figure}{\numberline {268.4}{\ignorespaces Add results that aren't normally returned.}}{927}{figure.268.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {268.8}Re-Ordering Pinned Results}{928}{section.268.8}\protected@file@percent } \newlabel{re-ordering-pinned-results}{{268.8}{928}{Re-Ordering Pinned Results}{section.268.8}{}} \@writefile{lof}{\contentsline {figure}{\numberline {268.5}{\ignorespaces Re-order the pinned rankings if you want to emphasize or de-emphasize certain results.}}{928}{figure.268.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {268.9}Result Rankings Scope and Permissions}{928}{section.268.9}\protected@file@percent } \newlabel{result-rankings-scope-and-permissions}{{268.9}{928}{Result Rankings Scope and Permissions}{section.268.9}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.10}Result Rankings Aliases versus Synonyms}{929}{section.268.10}\protected@file@percent } \newlabel{result-rankings-aliases-versus-synonyms}{{268.10}{929}{Result Rankings Aliases versus Synonyms}{section.268.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {268.11}Known Issues}{929}{section.268.11}\protected@file@percent } \newlabel{known-issues-1}{{268.11}{929}{Known Issues}{section.268.11}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {269}Forms}{931}{chapter.269}\protected@file@percent } \newlabel{forms}{{269}{931}{Forms}{chapter.269}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.1}Forms and Lists}{932}{section.269.1}\protected@file@percent } \newlabel{forms-and-lists}{{269.1}{932}{Forms and Lists}{section.269.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {269.2}Which Form Builder Should I Use?}{932}{section.269.2}\protected@file@percent } \newlabel{which-form-builder-should-i-use}{{269.2}{932}{Which Form Builder Should I Use?}{section.269.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {270}Creating and Managing Forms}{933}{chapter.270}\protected@file@percent } \newlabel{creating-and-managing-forms}{{270}{933}{Creating and Managing Forms}{chapter.270}{}} \@writefile{toc}{\contentsline {section}{\numberline {270.1}Viewing Forms}{933}{section.270.1}\protected@file@percent } \newlabel{viewing-forms}{{270.1}{933}{Viewing Forms}{section.270.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {270.1}{\ignorespaces Get feedback from guests of The Lunar Resort.}}{934}{figure.270.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {270.2}{\ignorespaces Forms are displayed in List format by default.}}{934}{figure.270.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {270.2}Building a Form}{935}{section.270.2}\protected@file@percent } \newlabel{building-a-form}{{270.2}{935}{Building a Form}{section.270.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {270.3}{\ignorespaces You can choose from nine field types when creating forms.}}{936}{figure.270.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {270.4}{\ignorespaces The form builder page lets you preview your form layout, add a page to the form, or add some more fields.}}{937}{figure.270.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {270.3}Accessing Forms}{938}{section.270.3}\protected@file@percent } \newlabel{accessing-forms}{{270.3}{938}{Accessing Forms}{section.270.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {270.5}{\ignorespaces Add a page for guests to view and fill out your new form.}}{938}{figure.270.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {270.6}{\ignorespaces You must first publish a form before you can get a shareable link.}}{939}{figure.270.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {270.7}{\ignorespaces Copy the link to your form.}}{939}{figure.270.7}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {270.8}{\ignorespaces Lunar Resort guests can use a simple form to record their feelings about the resort.}}{940}{figure.270.8}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {271}Managing Form Entries}{941}{chapter.271}\protected@file@percent } \newlabel{managing-form-entries}{{271}{941}{Managing Form Entries}{chapter.271}{}} \@writefile{toc}{\contentsline {section}{\numberline {271.1}Viewing Form Entries}{941}{section.271.1}\protected@file@percent } \newlabel{viewing-form-entries}{{271.1}{941}{Viewing Form Entries}{section.271.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {271.1}{\ignorespaces You can view the entries right in the Forms application.}}{941}{figure.271.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {271.2}{\ignorespaces You can view a single entry right in the Forms application.}}{942}{figure.271.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {271.2}Exporting Form Entries}{942}{section.271.2}\protected@file@percent } \newlabel{exporting-form-entries}{{271.2}{942}{Exporting Form Entries}{section.271.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {271.3}{\ignorespaces You can export entries as CSV, JSON, XLS, or XML.}}{943}{figure.271.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {271.3}Deleting Form Entries}{943}{section.271.3}\protected@file@percent } \newlabel{deleting-form-entries}{{271.3}{943}{Deleting Form Entries}{section.271.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {271.4}{\ignorespaces Delete all form entries in one fell swoop.}}{944}{figure.271.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {272}Form Field Types}{945}{chapter.272}\protected@file@percent } \newlabel{form-field-types}{{272}{945}{Form Field Types}{chapter.272}{}} \@writefile{lof}{\contentsline {figure}{\numberline {272.1}{\ignorespaces There are many useful out-of-the-box form field types.}}{946}{figure.272.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.2}{\ignorespaces Use Paragraph fields to enter longer instructions on Form Pages.}}{946}{figure.272.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.3}{\ignorespaces Text fields can be single line or multi-line.}}{947}{figure.272.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.4}{\ignorespaces Use a select from list field to let Users choose predefined options.}}{947}{figure.272.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.5}{\ignorespaces Single selection fields allow only one selection.}}{947}{figure.272.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.6}{\ignorespaces Date fields show a date picker so Users enter a valid date.}}{948}{figure.272.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.7}{\ignorespaces A multiple selection field can use a toggle.}}{948}{figure.272.7}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.8}{\ignorespaces Grid fields use the same options (columns) for multiple categories (rows).}}{949}{figure.272.8}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.9}{\ignorespaces Numeric fields accept only numeric input.}}{949}{figure.272.9}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {272.10}{\ignorespaces : Upload fields let Users attach files to the form.}}{949}{figure.272.10}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {273}Form Rules}{951}{chapter.273}\protected@file@percent } \newlabel{form-rules}{{273}{951}{Form Rules}{chapter.273}{}} \@writefile{toc}{\contentsline {section}{\numberline {273.1}The Anatomy of a Form Rule}{951}{section.273.1}\protected@file@percent } \newlabel{the-anatomy-of-a-form-rule}{{273.1}{951}{The Anatomy of a Form Rule}{section.273.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {273.2}Creating Form Rules: Rule Builder}{952}{section.273.2}\protected@file@percent } \newlabel{creating-form-rules-rule-builder}{{273.2}{952}{Creating Form Rules: Rule Builder}{section.273.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {273.1}{\ignorespaces The Rule Builder gives you a handy interface for creating dynamic form rules.}}{952}{figure.273.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {273.3}Conditions}{953}{section.273.3}\protected@file@percent } \newlabel{conditions}{{273.3}{953}{Conditions}{section.273.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {274}Action: Show and Hide}{955}{chapter.274}\protected@file@percent } \newlabel{action-show-and-hide}{{274}{955}{Action: Show and Hide}{chapter.274}{}} \@writefile{lof}{\contentsline {figure}{\numberline {274.1}{\ignorespaces Build form rules quickly by defining your conditions and actions.}}{956}{figure.274.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {274.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{956}{figure.274.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {275}Action: Require}{957}{chapter.275}\protected@file@percent } \newlabel{action-require}{{275}{957}{Action: Require}{chapter.275}{}} \@writefile{lof}{\contentsline {figure}{\numberline {275.1}{\ignorespaces Build form rules quickly by defining your conditions and actions.}}{958}{figure.275.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {275.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{958}{figure.275.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {276}Action: Enable and Disable}{959}{chapter.276}\protected@file@percent } \newlabel{action-enable-and-disable}{{276}{959}{Action: Enable and Disable}{chapter.276}{}} \@writefile{lof}{\contentsline {figure}{\numberline {276.1}{\ignorespaces Build form rules quickly by defining your conditions and actions.}}{960}{figure.276.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {276.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{960}{figure.276.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {277}Action: Jump to Page}{961}{chapter.277}\protected@file@percent } \newlabel{action-jump-to-page}{{277}{961}{Action: Jump to Page}{chapter.277}{}} \@writefile{lof}{\contentsline {figure}{\numberline {277.1}{\ignorespaces Build form rules quickly by defining your conditions and actions.}}{962}{figure.277.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {277.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{962}{figure.277.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {278}Action: Autofill}{963}{chapter.278}\protected@file@percent } \newlabel{action-autofill}{{278}{963}{Action: Autofill}{chapter.278}{}} \@writefile{lof}{\contentsline {figure}{\numberline {278.1}{\ignorespaces Build form rules quickly by defining your conditions and actions.}}{964}{figure.278.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {278.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{964}{figure.278.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {278.1}Using Inputs with Autofill}{964}{section.278.1}\protected@file@percent } \newlabel{using-inputs-with-autofill}{{278.1}{964}{Using Inputs with Autofill}{section.278.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {278.3}{\ignorespaces Create a data provider for the autofill rule.}}{965}{figure.278.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {278.4}{\ignorespaces Create a form with a text field and a select from list field. These are used to provide the input to the data provider and be autofilled by its output.}}{966}{figure.278.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {278.5}{\ignorespaces Create the autofill rule. Brag of your prowess.}}{967}{figure.278.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {279}Action: Calculate}{969}{chapter.279}\protected@file@percent } \newlabel{action-calculate}{{279}{969}{Action: Calculate}{chapter.279}{}} \@writefile{lof}{\contentsline {figure}{\numberline {279.1}{\ignorespaces Build calculate actions with a handy calculator.}}{970}{figure.279.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {279.2}{\ignorespaces Once a rule is saved, it is displayed so that you can easily understand what it does.}}{970}{figure.279.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {280}Form Element Sets}{971}{chapter.280}\protected@file@percent } \newlabel{form-element-sets}{{280}{971}{Form Element Sets}{chapter.280}{}} \@writefile{toc}{\contentsline {section}{\numberline {280.1}Creating Element Sets}{971}{section.280.1}\protected@file@percent } \newlabel{creating-element-sets}{{280.1}{971}{Creating Element Sets}{section.280.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {280.2}Using Element Sets}{971}{section.280.2}\protected@file@percent } \newlabel{using-element-sets}{{280.2}{971}{Using Element Sets}{section.280.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {280.1}{\ignorespaces Creating Element Sets is just like creating Forms. You just can't publish them.}}{972}{figure.280.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {280.2}{\ignorespaces Add an Element Set the same way you add other Form Elements, like fields.}}{973}{figure.280.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {281}Data Providers}{975}{chapter.281}\protected@file@percent } \newlabel{data-providers}{{281}{975}{Data Providers}{chapter.281}{}} \@writefile{toc}{\contentsline {section}{\numberline {281.1}Adding a Basic Data Provider}{976}{section.281.1}\protected@file@percent } \newlabel{adding-a-basic-data-provider}{{281.1}{976}{Adding a Basic Data Provider}{section.281.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {281.2}Using a Data Provider in a Select Field}{976}{section.281.2}\protected@file@percent } \newlabel{using-a-data-provider-in-a-select-field}{{281.2}{976}{Using a Data Provider in a Select Field}{section.281.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {281.1}{\ignorespaces Set up a simple data provider in no time.}}{977}{figure.281.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {281.2}{\ignorespaces Form users select an option form the list populated by the Data Provider.}}{978}{figure.281.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {281.3}Granting Data Provider Permissions}{978}{section.281.3}\protected@file@percent } \newlabel{granting-data-provider-permissions}{{281.3}{978}{Granting Data Provider Permissions}{section.281.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {281.4}Data Provider Configuration}{978}{section.281.4}\protected@file@percent } \newlabel{data-provider-configuration}{{281.4}{978}{Data Provider Configuration}{section.281.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {281.5}Troubleshooting Data Provider Errors}{979}{section.281.5}\protected@file@percent } \newlabel{troubleshooting-data-provider-errors}{{281.5}{979}{Troubleshooting Data Provider Errors}{section.281.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {281.3}{\ignorespaces Set up Data Providers to display data retrieved from a REST service.}}{980}{figure.281.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {282}Auto-Save}{981}{chapter.282}\protected@file@percent } \newlabel{auto-save}{{282}{981}{Auto-Save}{chapter.282}{}} \@writefile{lof}{\contentsline {figure}{\numberline {282.1}{\ignorespaces Configure the auto-save duration.}}{981}{figure.282.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {283}Translating Forms}{983}{chapter.283}\protected@file@percent } \newlabel{translating-forms}{{283}{983}{Translating Forms}{chapter.283}{}} \@writefile{lof}{\contentsline {figure}{\numberline {283.1}{\ignorespaces Add a translation for the form.}}{984}{figure.283.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {283.2}{\ignorespaces Translate as much of the form as possible into each language you expect users to need.}}{984}{figure.283.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {283.3}{\ignorespaces Select the form's language.}}{985}{figure.283.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {284}Autocompleting Text Fields}{987}{chapter.284}\protected@file@percent } \newlabel{autocompleting-text-fields}{{284}{987}{Autocompleting Text Fields}{chapter.284}{}} \@writefile{toc}{\contentsline {section}{\numberline {284.1}Configuring Autocomplete}{987}{section.284.1}\protected@file@percent } \newlabel{configuring-autocomplete}{{284.1}{987}{Configuring Autocomplete}{section.284.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {284.1}{\ignorespaces You can configure a manual data provider to specify the options users can select from.}}{988}{figure.284.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {284.2}{\ignorespaces When typing in a field with autocomplete, users are presented a list of selections from the configured data provider. The displayed results are filtered to include only selections containing the text entered by the user.}}{988}{figure.284.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {285}Form Success Pages}{991}{chapter.285}\protected@file@percent } \newlabel{form-success-pages}{{285}{991}{Form Success Pages}{chapter.285}{}} \@writefile{lof}{\contentsline {figure}{\numberline {285.1}{\ignorespaces The default success message alerts users when their request completes successfully.}}{991}{figure.285.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {285.2}{\ignorespaces There's a default Success Page message if you can't think of anything else to say.}}{992}{figure.285.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {285.3}{\ignorespaces Add a Success Page using the edit menu for the form page.}}{992}{figure.285.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {286}Workflow and Forms}{993}{chapter.286}\protected@file@percent } \newlabel{workflow-and-forms}{{286}{993}{Workflow and Forms}{chapter.286}{}} \@writefile{lof}{\contentsline {figure}{\numberline {286.1}{\ignorespaces Workflow is enabled in the Control Panel or in Site Administration for most Liferay DXP assets.}}{993}{figure.286.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {286.1}Enabling Workflow in a Form}{994}{section.286.1}\protected@file@percent } \newlabel{enabling-workflow-in-a-form}{{286.1}{994}{Enabling Workflow in a Form}{section.286.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {286.2}{\ignorespaces Enable workflow for each form in its Settings window.}}{994}{figure.286.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {286.2}Testing the Workflow}{995}{section.286.2}\protected@file@percent } \newlabel{testing-the-workflow}{{286.2}{995}{Testing the Workflow}{section.286.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {286.3}{\ignorespaces Each entry's status is visible in the Forms application's Form Entries screen.}}{995}{figure.286.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {287}Duplicating Forms and Form Fields}{997}{chapter.287}\protected@file@percent } \newlabel{duplicating-forms-and-form-fields}{{287}{997}{Duplicating Forms and Form Fields}{chapter.287}{}} \@writefile{lof}{\contentsline {figure}{\numberline {287.1}{\ignorespaces The Duplicate option works the same for forms and form fields.}}{997}{figure.287.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {287.2}{\ignorespaces You can duplicate form fields.}}{998}{figure.287.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {288}Form Pages}{999}{chapter.288}\protected@file@percent } \newlabel{form-pages}{{288}{999}{Form Pages}{chapter.288}{}} \@writefile{lof}{\contentsline {figure}{\numberline {288.1}{\ignorespaces The default pagination style.}}{999}{figure.288.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {288.2}{\ignorespaces The alternate pagination style as seen in the Form Builder.}}{1000}{figure.288.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {288.3}{\ignorespaces You can add new pages or reset the current page from the Page Actions menu.}}{1000}{figure.288.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {289}Help Text, Placeholder Text, and Predefined Values}{1003}{chapter.289}\protected@file@percent } \newlabel{help-text-placeholder-text-and-predefined-values}{{289}{1003}{Help Text, Placeholder Text, and Predefined Values}{chapter.289}{}} \@writefile{lof}{\contentsline {figure}{\numberline {289.1}{\ignorespaces Predefined values and placeholder text are entered in the Properties tab.}}{1004}{figure.289.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {289.2}{\ignorespaces The Full Name field here uses help text and placeholder text, while the sandwiches field uses a predefined value.}}{1004}{figure.289.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {290}Validating Text and Numeric Fields}{1005}{chapter.290}\protected@file@percent } \newlabel{validating-text-and-numeric-fields}{{290}{1005}{Validating Text and Numeric Fields}{chapter.290}{}} \@writefile{lof}{\contentsline {figure}{\numberline {290.1}{\ignorespaces Validate data to ensure you're collecting only useful information.}}{1005}{figure.290.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {290.1}Validating Text Fields}{1006}{section.290.1}\protected@file@percent } \newlabel{validating-text-fields}{{290.1}{1006}{Validating Text Fields}{section.290.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {290.2}If Input Contains/Does Not Contain}{1006}{section.290.2}\protected@file@percent } \newlabel{if-input-containsdoes-not-contain}{{290.2}{1006}{If Input Contains/Does Not Contain}{section.290.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {290.2}{\ignorespaces If \emph {Liferay} isn't part of the field's value, an error message is displayed.}}{1006}{figure.290.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {290.3}If Input Is not URL/Email}{1007}{section.290.3}\protected@file@percent } \newlabel{if-input-is-not-urlemail}{{290.3}{1007}{If Input Is not URL/Email}{section.290.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {290.3}{\ignorespaces Use text field validation to make sure users enter a valid email address or URL.}}{1007}{figure.290.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {290.4}If Input Does Not Match}{1007}{section.290.4}\protected@file@percent } \newlabel{if-input-does-not-match}{{290.4}{1007}{If Input Does Not Match}{section.290.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {290.5}Validating Numeric Fields}{1007}{section.290.5}\protected@file@percent } \newlabel{validating-numeric-fields}{{290.5}{1007}{Validating Numeric Fields}{section.290.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {290.4}{\ignorespaces Regular expression text validation opens up countless possibilities.}}{1008}{figure.290.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {290.5}{\ignorespaces Numeric conditions constrain user-entered numeric data.}}{1008}{figure.290.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {290.6}{\ignorespaces Make sure user-entered numeric data is within reasonable bounds. Nobody needs 11 sandwiches for lunch.}}{1009}{figure.290.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {291}Enabling CAPTCHA on Form Submissions}{1011}{chapter.291}\protected@file@percent } \newlabel{enabling-captcha-on-form-submissions}{{291}{1011}{Enabling CAPTCHA on Form Submissions}{chapter.291}{}} \@writefile{lof}{\contentsline {figure}{\numberline {291.1}{\ignorespaces You can enable CAPTCHA for your form in the Form Settings window.}}{1011}{figure.291.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {291.2}{\ignorespaces Once you enable CAPTCHA, your form has protection against bot submissions.}}{1012}{figure.291.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {292}Form Notifications}{1013}{chapter.292}\protected@file@percent } \newlabel{form-notifications}{{292}{1013}{Form Notifications}{chapter.292}{}} \@writefile{lof}{\contentsline {figure}{\numberline {292.1}{\ignorespaces Configure email notifications each time a form entry is submitted.}}{1014}{figure.292.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {293}Redirecting Users}{1015}{chapter.293}\protected@file@percent } \newlabel{redirecting-users}{{293}{1015}{Redirecting Users}{chapter.293}{}} \@writefile{lof}{\contentsline {figure}{\numberline {293.1}{\ignorespaces Redirect users after they submit a form.}}{1016}{figure.293.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {294}Form Permissions}{1017}{chapter.294}\protected@file@percent } \newlabel{form-permissions}{{294}{1017}{Form Permissions}{chapter.294}{}} \@writefile{lof}{\contentsline {figure}{\numberline {294.1}{\ignorespaces You can configure a form's permissions.}}{1018}{figure.294.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {295}Styling Form Pages}{1019}{chapter.295}\protected@file@percent } \newlabel{styling-form-pages}{{295}{1019}{Styling Form Pages}{chapter.295}{}} \@writefile{lof}{\contentsline {figure}{\numberline {295.1}{\ignorespaces This is the default single-column, vertically-oriented form.}}{1019}{figure.295.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.2}{\ignorespaces Putting form fields in multiple columns can give you more space.}}{1020}{figure.295.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.3}{\ignorespaces The first row is in two columns and the second row is in three columns.}}{1020}{figure.295.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.4}{\ignorespaces Form field borders.}}{1020}{figure.295.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.5}{\ignorespaces After resizing, the field is smaller.}}{1020}{figure.295.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.6}{\ignorespaces There are now two fields in the row.}}{1021}{figure.295.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {295.7}{\ignorespaces You can also move fields on form pages.}}{1021}{figure.295.7}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {296}Dynamic Data Lists}{1023}{chapter.296}\protected@file@percent } \newlabel{dynamic-data-lists}{{296}{1023}{Dynamic Data Lists}{chapter.296}{}} \@writefile{toc}{\contentsline {section}{\numberline {296.1}System Configuration}{1024}{section.296.1}\protected@file@percent } \newlabel{system-configuration}{{296.1}{1024}{System Configuration}{section.296.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {297}Creating Data Definitions}{1025}{chapter.297}\protected@file@percent } \newlabel{creating-data-definitions}{{297}{1025}{Creating Data Definitions}{chapter.297}{}} \@writefile{lof}{\contentsline {figure}{\numberline {297.1}{\ignorespaces The Data Definitions screen.}}{1025}{figure.297.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {297.2}{\ignorespaces After naming your data definition, expand the Details section of the form and give your definition a description and parent definition, if desired.}}{1026}{figure.297.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {297.3}{\ignorespaces Use the data definition designer to add fields to the data definition.}}{1027}{figure.297.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {297.4}{\ignorespaces Configure the settings for each field in your data definition.}}{1029}{figure.297.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {298}Managing Data Definitions}{1031}{chapter.298}\protected@file@percent } \newlabel{managing-data-definitions}{{298}{1031}{Managing Data Definitions}{chapter.298}{}} \@writefile{lof}{\contentsline {figure}{\numberline {298.1}{\ignorespaces You can copy an existing data definition, manage its templates, and more.}}{1031}{figure.298.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {299}Creating Data Lists}{1033}{chapter.299}\protected@file@percent } \newlabel{creating-data-lists}{{299}{1033}{Creating Data Lists}{chapter.299}{}} \@writefile{toc}{\contentsline {section}{\numberline {299.1}Creating List Records}{1033}{section.299.1}\protected@file@percent } \newlabel{creating-list-records}{{299.1}{1033}{Creating List Records}{section.299.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {299.1}{\ignorespaces The New List form.}}{1034}{figure.299.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {299.2}{\ignorespaces Dynamic Data Lists Display widget.}}{1035}{figure.299.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {299.2}Configuring the Dynamic Data Lists Display Widget}{1035}{section.299.2}\protected@file@percent } \newlabel{configuring-the-dynamic-data-lists-display-widget}{{299.2}{1035}{Configuring the Dynamic Data Lists Display Widget}{section.299.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {299.3}{\ignorespaces The Dynamic Data Lists Display widget's optional configuration.}}{1036}{figure.299.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {300}Using Templates to Display Forms and Lists}{1037}{chapter.300}\protected@file@percent } \newlabel{using-templates-to-display-forms-and-lists}{{300}{1037}{Using Templates to Display Forms and Lists}{chapter.300}{}} \@writefile{toc}{\contentsline {section}{\numberline {300.1}Managing Display and Form Templates}{1037}{section.300.1}\protected@file@percent } \newlabel{managing-display-and-form-templates}{{300.1}{1037}{Managing Display and Form Templates}{section.300.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {301}Creating Form Templates}{1039}{chapter.301}\protected@file@percent } \newlabel{creating-form-templates}{{301}{1039}{Creating Form Templates}{chapter.301}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {302}Creating Display Templates}{1041}{chapter.302}\protected@file@percent } \newlabel{creating-display-templates}{{302}{1041}{Creating Display Templates}{chapter.302}{}} \@writefile{lof}{\contentsline {figure}{\numberline {302.1}{\ignorespaces Create your display template in the editor.}}{1042}{figure.302.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {302.1}Display Template Editor}{1042}{section.302.1}\protected@file@percent } \newlabel{display-template-editor}{{302.1}{1042}{Display Template Editor}{section.302.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {302.2}{\ignorespaces Extract appropriate display information, rather than spitting out the whole object.}}{1044}{figure.302.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {303}Workflow}{1045}{chapter.303}\protected@file@percent } \newlabel{workflow}{{303}{1045}{Workflow}{chapter.303}{}} \@writefile{toc}{\contentsline {section}{\numberline {303.1}What's New with Workflow?}{1045}{section.303.1}\protected@file@percent } \newlabel{whats-new-with-workflow}{{303.1}{1045}{What's New with Workflow?}{section.303.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {303.2}DXP Feature: Workflow Metrics}{1046}{section.303.2}\protected@file@percent } \newlabel{dxp-feature-workflow-metrics}{{303.2}{1046}{DXP Feature: Workflow Metrics}{section.303.2}{}} \@writefile{toc}{\contentsline {subsection}{Service Level Agreements (SLAs)}{1046}{section.303.2}\protected@file@percent } \newlabel{service-level-agreements-slas}{{303.2}{1046}{Service Level Agreements (SLAs)}{section.303.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {303.1}{\ignorespaces Use Service Level Agreements (SLAs) to define how workflow metrics are reported.}}{1046}{figure.303.1}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Workflow Reports}{1046}{figure.303.1}\protected@file@percent } \newlabel{workflow-reports-1}{{303.2}{1046}{Workflow Reports}{figure.303.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {303.3}Control Panel Reorganization}{1046}{section.303.3}\protected@file@percent } \newlabel{control-panel-reorganization}{{303.3}{1046}{Control Panel Reorganization}{section.303.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {303.2}{\ignorespaces See Workflow Reports generated based on your SLAs.}}{1047}{figure.303.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {303.3}{\ignorespaces Workflow has a top-level entry in the Control Panel.}}{1047}{figure.303.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {303.4}Workflow Definition Permissions: System Settings}{1048}{section.303.4}\protected@file@percent } \newlabel{workflow-definition-permissions-system-settings}{{303.4}{1048}{Workflow Definition Permissions: System Settings}{section.303.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {303.4}{\ignorespaces Explicit permission must be granted before administrators are allowed to publish and edit workflow definitions.}}{1048}{figure.303.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {303.5}Embedded Workflows}{1048}{section.303.5}\protected@file@percent } \newlabel{embedded-workflows}{{303.5}{1048}{Embedded Workflows}{section.303.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {304}Activating Workflow}{1049}{chapter.304}\protected@file@percent } \newlabel{activating-workflow}{{304}{1049}{Activating Workflow}{chapter.304}{}} \@writefile{toc}{\contentsline {section}{\numberline {304.1}Workflow Assets}{1049}{section.304.1}\protected@file@percent } \newlabel{workflow-assets}{{304.1}{1049}{Workflow Assets}{section.304.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {304.2}Activating Workflow in Applications}{1050}{section.304.2}\protected@file@percent } \newlabel{activating-workflow-in-applications}{{304.2}{1050}{Activating Workflow in Applications}{section.304.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {304.3}Workflow Behavior}{1050}{section.304.3}\protected@file@percent } \newlabel{workflow-behavior}{{304.3}{1050}{Workflow Behavior}{section.304.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {304.1}{\ignorespaces Activate workflow on Web Content folders from the folder's edit screen.}}{1051}{figure.304.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {304.2}{\ignorespaces Activate workflow on Documents and Media folders from the folder's edit screen.}}{1052}{figure.304.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {304.3}{\ignorespaces Activate workflow for each individual Dynamic Data List.}}{1052}{figure.304.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {304.4}{\ignorespaces Activate workflow on each form's entries from the Form Settings window.}}{1053}{figure.304.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {304.5}{\ignorespaces Instead of a Publish button, a Submit for Publication button appears for workflow-enabled resources.}}{1053}{figure.304.5}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {304.6}{\ignorespaces With workflow enabled on Page Revisions, the Site administrator must submit their page variation for publication before it can go live.}}{1053}{figure.304.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {305}Managing Workflows}{1055}{chapter.305}\protected@file@percent } \newlabel{managing-workflows}{{305}{1055}{Managing Workflows}{chapter.305}{}} \@writefile{toc}{\contentsline {section}{\numberline {305.1}Workflow Definition Publication Permissions}{1055}{section.305.1}\protected@file@percent } \newlabel{workflow-definition-publication-permissions}{{305.1}{1055}{Workflow Definition Publication Permissions}{section.305.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {305.2}Adding, Editing, and Deleting}{1056}{section.305.2}\protected@file@percent } \newlabel{adding-editing-and-deleting}{{305.2}{1056}{Adding, Editing, and Deleting}{section.305.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {305.3}Uploading Workflow Definitions}{1056}{section.305.3}\protected@file@percent } \newlabel{uploading-workflow-definitions}{{305.3}{1056}{Uploading Workflow Definitions}{section.305.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {305.4}Published Versus Unpublished}{1057}{section.305.4}\protected@file@percent } \newlabel{published-versus-unpublished}{{305.4}{1057}{Published Versus Unpublished}{section.305.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {305.5}Workflow Versions}{1057}{section.305.5}\protected@file@percent } \newlabel{workflow-versions}{{305.5}{1057}{Workflow Versions}{section.305.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {305.1}{\ignorespaces View and restore prior versions of a workflow.}}{1058}{figure.305.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {306}Reviewing Assets}{1059}{chapter.306}\protected@file@percent } \newlabel{reviewing-assets}{{306}{1059}{Reviewing Assets}{chapter.306}{}} \@writefile{lof}{\contentsline {figure}{\numberline {306.1}{\ignorespaces Users manage workflow tasks from their My Workflow Tasks widget.}}{1059}{figure.306.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {306.1}Asset Submission to Workflow}{1060}{section.306.1}\protected@file@percent } \newlabel{asset-submission-to-workflow}{{306.1}{1060}{Asset Submission to Workflow}{section.306.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {306.2}{\ignorespaces A User with VIEW permission on Web Content cannot manage Approved Articles.}}{1060}{figure.306.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {306.2}Assigning the Task}{1060}{section.306.2}\protected@file@percent } \newlabel{assigning-the-task}{{306.2}{1060}{Assigning the Task}{section.306.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {306.3}{\ignorespaces A User with access to Web Content in the Workflow can manage Pending Articles.}}{1061}{figure.306.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {306.4}{\ignorespaces The assets assigned to a user are listed in \emph {Assigned to Me}.}}{1061}{figure.306.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {306.5}{\ignorespaces The Assets assigned to Roles are listed in each associated user's \emph {Assigned to My Roles} tab.}}{1061}{figure.306.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {306.3}Completing the Task}{1062}{section.306.3}\protected@file@percent } \newlabel{completing-the-task}{{306.3}{1062}{Completing the Task}{section.306.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {306.6}{\ignorespaces Complete Tasks right from the \emph {Assigned to Me} list.}}{1062}{figure.306.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {306.7}{\ignorespaces Inspect Assets before completing the Task.}}{1063}{figure.306.7}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {307}Workflow Metrics: The Service Level Agreement (SLA)}{1065}{chapter.307}\protected@file@percent } \newlabel{workflow-metrics-the-service-level-agreement-sla}{{307}{1065}{Workflow Metrics: The Service Level Agreement (SLA)}{chapter.307}{}} \@writefile{toc}{\contentsline {section}{\numberline {307.1}Adding SLAs}{1065}{section.307.1}\protected@file@percent } \newlabel{adding-slas}{{307.1}{1065}{Adding SLAs}{section.307.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {307.1}{\ignorespaces Add SLAs to a workflow definition from the Metrics application.}}{1066}{figure.307.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {307.2}{\ignorespaces Manage SLAs from the SLAs screen.}}{1067}{figure.307.2}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Valid Start and Stop Events}{1067}{figure.307.2}\protected@file@percent } \newlabel{valid-start-and-stop-events}{{307.1}{1067}{Valid Start and Stop Events}{figure.307.2}{}} \@writefile{toc}{\contentsline {subsection}{Durations}{1067}{figure.307.2}\protected@file@percent } \newlabel{durations}{{307.1}{1067}{Durations}{figure.307.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {308}Workflow Metrics: Reports}{1069}{chapter.308}\protected@file@percent } \newlabel{workflow-metrics-reports}{{308}{1069}{Workflow Metrics: Reports}{chapter.308}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.1}{\ignorespaces In this view, the only process with pending items is the Single Approver.}}{1069}{figure.308.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {308.1}Understanding Reports}{1069}{section.308.1}\protected@file@percent } \newlabel{understanding-reports}{{308.1}{1069}{Understanding Reports}{section.308.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.2}{\ignorespaces See data on the Pending Items and the Workload by Step for a process.}}{1070}{figure.308.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {308.2}Pending Items}{1070}{section.308.2}\protected@file@percent } \newlabel{pending-items}{{308.2}{1070}{Pending Items}{section.308.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.3}Workload by Step}{1070}{section.308.3}\protected@file@percent } \newlabel{workload-by-step}{{308.3}{1070}{Workload by Step}{section.308.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.4}Completed Items}{1071}{section.308.4}\protected@file@percent } \newlabel{completed-items}{{308.4}{1071}{Completed Items}{section.308.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.5}Completion Velocity}{1071}{section.308.5}\protected@file@percent } \newlabel{completion-velocity}{{308.5}{1071}{Completion Velocity}{section.308.5}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.3}{\ignorespaces View the completion rate of items in a workflow process over time.}}{1071}{figure.308.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {308.6}Items View}{1072}{section.308.6}\protected@file@percent } \newlabel{items-view}{{308.6}{1072}{Items View}{section.308.6}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.7}Filtering by SLA Status}{1072}{section.308.7}\protected@file@percent } \newlabel{filtering-by-sla-status}{{308.7}{1072}{Filtering by SLA Status}{section.308.7}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.8}Filtering by Process Status and Completion Period}{1072}{section.308.8}\protected@file@percent } \newlabel{filtering-by-process-status-and-completion-period}{{308.8}{1072}{Filtering by Process Status and Completion Period}{section.308.8}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.4}{\ignorespaces Filter by SLA status: Overdue, On Time, or Untracked.}}{1073}{figure.308.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {308.9}Filtering by Process Step}{1073}{section.308.9}\protected@file@percent } \newlabel{filtering-by-process-step}{{308.9}{1073}{Filtering by Process Step}{section.308.9}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.5}{\ignorespaces Filter by Process Status and Completion Period.}}{1074}{figure.308.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {308.10}Combining Filters}{1074}{section.308.10}\protected@file@percent } \newlabel{combining-filters}{{308.10}{1074}{Combining Filters}{section.308.10}{}} \@writefile{toc}{\contentsline {section}{\numberline {308.11}Item Details}{1074}{section.308.11}\protected@file@percent } \newlabel{item-details}{{308.11}{1074}{Item Details}{section.308.11}{}} \@writefile{lof}{\contentsline {figure}{\numberline {308.6}{\ignorespaces Combine filters to see just the items you want.}}{1075}{figure.308.6}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {308.7}{\ignorespaces Item Details include SLA status information and whether the item is Resolved or Open.}}{1076}{figure.308.7}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {309}Workflow Designer}{1077}{chapter.309}\protected@file@percent } \newlabel{workflow-designer}{{309}{1077}{Workflow Designer}{chapter.309}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {310}Managing Workflows with the Workflow Designer}{1079}{chapter.310}\protected@file@percent } \newlabel{managing-workflows-with-the-workflow-designer}{{310}{1079}{Managing Workflows with the Workflow Designer}{chapter.310}{}} \@writefile{toc}{\contentsline {section}{\numberline {310.1}Adding New Workflow Definitions with the Workflow Designer}{1079}{section.310.1}\protected@file@percent } \newlabel{adding-new-workflow-definitions-with-the-workflow-designer}{{310.1}{1079}{Adding New Workflow Definitions with the Workflow Designer}{section.310.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {310.2}Saving and Publishing Workflow Definitions}{1079}{section.310.2}\protected@file@percent } \newlabel{saving-and-publishing-workflow-definitions}{{310.2}{1079}{Saving and Publishing Workflow Definitions}{section.310.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {310.1}{\ignorespaces The Workflow Designer's graphical interface makes designing workflows intuitive.}}{1080}{figure.310.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {310.3}Adding Nodes}{1080}{section.310.3}\protected@file@percent } \newlabel{adding-nodes}{{310.3}{1080}{Adding Nodes}{section.310.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {310.2}{\ignorespaces View a list of the current workflows that can be edited in the Workflow Designer.}}{1081}{figure.310.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {310.3}{\ignorespaces You can add a node by creating a transition that ends at a blank spot on your Designer canvas.}}{1081}{figure.310.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {310.4}Node Settings}{1081}{section.310.4}\protected@file@percent } \newlabel{node-settings}{{310.4}{1081}{Node Settings}{section.310.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {310.4}{\ignorespaces You can edit a node's settings.}}{1082}{figure.310.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {310.5}Related Topics}{1082}{section.310.5}\protected@file@percent } \newlabel{related-topics-3}{{310.5}{1082}{Related Topics}{section.310.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {311}Workflow Definition Nodes}{1083}{chapter.311}\protected@file@percent } \newlabel{workflow-definition-nodes}{{311}{1083}{Workflow Definition Nodes}{chapter.311}{}} \@writefile{toc}{\contentsline {section}{\numberline {311.1}Node Actions and Notifications}{1083}{section.311.1}\protected@file@percent } \newlabel{node-actions-and-notifications}{{311.1}{1083}{Node Actions and Notifications}{section.311.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {311.2}Actions}{1084}{section.311.2}\protected@file@percent } \newlabel{actions}{{311.2}{1084}{Actions}{section.311.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {311.1}{\ignorespaces You can add an Action to a Task node.}}{1084}{figure.311.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {311.3}Notifications}{1085}{section.311.3}\protected@file@percent } \newlabel{notifications}{{311.3}{1085}{Notifications}{section.311.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {311.2}{\ignorespaces You can send a Notification from a Task node.}}{1086}{figure.311.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {311.4}Start and End Nodes}{1087}{section.311.4}\protected@file@percent } \newlabel{start-and-end-nodes}{{311.4}{1087}{Start and End Nodes}{section.311.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {311.5}State Nodes}{1087}{section.311.5}\protected@file@percent } \newlabel{state-nodes}{{311.5}{1087}{State Nodes}{section.311.5}{}} \@writefile{toc}{\contentsline {section}{\numberline {311.6}Related Topics}{1087}{section.311.6}\protected@file@percent } \newlabel{related-topics-4}{{311.6}{1087}{Related Topics}{section.311.6}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {312}Affecting the Processing of Workflow Definitions}{1089}{chapter.312}\protected@file@percent } \newlabel{affecting-the-processing-of-workflow-definitions}{{312}{1089}{Affecting the Processing of Workflow Definitions}{chapter.312}{}} \@writefile{toc}{\contentsline {section}{\numberline {312.1}Transitions}{1089}{section.312.1}\protected@file@percent } \newlabel{transitions}{{312.1}{1089}{Transitions}{section.312.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {312.1}{\ignorespaces You connect nodes and direct workflow processing with transitions. The Single Approver workflow has transitions named Submit, Resubmit, Reject, and Approve.}}{1090}{figure.312.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {312.2}{\ignorespaces In the Single Approver workflow, a user in the Review task can choose to Approve or Reject the asset, which sends the asset either to the EndNode or to the Update task.}}{1090}{figure.312.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {312.2}Forks and Joins}{1091}{section.312.2}\protected@file@percent } \newlabel{forks-and-joins}{{312.2}{1091}{Forks and Joins}{section.312.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {312.3}{\ignorespaces Forks and Joins are used to enable parallel processing in the workflow.}}{1091}{figure.312.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {312.3}Conditions}{1092}{section.312.3}\protected@file@percent } \newlabel{conditions-1}{{312.3}{1092}{Conditions}{section.312.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {312.4}{\ignorespaces The Category Specific Approval definition starts with a Condition node.}}{1092}{figure.312.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {312.4}Related Topics}{1093}{section.312.4}\protected@file@percent } \newlabel{related-topics-5}{{312.4}{1093}{Related Topics}{section.312.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {313}Creating Tasks in the Workflow Designer}{1095}{chapter.313}\protected@file@percent } \newlabel{creating-tasks-in-the-workflow-designer}{{313}{1095}{Creating Tasks in the Workflow Designer}{chapter.313}{}} \@writefile{toc}{\contentsline {section}{\numberline {313.1}Assignments}{1095}{section.313.1}\protected@file@percent } \newlabel{assignments}{{313.1}{1095}{Assignments}{section.313.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {313.1}{\ignorespaces You can add an Assignment to a Task node.}}{1096}{figure.313.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {313.2}Resource Action Assignments}{1096}{section.313.2}\protected@file@percent } \newlabel{resource-action-assignments}{{313.2}{1096}{Resource Action Assignments}{section.313.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {313.2}{\ignorespaces Configure resource action assignments in the Workflow Designer.}}{1097}{figure.313.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {313.3}Scripted Assignments}{1097}{section.313.3}\protected@file@percent } \newlabel{scripted-assignments}{{313.3}{1097}{Scripted Assignments}{section.313.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {313.4}Related Topics}{1098}{section.313.4}\protected@file@percent } \newlabel{related-topics-6}{{313.4}{1098}{Related Topics}{section.313.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {314}Kaleo Forms}{1099}{chapter.314}\protected@file@percent } \newlabel{kaleo-forms}{{314}{1099}{Kaleo Forms}{chapter.314}{}} \@writefile{toc}{\contentsline {section}{\numberline {314.1}Creating Kaleo Forms Process}{1099}{section.314.1}\protected@file@percent } \newlabel{creating-kaleo-forms-process}{{314.1}{1099}{Creating Kaleo Forms Process}{section.314.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {314.1}{\ignorespaces Add a Kaleo Forms Process to link a form with a workflow definition.}}{1100}{figure.314.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {314.2}{\ignorespaces Define and choose your form's fields.}}{1100}{figure.314.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {314.3}{\ignorespaces This example workflow has three tasks that happen sequentially.}}{1101}{figure.314.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {314.4}{\ignorespaces Assign a form to each task in the workflow, and for the initial state.}}{1102}{figure.314.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {314.2}Adding Records to a Process}{1102}{section.314.2}\protected@file@percent } \newlabel{adding-records-to-a-process}{{314.2}{1102}{Adding Records to a Process}{section.314.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {315}Segmentation and Personalization}{1103}{chapter.315}\protected@file@percent } \newlabel{segmentation-and-personalization}{{315}{1103}{Segmentation and Personalization}{chapter.315}{}} \@writefile{toc}{\contentsline {section}{\numberline {315.1}Defining Segments}{1103}{section.315.1}\protected@file@percent } \newlabel{defining-segments}{{315.1}{1103}{Defining Segments}{section.315.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {315.2}Integrating Segments with Analytics Cloud}{1104}{section.315.2}\protected@file@percent } \newlabel{integrating-segments-with-analytics-cloud}{{315.2}{1104}{Integrating Segments with Analytics Cloud}{section.315.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {315.3}Personalizing Experiences}{1104}{section.315.3}\protected@file@percent } \newlabel{personalizing-experiences}{{315.3}{1104}{Personalizing Experiences}{section.315.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {315.4}Content Page Personalization}{1104}{section.315.4}\protected@file@percent } \newlabel{content-page-personalization}{{315.4}{1104}{Content Page Personalization}{section.315.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {315.5}Content Set Personalization}{1104}{section.315.5}\protected@file@percent } \newlabel{content-set-personalization-1}{{315.5}{1104}{Content Set Personalization}{section.315.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {316}The Segment Editor}{1105}{chapter.316}\protected@file@percent } \newlabel{the-segment-editor}{{316}{1105}{The Segment Editor}{chapter.316}{}} \@writefile{lof}{\contentsline {figure}{\numberline {316.1}{\ignorespaces The top portion of the Segment Editor has the segment name and its members.}}{1105}{figure.316.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {316.2}{\ignorespaces You use the Segment Editor to create new Segments.}}{1106}{figure.316.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {316.1}User Properties}{1106}{section.316.1}\protected@file@percent } \newlabel{user-properties}{{316.1}{1106}{User Properties}{section.316.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {316.2}Organization Properties}{1106}{section.316.2}\protected@file@percent } \newlabel{organization-properties}{{316.2}{1106}{Organization Properties}{section.316.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {316.3}Session Properties}{1107}{section.316.3}\protected@file@percent } \newlabel{session-properties}{{316.3}{1107}{Session Properties}{section.316.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {316.4}Operations and Conjunctions}{1107}{section.316.4}\protected@file@percent } \newlabel{operations-and-conjunctions}{{316.4}{1107}{Operations and Conjunctions}{section.316.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {317}Creating User Segments}{1109}{chapter.317}\protected@file@percent } \newlabel{creating-user-segments}{{317}{1109}{Creating User Segments}{chapter.317}{}} \@writefile{toc}{\contentsline {section}{\numberline {317.1}Creating a Custom Segment}{1109}{section.317.1}\protected@file@percent } \newlabel{creating-a-custom-segment}{{317.1}{1109}{Creating a Custom Segment}{section.317.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {317.1}{\ignorespaces Setting the comparator to \emph {contains} includes variations of ``Engineer'' like ``Software Engineer'' in the segment.}}{1110}{figure.317.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {317.2}{\ignorespaces You can prevent typos by directly selecting Organizations through the interface.}}{1110}{figure.317.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {317.2}Managing Segments}{1110}{section.317.2}\protected@file@percent } \newlabel{managing-segments}{{317.2}{1110}{Managing Segments}{section.317.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {317.3}{\ignorespaces You can view the list of Segment members at any time.}}{1111}{figure.317.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {317.4}{\ignorespaces You can edit, delete or manage permissions from the options menu.}}{1111}{figure.317.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {318}Creating Segments with Custom Fields and Session Data}{1113}{chapter.318}\protected@file@percent } \newlabel{creating-segments-with-custom-fields-and-session-data}{{318}{1113}{Creating Segments with Custom Fields and Session Data}{chapter.318}{}} \@writefile{toc}{\contentsline {section}{\numberline {318.1}Creating a Custom Field}{1113}{section.318.1}\protected@file@percent } \newlabel{creating-a-custom-field}{{318.1}{1113}{Creating a Custom Field}{section.318.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {318.1}{\ignorespaces You can easily create custom fields to capture whatever kind of data you need.}}{1114}{figure.318.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {318.2}Defining a Segment with a Custom Field}{1114}{section.318.2}\protected@file@percent } \newlabel{defining-a-segment-with-a-custom-field}{{318.2}{1114}{Defining a Segment with a Custom Field}{section.318.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {318.2}{\ignorespaces The custom field you created is seamlessly integrated into segment creation.}}{1115}{figure.318.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {318.3}Extending a Segment With Session Data}{1115}{section.318.3}\protected@file@percent } \newlabel{extending-a-segment-with-session-data}{{318.3}{1115}{Extending a Segment With Session Data}{section.318.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {319}Using Analytics Cloud With User Segments}{1117}{chapter.319}\protected@file@percent } \newlabel{using-analytics-cloud-with-user-segments}{{319}{1117}{Using Analytics Cloud With User Segments}{chapter.319}{}} \@writefile{toc}{\contentsline {section}{\numberline {319.1}Creating a New Segment}{1117}{section.319.1}\protected@file@percent } \newlabel{creating-a-new-segment}{{319.1}{1117}{Creating a New Segment}{section.319.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {319.2}Getting Segment Analytics}{1117}{section.319.2}\protected@file@percent } \newlabel{getting-segment-analytics}{{319.2}{1117}{Getting Segment Analytics}{section.319.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {319.1}{\ignorespaces When you see Analytics Cloud Segments in the list of Segments, they are marked with the Analytics Cloud icon.}}{1118}{figure.319.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {320}Personalization Experience Management}{1119}{chapter.320}\protected@file@percent } \newlabel{personalization-experience-management}{{320}{1119}{Personalization Experience Management}{chapter.320}{}} \@writefile{toc}{\contentsline {section}{\numberline {320.1}Managing Content Page Personalization}{1119}{section.320.1}\protected@file@percent } \newlabel{managing-content-page-personalization}{{320.1}{1119}{Managing Content Page Personalization}{section.320.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {320.1}{\ignorespaces You can add, edit, delete, or change priority for Experiences.}}{1120}{figure.320.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {320.2}{\ignorespaces You can add a new Segment while creating a new Experience.}}{1120}{figure.320.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {320.2}Managing Content Set Personalization}{1121}{section.320.2}\protected@file@percent } \newlabel{managing-content-set-personalization}{{320.2}{1121}{Managing Content Set Personalization}{section.320.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {320.3}{\ignorespaces Select a Segment to create a variation for.}}{1121}{figure.320.3}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {320.3}Previewing User Experiences}{1121}{section.320.3}\protected@file@percent } \newlabel{previewing-user-experiences}{{320.3}{1121}{Previewing User Experiences}{section.320.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {320.4}{\ignorespaces You can preview or delete a Personalized Variation from the \emph {Actions} menu.}}{1122}{figure.320.4}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {320.5}{\ignorespaces You can preview different experiences from the Preview Panel.}}{1122}{figure.320.5}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {321}Content Page Personalization}{1123}{chapter.321}\protected@file@percent } \newlabel{content-page-personalization-1}{{321}{1123}{Content Page Personalization}{chapter.321}{}} \@writefile{toc}{\contentsline {section}{\numberline {321.1}Creating the Default Page}{1123}{section.321.1}\protected@file@percent } \newlabel{creating-the-default-page}{{321.1}{1123}{Creating the Default Page}{section.321.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {321.1}{\ignorespaces Open Layouts from the Section Builder.}}{1124}{figure.321.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {321.2}Defining Custom Experiences}{1124}{section.321.2}\protected@file@percent } \newlabel{defining-custom-experiences}{{321.2}{1124}{Defining Custom Experiences}{section.321.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {321.2}{\ignorespaces Your final result might look something like this.}}{1125}{figure.321.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {321.3}{\ignorespaces Click on the current experience to create a new one or select a different existing experience.}}{1125}{figure.321.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {321.4}{\ignorespaces Your final result for the card prospects might look something like this.}}{1126}{figure.321.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {322}Content Set Personalization}{1127}{chapter.322}\protected@file@percent } \newlabel{content-set-personalization-2}{{322}{1127}{Content Set Personalization}{chapter.322}{}} \@writefile{toc}{\contentsline {section}{\numberline {322.1}Creating and Setting the Default Content Set}{1127}{section.322.1}\protected@file@percent } \newlabel{creating-and-setting-the-default-content-set}{{322.1}{1127}{Creating and Setting the Default Content Set}{section.322.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {322.1}{\ignorespaces Click \emph {Select} to add a new Asset Entries.}}{1128}{figure.322.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {322.2}Personalizing the Content Set}{1128}{section.322.2}\protected@file@percent } \newlabel{personalizing-the-content-set}{{322.2}{1128}{Personalizing the Content Set}{section.322.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {322.2}{\ignorespaces Create a new Personalized Variation.}}{1128}{figure.322.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {323}Recommending Content Based on User Behavior}{1129}{chapter.323}\protected@file@percent } \newlabel{recommending-content-based-on-user-behavior}{{323}{1129}{Recommending Content Based on User Behavior}{chapter.323}{}} \@writefile{lof}{\contentsline {figure}{\numberline {323.1}{\ignorespaces A user's interests are stored and accessible from Analytics Cloud.}}{1130}{figure.323.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {323.1}Adding Tags to Track User Behavior}{1130}{section.323.1}\protected@file@percent } \newlabel{adding-tags-to-track-user-behavior}{{323.1}{1130}{Adding Tags to Track User Behavior}{section.323.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {323.2}Displaying Content Based on User Behavior}{1131}{section.323.2}\protected@file@percent } \newlabel{displaying-content-based-on-user-behavior}{{323.2}{1131}{Displaying Content Based on User Behavior}{section.323.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {323.2}{\ignorespaces Enable Content Recommendation for your Content Set.}}{1131}{figure.323.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {324}A/B Testing}{1133}{chapter.324}\protected@file@percent } \newlabel{ab-testing}{{324}{1133}{A/B Testing}{chapter.324}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {325}Enabling A/B Testing}{1135}{chapter.325}\protected@file@percent } \newlabel{enabling-ab-testing}{{325}{1135}{Enabling A/B Testing}{chapter.325}{}} \@writefile{toc}{\contentsline {section}{\numberline {325.1}Setting A/B Testing Permissions}{1135}{section.325.1}\protected@file@percent } \newlabel{setting-ab-testing-permissions}{{325.1}{1135}{Setting A/B Testing Permissions}{section.325.1}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {326}Creating A/B Tests}{1137}{chapter.326}\protected@file@percent } \newlabel{creating-ab-tests}{{326}{1137}{Creating A/B Tests}{chapter.326}{}} \@writefile{lof}{\contentsline {figure}{\numberline {326.1}{\ignorespaces Fill out the form to create your A/B test.}}{1138}{figure.326.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {326.2}{\ignorespaces You now have an A/B test, but there are additional configurations you can apply.}}{1139}{figure.326.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {326.3}{\ignorespaces Set the click target to be tracked.}}{1140}{figure.326.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {326.4}{\ignorespaces Once the click target is set, you can run the A/B test.}}{1140}{figure.326.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {327}Running A/B Tests}{1141}{chapter.327}\protected@file@percent } \newlabel{running-ab-tests}{{327}{1141}{Running A/B Tests}{chapter.327}{}} \@writefile{lof}{\contentsline {figure}{\numberline {327.1}{\ignorespaces Configure the final parameters of your A/B test before running it.}}{1142}{figure.327.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {328}Monitoring A/B Test Results}{1143}{chapter.328}\protected@file@percent } \newlabel{monitoring-ab-test-results}{{328}{1143}{Monitoring A/B Test Results}{chapter.328}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {329}Publishing A/B Test Variants}{1145}{chapter.329}\protected@file@percent } \newlabel{publishing-ab-test-variants}{{329}{1145}{Publishing A/B Test Variants}{chapter.329}{}} \@writefile{lof}{\contentsline {figure}{\numberline {329.1}{\ignorespaces If you're satisfied with the A/B test's results, publish the winning Variant.}}{1146}{figure.329.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {329.2}{\ignorespaces Once you've published a Variant, the A/B test is complete.}}{1147}{figure.329.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {330}Content Publication Management}{1149}{chapter.330}\protected@file@percent } \newlabel{content-publication-management}{{330}{1149}{Content Publication Management}{chapter.330}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {331}Staging}{1151}{chapter.331}\protected@file@percent } \newlabel{staging}{{331}{1151}{Staging}{chapter.331}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {332}Enabling Staging}{1153}{chapter.332}\protected@file@percent } \newlabel{enabling-staging}{{332}{1153}{Enabling Staging}{chapter.332}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {333}Enabling Local Live Staging}{1155}{chapter.333}\protected@file@percent } \newlabel{enabling-local-live-staging}{{333}{1155}{Enabling Local Live Staging}{chapter.333}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {334}Enabling Remote Live Staging}{1157}{chapter.334}\protected@file@percent } \newlabel{enabling-remote-live-staging}{{334}{1157}{Enabling Remote Live Staging}{chapter.334}{}} \@writefile{lof}{\contentsline {figure}{\numberline {334.1}{\ignorespaces After your remote Liferay server and local Liferay server have been configured to communicate with each other, you have to specify a few Remote Live connection settings.}}{1158}{figure.334.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {335}Configuring Servers for Remote Live Staging}{1161}{chapter.335}\protected@file@percent } \newlabel{configuring-servers-for-remote-live-staging}{{335}{1161}{Configuring Servers for Remote Live Staging}{chapter.335}{}} \@writefile{toc}{\contentsline {section}{\numberline {335.1}Applying Patches When Using Remote Staging}{1163}{section.335.1}\protected@file@percent } \newlabel{applying-patches-when-using-remote-staging}{{335.1}{1163}{Applying Patches When Using Remote Staging}{section.335.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {335.2}Configuring Remote Staging's Buffer Size}{1163}{section.335.2}\protected@file@percent } \newlabel{configuring-remote-stagings-buffer-size}{{335.2}{1163}{Configuring Remote Staging's Buffer Size}{section.335.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {336}Enabling Page Versioning and Staged Content}{1165}{chapter.336}\protected@file@percent } \newlabel{enabling-page-versioning-and-staged-content}{{336}{1165}{Enabling Page Versioning and Staged Content}{chapter.336}{}} \@writefile{lof}{\contentsline {figure}{\numberline {336.1}{\ignorespaces You can decide to use versioning and choose what content should be staged.}}{1166}{figure.336.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {337}Publishing Staged Content Efficiently}{1169}{chapter.337}\protected@file@percent } \newlabel{publishing-staged-content-efficiently}{{337}{1169}{Publishing Staged Content Efficiently}{chapter.337}{}} \@writefile{toc}{\contentsline {section}{\numberline {337.1}Understanding the Publication Process}{1169}{section.337.1}\protected@file@percent } \newlabel{understanding-the-publication-process}{{337.1}{1169}{Understanding the Publication Process}{section.337.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {337.2}Planning Ahead for Staging}{1170}{section.337.2}\protected@file@percent } \newlabel{planning-ahead-for-staging}{{337.2}{1170}{Planning Ahead for Staging}{section.337.2}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {338}Using the Staging Environment}{1171}{chapter.338}\protected@file@percent } \newlabel{using-the-staging-environment}{{338}{1171}{Using the Staging Environment}{chapter.338}{}} \@writefile{lof}{\contentsline {figure}{\numberline {338.1}{\ignorespaces You can see the new staging options added to the top and left of your screen.}}{1171}{figure.338.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {339}Staging Content}{1173}{chapter.339}\protected@file@percent } \newlabel{staging-content}{{339}{1173}{Staging Content}{chapter.339}{}} \@writefile{lof}{\contentsline {figure}{\numberline {339.1}{\ignorespaces The staging toolbar indicates whether you're able to publish to the live site.}}{1174}{figure.339.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {339.2}{\ignorespaces The Simple Publication menu displays the changes since last publication and a way to name your publication.}}{1174}{figure.339.2}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {340}Advanced Publication with Staging}{1177}{chapter.340}\protected@file@percent } \newlabel{advanced-publication-with-staging}{{340}{1177}{Advanced Publication with Staging}{chapter.340}{}} \@writefile{toc}{\contentsline {section}{\numberline {340.1}Date}{1177}{section.340.1}\protected@file@percent } \newlabel{date}{{340.1}{1177}{Date}{section.340.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {340.2}Pages}{1177}{section.340.2}\protected@file@percent } \newlabel{pages-1}{{340.2}{1177}{Pages}{section.340.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {340.1}{\ignorespaces You have several ways to specify the pages you want included in your publication.}}{1178}{figure.340.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {340.3}Content}{1178}{section.340.3}\protected@file@percent } \newlabel{content}{{340.3}{1178}{Content}{section.340.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {340.4}Deletions}{1179}{section.340.4}\protected@file@percent } \newlabel{deletions}{{340.4}{1179}{Deletions}{section.340.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {340.5}Permissions}{1180}{section.340.5}\protected@file@percent } \newlabel{permissions}{{340.5}{1180}{Permissions}{section.340.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {341}Managing Content Types in Staging}{1181}{chapter.341}\protected@file@percent } \newlabel{managing-content-types-in-staging}{{341}{1181}{Managing Content Types in Staging}{chapter.341}{}} \@writefile{lof}{\contentsline {figure}{\numberline {341.1}{\ignorespaces Click the \emph {Change} button for a content group to manage its specific content.}}{1181}{figure.341.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {341.1}Referenced Content}{1182}{section.341.1}\protected@file@percent } \newlabel{referenced-content}{{341.1}{1182}{Referenced Content}{section.341.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {341.2}Version History}{1182}{section.341.2}\protected@file@percent } \newlabel{version-history}{{341.2}{1182}{Version History}{section.341.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {341.3}Previews and Thumbnails}{1182}{section.341.3}\protected@file@percent } \newlabel{previews-and-thumbnails}{{341.3}{1182}{Previews and Thumbnails}{section.341.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {341.4}Vocabularies}{1183}{section.341.4}\protected@file@percent } \newlabel{vocabularies}{{341.4}{1183}{Vocabularies}{section.341.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {341.5}Deletions}{1183}{section.341.5}\protected@file@percent } \newlabel{deletions-1}{{341.5}{1183}{Deletions}{section.341.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {342}Staging Processes and Templates}{1185}{chapter.342}\protected@file@percent } \newlabel{staging-processes-and-templates}{{342}{1185}{Staging Processes and Templates}{chapter.342}{}} \@writefile{lof}{\contentsline {figure}{\numberline {342.1}{\ignorespaces Your staging processes can be viewed at any time.}}{1185}{figure.342.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {343}Disabling Staging}{1187}{chapter.343}\protected@file@percent } \newlabel{disabling-staging}{{343}{1187}{Disabling Staging}{chapter.343}{}} \@writefile{toc}{\contentsline {section}{\numberline {343.1}Disabling Local Live Staging}{1187}{section.343.1}\protected@file@percent } \newlabel{disabling-local-live-staging}{{343.1}{1187}{Disabling Local Live Staging}{section.343.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {343.2}Disabling Remote Live Staging}{1187}{section.343.2}\protected@file@percent } \newlabel{disabling-remote-live-staging}{{343.2}{1187}{Disabling Remote Live Staging}{section.343.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {343.3}Steps to Disable Staging}{1188}{section.343.3}\protected@file@percent } \newlabel{steps-to-disable-staging}{{343.3}{1188}{Steps to Disable Staging}{section.343.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {344}Publishing Single Assets From a Staged Site}{1189}{chapter.344}\protected@file@percent } \newlabel{publishing-single-assets-from-a-staged-site}{{344}{1189}{Publishing Single Assets From a Staged Site}{chapter.344}{}} \@writefile{lof}{\contentsline {figure}{\numberline {344.1}{\ignorespaces You can publish the single web content article to the live site.}}{1190}{figure.344.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {345}Organizing Pages for Staging}{1193}{chapter.345}\protected@file@percent } \newlabel{organizing-pages-for-staging}{{345}{1193}{Organizing Pages for Staging}{chapter.345}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {346}Using Multi and Single Page Variations}{1195}{chapter.346}\protected@file@percent } \newlabel{using-multi-and-single-page-variations}{{346}{1195}{Using Multi and Single Page Variations}{chapter.346}{}} \@writefile{toc}{\contentsline {section}{\numberline {346.1}Enabling Page Versioning}{1196}{section.346.1}\protected@file@percent } \newlabel{enabling-page-versioning}{{346.1}{1196}{Enabling Page Versioning}{section.346.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {346.1}{\ignorespaces You can enable page versioning for public and/or private pages.}}{1196}{figure.346.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {346.2}Using Site Pages Variations}{1196}{section.346.2}\protected@file@percent } \newlabel{using-site-pages-variations}{{346.2}{1196}{Using Site Pages Variations}{section.346.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {346.2}{\ignorespaces Select the \emph {Enable} button to create a missing page in the current Site pages variation.}}{1197}{figure.346.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {346.3}Using Page Variations}{1197}{section.346.3}\protected@file@percent } \newlabel{using-page-variations}{{346.3}{1197}{Using Page Variations}{section.346.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {346.4}Managing Variation Permissions}{1198}{section.346.4}\protected@file@percent } \newlabel{managing-variation-permissions}{{346.4}{1198}{Managing Variation Permissions}{section.346.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {346.3}{\ignorespaces Configure the roles that can access and modify your variation.}}{1198}{figure.346.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {347}Creating Multi and Single Page Variations}{1199}{chapter.347}\protected@file@percent } \newlabel{creating-multi-and-single-page-variations}{{347}{1199}{Creating Multi and Single Page Variations}{chapter.347}{}} \@writefile{lof}{\contentsline {figure}{\numberline {347.1}{\ignorespaces When selecting the \emph {Site Pages Variation} link from the Staging Bar, you're able to add and manage your Site pages variations.}}{1199}{figure.347.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {348}Merging Site Pages Variations}{1201}{chapter.348}\protected@file@percent } \newlabel{merging-site-pages-variations}{{348}{1201}{Merging Site Pages Variations}{chapter.348}{}} \@writefile{lof}{\contentsline {figure}{\numberline {348.1}{\ignorespaces Select the site pages variation you'd like to merge with your base variation.}}{1201}{figure.348.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {349}Managing Permissions}{1203}{chapter.349}\protected@file@percent } \newlabel{managing-permissions}{{349}{1203}{Managing Permissions}{chapter.349}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {350}Scheduling Web Content Publication}{1205}{chapter.350}\protected@file@percent } \newlabel{scheduling-web-content-publication}{{350}{1205}{Scheduling Web Content Publication}{chapter.350}{}} \@writefile{lof}{\contentsline {figure}{\numberline {350.1}{\ignorespaces The web content scheduler can be easily accessed from the right panel of the page.}}{1205}{figure.350.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {351}Managing Apps}{1207}{chapter.351}\protected@file@percent } \newlabel{managing-apps}{{351}{1207}{Managing Apps}{chapter.351}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {352}Managing and Configuring Apps}{1209}{chapter.352}\protected@file@percent } \newlabel{managing-and-configuring-apps}{{352}{1209}{Managing and Configuring Apps}{chapter.352}{}} \@writefile{toc}{\contentsline {section}{\numberline {352.1}Managing Apps in Production}{1209}{section.352.1}\protected@file@percent } \newlabel{managing-apps-in-production}{{352.1}{1209}{Managing Apps in Production}{section.352.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {352.2}Using the App Manager}{1210}{section.352.2}\protected@file@percent } \newlabel{using-the-app-manager}{{352.2}{1210}{Using the App Manager}{section.352.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {352.1}{\ignorespaces The App Manager lets you manage the apps, modules, and components installed in your Liferay DXP instance.}}{1210}{figure.352.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {352.3}Using the Components Listing}{1211}{section.352.3}\protected@file@percent } \newlabel{using-the-components-listing}{{352.3}{1211}{Using the Components Listing}{section.352.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {352.2}{\ignorespaces The components listing lets you manage the portlets, themes, and layout templates installed in your Liferay DXP instance.}}{1211}{figure.352.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {352.3}{\ignorespaces You can activate or deactivate a component, and change its permissions.}}{1212}{figure.352.3}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {353}Using the Liferay Marketplace}{1213}{chapter.353}\protected@file@percent } \newlabel{using-the-liferay-marketplace}{{353}{1213}{Using the Liferay Marketplace}{chapter.353}{}} \@writefile{toc}{\contentsline {section}{\numberline {353.1}Finding and Purchasing Apps}{1213}{section.353.1}\protected@file@percent } \newlabel{finding-and-purchasing-apps}{{353.1}{1213}{Finding and Purchasing Apps}{section.353.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {353.1}{\ignorespaces The Liferay Marketplace home page lets you browse and search for apps.}}{1214}{figure.353.1}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {353.2}{\ignorespaces Click an app to view its details.}}{1215}{figure.353.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {353.2}Managing Purchased Apps}{1215}{section.353.2}\protected@file@percent } \newlabel{managing-purchased-apps}{{353.2}{1215}{Managing Purchased Apps}{section.353.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {353.3}{\ignorespaces You can manage your purchased apps from your liferay.com account's home page.}}{1216}{figure.353.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {353.4}{\ignorespaces You can also manage your purchased apps from within a running Liferay instance.}}{1216}{figure.353.4}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {353.3}Renewing a Purchased App}{1217}{section.353.3}\protected@file@percent } \newlabel{renewing-a-purchased-app}{{353.3}{1217}{Renewing a Purchased App}{section.353.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {354}Installing Apps Manually}{1219}{chapter.354}\protected@file@percent } \newlabel{installing-apps-manually}{{354}{1219}{Installing Apps Manually}{chapter.354}{}} \@writefile{toc}{\contentsline {section}{\numberline {354.1}Using the Control Panel to Install Apps}{1219}{section.354.1}\protected@file@percent } \newlabel{using-the-control-panel-to-install-apps}{{354.1}{1219}{Using the Control Panel to Install Apps}{section.354.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {354.2}Using Your File System to Install Apps}{1220}{section.354.2}\protected@file@percent } \newlabel{using-your-file-system-to-install-apps}{{354.2}{1220}{Using Your File System to Install Apps}{section.354.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {354.3}Manually Deploying an LPKG App}{1220}{section.354.3}\protected@file@percent } \newlabel{manually-deploying-an-lpkg-app}{{354.3}{1220}{Manually Deploying an LPKG App}{section.354.3}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {355}App Types}{1221}{chapter.355}\protected@file@percent } \newlabel{app-types}{{355}{1221}{App Types}{chapter.355}{}} \@writefile{toc}{\contentsline {section}{\numberline {355.1}OSGi Modules}{1221}{section.355.1}\protected@file@percent } \newlabel{osgi-modules}{{355.1}{1221}{OSGi Modules}{section.355.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {355.2}Portlets}{1222}{section.355.2}\protected@file@percent } \newlabel{portlets}{{355.2}{1222}{Portlets}{section.355.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {355.3}Web Plugins}{1222}{section.355.3}\protected@file@percent } \newlabel{web-plugins}{{355.3}{1222}{Web Plugins}{section.355.3}{}} \@writefile{toc}{\contentsline {section}{\numberline {355.4}Templates and Themes}{1222}{section.355.4}\protected@file@percent } \newlabel{templates-and-themes}{{355.4}{1222}{Templates and Themes}{section.355.4}{}} \@writefile{toc}{\contentsline {section}{\numberline {355.5}Liferay Marketplace App Packages}{1223}{section.355.5}\protected@file@percent } \newlabel{liferay-marketplace-app-packages}{{355.5}{1223}{Liferay Marketplace App Packages}{section.355.5}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {356}Blacklisting OSGi Bundles and Components}{1225}{chapter.356}\protected@file@percent } \newlabel{blacklisting-osgi-bundles-and-components}{{356}{1225}{Blacklisting OSGi Bundles and Components}{chapter.356}{}} \@writefile{toc}{\contentsline {section}{\numberline {356.1}Blacklisting Bundles}{1225}{section.356.1}\protected@file@percent } \newlabel{blacklisting-bundles}{{356.1}{1225}{Blacklisting Bundles}{section.356.1}{}} \gdef \LT@ii {\LT@entry {1}{196.74754pt}\LT@entry {1}{273.00746pt}} \@writefile{lof}{\contentsline {figure}{\numberline {356.1}{\ignorespaces This blacklist uninstalls the \texttt {com.liferay.docs.greeting.api} bundle, Liferay Marketplace LPKG, and \texttt {classic-theme} WAR.}}{1226}{figure.356.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {356.2}Reinstalling Blacklisted Bundles}{1226}{section.356.2}\protected@file@percent } \newlabel{reinstalling-blacklisted-bundles}{{356.2}{1226}{Reinstalling Blacklisted Bundles}{section.356.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {356.3}Blacklisting Components}{1227}{section.356.3}\protected@file@percent } \newlabel{blacklisting-components}{{356.3}{1227}{Blacklisting Components}{section.356.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {356.2}{\ignorespaces This blacklist disables the components \texttt {com.liferay.portal.security.ldap.internal.authenticator.LDAPAuth} and \texttt {com.liferay.ip.geocoder.sample.web.internal.portlet.IPGeocoderSamplePortlet}.}}{1228}{figure.356.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {356.4}Re-enabling Blacklisted Components}{1228}{section.356.4}\protected@file@percent } \newlabel{re-enabling-blacklisted-components}{{356.4}{1228}{Re-enabling Blacklisted Components}{section.356.4}{}} \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {357}Polls}{1229}{chapter.357}\protected@file@percent } \newlabel{polls}{{357}{1229}{Polls}{chapter.357}{}} \@writefile{toc}{\contentsline {section}{\numberline {357.1}Creating a Poll}{1229}{section.357.1}\protected@file@percent } \newlabel{creating-a-poll}{{357.1}{1229}{Creating a Poll}{section.357.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {357.1}{\ignorespaces Besides the Title and the Polls Question, you must enter data for each of the Choices fields when creating a new poll.}}{1230}{figure.357.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {357.2}Adding a Poll to a Page}{1230}{section.357.2}\protected@file@percent } \newlabel{adding-a-poll-to-a-page}{{357.2}{1230}{Adding a Poll to a Page}{section.357.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {357.2}{\ignorespaces These buttons provide shortcuts to the widget's configuration, as well as to some of the Polls Application's functionality.}}{1231}{figure.357.2}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {357.3}Viewing Poll Results}{1231}{section.357.3}\protected@file@percent } \newlabel{viewing-poll-results}{{357.3}{1231}{Viewing Poll Results}{section.357.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {357.3}{\ignorespaces Selecting a poll in the Polls portlet puts the data at your fingertips.}}{1232}{figure.357.3}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {357.4}{\ignorespaces This is what the vertical bar graph for the Lunar Resort poll results looks like.}}{1233}{figure.357.4}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {358}Using the Calendar}{1235}{chapter.358}\protected@file@percent } \newlabel{using-the-calendar}{{358}{1235}{Using the Calendar}{chapter.358}{}} \@writefile{toc}{\contentsline {section}{\numberline {358.1}Configuring the Calendar}{1235}{section.358.1}\protected@file@percent } \newlabel{configuring-the-calendar}{{358.1}{1235}{Configuring the Calendar}{section.358.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {358.1}{\ignorespaces The Setup → User Settings tab provides the options you need to get started quickly.}}{1236}{figure.358.1}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {359}Using the Calendar Widget}{1239}{chapter.359}\protected@file@percent } \newlabel{using-the-calendar-widget}{{359}{1239}{Using the Calendar Widget}{chapter.359}{}} \@writefile{lof}{\contentsline {figure}{\numberline {359.1}{\ignorespaces The default view is set in configuration, but a user can change it at any time.}}{1239}{figure.359.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {359.1}Adding New Calendars}{1240}{section.359.1}\protected@file@percent } \newlabel{adding-new-calendars}{{359.1}{1240}{Adding New Calendars}{section.359.1}{}} \@writefile{toc}{\contentsline {section}{\numberline {359.2}Adding Events to a Calendar}{1240}{section.359.2}\protected@file@percent } \newlabel{adding-events-to-a-calendar}{{359.2}{1240}{Adding Events to a Calendar}{section.359.2}{}} \@writefile{toc}{\contentsline {section}{\numberline {359.3}Additional Event Functions}{1240}{section.359.3}\protected@file@percent } \newlabel{additional-event-functions}{{359.3}{1240}{Additional Event Functions}{section.359.3}{}} \@writefile{lof}{\contentsline {figure}{\numberline {359.2}{\ignorespaces Personal and Site calendars are shown in the lower left. This image shows calendars belonging to User \emph {Test Test} and Site \emph {Liferay DXP}.}}{1241}{figure.359.2}\protected@file@percent } \@writefile{lof}{\contentsline {figure}{\numberline {359.3}{\ignorespaces When you click anywhere on the calendar, you'll see the event creation pop up appear. Click \emph {Edit} to specify details for your event.}}{1242}{figure.359.3}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Details}{1242}{section.359.3}\protected@file@percent } \newlabel{details-2}{{359.3}{1242}{Details}{section.359.3}{}} \@writefile{toc}{\contentsline {subsection}{Invitations}{1242}{section.359.3}\protected@file@percent } \newlabel{invitations}{{359.3}{1242}{Invitations}{section.359.3}{}} \@writefile{toc}{\contentsline {subsection}{Reminders}{1242}{Item.1549}\protected@file@percent } \newlabel{reminders}{{359.3}{1242}{Reminders}{Item.1549}{}} \@writefile{toc}{\contentsline {subsection}{Categorization}{1242}{Item.1549}\protected@file@percent } \newlabel{categorization-2}{{359.3}{1242}{Categorization}{Item.1549}{}} \@writefile{lof}{\contentsline {figure}{\numberline {359.4}{\ignorespaces You can specify event details such as the event title, start date, end date, description, location, and more.}}{1243}{figure.359.4}\protected@file@percent } \@writefile{toc}{\contentsline {subsection}{Related Assets}{1243}{Item.1549}\protected@file@percent } \newlabel{related-assets}{{359.3}{1243}{Related Assets}{Item.1549}{}} \@writefile{toc}{\contentsline {subsection}{Saving and Drafting Changes and Updating Permissions}{1243}{Item.1549}\protected@file@percent } \newlabel{saving-and-drafting-changes-and-updating-permissions}{{359.3}{1243}{Saving and Drafting Changes and Updating Permissions}{Item.1549}{}} \@writefile{lof}{\contentsline {figure}{\numberline {359.5}{\ignorespaces The \emph {Repeat} box allows you to specify whether an events repeats daily, weekly, monthly, or yearly, how often it repeats, and when (or if) it ends.}}{1244}{figure.359.5}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {359.4}Customizing Email Notifications}{1245}{section.359.4}\protected@file@percent } \newlabel{customizing-email-notifications}{{359.4}{1245}{Customizing Email Notifications}{section.359.4}{}} \@writefile{lof}{\contentsline {figure}{\numberline {359.6}{\ignorespaces Email templates apply to a single calendar and all its events.}}{1246}{figure.359.6}\protected@file@percent } \@writefile{lof}{\addvspace {10pt}} \@writefile{lot}{\addvspace {10pt}} \@writefile{toc}{\contentsline {chapter}{\chapternumberline {360}Calendar Resources and Porting}{1247}{chapter.360}\protected@file@percent } \newlabel{calendar-resources-and-porting}{{360}{1247}{Calendar Resources and Porting}{chapter.360}{}} \@writefile{toc}{\contentsline {section}{\numberline {360.1}Calendar Resources}{1247}{section.360.1}\protected@file@percent } \newlabel{calendar-resources}{{360.1}{1247}{Calendar Resources}{section.360.1}{}} \@writefile{lof}{\contentsline {figure}{\numberline {360.1}{\ignorespaces Resources are accessed from the tab menu at the top of the widget.}}{1247}{figure.360.1}\protected@file@percent } \@writefile{toc}{\contentsline {section}{\numberline {360.2}Exporting and Importing Calendar Data}{1248}{section.360.2}\protected@file@percent } \newlabel{exporting-and-importing-calendar-data}{{360.2}{1248}{Exporting and Importing Calendar Data}{section.360.2}{}} \@writefile{lof}{\contentsline {figure}{\numberline {360.2}{\ignorespaces This LAR is ready to be downloaded.}}{1248}{figure.360.2}\protected@file@percent } \@setckpt{user/user}{ \setcounter{page}{1250} \setcounter{equation}{0} \setcounter{enumi}{7} \setcounter{enumii}{0} \setcounter{enumiii}{0} \setcounter{enumiv}{0} \setcounter{footnote}{0} \setcounter{mpfootnote}{0} \setcounter{@memmarkcntra}{0} \setcounter{storedpagenumber}{1} \setcounter{book}{0} \setcounter{part}{1} \setcounter{chapter}{360} \setcounter{section}{2} \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}{1298} \setcounter{lastsheet}{1795} \setcounter{lastpage}{1747} \setcounter{figure}{2} \setcounter{lofdepth}{1} \setcounter{table}{0} \setcounter{lotdepth}{1} \setcounter{Item}{1566} \setcounter{Hfootnote}{5} \setcounter{bookmark@seq@number}{0} \setcounter{memhycontfloat}{0} \setcounter{mem@Hpagenote}{0} \setcounter{r@tfl@t}{0} \setcounter{float@type}{4} \setcounter{LT@tables}{2} \setcounter{LT@chunks}{3} \setcounter{@anim@ltxcnt}{0} \setcounter{parentequation}{0} \setcounter{FancyVerbLine}{0} } ================================================ FILE: book/user/user.tex ================================================ \chapter{The Liferay Distinction}\label{the-liferay-distinction} Your web presence is a big deal, and the software platform that runs it must be up to the task. You must be able to update your site easily, to provide your partners with the services they need, and to enable your users and customers to interact with you. It shouldn't be complicated: in today's cloud-based environment, you should be able to make changes with a click of a button and dynamically allocate nodes whenever you need them. Liferay DXP offers all of this and more. It is a mature, stable, open source platform with a strong heritage of serving some of the biggest sites in the world---and some of the smallest, too. The documentation here shows you how to use it. This is the \textbf{User} section, which describes the features of an installed Liferay DXP, how to configure its applications, and how to build your website. The \textbf{Developer} section is divided into five subsections: \href{/docs/7-2/appdev/-/knowledge_base/a/application-development}{\textbf{Application Development}} shows you how you can build applications using industry standard tools and frameworks on Liferay DXP. \href{/docs/7-2/tutorials/-/knowledge_base/t/developer-tutorials}{\textbf{Tutorials}} lead you step-by-step through specific tasks, such as developing web applications, upgrading old applications, creating themes, and more. \href{/docs/7-2/frameworks/-/knowledge_base/f/frameworks}{\textbf{Frameworks}} show you all the Liferay APIs and development frameworks you can use in your applications to streamline development. \href{/docs/7-2/customization/-/knowledge_base/c/liferay-customization}{\textbf{Customization}} explains the myriad of ways Liferay DXP can be customized to your exact specifications. \href{/docs/7-2/reference/-/knowledge_base/r/developer-reference}{\textbf{Reference}} is a collection of material showing developers the options available for various APIs. The \href{https://learn.liferay.com/dxp/latest/en/installation-and-upgrades.html}{\textbf{Installation and Upgrades}} section shows administrators how to obtain, install, and configure Liferay DXP on bare metal, virtualized environments, and the cloud. If you're coming from an older version, read on to learn what's new in 7.2. \chapter{What's New in Liferay 7.2!}\label{whats-new-in-liferay-7.2} The latest version of Liferay DXP delivers powerful tools to help businesses create and personalize any experience across their solutions, leverage the flexibility of a decoupled CMS architecture, and streamline business operations. Liferay DXP meets the needs of today's digital-first business teams to create experiences rapidly across channels. It equips enterprises with a wide variety of easy-to-use applications and tools to build tailored solutions and experiences on a flexible platform. For a full feature list, please read the \href{https://www.liferay.com/resource?folderId=3292406&title=Liferay+DXP+7.2+Features+Overview&utm_source=whitepaper&utm_medium=content&utm_content=liferay\%20dxp\%207.2\%20new\%20features\%20summary}{\emph{Liferay DXP 7.2 Features Overview}} or contact sales@liferay.com. \section{Key New Features}\label{key-new-features} Ensure your business evolves and stays relevant with new features designed to support great customer experiences across all stages of the customer journey. \section{Experience Creation}\label{experience-creation} Great digital experiences are imperative in today's competition for new business. Companies also need to make sure they stay relevant after the sale, investing in long-term customer relationships that cultivate loyalty and repeat business. Here are some new features to help you ensure you're creating and delivering excellent experiences all the time. \subsection{Content Authoring}\label{content-authoring} 7.0 evolves our content authoring and management capabilities significantly, making it even easier and more intuitive for the non-technical content creator to create and manage engaging content. \begin{figure} \centering \includegraphics{./images/build-pages.png} \caption{Content authors can build pages out of building blocks called \emph{Fragments}.} \end{figure} \subsection{Site Building}\label{site-building} Content pages now allow users to create experiences that have both applications and content on the same page, giving businesses increased flexibility to create experiences tailored to fit the needs of different end users. More tools have been provided to allow for richer layouts and functionality for the non-technical user to build a visually appealing site experience easily. \subsection{Fragments}\label{fragments} This release expands on the functionality available to create fragments, which previously required developer involvement. For marketers looking to create a fragment for their use case, we provide new functionality that allows a non-technical user to create simple fragments easily through the page editor itself by dragging and dropping out-of-the-box components into a container. \begin{figure} \centering \includegraphics{./images/fragments-pages.png} \caption{Fragments make it easy to build pages.} \end{figure} \subsection{Fragments Toolkit/CLI}\label{fragments-toolkitcli} Improvements to the web developer's experience has also been made for 7.0. A set of front-end toolkits---including a CLI tool---is provided so web developers can write fragments in their own code editors and import/export them without needing to redeploy. \begin{figure} \centering \includegraphics{./images/fragments-toolkit.png} \caption{Use the Fragments Editor or download the toolkit and use your own tools.} \end{figure} \subsection{In-Context Editing and Content Previews}\label{in-context-editing-and-content-previews} An improved site building interface shows users how different content would appear to different visitors. Liferay DXP allows content creators to preview content in context of how it would look like on a live site. \subsection{Content Usages}\label{content-usages} For structured content that can be mapped and delivered across multiple locations, a new feature has been introduced to allow the content creator to view where specific pieces of web content are being used and reused across their channels. \subsection{A/B Testing (DXP only)}\label{ab-testing-dxp-only} Content creators can use A/B testing to create and customize tests to evaluate which elements on Content Pages perform better and edit content accordingly. Liferay DXP's A/B tests leverage Bayesian statistics to identify the probable values of lift for a variant, allowing your business to make more informed decisions. Native integration with Liferay Analytics Cloud allows for data collected for the running test to determine a winning variant. \section{Personalization}\label{personalization} In 7.0, segmentation and personalization capabilities have been moved into the core product. This allows for a more seamless integration of content creation and personalization functionality and helps streamline the process of creating segments while creating personalized experiences. \subsection{Session Rules}\label{session-rules} There are many different pieces of information available to track a web visitor. Session rules help further identify their distinct digital fingerprint per visit and is something that marketers can leverage to identify who is a repeat visitor and who is a new visitor. \subsection{Rule Builder}\label{rule-builder} Being able to sort intelligently through vast amounts of data and associated individuals can be tedious; this process should be easy and not have to be curated manually. Marketers should instead be able to build lists of individuals based on a specific set of criteria using rules, leading to time savings and efficiency. The rule builder helps enable more effective targeting by reducing the amount of manual work needed to create a specific list of people who fall under certain pieces of criteria. \begin{figure} \centering \includegraphics{./images/rule-builder.png} \caption{The Rule Builder provides a drag-and-drop interface that helps you build exactly the criteria you need to target the right information to the right users.} \end{figure} \subsection{Content Sets}\label{content-sets} Content sets help address the use case of personalizing a feed of content for different groups of people. Users can easily target and personalize multiple pieces of content in a fragment or widget and have it display accordingly for the visitor. \subsection{Experiences}\label{experiences} The new Experiences feature allows a user to create different variations of a page. All the tools available for creating a page in the layout editor are also available for making these page variations. Non-technical users can easily tailor the messaging, images and even widgets on a page. \subsection{Content Recommendations (DXP only)}\label{content-recommendations-dxp-only} Content recommendations leverages interest models generated in Liferay Analytics Cloud to recommend content on Liferay DXP. The option to do so is available through Content Sets and automatically filters content based on interest keywords. Liferay Analytics Cloud uses AI to cluster topics and model long-term interest for known and unknown visitors. Keywords are taken from the categories, tags, and keywords of content. \section{Bulk Management}\label{bulk-management} Enterprises regularly manage large volumes of content and work that gets generated on their digital properties. The need to manage and accurately categorize these assets can be overwhelming, if not impossible, to maintain and use. Bulk management features provided in 7.0 help reduce the amount of manual labor through automation and tools that perform bulk actions. \subsection{Auto-Tagging}\label{auto-tagging} Users can automatically assign the correct metadata for images, documents and web content. Having rich metadata assigned to the proper content helps to not only reduce the amount of manual, tedious work, but also improves the searchability of assets. The resulting metadata that accumulates helps establish the foundation needed for content personalization efforts and content automation. An Auto-Tagging API lets developers extend the functionality to tag any asset with any selected auto-tagging service. \subsection{Bulk Operations}\label{bulk-operations} Improvements have been made to make the experience of managing tags, categories, and file operations much easier at scale. \subsection{Automatic Document Versioning Policies}\label{automatic-document-versioning-policies} Refinements to how documents can be versioned are provided in 7.2. The functionality for document versioning has been expanded to let users define their own versioning policies and apply them to Liferay DXP. This is in addition to the existing default versioning system Liferay DXP provides out-of-the-box. \section{Business Operations}\label{business-operations} Digital enterprises today must possess efficient back office organization to support the delivery of great customer experiences. It is crucial to fulfill these customer interactions and ensure those experiences are delivered and supported in a timely manner. Achieving this kind of efficiency, for a great customer experience, requires involvement from different departments and stakeholders and affects a lot of disparate content and processes. 7.0 helps enterprises navigate these problems with features to help support streamlining and optimizing those processes and operations. \subsection{Forms API}\label{forms-api} Reduce the time it takes for IT to deliver custom applications for the business. Automate data collection through applications. \subsection{Workflow Metrics}\label{workflow-metrics} Workflow metrics helps users gain insight into how long certain workflow events take to complete. Users can set deadlines on a workflow process's events; these configurations are called Service Level Agreements (SLAs). Once defined, use Workflow Reports to measure compliance with the SLAs. The analytics and metrics generated can help you understand the throughput performance of your processes in a given timeframe, allowing users to better optimize their processes. \subsection{Workflow Reports}\label{workflow-reports} Cut down the time it takes to serve your customers. New reports for workflow processes help users identify operational bottlenecks and work efficiently to increase revenue goals. \subsection{Online Document Editing}\label{online-document-editing} Integration with Office 365 and Google Docs allows the creation and editing of documents stored in Liferay DXP. Users can manage documents, presentations, and spreadsheets with the power that the suite provides and store them in Liferay DXP's document repository for future access. This feature takes advantage of the existing permissions system, versioning, and sharing capabilities already included with 7.0. \subsection{P2P Asset Sharing}\label{p2p-asset-sharing} In addition to being able to create and edit documents easily, users can now comment and share documents with other registered users. This allows for faster coordination without the need of assigning custom permissions. \begin{figure} \centering \includegraphics{./images/share-assets.png} \caption{You can share assets with other users easily.} \end{figure} \subsection{Improved Identity Management Tools}\label{improved-identity-management-tools} New improvements have also been made to help tackle tedious tasks of handling personal data erasure requests. Logical hierarchies for assets are now in place to help simplify bulk deletion and anonymization during the review process. Filtering and scoping capabilities across Liferay DXP help provide greater context when reviewing personal data, and administrators can be informed when specific applications will delete or anonymize data during the auto-anonymization process. All of these improvements help ensure compliance with GDPR and other data protection laws. \section{Headless Capabilities}\label{headless-capabilities} Give your developers the freedom to create any presentation layer and content creators the ease of traditional content management tools. 7.0 provides a decoupled architecture to allow for a range of approaches towards content management and delivery. Enterprises can choose a traditional CMS or experience management approach, a full headless approach or a hybrid approach. \subsection{OpenAPI}\label{openapi} Liferay's API layer supports the \href{https://swagger.io/resources/open-api/}{OpenAPI} specification, the most popular open source framework for REST APIs. This allows for greater flexibility, security and ease of integration as the OpenAPI specification is language-agnostic, allows for security audits, and clients can understand and consume services without knowledge of server implementation or access to the server code. \subsection{Headless CMS}\label{headless-cms} Enable developers to manage web content headlessly. Headless capabilities in this version are also provided to help enable omnichannel experiences from a single source of data. Content delivery APIs allow for developers to deliver richer, faster and more responsive user experiences, no matter the device. Front-end developers can leverage their native tools and frameworks they're already familiar with to build sites. These APIs provide access to a variety of assets and content in Liferay DXP. Content participation APIs are also provided to support user interactions with comments and ratings for web content and assets. \section{Developer Experience}\label{developer-experience} In addition to the new headless capabilities that are provided on 7.0, improvements to developer tools and the developer experience has been made. These tools help developers accelerate time to market and tailor the platform for your business needs. \subsection{Upgrade Tool}\label{upgrade-tool} A revamped Upgrade Tool simplifies the upgrade process from previous versions of Liferay DXP. This all-in-one tool allows system administrators to reduce the time spent troubleshooting by helping them plan their upgrade process for database migrations, checking properties, and restarting failed upgrades. \subsection{Front-end Toolkits}\label{front-end-toolkits} Front-end toolkits allow developers to create an application using their favorite JavaScript framework. The toolkit resolves any conflicting JavaScript packages and bundles the application properly to deploy it into Liferay DXP seamlessly. \subsection{Struts Removed}\label{struts-removed} Though Liferay DXP never used a version of Struts with security vulnerabilities, the Struts library has been removed, and it is no longer a part of the product. \section{What's Next}\label{whats-next} Learn more about how Liferay can help your business take the next step in your digital strategy. Request a demo from one of our team members at \href{https://www.liferay.com/request-a-demo?utm_source=whitepaper\%20&utm_medium=content&utm_content=liferay\%20dxp\%207.2\%20new\%20features\%20summary}{\emph{liferay.com/request-a-demo}}. \chapter{Setting Up}\label{setting-up} If Liferay DXP is anything, it's configurable. As the core is shrinking due to its increased modularity, it's important that all the applications in Liferay are also configurable. Breaking it down, three types of applications must be configurable: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item The platform itself \item Liferay's out-of-the-box applications \item Custom applications \end{enumerate} To this end, Liferay's engineers have made the platform and its applications configurable, and created a mechanism for developers to make their \href{/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications}{applications configurable}. \chapter{Where Configuration Happens}\label{where-configuration-happens} Liferay's configuration takes place in the following places: \textbf{User Interface:} configuration through Liferay's UI is stored in a database. The values set in the UI always override configurations set in properties files. \textbf{Properties files:} properties files that set default behavior may be included in the platform or the modules. Keep in mind that these settings can always be overridden by a system administrator in the UI. To find what properties are configurable this way, visit \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc}{docs.liferay.com}. The UI location where these configuration options appear depends on the scope you want to affect with the settings you choose. \chapter{Configuration Scope}\label{configuration-scope} Depending on the configuration scope of a setting you change, you'll impact the platform and its applications with more or less granularity. At one end of the spectrum, you can affect the scope of the whole system. Configurations made at the system scope affect all virtual instances, Sites, and widgets. At the opposite end of the spectrum, configurations made at the widget level provide configuration settings only for that instance of the widget. Take Language settings, for example. You can set the default language used by the virtual instance. You can also set the default language of a Site. Some applications even let you set the default language used by a specific piece of content. Here's an overview of the available configuration scopes: \textbf{System:} Configuring Liferay and its applications through System Settings provides system scoped configurations and sets default values for all virtual instances, sites, or widgets. \textbf{Virtual Instance:} Configuring in Instance Settings provides settings that act on the specific virtual instance for which they are made, including Sites and widgets in the virtual instance. \textbf{Site:} Configurations made at the Site scope, where you select the Site to configure in the Site selector, provide settings that take place only in that Site. Alternate configurations can be made in different Sites. \textbf{Widget Scope:} Configuring a specific widget only provides a configuration for that particular widget. Scopes in Liferay are hierarchical so that one scope can set the default values for the underlying sub-scopes. For example, making a system-wide configuration sets the default values for all virtual instances, sites or widgets of the system. If a different configuration is set at a level with more granularity (for example, the widget scope), it takes precedence over the settings made at less granular scopes (for example, the virtual instance scope). This section contains articles on configuring Liferay at the System and Instance scopes: System wide configuration: \begin{itemize} \item System Settings is the primary location for system configuration. \item Server Administration contains some lower-level server configuration options, such as logging. \end{itemize} Setting up a virtual instance: \begin{itemize} \item Virtual Instances is where virtual instances are added and edited. \item Instance Settings is the primary location for a virtual instance's configuration. \item Custom Fields is where additional database fields are added to existing virtual instance entities. \end{itemize} All of these are accessed through the Control Panel. Start by learning to configure modules system-wide in the System Settings Control Panel app. \chapter{System Wide Settings}\label{system-wide-settings} It can be hard to keep track of all the configuration interfaces. The Control Panel's Configuration section houses a lot of the higher level (for example, system and instance scoped) configuration options. This section considers the configuration options dealing with the \emph{System} scope. Configuration at the system level affects all the \href{/docs/7-2/user/-/knowledge_base/u/setting-up-a-virtual-instance}{\emph{Virtual Instances}} of Liferay in the system. \begin{itemize} \item System Settings is the primary location for system configuration. \item Server Administration contains some lower-level server configuration options, such as logging. \end{itemize} Get started by learning about System Settings. \chapter{System Settings}\label{system-settings} Liferay DXP is modular, meaning it's composed of many applications divided into even smaller ``chunks'' of functionality. The applications, and sometimes even code chunks, are configurable at several scopes, as discussed in the introductory article for this section. In System Settings, administrators make system scoped configuration changes and set system-wide default configurations for other scopes. System Settings is located in Control Panel → Configuration → System Settings. \begin{figure} \centering \includegraphics{./images/system-settings-categories.png} \caption{System Settings are accessed through the Control Panel.} \end{figure} \section{Editing System Configurations}\label{editing-system-configurations} System Settings is organized into sections (for example, Content) and categories (for example, Workflow) based on the functionality being configured. There's also a Search bar to make finding configuration entries easier. Search for the name of a specific configuration entry, or even a specific field within an entry. \begin{figure} \centering \includegraphics{./images/system-settings-nav-search.png} \caption{System Settings are organized by section and category.} \end{figure} Changing a configuration isn't difficult: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Find the configuration entry you need, either by searching or browsing the sections and categories. \item Open the configuration form for the entry. \item Make any changes you'd like, then click \emph{Save}. Your configuration changes are saved and applied throughout the system. \end{enumerate} \noindent\hrulefill \textbf{Important:} Content generated using templates (e.g., FreeMarker templates and Application Display Templates) is cached. Cached content might not reflect configuration changes until the cache is invalidated (cleared). The \href{/docs/7-2/user/-/knowledge_base/u/server-administration-resources}{Server Administration → Resources tab} provides cache clearing options. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/system-settings-actions.png} \caption{After saving changes to a configuration, the actions \emph{Reset Default Values} and \emph{Export} are available for it.} \end{figure} If you make some regrettable configuration decisions and can't recall exactly what you did, start over by clicking the actions button (\includegraphics{./images/icon-actions.png}), then clicking \emph{Reset Default Values}. \section{Configuration Scope}\label{configuration-scope-1} While browsing the categories of System Settings, you'll notice that clicking into a topic (for example, Blogs) reveals entries at different scopes. All the settings here act at the system scope. For scopes labeled other than System, these configurations act as defaults. In other words, they identify where the system-wide configuration is overridden. True system-scoped configurations (those under a category's \emph{System Scope} heading) are not overridden anywhere. There are four values that you'll see under Scope: \begin{itemize} \item \emph{System Scope:} Any System Settings configuration made for system scoped entries becomes the final value for the application in a system-wide fashion. It affects the whole system and isn't overridden anywhere else. \begin{figure} \centering \includegraphics{./images/system-settings-system-scope.png} \caption{Some System Settings entries are system scoped.} \end{figure} \item \emph{Virtual Instance Scope:} Configuration at the Virtual Instance level is overridden in Instance Settings. \begin{figure} \centering \includegraphics{./images/system-settings-instance-scope.png} \caption{Some System Settings are virtual instance scoped.} \end{figure} \item \emph{Site Scope:} Configuration at this scope is overridden in each site. \begin{figure} \centering \includegraphics{./images/system-settings-site-scope.png} \caption{Some System Settings are site scoped.} \end{figure} \item \emph{Widget Scope:} Configuration at this scope is overridden in each Widget Instance (like the Blogs example below). \begin{figure} \centering \includegraphics{./images/system-settings-application-scope.png} \caption{Some System Settings entries are widget scoped.} \end{figure} \end{itemize} If a configuration changed in System Settings is also configurable at a different scope, the System Settings value acts as a default that can be overridden. Once a configuration change is made at a more granular scope, making a change at the system level doesn't do anything. For example, allowing comments is configurable for each Blogs Entry. Set the default behavior at Control Panel → Configuration → System Settings → Blogs. In the Blogs Entry under Widget Scope, disable the \emph{Enable Comments} checkbox. Now add a Blog Entry to a Site's Content \& Data → Blogs application. Then go to a public page and add the Blogs Widget to the page. Click the Options button (\includegraphics{./images/icon-app-options.png}) for the widget and select \emph{Configuration}. You'll see the same Enable Comments checkbox, and its default is now false (unchecked). Checking the box in the Widget Configuration screen breaks its link with the System Settings entry. Changing the System Settings configuration has no effect on this widget anymore. \section{Exporting and Importing Configurations}\label{exporting-and-importing-configurations} What if you change many default configurations in System Settings, and then need to make the same changes in another installation? Don't worry, you don't need to remember and duplicate every choice you made. The System Settings application lets you export a single entry's configurations, or all the settings you made in the System Settings interface. The exported files are deployable to any other installation of the same version. To export a single entry's configurations, click the actions button (\includegraphics{./images/icon-actions.png}), then click \emph{Export}. A \texttt{.config} file containing your configuration downloads to your system. To export all the configuration changes you've made in System Settings, click the System Settings options button (\includegraphics{./images/icon-options.png}), then click \emph{Export All Settings}. The \texttt{.config} files for all the entries you edited then download in a ZIP file. To make these configurations active in the destination system, unzip and place the \texttt{.config} files in the \texttt{{[}Liferay\_Home{]}/osgi/configs} folder. Now you know what System Settings is and how to use it. All that's left is to explore the entries to see what configuration options are available. If you aren't sure what something does, check the documentation for the feature you're interested in, as specific configurations are covered there. \chapter{Understanding System Configuration Files}\label{understanding-system-configuration-files} The \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System Settings application} is convenient for making system-scoped configuration changes and setting default configurations for other \href{/docs/7-2/user/-/knowledge_base/u/setting-up\#configuration-scope}{scopes}. But there's another supported configuration approach: configuration files. You can use configuration files to transfer configurations from pre-production systems to production systems, or between any other Liferay DXP systems. Sometimes developers choose to distribute the default configuration for their applications via configuration file. Whatever the reason, configuration files offer another configuration approach. Configuration files use the \texttt{.config} property value format defined by the \href{http://felix.apache.org/documentation/subprojects/apache-felix-config-admin.html}{Apache Felix Configuration Admin framework}. \noindent\hrulefill \textbf{Important:} Content generated using templates (e.g., FreeMarker templates and Application Display Templates) is cached. Cached content might not reflect configuration changes until the cache is invalidated (cleared). The \href{/docs/7-2/user/-/knowledge_base/u/server-administration-resources}{Server Administration → Resources tab} provides cache clearing options. \textbf{Note:} The \texttt{.cfg} file format is common in OSGi environments, and it's a supported format, but \texttt{.config} files are preferable since they allow specifying a property value's type, and allow multi-valued properties. The syntax described in these articles is for \texttt{.config} files. \chapter{Creating Configuration Files}\label{creating-configuration-files} System Settings provides an \href{/docs/7-2/user/-/knowledge_base/u/system-settings\#exporting-and-importing-configurations}{\emph{Export}} option that becomes available once you modify a configuration entry. Exporting is the recommended way to create \texttt{.config} files: you download a \texttt{.config} file containing the entry's settings in a \texttt{key=value} format. Liferay DXP exports an entry's total available configuration keys and values, even if only one value was changed. You can export a single configuration entry or the entire set of modified configurations. To avoid a file name conflict, name configuration files using a unique identifier. For example, the Journal Service entry, which backs Web Content functionality, has this file name: \begin{verbatim} com.liferay.journal.configuration.JournalServiceConfiguration.config \end{verbatim} \begin{figure} \centering \includegraphics{./images/config-web-content-entry.png} \caption{The Web Content System Settings entry has the back-end ID \texttt{com.liferay.journal.configuration.JournalServiceConfiguration}.} \end{figure} \section{Key/Value Syntax}\label{keyvalue-syntax} The syntax for all keys and values in a \texttt{.config} file is the same: \begin{verbatim} configurationName="value" \end{verbatim} For single value configurations without special characters, that's all there is to know. Settings with multiple values and certain characters require slight modifications. \section{Multi-Value Settings}\label{multi-value-settings} Configuration entries can have properties that accept multiple values. For example, a configuration property for specifying supported file extensions needs more than one value. Here's how to write a multi-value setting in a \texttt{.config} file: \begin{verbatim} multiValueSetting=["Value 1","Value 2", ...] \end{verbatim} Do not use a space character between values (after the comma). The property won't be loaded. Open the Web Content category in System Settings (under the Content section), and select \emph{Web Content} for the virtual instance scope. You'll see what looks like multiple single value entries for \emph{Characters Blacklist}: \begin{figure} \centering \includegraphics{./images/config-web-content-blacklist.png} \caption{The Web Content System Settings entry has many \emph{Characters Blacklist} fields.} \end{figure} In the configuration file, this is really a single key with an array of comma-separated values: \begin{verbatim} charactersblacklist=["&","'","@","\\","]","}",":","\=",">","/","<","[","{","%","+","#","`","?","\"",";","*","~"] \end{verbatim} \section{Escaping Characters}\label{escaping-characters} Double quotes (\texttt{"}) and equals signs (\texttt{=}) must be \emph{escaped} in \texttt{.config} files. Escaping is using another character to denote that a character shouldn't be used in its normal way. Since double quotes and equals signs are already used in \texttt{.config} files, escaping them tells the framework not to read them the normal way, but to pass them through as part of the value. Use a backslash to escape characters in the \texttt{.config} file: \begin{verbatim} charactersblacklist=["&","\"","\="] \end{verbatim} This setting illustrates a multi-value setting with a regular, unescaped character (\texttt{\&}), and two escaped ones (\texttt{\textbackslash{}"} and \texttt{\textbackslash{}=}). Along with the mandatory escaping of double quotes and equals characters, it's beneficial to escape spaces inside values to avoid problems. In this example, a backslash is used before each space character to ensure it's read and processed properly: \begin{verbatim} blacklistBundleSymbolicNames=["Liferay\ Marketplace","Liferay\ Sharepoint\ Connector"] \end{verbatim} If you don't escape spaces yourself, the framework adds the backslash for you after deployment. \section{Typed Values}\label{typed-values} The \texttt{.config} file format supports specifying the type of a configuration value by inserting a special type marker character. Because Liferay DXP already knows the correct type for each configuration property, the type characters are only useful for informational purposes. For example, a configuration with a boolean type has \emph{B} just before the value to mark it as a boolean type: \begin{verbatim} addDefaultStructures=B"true" \end{verbatim} If you see type markers in \texttt{.config} files, you can safely ignore them. The example included above functions identically without the type marker: \begin{verbatim} addDefaultStructures="true" \end{verbatim} \section{Deploying a Configuration File}\label{deploying-a-configuration-file} Once you have a configuration file, deploy it. It's registered and the targeted configuration values are updated automatically. To deploy the \texttt{.config} file, place it in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home's} \texttt{osgi/configs} folder. To change the configuration further, you can edit the \texttt{.config} file directly or use System Settings. \section{Configuration Files and Clustering}\label{configuration-files-and-clustering} In a clustered environment, each node needs the same configuration values for each entry. For example, all nodes should use the same \emph{Blogs} configuration settings. To accomplish this, deploy copies of the same \texttt{.config} file to each node's \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. --- header-id: factory-configurations Factory Configurations {[}TOC levels=1-4{]} Configurations supporting multiple entries are called factory configurations. Factory Configuration Example: JAX-WS and JAX-RS web services are \textbar{} supported. These services must use a CXF Endpoint, which is a context path where \textbar{} the web services are deployed and accessed. Endpoints can be created via \textbar{} factory configuration by navigating to the CXF Endpoints entry in System \textbar{} Settings (System Settings → Platform → Web API → CXF Endpoints). \textbar{} Click ADD, enter the desired configuration values, then repeat the process to \textbar{} add as many CXF Endpoint configurations as needed. Creating CXF Endpoint \textbar{} configurations also creates CXF Endpoints themselves. This is how factory \textbar{} configurations work. If your service supports factory configurations, use the convention of calling the configuration's first instance -default.config: The next instance contains a unique subname (something other than default). It's good practice to use a descriptive name: To follow the CXF Endpoints example described above, if Liferay's developers had shipped an initial CXF Endpoint .config file with Liferay DXP, it would have been named this: If this -default.config configuration specifies a context path for REST web services, and you create another endpoint with a different context path for SOAP web services, your second configuration file could be named: Note: Some System Settings entries (like the CXF Endpoints entry) don't ship \textbar{} with a configuration file, so anything you create is the first occurrence. \textbar{} However, if you configure one and export it to obtain the .config file, it \textbar{} doesn't use the -default.config naming convention. Instead it's given a \textbar{} guaranteed unique identifier for its subname, like this: \textbar{} \textbar{} com.liferay.portal.remote.cxf.common.configuration.CXFEndpointPublisherConfiguration-a6f67e48-6dca-49c6-bf6b-8fd5e6016b2d.config \textbar{} \textbar{} This guarantees that the file has a unique name. If you're exporting the \textbar{} configuration file for deployment in a separate system, you can rename \textbar{} the exported file to use a more descriptive subname. If you rename the file and \textbar{} deploy it to the same system it was exported from, the new subname marks it as \textbar{} an entirely new configuration. You'll end up with an additional configuration \textbar{} instance in this case, not just a renamed one. \chapter{Factory Configurations}\label{factory-configurations} Configurations supporting multiple entries are called \emph{factory configurations}. \begin{quote} Factory Configuration Example: Adding Organization types is supported, and is useful if you need to model real-life hierarchies or enforce hierarchical rules. In Liferay DXP, each Organization type is created via a factory configuration entry in System Settings. \end{quote} If a service is meant to support factory configurations, its System Settings entry has an ADD button. \begin{figure} \centering \includegraphics{./images/factory-configuration-entry.png} \caption{If a System Settings entry has an ADD button, it's suitable for factory configurations.} \end{figure} As with single-instance configurations, you can set factory configurations in the System Settings interface (as described in the example above) or via configuration files. Name a standard single-instance configuration file like this: \begin{verbatim} my.service.ServiceConfiguration.config \end{verbatim} If your service supports factory configurations, use the convention of calling the configuration's first instance \texttt{-default.config}: \begin{verbatim} my.service.ServiceConfiguration-default.config \end{verbatim} The next instance contains a unique \emph{subname} (something other than \emph{default}). It's good practice to use a descriptive name: \begin{verbatim} my.service.ServiceConfiguration-port9080.config \end{verbatim} In the Organization type example, the default Organization type (aptly named \emph{organization}) is created by a \texttt{-default.config} file named \begin{verbatim} com.liferay.organizations.internal.configuration.OrganizationTypeConfiguration-default.config \end{verbatim} Following the example from the \href{../../users-and-permissions/organizations/adding-a-new-organization-type.md}{Adding a New Organization Type} article, you could add the \emph{League} type with a configuration file named \begin{verbatim} com.liferay.organizations.internal.configuration.OrganizationTypeConfiguration-league.config \end{verbatim} Some System Settings entries that support factory configuration don't ship with a configuration file for the default instance (e.g., the Anonymous User entry). If you export a factory configuration file to obtain the \texttt{.config} file, it doesn't use the \texttt{-default.config} naming convention. Instead, whether it's the first occurrence or an additional one, it's given a guaranteed unique identifier for its subname: \begin{verbatim} com.liferay.user.associated.data.web.internal.configuration.AnonymousUserConfiguration-6befcd73-7c8b-4597-b396-a18f64f8c308.config \end{verbatim} This guarantees that the file has a unique name. If you're exporting the configuration file for deployment in a separate system, you can rename the exported file to use a more descriptive subname. If you rename the file and deploy it to the same system it was exported from, the new subname marks it as an entirely new configuration. You'll end up with an additional configuration instance in this case, not just a renamed one. \noindent\hrulefill \textbf{Warning::} For configuration entries supporting factory configurations, omitting the subname from a \texttt{.config} file's name causes System Settings to disallow adding new entries for the configuration (only the configuration entry targeted by this \texttt{.config} file). This is caused by a known bug. See \href{https://issues.liferay.com/browse/LPS-76352}{LPS-76352} for more information. Once an improperly named configuration file is deployed, you can't add any entries for the configuration in question from its System Settings entry. For example, if you deploy the following file to configure an Organization Type, not only does this not add an Organiaztion Type, it also prevents you from adding any via System Settings: \begin{verbatim} com.liferay.organizations.internal.configuration.OrganizationTypeConfiguration.config \end{verbatim} Deploying an erroneous (lacking a subname) \texttt{.config} file doesn't disable anything permanently. Just rename the file using the proper convention described above or remove it entirely and start over. \noindent\hrulefill In many cases, configuration files can be used to force a factory configuration scenario, but not all configurations can be used this way. It's best to stick to the intended use cases. Use System Settings as described above to determine if using factory configurations is a good idea. If not, stick to the single occurrence mode of configuration (specifying only one configuration file for the service). --- header-id: server-administration Server Administration {[}TOC levels=1-4{]} Server Administration lets you manage and monitor your Liferay DXP server. Access the application by clicking Control Panel → Configuration → Server Administration. Figure 1: The Resources tab of Server Administration shows a graph of your server's memory usage. Server Administration's functionality is segmented into these tabs: Resources: View memory usage and perform management tasks like running the garbage collector, clearing the database cache, and more. For more information, see Resources. Log Levels: View and set logging levels. You can make dynamic modifications of log levels for any class hierarchy in Liferay DXP. Custom objects not on the list can be added with the Add Category tab. Changes to the log level near the top of the class hierarchy (such as at com.liferay) also change log levels for all the classes under that hierarchy. Modifications unnecessarily high in the hierarchy generate too many messages to be useful. Properties: View JVM and portal properties. This tab has two sub-tabs: System Properties and Portal Properties. The System Properties tab shows an exhaustive list of system properties for the JVM, as well as many Create a backup copy of the Document Library repository and Liferay DXP database. Configure the new file store in System Settings → Platform: File Storage. In this tab (Server Administration → Data Migration), select the repository hook for the file store you configured and click Execute. Make sure the data migrated correctly. Configure the new repository as the default. If you used a portal-ext.properties file to configure the repository, restart the server. Mail: Instead of using a portal-ext.properties file to configure a mail server, you can configure a mail server from this tab. If your message boards receive mail, you can connect a POP mail server. If Liferay DXP sends mail (useful for sending notifications to users), you can connect to an SMTP server. Note that if you configure mail server settings here in System Settings, these settings override any mail server settings in your portal-ext.properties file. External Services: Configure external services for generating file previews. For more information, see the article on External Services. Script: A scripting console for executing migration or management code. The Groovy scripting language is supported out of the box. . Shutdown: Schedule a shutdown that notifies logged-in users of the impending shutdown. You can define the number of minutes until the shutdown and a message to display. Liferay DXP displays the message at the top of users' pages for the duration of time you specified. When the time expires, all pages display a message saying the portal has been shut down. The server must then be restarted to restore access. \chapter{Server Administration}\label{server-administration} Server Administration lets you manage and monitor your Liferay DXP server. Access the application by clicking \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration}. \begin{figure} \centering \includegraphics{./images/server-admin-memory.png} \caption{The Resources tab of Server Administration shows a graph of your server's memory usage.} \end{figure} Server Administration's functionality is segmented into these tabs: \textbf{Resources:} View memory usage and perform management tasks like running the garbage collector, clearing the database cache, and more. For more information, see \href{/docs/7-2/user/-/knowledge_base/u/server-administration-resources}{Resources}. \textbf{Log Levels:} View and set logging levels. You can make dynamic modifications of log levels for any class hierarchy in Liferay DXP. Custom objects not on the list can be added with the \emph{Add Category} tab. Changes to the log level near the top of the class hierarchy (such as at \texttt{com.liferay}) also change log levels for all the classes under that hierarchy. Modifications unnecessarily high in the hierarchy generate too many messages to be useful. \textbf{Properties:} View JVM and portal properties. This tab has two sub-tabs: System Properties and Portal Properties. The System Properties tab shows an exhaustive list of system properties for the JVM, as well as many Liferay DXP system properties. You can use this information for debugging purposes or to check the currently running configuration. The Portal Properties tab shows an exhaustive list of the current portal property values. For explanations of these properties, see the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html}{portal properties reference documentation}. \textbf{Data Migration:} Migrate documents from one repository to another. For example, you can migrate your documents to a new repository on a different disk or in a \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{new format}. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Create a backup copy of the Document Library repository and \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{Liferay DXP database}. \item Configure the new file store in \emph{System Settings} → \emph{Platform: File Storage}. \item In this tab (\emph{Server Administration} → \emph{Data Migration}), select the repository hook for the file store you configured and click \emph{Execute}. \item Make sure the data migrated correctly. \item \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Configure the new repository} as the default. \item If you used a \texttt{portal-ext.properties} file to configure the repository, restart the server. \end{enumerate} \textbf{Mail:} Instead of using a \texttt{portal-ext.properties} file to configure a mail server, you can configure a mail server from this tab. If your \href{/docs/7-2/user/-/knowledge_base/u/user-subscriptions-and-mailing-lists}{message boards receive mail}, you can connect a POP mail server. If Liferay DXP sends mail (useful for sending notifications to users), you can connect to an SMTP server. Note that if you configure mail server settings here in System Settings, these settings override any mail server settings in your \texttt{portal-ext.properties} file. \textbf{External Services:} Configure external services for generating file previews. For more information, see \href{/docs/7-2/user/-/knowledge_base/u/server-administration-external-services}{the article on External Services}. \textbf{Script:} A scripting console for executing migration or management code. The Groovy scripting language is supported out of the box. \textbf{Shutdown:} Schedule a shutdown that notifies logged-in users of the impending shutdown. You can define the number of minutes until the shutdown and a message to display. Liferay DXP displays the message at the top of users' pages for the duration of time you specified. When the time expires, all pages display a message saying the portal has been shut down. The server must then be restarted to restore access. \chapter{Server Administration: Resources}\label{server-administration-resources} The Server Administration app's Resources tab contains several server wide actions that an administrator can execute. These include the following items: \textbf{Run the garbage collector:} Tells the JVM to free memory by running the garbage collector. \textbf{Generate a thread dump:} Generates a thread dump for later scrutiny to determine the presence and location of any deadlocks. Useful during performance testing, but you must add a logger category for \texttt{com.liferay.server.admin.web.internal.portlet.action.EditServerMVCActionCommand} and set it to \texttt{INFO} before executing. \textbf{Clear content cached by this VM:} Clears content stored in the local cache. Only local JVM scope Ehcache content is cleared, not clustered Ehcache. \hyperref[one]{1} \textbf{Clear content cached across the cluster:} Clears the content of the entire clustered Ehcache. \hyperref[one]{1} \textbf{Clear the database cache:} Clears the database cache. Does not clear any Ehcache content except database results at the persistence layer. \hyperref[one]{1} \textbf{Clear the direct servlet cache:} Clears the direct servlet cache. In case emergency fixes must be applied, this action allows an administrator to clear out the cache manually to force JSPs to reload. The direct servlet context optimizes JSP serving performance by caching and accessing the generated servlets directly instead of accessing them over the application server's dispatcher chain. This function is only suitable for cases where no filter is required for the JSPs; it should be enabled for production mode to improve performance, but disabled for development mode to allow JSP servlets to be reloaded on the fly. See the Direct Servlet Context section of the \texttt{portal.properties} file for details. \hyperref[one]{1} \textbf{Verify database tables of all plugins:} Checks all tables against their indexes for data retrieval accuracy. \textbf{Verify membership policies:} Checks that existing Site membership policies were correctly applied and automatically makes updates. If the Liferay DXP database is changed manually or is hacked---resulting in a user assigned to a Site in violation of a site membership policy---this action triggers the verification methods of all implemented Site membership policies. Changes are automatically made to any memberships in violation. \textbf{Reset preview and thumbnail files for Documents and Media:} Regenerates previews of each item in your Documents and Media libraries. \textbf{Clean up permissions:} Removes permissions on the Guest, User, and Power User Roles to simplify the management of User Customizable Pages. The Add To Page permission is removed from the Guest and User Roles for all portlets, and is reduced in scope for Power Users from portal-wide to User Personal Site. \textbf{Clean up portlet preferences:} This action cleans up database entries if portlet preferences become orphaned in the Liferay DXP database. \begin{figure} \centering \includegraphics{./images/server-admin-resources.png} \caption{The Resources tab of Server Administration lets you execute several server maintenance tasks.} \end{figure} {[}1{]} Caching occurs at multiple levels. Some higher caching layers aren't aware of lower caching layers. Always clear the cache at the lowest (most granular) layer possible, even if you've already cleared a higher level cache. \chapter{Server Administration: External Services}\label{server-administration-external-services} Users can upload and share any type of file via the Documents and Media library, a customizable and permissions-enabled online repository for files (see \href{/docs/7-2/user/-/knowledge_base/u/publishing-files}{publishing files} for more information). \href{https://pdfbox.apache.org/}{PDFBox} is included with Liferay DXP and generates automatic previews for certain file types (mostly PDFs). You can install two additional tools to generate previews for other file types: \begin{itemize} \item \href{https://www.openoffice.org/}{\textbf{OpenOffice:}} or \href{https://www.libreoffice.org/}{\textbf{LibreOffice:}} Convert and generate previews for many types of documents. \item \href{https://www.imagemagick.org/script/index.php}{\textbf{ImageMagick:}} Generate higher-quality image previews for many types of images. \item \href{http://www.xuggle.com/xuggler/}{\textbf{Xuggler:}} Convert and generate previews for audio and video files. \end{itemize} As of Liferay 7.1, OpenOffice/LibreOffice is configured in OSGi Configuration Admin instead of portal properties. To adjust the these settings, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Connectors} → \emph{OpenOffice Integration}. You can also adjust these settings by deploying a \texttt{com.liferay.document.library.document.conversion.internal.configuration.OpenOfficeConfiguration.config} file to your \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. See the \href{/docs/7-1/reference/-/knowledge_base/r/breaking-changes\#moved-openoffice-properties-to-osgi-configuration}{Breaking Changes} document for details. Once you've installed ImageMagick and Xuggler, you can use the Server Administration app's External Services tab to configure Liferay DXP to use them. Make sure to choose the correct versions of these tools for your operating system. Install the latest stable versions, as older versions may not run properly with Liferay DXP. ImageMagick must be installed manually, but you can install Xuggler from the Control Panel. \noindent\hrulefill \textbf{Tip:} If you're running Liferay DXP on a Linux server and experience a problem enabling Xuggler, check your server's glibc version. For Xuggler to work, you may need to update glibc to version 2.6 or later. \noindent\hrulefill \section{ImageMagick Configuration}\label{imagemagick-configuration} Before configuring ImageMagick to generate image and PDF previews, install it and its dependency, Ghostscript. This differs by operating system: on Linux, both are likely already installed. They are not likely to be installed on Windows, but may be on macOS. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Download and install \href{https://www.imagemagick.org/script/index.php}{ImageMagick}. \item Download and install \href{https://www.ghostscript.com/}{Ghostscript}. \end{enumerate} Once installed, you must enable ImageMagick in the Server Administration app's External Services tab, or in a \texttt{portal-ext.properties} file. If using \texttt{portal-ext.properties}, add the following lines and make sure the search path points to the directories containing the ImageMagick and Ghostscript executables. You may also need to configure the path for fonts used by Ghostscript when in macOS or Unix environments. \begin{verbatim} imagemagick.enabled=true imagemagick.global.search.path[apple]=/opt/local/bin:/opt/local/share/ghostscript/fonts:/opt/local/share/fonts/urw-fonts imagemagick.global.search.path[unix]=/usr/local/bin:/usr/local/share/ghostscript/fonts:/usr/local/share/fonts/urw-fonts imagemagick.global.search.path[windows]=C:\\Program Files\\ImageMagick \end{verbatim} Follow these steps to instead enable ImageMagick from the Server Administration app's External Services tab: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the \emph{Control Panel}, navigate to \emph{Configuration} → \emph{Server Administration}, then click the \emph{External Services} tab. \item Expand the ImageMagick and Ghostscript section and select \emph{Enabled}. \item Verify that the paths to the ImageMagick and Ghostscript executables are correct. \end{enumerate} \begin{figure} \centering \includegraphics{./images/imagemagick-ghostscript.png} \caption{Enable ImageMagick and Ghostscript, and verify that the paths are correct.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \tightlist \item Set the Resource limits to use. \end{enumerate} \section{Xuggler Configuration}\label{xuggler-configuration} Follow these steps to install and configure Xuggler: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the \emph{Control Panel}, navigate to \emph{Configuration} → \emph{Server Administration}, then click the \emph{External Services} tab. \item In the Xuggler section, select the Xuggler \texttt{.jar} file that matches your operating system. Then click \emph{Install}. \item Restart your application server. \item Enable Xuggler for your portal. There are two ways to do this: \begin{itemize} \item In the Control Panel, navigate to the \emph{Server Administration} → \emph{External Services} tab, select \emph{Enabled}, then click \emph{Save}. \item Add the following line to your \texttt{portal-ext.properties} file and restart your application server: \begin{verbatim} xuggler.enabled=true \end{verbatim} \end{itemize} \end{enumerate} \begin{figure} \centering \includegraphics{./images/xuggler-install.png} \caption{Install Xuggler.} \end{figure} \chapter{Setting Up a Virtual Instance}\label{setting-up-a-virtual-instance} Once Liferay DXP is installed, the configuration begins. Recall that configuration happens at different scopes. Here we're covering configuration at the virtual instance scope. There's an important difference between the system scope and the instance scope. The system scope is the highest level scope you can make configurations at. All virtual instances are impacted by configuration done at this scope. The instance scope applies only to one particular virtual instance. Virtual instances have unique domain names but share a server and database. Each virtual instance can have independent data and configurations. The articles in this section cover these topics: \begin{itemize} \tightlist \item Adding a virtual instance \item Configuring a virtual instance \end{itemize} Get started by learning how to add a virtual instance. \chapter{Virtual Instances}\label{virtual-instances} Here's a quick scenario: you already have a server hosting a Liferay DXP installation and a database. It has many \href{/docs/7-2/user/-/knowledge_base/u/users-and-organizations}{Users}, \href{/docs/7-2/user/-/knowledge_base/u/building-a-site}{Sites}, and specific \href{/docs/7-2/user/-/knowledge_base/u/instance-configuration-instance-settings\#general}{instance settings}. If you require a second similar installation, then adding a \emph{Virtual Instance} might be right for you. You can run more than one Virtual Instance on a single server with a shared database, but separate data and configurations. Users are directed to the correct Virtual Instance via its unique domain name. And because Virtual Instances share an application server and OSGi container, they also share these customizations: \begin{itemize} \tightlist \item Custom code deployed by developers and administrators. \item \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System-scoped configurations} (e.g., \texttt{.config} files, changes made in \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}). \item Application server configuration. \end{itemize} Administrators can manage Virtual Instances in \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \begin{figure} \centering \includegraphics{./images/virtual-instances.png} \caption{Add and manage virtual instances of Liferay in the Control Panel's \emph{Configuration} → \emph{Virtual Instances} section.} \end{figure} Follow these steps to create a Virtual Instance: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Before you create a Virtual Instance, configure its domain name in your network. \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{Virtual Instances}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This opens the \emph{New Instance} form. \item Complete the New Instance form as follows: \end{enumerate} \textbf{Web ID:} The instance's ID. Using the domain name is a common convention. \textbf{Virtual Host:} The domain name you configured in your network. When users are directed to your server via this domain name, they'll be sent to the Virtual Instance that contains their data. \textbf{Mail Domain:} The mail host's domain name for the Virtual Instance. Email notifications are sent from the instance using this domain. \textbf{Max Users:} The maximum number of user accounts the Virtual Instance supports. Enter \emph{0} to support unlimited users. \textbf{Active:} Whether the Virtual Instance is active. Note that inactive Virtual Instances aren't accessible to anyone, even the administrator. Click \emph{Save} when you're done filling out the form. Now you can navigate to the instance using its domain name. You're brought to what looks like a clean install of Liferay DXP. This is your new Virtual Instance! You can configure it any way you like. The remaining articles in this section show you how to configure an instance's settings. \chapter{Configuring Virtual Instances}\label{configuring-virtual-instances} To access instance settings, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings}. The Instance Settings are organized into three sections: PLATFORM, SECURITY, and CONTENT AND DATA. Here we focus on the instance settings available under the PLATFORM section. The CONTENT AND DATA settings are specific to each application, and the SECURITY instance settings are covered in their respective articles, listed below for reference: \begin{itemize} \item \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-ldap}{LDAP} \item \href{/docs/7-2/deploy/-/knowledge_base/d/oauth2-scopes\#creating-the-authorization-page}{OAuth 2} \item SSO: \begin{itemize} \tightlist \item \href{/docs/7-2/deploy/-/knowledge_base/d/cas-central-authentication-service-single-sign-on-authentication}{CAS Server} \item \href{/docs/7-2/deploy/-/knowledge_base/d/token-based-single-sign-on-authentication}{Token based SSO} \item \href{/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-openid-connect\#enabling-openid-connect-authentication}{Open ID Connect} \item \href{/docs/7-2/deploy/-/knowledge_base/d/opensso-single-sign-on-authentication}{OpenSSO} \end{itemize} The PLATFORM instance settings are covered in the articles shown below: \begin{itemize} \tightlist \item \href{/docs/7-2/user/-/knowledge_base/u/email-instance-settings}{Email} \item \href{/docs/7-2/user/-/knowledge_base/u/instance-configuration-instance-settings}{Instance Configuration} \item \href{/docs/7-2/user/-/knowledge_base/u/user-authentication-instance-settings}{User Authentication} \item \href{/docs/7-2/user/-/knowledge_base/u/users-instance-settings}{Users} \item \href{/docs/7-2/user/-/knowledge_base/u/more-platform-section-instance-settings}{More Platform Settings} \end{itemize} \end{itemize} \chapter{Email Instance Settings}\label{email-instance-settings} The Email configuration is your one-stop-shop for all your email notification needs. To access the Email configuration settings for your instance, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings} and select the \emph{Email} category under the PLATFORM section. The Email configuration contains six entries: \begin{itemize} \tightlist \item Account Created Notification \item Email Sender \item Email Verification Notification \item Mail Host Names \item Password Changed Notification \item Password Reset Notification \end{itemize} Each configuration entry is described in the corresponding section below. \section{Account Created Notification}\label{account-created-notification} The Account Created Notification entry defines the email templates, with and without the new User's password included in the body of the text, for the message sent to notify Users when they create a new account. You can specify whether this email is sent by checking/unchecking the \emph{Enabled} checkbox at the top. Use the template variables listed at the bottom of the configuration under the ``Definition of Terms'' heading to help build your email template. \section{Email Sender}\label{email-sender} The Email Sender entry specifies the virtual instance's administrative Name and Address for email notifications, declared as the \texttt{{[}\$FROM\_NAME\${]}} and \texttt{{[}\$FROM\_ADDRESS\${]}} variables respectively in the email templates. By default, they are from the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Admin\%20Portlet}{\texttt{admin.email.from.name} and \texttt{admin.email.from.address}} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties}). This name and email address appear in the \emph{From} field in all email messages sent by the virtual instance. \begin{figure} \centering \includegraphics{./images/instance-settings-account-created.png} \caption{Customize the email template for the email messages sent to new Users.} \end{figure} \section{Email Verification Notification}\label{email-verification-notification} The Email Verification Notification entry defines the email template for the message sent to Users when asked to verify their Email Address. Use the template variables listed at the bottom of the configuration under the ``Definition of Terms'' heading to help build your email template. \section{Mail Host Names}\label{mail-host-names} The Mail Host Names entry specifies which mail host names are owned by your organization for the virtual instance. Enter one mail host name per line, besides the one specified in the \href{}{General tab}. \section{Password Changed Notification}\label{password-changed-notification} The Password Changed Notification entry defines the email template for the message sent to notify Users when their password has changed. Use the template variables listed at the bottom of the configuration under the ``Definition of Terms'' heading to help build your email template. \section{Password Reset Notification}\label{password-reset-notification} The Password Reset Notification entry defines the email template for the message sent to notify Users when a request has been made to reset their password. Use the template variables listed at the bottom of the configuration under the ``Definition of Terms'' heading to help build your email template. \begin{figure} \centering \includegraphics{./images/instance-settings-definition-of-terms.png} \caption{There are some handy variables available for use in email templates.} \end{figure} \chapter{Instance Configuration Instance Settings}\label{instance-configuration-instance-settings} The Instance Configuration settings define the basic configuration information for the virtual instance, from the appearance to the Terms of Use for your Users to agree to. Access the Instance Configuration settings from the Control Panel's \emph{Configuration} → \emph{Instance Settings} section, and select the \emph{Instance Configuration} category under the \emph{PLATFORM} section. The Instance Configuration contains four entries: \begin{itemize} \tightlist \item Appearance \item Contact Information \item General \item Terms of Use \end{itemize} Each configuration entry is described in the corresponding section below. \section{Appearance}\label{appearance} The Appearance configuration entry defines the default logo and overall look and feel for the virtual instance. It's organized into LOGO and LOOK AND FEEL sections: \textbf{LOGO:} Change the default logo and check/uncheck the \emph{Allow site administrators to use their own logo?} checkbox, enabled by default, to specify whether site administrators can upload a logo when they configure a site. When configuring a new logo, be careful to choose an image file that fits the space. Large images might overlap with the navigation. \textbf{LOOK AND FEEL:} Set the default theme(s) for the instance and Control Panel. \section{Contact Information}\label{contact-information} The Contact Information configuration entry specifies how to contact the organization that owns the virtual instance. It's divided into several sections: \textbf{ADDRESSES:} Specify the primary, mailing, shipping, P.O. Box, etc. address of the organization. \textbf{PHONE NUMBERS:} Provide the fax, local, etc. phone numbers for the organization. \textbf{ADDITIONAL EMAIL ADDRESSES:} Specify any additional email addresses associated with the organization. \textbf{WEBSITES:} Specify the public and/or intranet websites for the organization. Developers can query for this contact information in their applications. Note that you can add and remove additional entries in a section with the plus and minus buttons respectively. \section{General}\label{general} The General entry specifies the virtual instance's configuration information, landing pages, and the associated organization's basic legal information. It has two sections: \textbf{Main Configuration:} Configure this information for the virtual instance: \begin{itemize} \tightlist \item Set the name of the entity responsible for running the virtual instance. \item Set the mail domain. \item Set the virtual host. \item Set the URLs to a CDN configured to serve static resources. \end{itemize} \textbf{Navigation:} Set a home page for your virtual instance as well as default landing and logout pages. To set these pages, use the page's relative URL that follows the domain. For example, to set the default landing page to \texttt{http://localhost:8080/web/guest/login}, use \texttt{/web/guest/login}. You can use the variable \texttt{\$\{liferay:screenName\}} as part of the address to redirect Users to their personal pages upon login. For example, the User \texttt{marvin} has this default URL to his personal page: \begin{verbatim} http://localhost:8080/user/marvin \end{verbatim} To make sure he's directed there on login, place \texttt{/user/\$\{liferay:screenName\}} in the Default Landing Page field. These URLs can also be set at the system scope, in a \texttt{portal-ext.properties} file: \begin{verbatim} default.landing.page.path= default.logout.page.path= company.default.home.url= \end{verbatim} All virtual instances share the values specified in the properties file. Changes made in Instance Settings override the values set in the properties file. For more information, see the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html}{Portal Properties documentation}. \textbf{Additional Information:} Specify a Legal name, ID, company type, SIC code, ticker symbol, industry and industry type for the owner of the virtual instance. \section{Terms of Use}\label{terms-of-use} The Terms of Use entry contains everything you need to provide a custom Terms of Use agreement for your Users. Terms of Use are important when you need them, but not all Sites do. They're not listed first, but they're one of the first things to configure for your virtual instance, whether you require them or not. Since the Terms of Use page is enabled by default, one of your first actions should be to disable or replace the default, placeholder terms. You can disable the requirement for all Users to read the Terms of Use or set the Group ID and Article ID for the Web Content Article that contains your Terms of Use. ``How do I find a web content article's Group ID and Article ID,'' you ask? No problem. The Group ID is the ID of the Site the Web Content is associated with. To find the Group/Site ID, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Menu} → \emph{Configuration} → \emph{Settings}. \item Find the Site ID field in the General tab. Enter it into the Group ID field. \end{enumerate} \begin{figure} \centering \includegraphics{./images/instance-settings-group-id.png} \caption{The Site ID in Site Settings is the Group ID in the terms of Use configuration.} \end{figure} To find the Web Content Article's ID, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Site Menu} → \emph{Content \& Data} → \emph{Web Content}. \item Click on your Terms of Use article. Its ID appears at the top of the screen, with the Version and Workflow Status. \end{enumerate} \begin{figure} \centering \includegraphics{./images/instance-settings-wc-id.png} \caption{The Web Content Article ID is displayed in the edit screen.} \end{figure} Save the configuration. All your users signing in for the first time see your Terms of Use article. Users must agree to the Terms of Use to register their User Accounts. \begin{figure} \centering \includegraphics{./images/instance-settings-terms-of-use.png} \caption{Turn a Web Content Article into the Site's Terms of Use page.} \end{figure} \chapter{User Authentication Instance Settings}\label{user-authentication-instance-settings} The User Authentication settings define how Users can authenticate, the various authentication methods that are required for them, and the screen names and email addresses that are reserved and can't be registered by Users. Access the User Authentication settings in the Control Panel's \emph{Configuration} → \emph{Instance Settings} section, and select the \emph{User Authentication} category under the \emph{PLATFORM} section. User Authentication contains two entries: \begin{itemize} \tightlist \item General \item Reserved Credentials \end{itemize} Each configuration entry is described in the corresponding section below. \begin{figure} \centering \includegraphics{./images/instance-settings-auth.png} \caption{Configure general authentication behavior and settings for external authentication systems.} \end{figure} \section{General}\label{general-1} The General configuration entry contains several general authentication settings: \begin{itemize} \item Authenticate by email address (default), screen name, or User ID (a numerical ID auto-generated in the database---not recommended). \item Enable/Disable automatic log in. If enabled, a User can check a box which will cause the Site to ``remember'' the login information by placing a cookie on the browser. If disabled, Users must always log in manually. \item Enable/Disable forgotten password functionality. \item Enable/Disable request password reset links. \item Enable/Disable account creation by strangers. If running an Internet site, leave this enabled so visitors can create accounts on your Site. \item Enable/Disable account creation by those using an email address in the domain of the company running the Site (which is set on the General page of Instance Settings). This is handy if you're using Liferay to host both internal and external web sites. Make sure all internal IDs are created by administrators but external Users can register for IDs themselves. \item Enable/Disable email address verification. If enabled, Users receive a verification email with a link back to the virtual instance, verifying that the email address they entered is valid. \end{itemize} By default, all settings except for the last are enabled. User authentication by email address is an important default for the following reasons: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item An email address is unique to the User who owns it. \item People remember their email addresses. A Users who hasn't logged in for a while could forget their screen name. \item If email address isn't used to authenticate, a User might change her email address then forget to update the email address in her profile. If this occurs, no notifications sent by the virtual instance will reach the User. Keeping the email address at the forefront of a User's mind when she logs in helps ensure the User keeps it current. \end{enumerate} \section{Reserved Credentials}\label{reserved-credentials} The Reserved Credentials configuration entry specifies the screen names and email addresses Users aren't allowed to use. This prevents Users from creating IDs that look like administrative IDs or that have reserved words in their names. Learn to configure a third party authentication service or set up Single Sign On (SSO) in the \href{/docs/7-2/deploy/-/knowledge_base/d/securing-product}{security documentation}. \chapter{Users Instance Settings}\label{users-instance-settings} The Users configuration defines the look and feel of the Personal Menu, the default user associations for Users, and defines whether specific fields are available when a new User creates an account. To access the Users settings, Open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings}, and select the \emph{Users} category under the \emph{PLATFORM} section. The Users Instance configuration contains three entries: \begin{itemize} \tightlist \item Personal Menu \item Default User Associations \item Fields \end{itemize} Each configuration entry is described in the corresponding section below. \section{Personal Menu}\label{personal-menu} The Personal Menu configuration entry specifies whether personal applications use the same look and feel as the current site or if they should use the look and feel of the My Dashboard pages instead. You can also specify whether the personal menu appears in the Control Menu (as it did in past versions of Liferay DXP) by toggling the \emph{Show in Control Menu} option on and off. \section{Default User Associations}\label{default-user-associations} The Default User Associations configuration entry defines the default Sites, Organization Sites, Roles, and User Groups you want all new Users assigned to automatically. By default, new Users are only assigned to the Users Role. User groups are handy tools for pre-populating your Users' private Sites with pages, assigning Roles and permissions, and managing site membership. If you update this configuration after your Users have created their accounts, don't worry. You can apply the updates to existing Users by checking the \emph{Apply to Existing Users} checkbox. Changes take effect the next time the User signs in. \section{Fields}\label{fields} The Fields configuration entry contains settings for enabling/disabling the fields listed below on the Add/Edit User Form: \begin{itemize} \tightlist \item Autogeneration of screen names \item Birthday field \item Gender field \end{itemize} \chapter{More Platform Section Instance Settings}\label{more-platform-section-instance-settings} The Instance Settings also contain settings for Infrastructure and Localization, as well as integrations for Analytics and Third Party map APIs. Each configuration entry is described in the corresponding section below. \section{Infrastructure}\label{infrastructure} The Infrastructure category contains settings to specify which content types are validated during the Import/Export process and whether to delete temporary LAR files during the Import/Export process. To access these settings, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings} and select \emph{Infrastructure} under the PLATFORM heading. \section{Localization}\label{localization} The Localization category contains the configuration for setting the default instance language and time zone for the virtual instance. You can also configure the Language Selector's look and feel from the Widget Scope. To access the Localization settings, Open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings}, and select the \emph{Localization} category under the \emph{PLATFORM} section. \section{Analytics}\label{analytics} The Analytics category defines the available analytics systems for the virtual instance. Enter an analytics system or remove one of the two pre-configured options (\texttt{google} and \texttt{piwik}). Activate these systems here, and configure them at the \href{/docs/7-2/user/-/knowledge_base/u/advanced-site-settings\#analytics}{site level}. To access Analytics configuration settings, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings}, and select the \emph{Analytics} category under the \emph{PLATFORM} section. \section{Third Party}\label{third-party} The Third Party category specifies the maps API provider for geolocalized assets. Choose OpenStreetMap or Google Maps as the maps API provider. To access Third Party configuration settings, open the Control Panel and navigate to \emph{Configuration} → \emph{Instance Settings}, and select the \emph{Third Party} category under the \emph{PLATFORM} section. \chapter{Using Liferay's Script Engine}\label{using-liferays-script-engine} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP provides a robust script engine for executing \href{http://groovy-lang.org/}{Groovy} scripts. You can execute scripts to perform maintenance tasks like data cleanup, user maintenance operations, bulk invocations of Liferay's API, or even system level operations in the scripting console. These tutorials cover the following scripting topics: \begin{itemize} \item \href{/docs/7-2/user/-/knowledge_base/u/invoking-liferay-services-from-scripts}{Invoking Liferay services} \item \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{Running scripts from the script console} \item \href{/docs/7-2/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{Using the script engine with workflow} \item \href{/docs/7-2/user/-/knowledge_base/u/script-examples}{Script examples} \end{itemize} The most common use of the scripting console is to invoke Liferay's services, so that's covered first. Familiarity with Liferay's API makes the scripting console a useful tool. \chapter{Invoking Liferay Services From Scripts}\label{invoking-liferay-services-from-scripts} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Many scripting scenarios require invoking Liferay services. \href{https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-kernel/}{Liferay \texttt{*ServiceUtil} classes} are the fastest and most convenient way to invoke Liferay services in the \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{script console}. You can use Groovy to invoke Liferay services the same way you would use Java. Groovy's syntax facilitates writing concise, elegant scripts. This first example illustrates correct syntax for interacting with Liferay services. It uses \texttt{UserLocalServiceUtil} to retrieve a list of users and print their names to Liferay's log. To do this, you could deploy a module with Java code like this: \begin{verbatim} import com.liferay.portal.kernel.model.User; import com.liferay.portal.kernel.service.UserLocalServiceUtil; import java.util.List; ... int userCount = UserLocalServiceUtil.getUsersCount(); List users = UserLocalServiceUtil.getUsers(0, userCount); for (User user:users) { System.out.println("User Name: " + user.getFullName()); } ... \end{verbatim} Or you could use Groovy---based on Java---and do the whole thing right from the \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{script console} with the same code: \begin{verbatim} import com.liferay.portal.kernel.model.User; import com.liferay.portal.kernel.service.UserLocalServiceUtil; import java.util.List; int userCount = UserLocalServiceUtil.getUsersCount(); List users = UserLocalServiceUtil.getUsers(0, userCount); for (User user:users) { System.out.println("User Name: " + user.getFullName()); } You can even make the code somewhat Groovier: import com.liferay.portal.kernel.service.UserLocalServiceUtil userCount = UserLocalServiceUtil.getUsersCount() users = UserLocalServiceUtil.getUsers(0, userCount) for (user in users){ System.out.println("User Name: " + user.getFullName()) } \end{verbatim} Groovy scripts that invoke Liferay services are easy to write and execute in the script console. How to run scripts is next. \section{Related Topics}\label{related-topics} \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{Running Scripts From the Script Console} \href{/docs/7-2/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{Leveraging the Script Engine in Workflow} \href{/docs/7-2/user/-/knowledge_base/u/script-examples}{Script Examples} \chapter{Running Scripts From the Script Console}\label{running-scripts-from-the-script-console} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The script console provides a single view for executing Groovy scripts and printing their output. It has predefined variables that facilitate printing output and working with portlets and users. Here you'll learn these things: \begin{itemize} \item \hyperref[running-the-sample-script]{How to execute a script in the script console} \item \hyperref[predefined-variables]{The predefined variables available in the script console} \item \hyperref[tips]{Tips for running scripts in the script console} \end{itemize} \noindent\hrulefill \textbf{Important:} The script console is for system operations and maintenance and not for end users. Limit script console access to portal administrators. \noindent\hrulefill Start with running the script console's sample script. \section{Running the Sample Script}\label{running-the-sample-script} Here's how to run the sample script in the script console: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Sign in as an administrator. \item In the Product Menu, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration}. \item Click on \emph{Script}. This is the script console. The default sample script prints the User count to the console output. \begin{verbatim} // ## Groovy Sample ## number = com.liferay.portal.kernel.service.UserLocalServiceUtil.getUsersCount(); out.println(number); \end{verbatim} \item Click \emph{Execute} and check the script console \emph{Output} for the User count. \end{enumerate} \begin{figure} \centering \includegraphics{./images/groovy-script-sample.png} \caption{The script console's sample Groovy script prints the User count to the console's \emph{Output} section.} \end{figure} The Groovy sample invokes the Liferay service utility \texttt{UserLocalServiceUtil} to get the user count. Then it uses \texttt{out} (a built-in \texttt{PrintWriter}) to write the count to the script console. Note that if you use \texttt{System.out.println} instead of \texttt{out.println}, your output is printed to Liferay's log file rather than to the script console. \section{Predefined Variables}\label{predefined-variables} Here are the predefined variables available to scripts executed in the script console: \begin{itemize} \tightlist \item \texttt{out} (\texttt{java.io.PrintWriter}) \item \texttt{actionRequest} (\texttt{javax.portlet.ActionRequest}) \item \texttt{actionResponse} (\texttt{javax.portlet.ActionReponse}) \item \texttt{portletConfig} (\texttt{javax.portlet.PortletConfig}) \item \texttt{portletContext} (\texttt{javax.portlet.PortletContext}) \item \texttt{preferences} (\texttt{javax.portlet.PortletPreferences}) \item \texttt{userInfo} (\texttt{java.util.Map\textless{}String,\ String\textgreater{}}) \end{itemize} This script demonstrates using the \texttt{actionRequest} variable to get the portal instance's \texttt{Company}: \begin{verbatim} import com.liferay.portal.kernel.util.* company = PortalUtil.getCompany(actionRequest) out.println("Current Company:${company.getName()}\n") out.println("User Info:") userInfo.each { k,v -> out.println("${k}:${v}") } \end{verbatim} \begin{figure} \centering \includegraphics{./images/groovy-script-current-user-info.png} \caption{Here's an example of invoking a Groovy script that uses the predefined \texttt{out}, \texttt{actionRequest}, and \texttt{userInfo} variables to print information about the company and current user.} \end{figure} \section{Tips}\label{tips} Keep these things in mind when using the script console: \begin{itemize} \tightlist \item There is no undo. \item There is no preview. \item Permissions checking is not enforced for local services. \item Scripts are executed synchronously. Avoid executing scripts that might take a long time. \end{itemize} For these reasons, use the script console cautiously. Test your scripts on non-production systems before running them on production. Of course, Liferay's script engine can be used outside of the script console. Next, you'll learn how workflows leverage Liferay's script engine. \chapter{Leveraging the Script Engine in Workflow}\label{leveraging-the-script-engine-in-workflow} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay's Kaleo workflow engine provides a robust system for reviewing and approving content in an enterprise environment. Even if you don't leverage scripts, it's a powerful and robust workflow solution. Adding scripts takes it to the next level. These scripts aren't run from the script console, but are embedded in \href{/docs/7-2/reference/-/knowledge_base/r/crafting-xml-workflow-definitions}{XML workflow definitions} and run during the execution of the workflow. \section{Injected Variables}\label{injected-variables} Usually when you're scripting in Groovy, you must define your variables. \begin{verbatim} KaleoInstanceToken kaleoInstanceToken=new KaleoInstanceToken(); \end{verbatim} In workflow scripts, there are several \href{https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-workflow/portal-workflow-kaleo-runtime-scripting-impl/src/main/java/com/liferay/portal/workflow/kaleo/runtime/scripting/internal/util/ScriptingContextBuilderImpl.java}{pre-defined variables} injected into your script context, to be called without defining them first. \section{Variables that are Always Available}\label{variables-that-are-always-available} These variables are available from anywhere that you can run a workflow script: \begin{description} \tightlist \item[\texttt{kaleoInstanceToken} (\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-workflow/portal-workflow-kaleo-api/src/main/java/com/liferay/portal/workflow/kaleo/model/KaleoInstanceToken.java}{\texttt{KaleoInstanceToken}})] A workflow instance and corresponding instance token (the \texttt{KaleoInstanceToken}) are created each time a User clicks \emph{Submit for Publication}. Use the injected token to retrieve its ID, by calling \texttt{kaleoInstanceToken.getKaleoInstanceTokenId()}. This is often passed as a method parameter in a script. \item[\texttt{userId}] The \texttt{userId} returned is context dependent. Technically, the logic works like this: if the \texttt{KaleoTaskInstanceToke.getcompletionUserId()} is null, check \texttt{KaloeTaskInstanceToken.getUserId()}. If that's null too, call \texttt{KaleoInstanceToken.getUserId()}. It's the ID of the last User to intervene in the workflow at the time the script is run. In the \texttt{created} node, this would be the User that clicked \emph{Submit for Publication}, whereas it's the ID of the reviewer upon exit of the \texttt{review} node of the Single Approver definition. \item[\texttt{workflowContext} (\texttt{Map\textless{}String,\ Serializable\textgreater{}})] The workflow context is full of useful information you can use in your scripts. Usually you'll pass this as a parameter to a method that requires a \texttt{WorkflowContext} object, but all of the \texttt{WorkflowContext}'s attributes are available in the script as well. The workflow context in the script is context dependent. If a call to \texttt{ExecutionContext.getWorkflowContext()} comes back null, then the workflow context is obtained by \texttt{KaleoInstanceModel.getWorkflowContext()}. \end{description} \section{Variables Injected into Task Nodes}\label{variables-injected-into-task-nodes} If a \texttt{kaleoTaskInstanceToken} has been created: \begin{description} \tightlist \item[\texttt{kaleoTaskInstanceToken} (\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-workflow/portal-workflow-kaleo-api/src/main/java/com/liferay/portal/workflow/kaleo/model/KaleoTaskInstanceToken.java}{\texttt{KaleoTaskInstanceToken}})] The task's token itself is available in the workflow script. Use it to get its ID, to use in other useful programmatic workflow activities, like programmatic assignment. \item[\texttt{taskName} (\texttt{String})] The task's own name is accessible (returns the same as \texttt{KaleoTak.getName()}). \item[\texttt{workflowTaskAssignees} (\texttt{List\textless{}}\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/portal-kernel/src/com/liferay/portal/kernel/workflow/WorkflowTaskAssignee.java}{\texttt{WorkflowTaskAssignee}}\texttt{\textgreater{}})] If the script is inside a task node, get a \texttt{List} of its assignees. \item[\texttt{kaleoTimerInstanceToken} (\href{https://github.com/liferay/liferay-portal/blob/7.2.0-ga1/modules/apps/portal-workflow/portal-workflow-kaleo-api/src/main/java/com/liferay/portal/workflow/kaleo/model/KaleoTimerInstanceToken.java}{\texttt{KaleoTimerInstanceToken}})] If a \href{/docs/7-2/reference/-/knowledge_base/r/workflow-task-nodes\#task-timers}{task timer} exists, use the \texttt{kaleoTimerInstanceToken} to get its ID, by calling \texttt{kaleoTimerInstanceToken.getKaleoTimerInstanceTokenId()}. \end{description} \section{Scripting Examples}\label{scripting-examples} The final step in a workflow runs a script that makes content available for use. The snippet below accesses the Java class associated with the workflow to set content's status to \emph{approved}. \begin{verbatim} groovy \end{verbatim} At virtually any point in a workflow, you can use Liferay's script engine to access workflow APIs or other Liferay APIs. There are a lot of different ways you could use this. Here are a few practical examples: \begin{itemize} \tightlist \item Getting a list of users with a specific workflow-related role \item Sending an email to the designated content approver with a list of people to contact if he is unable to review the content \item Creating an alert to be displayed in the Alerts portlet for any user assigned to approve content \end{itemize} Of course, before you try any of this, you need to know the appropriate syntax for inserting a script into a workflow. In an XML workflow definition, a script can be used in any XML type that can contain an \texttt{actions} tag: those types are \texttt{\textless{}state\textgreater{}}, \texttt{\textless{}task\textgreater{}}, \texttt{\textless{}fork\textgreater{}} and \texttt{\textless{}join\textgreater{}}. Inside of one of those types, format your script like this: \begin{verbatim} *your scripting language of choice* ... \end{verbatim} Here's an example of a workflow script created in Groovy. This one is used with a \texttt{Condition} statement in Kaleo. It accesses Liferay's asset framework to determine the category of an asset. The script uses the category to determine the correct approval process automatically. If the category \texttt{legal} has been applied to the asset, the asset is sent to the \texttt{Legal\ Review} task upon submission. Otherwise, the asset is sent to the \texttt{Default\ Review} task. \begin{verbatim} groovy \end{verbatim} Within a workflow, the next task or state is chosen based on the return value. See some examples of workflow scripts by accessing the \href{/docs/7-2/user/-/knowledge_base/u/workflow\#embedded-workflows}{embedded workflows} and inspecting the XML. \section{Calling OSGi Services}\label{calling-osgi-services} How do you call OSGi services from a workflow script, accounting for the dynamic environment of the OSGi runtime, where services your script depends on can disappear without notice? \href{/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services}{Use a service tracker}. That way you can make sure your code has access to the service it needs, and if not, do something appropriate in response. Here's a little example code to show you how this might look in Groovy: \begin{verbatim} import com.liferay.journal.model.JournalArticle; import com.liferay.journal.service.JournalArticleLocalService; import com.liferay.portal.scripting.groovy.internal.GroovyExecutor; import org.osgi.framework.Bundle; import org.osgi.framework.FrameworkUtil; import org.osgi.util.tracker.ServiceTracker; ServiceTracker st; try { Bundle bundle = FrameworkUtil.getBundle(GroovyExecutor.class); st = new ServiceTracker(bundle.getBundleContext(), JournalArticleLocalService.class, null); st.open(); JournalArticleLocalService jaService = st.waitForService(500); if (jaService == null) { _log.warn("The required service 'JournalArticleLocalService' is not available."); } else { java.util.Listarticles = jaService.getArticles(); if (articles != null) { _log.info("Article count: " + articles.size()); } else { _log.info("no articles"); } } } catch(Exception e) { //Handle error appropriately } finally { if (st != null) { st.close(); } } \end{verbatim} If you read the article on \href{/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services}{service trackers}, the only odd looking piece of the above code is the \texttt{getBundle} call: why is \texttt{GroovyExecutor.class} passed as a parameter? The parameter passed to the \texttt{FrameworkUtil.getBundle} call must be a class from the OSGi bundle executing the workflow script. This is different from the context of a plugin project, where you'd want to get the bundle hosting the class where you're making the call (using \texttt{this.getClass()}, for example). Note that for another scripting engine, you must pass in a concrete class from the particular bundle executing your script. The combination of Liferay's script and workflow engines is incredibly powerful. Since, however, it enables users to execute code, it can be dangerous. When configuring your permissions, be aware of the potential consequences of poorly or maliciously written scripts inside a workflow definition. For more information on creating workflow definitions, see the \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow documentation}. \section{Related Topics}\label{related-topics-1} \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{Running Scripts From the Script Console} \href{/docs/7-2/user/-/knowledge_base/u/script-examples}{Script Examples} \chapter{Script Examples}\label{script-examples} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Here are some examples to help you use Liferay's script console. Note: Most of these originated from a \href{https://liferay.dev/blogs/-/blogs/5-tips-to-improve-usage-of-the-liferay-script-console}{Liferay blog post}. The following scripts are Groovy scripts but they can be adapted to other languages. \begin{itemize} \item \hyperref[example-1-presenting-new-terms-of-use-to-users]{Example 1: Presenting New Terms of Use to Users} \item \hyperref[example-2-embedding-html-markup-in-script-outputs]{Example 2: Embedding HTML Markup in Script Outputs} \item \hyperref[example-3-show-exceptions-in-the-script-console]{Example 3: Show Exceptions in the Script Console} \item \hyperref[example-4-implement-a-preview-mode]{Example 4: Implement a Preview Mode} \item \hyperref[example-5-plan-a-file-output-for-long-running-scripts]{Example 5: Plan a File Output for Long-Running Scripts} \end{itemize} \section{Example 1: Presenting New Terms of Use to Users}\label{example-1-presenting-new-terms-of-use-to-users} This example retrieves user information from the database, makes changes, and then saves the changes in the database. Suppose that your company has updated the \href{/docs/7-2/user/-/knowledge_base/u/instance-configuration-instance-settings\#terms-of-use}{terms of use} and wants present users with the updated terms of use whenever they sign in next. When they agree to the terms of use, a boolean attribute called \texttt{agreedToTermsOfUse} is set in their user records. As long as the value of this variable is \texttt{true}, users aren't presented with the terms of use when they sign in. If you set this flag to \texttt{false} for each user, each user must agree to the terms of use again before they can sign in. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enter and execute the following code in the script console: \begin{verbatim} import com.liferay.portal.kernel.service.UserLocalServiceUtil userCount = UserLocalServiceUtil.getUsersCount() users = UserLocalServiceUtil.getUsers(0, userCount) for (user in users) { println("User Name: " + user.getFullName() + " -- " + user.getAgreedToTermsOfUse()) } \end{verbatim} This code prints each user's \texttt{agreedToTermsOfUse} attribute value. \item Replace that with this script: \begin{verbatim} import com.liferay.portal.kernel.service.UserLocalServiceUtil userCount = UserLocalServiceUtil.getUsersCount() users = UserLocalServiceUtil.getUsers(0, userCount) long currentUserId = Long.parseLong(userInfo.get("liferay.user.id")) for (user in users){ if(!user.isDefaultUser() && (user.getUserId() != currentUserId)) { user.setAgreedToTermsOfUse(false) UserLocalServiceUtil.updateUser(user) } } \end{verbatim} This sets each user's \texttt{agreedToTermsOfUse} attribute to \texttt{false}. It skips the default user as well as the default admin user that's currently signed in and running the script. \item Click \emph{Execute}. \item Verify the script updated the records by running the first script again. All users (except the default user and your user) have been updated. \end{enumerate} You've enabled the new terms of use agreement for all users to accept. \section{Example 2: Embedding HTML Markup in Script Outputs}\label{example-2-embedding-html-markup-in-script-outputs} The output of the script console is rendered as HTML content. Thus, you can embed HTML markup in your output to change its look and feel. Here's an example: \begin{verbatim} import com.liferay.portal.kernel.service.* number = com.liferay.portal.kernel.service.UserLocalServiceUtil.getUsersCount(); out.println( """

    ${number}

    """); \end{verbatim} \begin{figure} \centering \includegraphics{./images/groovy-script-embed-html-markup.png} \caption{Here's an example of invoking a Groovy script that embeds HTML markup in the output of the script.} \end{figure} \section{Example 3: Show Exceptions in the Script Console}\label{example-3-show-exceptions-in-the-script-console} When any exception occurs during script execution, the error message is always the same: \begin{verbatim} Your request failed to complete. \end{verbatim} This message gives no detail about the error. To find information about the error and what caused it, you must usually examine the server logs. You can, however, use the following technique to make exception details appear in the script console. Wrap your code with a try / catch block and print the stacktrace to the console output from the catch clause. Note that even this technique does not catch script syntax errors. Here's an example: \begin{verbatim} try { nullVar = null out.println(nullVar.length()) } catch(e) { out.println("""
    ${e}
    """) e.printStackTrace(out) } \end{verbatim} \begin{figure} \centering \includegraphics{./images/groovy-script-show-exception.png} \caption{Here's an example of a Groovy script that catches exceptions and prints exception information to the script console.} \end{figure} \section{Example 4: Implement a Preview Mode}\label{example-4-implement-a-preview-mode} Since Liferay's script console does not provide an undo feature, it can be convenient to set up a kind of preview mode. The purpose of a preview mode is to determine any permanent effects of a script before any information is actually saved to the Liferay database. The preview mode consists in using a \texttt{previewMode} flag which determines whether the operations with permanent effects should be executed or not. If \texttt{previewMode} is \texttt{true}, all the data that would be permanently affected by the script is printed instead. Then you can see an outline of the data impacted by the script. If everything is okay, switch the flag so the script can make permanent updates to the database. Here's an example Groovy script that sets users to inactive. Clearly, you'd want to test this with preview mode before running it: \begin{verbatim} import java.util.Calendar import com.liferay.portal.kernel.service.* import com.liferay.portal.kernel.model.* import com.liferay.portal.kernel.dao.orm.* import static com.liferay.portal.kernel.workflow.WorkflowConstants.* // // Deactivate users never logged and created since more than 2 years // previewMode = true // Update this flag to false to really make changes Calendar twoYearsAgo = Calendar.getInstance() twoYearsAgo.setTime(new Date()) twoYearsAgo.add(Calendar.YEAR, -2) DynamicQuery query = DynamicQueryFactoryUtil.forClass(User.class) .add(PropertyFactoryUtil.forName("lastLoginDate").isNull()) .add(PropertyFactoryUtil.forName("createDate").lt(twoYearsAgo.getTime())) users = UserLocalServiceUtil.dynamicQuery(query) users.each { u -> if(!u.isDefaultUser() && u.getStatus() != STATUS_INACTIVE) { out.println(u.getEmailAddress()) if(!previewMode) { UserLocalServiceUtil.updateStatus(u.getUserId(), STATUS_INACTIVE) } } } if(previewMode) { out.println('Preview mode is on: switch off the flag and execute ' + 'again this script to make changes to the database') } \end{verbatim} \section{Example 5: Plan a File Output for Long-Running Scripts}\label{example-5-plan-a-file-output-for-long-running-scripts} \noindent\hrulefill \textbf{Important:} The script console is for system operations and maintenance and not for end users. Limit script console access to portal administrators. \noindent\hrulefill When a script has been running for a long time, the console could return an error even though the script can continue running and potentially conclude successfully. But it's impossible to know the outcome without the corresponding output! To bypass this limitation, you can send the output of the script console to a file instead of to the console itself or to the Liferay log. For example, consider this script: \begin{verbatim} import com.liferay.portal.kernel.service.* import com.liferay.portal.kernel.dao.orm.* // Output management final def SCRIPT_ID = "MYSCRIPT" outputFile = new File("""${System.getProperty("liferay.home")}/scripting/out-${SCRIPT_ID}.txt""") outputFile.getParentFile().mkdirs() def trace(message) { out.println(message) outputFile << "${message}\n" } // Main code users = UserLocalServiceUtil.getUsers(QueryUtil.ALL_POS, QueryUtil.ALL_POS) users.each { u -> trace(u.getFullName()) } \end{verbatim} The script above creates a subfolder of \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} called \texttt{scripting} and saves the script output to a file in this folder. After running the script above, you can read the generated file without direct access to the file system. Here's a second script that demonstrates this: \begin{verbatim} final def SCRIPT_ID = "MYSCRIPT" outputFile = new File("""${System.getProperty("liferay.home")}/scripting/out-${SCRIPT_ID}.txt""") out.println(outputFile.text) \end{verbatim} One advantage of using a dedicated output file instead of using a classic logger is that it's easier to get the script output data back. Getting the script output data would be more difficult to obtain from the portal log, for example, because of all the other information there. \section{Related Topics}\label{related-topics-2} \href{/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console}{Running Scripts From the Script Console} \href{/docs/7-2/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{Leveraging the Script Engine in Workflow} \href{/docs/7-2/user/-/knowledge_base/u/using-liferays-script-engine}{Using Liferay's Script Engine} \chapter{Custom Fields}\label{custom-fields} Have you ever wondered why there's no \emph{Head Circumference} field in the form for adding users to Liferay DXP? Probably because most sites based on Liferay DXP don't need it. If you're an administrator at the Lunar Resort, however, you certainly need to know your guests' head circumference so you can provide them with a properly fitting helmet. Many of Liferay DXP's assets and resources let you add new fields to their edit forms. Here's the complete list: \begin{itemize} \tightlist \item Blogs Entry \item Calendar Event \item Document \item Documents Folder \item Knowledge Base Article \item Knowledge Base Folder \item Message Boards Category \item Message Boards Message \item Organization \item Page \item Role \item Site \item User \item User Group \item Web Content Article \item Web Content Folder \item Wiki Page \end{itemize} \noindent\hrulefill \textbf{Developer Use Case:} Adding custom fields to Liferay DXP resources affords flexibility to developers. Suppose you must limit the number of users that can be assigned to a particular Role. First an administrator creates a custom field called \emph{max-users} for the Role. A developer then creates a module that inserts logic before a user is added to that Role. If the logic detects that the maximum number of Role users would be exceeded by completing the action, an exception is thrown and the action does not complete. \noindent\hrulefill \section{Adding Custom Fields}\label{adding-custom-fields} To add custom fields, find the Custom Fields entry beneath the Control Panel's Configuration heading. To add a custom field to one of the listed entities, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Choose a resource by clicking on it. \item Click the add (\includegraphics{./images/icon-add.png}) button. \item Choose a field type: Text Area, Input Field, Dropdown, Checkbox, Radio, Geolocation, Date, True/False. \item Add a name that's used as a key for accessing the field programmatically. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** The Key you enter here is the name of the new field. It's stored in the database and used by developers to access the custom field with the `` tag. It is also used to label the field in the UI. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Choose the Data Type of field and set any advanced properties. \begin{figure} \centering \includegraphics{./images/custom-fields-user-head-circumference.png} \caption{At The Lunar Resort, a Head Circumference field is necessary for all users.} \end{figure} \item Click Save. \end{enumerate} That's it. Once you have a custom field for a resource, go find it in the UI of the resource. First find the UI location for the resource, and all its custom fields are displayed in a Custom Fields panel. For example, consider the Users UI: Navigate to \emph{Control Panel → Users and Organizations}. Click on a User to open the Edit User form and scroll down to find your custom field. \begin{figure} \centering \includegraphics{./images/custom-fields-panel.png} \caption{The Custom Fields panel is found at the bottom of the Edit User form.} \end{figure} You can also leverage your custom field in Content Pages and Display Page Templates. See \href{/docs/7-2/user/-/knowledge_base/u/content-page-elements\#editable-elements}{Editable Elements} for more information. \section{Editing a Custom Field}\label{editing-a-custom-field} You can't change the key or field type of a custom field, but you can delete it and create a new one if necessary. \begin{figure} \centering \includegraphics{./images/custom-fields-configuration.png} \caption{The exact Custom Fields configuration options you use depend on the field type you choose.} \end{figure} Edit an individual custom field's permissions by clicking the field's kebab menu (\includegraphics{./images/icon-actions.png}), then \emph{Permissions}. Permission can be granted or removed for these actions: \begin{itemize} \tightlist \item Delete \item Permissions \item Update \item View \end{itemize} \begin{figure} \centering \includegraphics{./images/custom-fields-edit.png} \caption{You can delete a custom field, edit it, or configure its permissions.} \end{figure} Custom fields make many of Liferay DXP's entities extensible directly from the administrative user interface. Use them as is or combine them with some back-end code, and you have yet another powerful, flexible feature at your disposal. As they're fond of saying at The Lunar Resort, ``The sky is certainly not the limit.'' \chapter{Managing Users}\label{managing-users} Ever heard a retailer advertise as a ``one stop shop'' for anything you want? The idea is they have so much stuff that whatever you want is probably there. Liferay's Control Panel is like this. Where do you create Users, Organizations, or Sites? Where do you configure permissions and plugins and pretty much anything else? You do it from the Control Panel. \begin{figure} \centering \includegraphics{./images/usrmgmt-control-panel.png} \caption{Administrators can access the Control Panel from the Product Menu.} \end{figure} The Control Panel is divided into six main areas: Users, Sites, Apps, Configuration, and Workflow. The Users section lets you create and manage Users, Organizations, User Groups, Roles, and Password Policies. If monitoring has been enabled, you can also view all the live sessions of your Users. \noindent\hrulefill \textbf{Anonymous User:} \emph{Anonymous Anonymous} is used for the new \href{/docs/7-2/user/-/knowledge_base/u/managing-user-data}{Managing User Data} functionality. Created the first time an administrator clicks \emph{Delete Personal Data} for a User, \emph{Anonymous Anonymous} is a deactivated User assigned \href{/docs/7-2/user/-/knowledge_base/u/managing-user-data\#anonymizing-data}{anonymized assets}. The Anonymous User is configurable, so the name and configuration details might be different in your virtual instance. \noindent\hrulefill Begin exploring Liferay's User Management functionality by reading about adding and editing users. \chapter{Users and Organizations}\label{users-and-organizations} \emph{Users} and \emph{Organizations} are fundamental entities. If your site requires people (even just a set of site administrators) to have accounts to do anything, you need to know about users. If your users are at all divided hierarchically, like into departments, you'll find that organizations are helpful. You're probably not surprised to hear that Users and Organizations are managed in the Control Panel's \emph{Users and Organizations} section. If it were any different, it'd be weird. Consider the Lunar Resort site. Consider what you'd do if \begin{itemize} \tightlist \item An employee leaves the company to join that pesky competitor, Martian Resort and Luxury Spa. \item An employee joins the resort as a new Mechanical Crew member. \item An employee is promoted from Crew Supervisor to Department Head and needs the requisite permissions. \item You need to organize the users by department. \item A new department is added to the Lunar Resort and the employees need their own internal website. \item An employee gets married, and their name changes. \end{itemize} The user tasks listed above are all resolved in the Users and Organizations section of the Control Panel. \section{What are Users?}\label{what-are-users} In case there's any confusion over the term, a User is an entity that can sign into the portal and do something. Generally a User has more privileges, called Permissions, than a Guest of your site, who does not sign in. Users are assigned Roles, and Roles define the User's privileges. Understanding Users is pretty straightforward. Organizations are a bit trickier, but a smart administrator like you is undoubtedly up to the challenge. Read more about Organizations \href{/docs/7-2/user/-/knowledge_base/u/organizations}{here}. The remaining articles in this section give you guidance on managing (creating, deleting, editing, and more) Users and Organizations. \chapter{Adding, Editing, and Deleting Users}\label{adding-editing-and-deleting-users} At the root of managing Users is adding, editing, and deleting them. As long as you're the Administrative user, you can do all these things and more. \section{Adding Users}\label{adding-users} Here's how to edit Users: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the Product Menu, click \emph{Control Panel} → \emph{Users} → \emph{Users and Organizations}. \item In the Users tab, click the \emph{Add} button . \begin{figure} \centering \includegraphics{./images/usrmgmt-add-user.png} \caption{Add Users from the Users and Organizations section of the Control Panel.} \end{figure} \item Fill out the Add User form and click \emph{Save}. At a minimum, provide a Screen Name, First Name, Last Name, and Email Address for the User. \end{enumerate} \noindent\hrulefill \begin{verbatim} Note: Screen names and email addresses are not interchangeable. A screen name cannot contain an `@` symbol because it is used in the URL to a User's private page. \end{verbatim} \noindent\hrulefill \begin{verbatim} The Add User functionality is split over several independent forms. Saving the first form creates the User, and then you'll see a success message saying Success. Your request completed successfully. \end{verbatim} After submission of the first form, you see a larger form with many sections. The one you're on is the Information section. To the left is a navigation pane where you can continue configuring the user you're adding by clicking through the available sections. The options in the left menu change as you click through the tabs at the top. Peruse the sections for the three tabs (General, Contact, Preferences) and fill in all the applicable information. \begin{figure} \centering \includegraphics{./images/add-user-forms-menu.png} \caption{At a minimum, enter a screen name, email address, and first name to create a new user account. Then you'll be taken to the Information form and can continue configuring the user.} \end{figure} You don't have to fill anything else out right now. Just note that when the user account was created, a password was automatically generated. If Liferay was correctly installed and a \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{mail server was set up}, an email message with the User's new password was sent to the User's email address. If you haven't set up a mail server, click the \emph{Password} item from the General menu and manually set a password for your new user. Enter the new password twice. \begin{figure} \centering \includegraphics{./images/usrmgmt-require-password-reset.png} \caption{Enter the password twice to manually set the password for a user. If the Password Policy you're using is configured to allow it, select whether to require the user to reset their password the first time they sign in to the portal.} \end{figure} \section{Editing Users}\label{editing-users} If you click on \emph{Users and Organizations} in the Control Panel, you'll see your own user's account in the list of Users, along with any others. To change something about a particular user, click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to that user. Choosing \emph{Edit} takes you back to the Edit User page where you can modify any aspect of the User account including the screen name, email address, first name, last name, Site and Organization memberships, Roles, etc. Choosing \emph{Permissions} allows you to define which Roles have permissions to edit the User. Choosing \emph{Manage Pages} allows you to configure the personal pages of a User. Choosing \emph{Impersonate User} opens another browser window that loads the site as if you were the User so you can test your User management on a User to make sure you're achieving the desired behavior, without having to repeatedly log out of your administrator account and into the User's account. Choosing \emph{Deactivate} deactivates the user's account. The User is still in your database along with all the rest of your Users, but the account is deactivated, so the User cannot sign in to the portal. You can toggle between active and inactive Users in the Users view. If all the Users are active, this filtering option doesn't appear. Choosing \emph{Erase Personal Data} \href{/docs/7-2/user/-/knowledge_base/u/managing-user-data}{deletes the User's personal data}. Choosing \emph{Export Personal Data} lets you \href{/docs-7-2/user/-/knowledge_base/u/exporting-user-data}{download the User's personal data}. \begin{figure} \centering \includegraphics{./images/usrmgmt-active.png} \caption{You can choose whether to view active or inactive (deactivated) portal users in the users list found at \emph{Product Menu} → \emph{Control Panel} → \emph{Users} → \emph{Users and Organizations}.} \end{figure} Most Users can't perform any of the above actions. In fact, most Users won't have access to the Control Panel at all. You can perform all of the above functions because you have administrative access. \section{Deleting Users}\label{deleting-users} You must be careful when deleting Users. To guard against accidental deletion of Users, a two-step process must be followed: deactivate first, then delete. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Find the User to delete in the Users tab of \emph{Control Panel} → \emph{Users} → \emph{Users and Organizations}. If you have a lot of Users, save time by searching for the User. \item Click the \emph{Actions} menu for the User and select \emph{Deactivate}. You're asked to confirm that you want to deactivate the User. Click \emph{OK}. You'll see a success message and the User disappears, but isn't gone yet. \item By default the Users table displays only Active users. Click on \emph{Filter and order} in the top of the table and a dropdown menu appears. Click \emph{Inactive}, and you can see the User you just deactivated. \item Click the Actions menu again, and click \emph{Delete} if you really mean to delete the User. Confirm that you want to delete the User, and now the User is gone. This time, it's for real. \end{enumerate} \noindent\hrulefill \textbf{Deactivated Users:} Deactivating a User means the User can't log in to the portal. He/she has no more permissions in the Sites and pages of the portal than a guest, although the account still exists in the system. Users are reactivated when an administrator finds them in the Users table (be sure you're filtering the table results by Deactivated users), clicks the Actions menu, and selects Activate. There's no confirmation window for activation: they're automatically restored to their former status once Activate is clicked. \noindent\hrulefill Now you understand the basic principles of User administration. There are important additional topics in the next article that you should consider mandatory information for all portal administrators, so do continue reading. \chapter{User Management: Additional Topics}\label{user-management-additional-topics} You've learned the basics on adding and editing Users, but there are additional important topics that go beyond the most basic tasks an administrator must perform. Read on to learn about these. \section{Password Resets}\label{password-resets} The Add User functionality includes a \emph{Require Password Reset} checkbox at the bottom of the Password form. The default password policy does not even allow administrators to deselect this option. As the administrator, however, you can modify the default password policy so that this box becomes usable. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Password Policies} in Control Panel → Users. \item Click on the \emph{Default Password Policy}. \item Deselect the \emph{Change Required} switcher in the Password Changes section. Now you can decide whether users you add must reset their passwords. \end{enumerate} See \href{/docs/7-2/user/-/knowledge_base/u/password-policies}{Password Policies} for more information on editing the default policy or creating your own. \section{Adding an Administrative User}\label{adding-an-administrative-user} If you're setting things up for the first time, you're likely to be using the default administrator account, the account of one of those famous Liferay Administrators, \emph{Test Test} or her cousin, \emph{Joe Bloggs}. Because these are default accounts, hackers know about them, so it's better to set up your own administrator account. Add a user with your information, then give your user account the same administrative rights as the default administrator's account: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Roles} link in the left navigation pane (in the \emph{Edit User} page's \emph{General} tab). This page of the form shows the Roles to which your account is currently assigned. No Roles appear by default (the User role does not appear since it can't be removed). \item Click \emph{Select} under Regular Roles and assign the Administrator Role to your user account. A dialog box pops up with a list of all the regular (portal-scoped) Roles in the portal. Select the Administrator Role from the list (click \emph{Choose}). The dialog box disappears and the Role is added to the list of Roles associated with your account. You are now a portal administrator. Log out and then log back in with your own user account. \end{enumerate} \noindent\hrulefill \textbf{Power Users:} Users are not assigned the Power User Role by default. The Power User Role grants more permissions than the User Role. If the User Role is sufficient for you, ignore the Power User Role. Alternatively, use it to provide a second level of User permissions and assign it to those Users. If there are certain custom permissions that you'd like all of your portal Users to have, you can grant these permissions to the User Role. You can also customize the default Roles a new User receives via \emph{Default User Associations}. This is covered in \href{/docs/7-2/user/-/knowledge_base/u/setting-up-a-virtual-instance}{Setting Up a Virtual Instance}. \noindent\hrulefill In production, you should always delete or disable the default administrator account to secure your portal. \section{Gender}\label{gender} To collect data on users' genders, enable the binary gender field in the \emph{Add User} form or create a \href{/docs/7-2/user/-/knowledge_base/u/custom-fields}{custom field} that meets your needs. Enable the binary field by including the following in \texttt{portal-ext.properties}: \begin{verbatim} `field.enable.com.liferay.portal.kernel.model.Contact.male=true` \end{verbatim} \section{User Profile Pictures}\label{user-profile-pictures} Users have profile pictures. Administrative Users can upload images in the Edit User form, and any User can update her own account information, including image, from her personal site (\emph{My account} → \emph{Account Settings}). \begin{figure} \centering \includegraphics{./images/usrmgmt-ray-avatar.png} \caption{Upload images for user avatars in the Edit User form.} \end{figure} If no image is explicitly uploaded for a User's profile picture, a default User icon is assigned as the User avatar. By default the User's initials are displayed (First Name then Last Name) over a random color. \begin{figure} \centering \includegraphics{./images/users-default-user-image.png} \caption{The default user profile picture is an icon with the user initials over a randomly colored bubble.} \end{figure} If the initials-based approach for generating User profile pictures isn't suitable for your portal, disable the inclusion of Users' initials in the default icons: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item In the Platform section, click \emph{Users} → \emph{User Images}. \item Deselect \emph{Use Initials for Default User Portrait}. \end{enumerate} Now, instead of the default icon, which is a colorful circle containing the user's initials, the icon is a gray circle containing the approximate shape of a human being. \begin{figure} \centering \includegraphics{./images/user-image-not-initials.png} \caption{If you disable the default initials-based profile picture, this icon is used instead.} \end{figure} This is just the default. To override it with your own default image: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create at least one image that is a 100x100 px square. Place it somewhere on the application server's classpath. For example, in Tomcat you could place it in the \texttt{tomcat/webapps/ROOT/WEB-INF/classes} folder. \item Set the following property in a \texttt{portal-ext.properties} file: \begin{verbatim} image.default.user.portrait=image-filename-here.png \end{verbatim} This overrides the value of this \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html}{portal property}: \begin{verbatim} image.default.user.portrait=com/liferay/portal/dependencies/user_portrait.png \end{verbatim} \end{enumerate} \noindent\hrulefill \begin{verbatim} **NOTE:** If you are using the binary field to collect information on users' genders (see above), then you'll have two default images to override. Set these properties instead: image.default.user.female.portrait=image-filename.png image.default.user.male.portrait=image-filename.png \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \tightlist \item Restart the application server. \end{enumerate} \noindent\hrulefill \textbf{Note:} There's a way to adjust which initials are displayed and in what order, so you can make the default user icon (with the user initials) work for your locale. These settings are configured in a \href{/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings}{Language Settings module}, so kidnap a friendly developer, give him a cup of coffee, and tell him the settings you want to change: \texttt{lang.user.default.portrait=initials} sets the type of icon to use for avatars. The default value is \emph{initials}. If set to initials, the next property configures which initials to display, and in what order. Alternatively, specify \emph{image}, which gives you the same non-initials default image shown above. \texttt{lang.user.initials.fields=first-name,last-name} determines which initials appear in the user portrait and in what order. The setting here only matters if \texttt{lang.user.default.portrait} is set to \emph{initials}. Valid values are first name, middle name, last name, with first and last name as the defaults. \noindent\hrulefill \section{Numeric Screen Names}\label{numeric-screen-names} In prior versions, numeric user screen names were disabled out of the box via the default portal property \begin{verbatim} users.screen.name.allow.numeric=false \end{verbatim} Other user management systems (LDAP, for example) did not have the same restriction, which made importing users more difficult. Administrators first had to set the above property to \texttt{true} before importing and hope that no screen names conflicted with site IDs. In 7.0, this property defaults to \texttt{true} and there's no danger of numeric screen names conflicting with site IDs: \begin{verbatim} users.screen.name.allow.numeric=true \end{verbatim} This means you're free to set a user screen name to \emph{24601}, or whatever other number you can think of, and imports from systems that allow numeric screen names go more smoothly. That's everything you need to know to take advantage of this feature. Keep reading to understand what enabled the change. Because users have personal sites, the URL to user \emph{24601}'s personal site is \begin{verbatim} http://localhost:8080/web/24601 \end{verbatim} Meanwhile, a default site URL to cleverly named \emph{Test Site} is \begin{verbatim} http://localhost:8080/web/test-site \end{verbatim} There's no conflict here, but two conditions could easily lead to one: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \emph{Test Site}'s group ID matches the number chosen for the user's screen name. Each site has a unique numeric identifier in the database, called group ID. There's nothing stopping it from matching the user's numeric screen name, so it could easily be \texttt{24601} just like the hypothetical user above. \item A site administrator comes along and changes the site's friendly URL to match its \texttt{groupId}. Hello, URL conflict! Now the site's URL matches the user's URL: \begin{verbatim} http://localhost:8080/web/24601 \end{verbatim} \end{enumerate} This conflict is no longer possible. In 7.0, a site's friendly URL is not allowed to be numeric. See for yourself: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the site's \emph{Configuration} → \emph{Site Settings} → \emph{Site URL} section. \item In the Friendly URL section, enter \emph{24601} and save the form. A failure message appears if you don't have a user with the matching screen name: \begin{verbatim} The friendly URL may conflict with another page. \end{verbatim} You'll see this failure message if there's an existing conflict with a user screen name: \begin{verbatim} Please enter a unique friendly URL. Site [user-first-name user-last-name] has the same friendly URL. \end{verbatim} \end{enumerate} Next, learn about collecting users in organizations. \chapter{Organizations}\label{organizations} An \emph{Organization} groups \href{/docs/7-2/user/-/knowledge_base/u/adding-editing-and-deleting-users}{\emph{Users}} hierarchically. For example, you can model a company's departments (i.e., Human Resources and Customer Support) with Organizations. Organizations often have their own Sites. The \emph{how-to} portion of managing Organizations is in the next article, \href{/docs/7-2/user/-/knowledge_base/u/managing-organizations}{Managing Organizations}. This article contains important conceptual information on what Organizations are and when they're needed. Many simple portal designs don't use Organizations at all; they only use sites. The main purpose of Organizations is to enable distributed User management. Portal administrators can delegate some user management responsibilities to Organization administrators. If you don't anticipate needing to delegate User management responsibilities, your portal design probably doesn't need to include Organizations. \noindent\hrulefill \textbf{User Groups and Organizations:} It's easy to confuse User Groups (covered in a separate article) with Organizations since they both group Users. User Groups are an ad hoc collection of Users, organized for a specific function. In the Lunar Resort, if you wanted a group of bloggers, for example, it wouldn't make sense to assign the Sales Department the role of blogging (see the article on Roles if you're not sure what they are). The Sales Department users could blog whenever a new T-shirt design became available in the Lunar Resort store, but they probably wouldn't be as diligent about announcing the new Rover Racing schedule. Instead, creating a User Group containing one individual from each department who is responsible for blogging would make more sense. Read the article on User Groups to learn more about how to use them in your portal. \noindent\hrulefill \section{When to Use Organizations}\label{when-to-use-organizations} To decide whether your portal design should include Organizations, think about its function. A photo-sharing web site could be powered by Sites only. On the other hand, Organizations are useful for corporations or educational institutions since their users can be placed into a hierarchical structure. Don't think that Organizations are only for large enterprises, though. Any group hierarchy, from large government agencies all the way down to small clubs, can be modeled with Organizations. Also, don't think that you must decide between an Organization-based structure or a Site-based structure for assembling your portal's Users. Users can belong both to Organizations and to independent Sites. For example, a corporation or educational institution could create a social networking site open to all Users, even ones from separate Organizations. To illustrate what an Organization is, consider a potential Organization of the Lunar Resort's Intranet. The company hierarchy has three tiers: The Lunar Resort, its departments, and divisions within each department. \begin{itemize} \tightlist \item Lunar Resort--The top-level Organization. \begin{itemize} \tightlist \item Physical Plant Department--Department of users that keep the place running. \begin{itemize} \tightlist \item Grounds Crew--Users that maintain the grounds. \item Janitorial Crew--Users who keep the resort clean. \item Mechanical Crew--Users who fix stuff, like lunar rovers. \end{itemize} \item Recreation Department--A department that makes sure much fun is had by guests of the Lunar Resort. \begin{itemize} \tightlist \item Golf Instructors--Teach guests how to golf on the moon. \item Rover Race Instructors--Teach guests how to drive the lunar rovers. \item Lunar Sherpas--Lead guests on moon hikes. \end{itemize} \item Sales Department--A department of users who sell things to Lunar Resort guests. \begin{itemize} \tightlist \item Up-sale Group--Make sure guests know how easy it is to improve their stay by spending more money. \item Souvenir and Memorabilia Group--Peddle souvenirs to Lunar Resort guests. \item Retail Group--Maintain the Lunar Resort store, which contains basic necessities, since guests are coming all the way from Earth. \end{itemize} \item Sentient Organism Resources Department--Department of Users that hire, fire and regulate intra-company relationships. We'd call it Human Resources, but what's stopping Martians from applying? Nothing! \end{itemize} \end{itemize} Each department is a sub-Organization of the resort, and each division is a sub-Organization of the department. \section{What can Organization Administrators Do?}\label{what-can-organization-administrators-do} Whenever you have a collection of Users that fits into a hierarchical structure, you can use Organizations to model those Users. Organization administrators can manage all the Users in their Organization \emph{and} in any sub-Organization. Referring to the hierarchy above, for example, an Organization administrator of the Lunar Resort could manage any Users belonging to the resort itself, to any of the departments, or to any of a department's subdivisions. An Organization Administrator of the Physical Plant Department can manage any Users belonging to the Physical Plant Department itself, or to the Grounds Crew, the Janitorial Crew, or the Mechanical Crew. However, an administrator of the Physical Plant Department can't manage Users belonging to the Recreation Department or the Retail Group organization. Organizations and sub-Organization hierarchies can nest to unlimited levels. Users can be members of one or many Organizations. The rights of an Organization administrator apply both to his/her Organization and to any child Organizations. Members of child Organizations are implicit members of their parent Organizations. This means, for example, that members of child Organizations can access the private pages of their parent Organizations. This behavior can be customized in the \texttt{Organizations} \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Organizations}{section of the portal-ext.properties} file where the properties specific to organizations are listed. Since Organizations are designed for distributed user administration, Organization Administrators have an entirely different set of privileges than Site Administrators. Site Administrators are responsible for the pages, portlets, content, and membership of their Sites. To this end, they can set the membership type to Open, Restricted, or Private. They can also add Users to or remove Users from their Sites but cannot manage the Users themselves. If an Organization has a Site attached to it, the Organization Administrator has the same rights as a Site Administrator for managing the Site's content, but an Organization Site's members are the members of the Organization. Thus Organization administrators have more user management permissions than Site administrators: they can edit users belonging to their Organization or any sub-Organization. They cannot add existing portal Users to their organization, but they can create new Users within their Organization. Only portal administrators can add existing users to an Organization. Organization Administrators can't access the Control Panel by default, but it's not necessary. In their personal Sites, Organization administrators can click the \emph{My Organizations} link to gain access to any Organizations they manage. \begin{figure} \centering \includegraphics{./images/orgs-my-organizations.png} \caption{The My Organizations application lets Organization Administrators manage their organizations in their personal site.} \end{figure} \section{Organization Roles and Permissions}\label{organization-roles-and-permissions} A huge time-saving benefit of including Organizations into your portal design is that Organization administrators can assign Organization-scoped Roles to members of the entire Organization. For example, consider an IT Security group in a corporate setting. You could have a sub-Organization of your IT organization that handles security for all applications company-wide. If you grant the IT Security Organization the portal administrator Role, all members of the Organization get administrative access to the entire system. Suppose further that a User in this Organization was later hired by the Human Resources department. The simple act of removing the User from the IT Security Organization also removes the User's administrative privileges, since the privilege came from the IT Security Organization's Role. By adding the User to the HR Organization, any roles the HR Organization has (such as access to a benefits system in the portal) are transferred to the User. In this manner, you can design your portal to correspond with your existing organization chart and Users' permissions are granted according to their positions in the chart. Of course, this is only one way to set things up. If you have more complex requirements for permissions within an Organization, you can create custom Organization-scoped Roles to assemble the permissions you wish to grant to particular Users. Alternatively, attach a Site to your Organization and use Site Teams to assemble the sets of permissions (see below). See the \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions article} for more detail. \section{Organization Sites}\label{organization-sites} Does your Organization need to have its own Site? If an organization has an attached Site, the Organization's administrators are treated as the Site administrators. This means that they can manage the pages, portlets, and content of the Site as well as the Users of the Organization. Members of an Organization with an attached Site are treated as members of the Organization's Site. This means that they can access the private pages of the Organization's Site, along with any portlets or content there. Attaching Sites to Organizations allows portal administrators to use Organizations to facilitate distributed portal administration, not just distributed User administration. That's a lot of information on Organizations. Next, learn how to create and manage Users and Organizations. \chapter{Managing Organizations}\label{managing-organizations} If you're not entirely sure what Organizations are or whether you need them, start \href{/docs/7-2/user/-/knowledge_base/u/organizations}{here}. This article gets right to the practical stuff: how to manage Organizations. \section{Adding Organizations}\label{adding-organizations} Add an Organization (perhaps start by adding the \emph{Physical Plant Department} organization to the Lunar Resort): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Users and Organizations} from Control Panel → Users. \item Go to the \emph{Organizations} tab and click the \emph{Add} button. Fill out the Name field at a minimum. \item If you're creating a child Organization, use the Parent Organization \emph{Select} button to select an Organization in the system to be the direct parent. Click the \emph{Remove} button to remove the currently configured parent. \item Click \emph{Save} when finished filling out the Add Organization form. \end{enumerate} As when creating a new user, once you submit the form a success message appears and you have access to a new form which lets you enter additional information about the Organization. Organizations can have associated multiple email addresses, postal addresses, web sites, and phone numbers. The Services link can be used to indicate the operating hours of the Organization, if any. \noindent\hrulefill \textbf{Tip:} After creating an Organization, assign the desired user to the Organization Owner Role. The Organization Owner can do everything that an organization Administrator can. In addition to their full administrative rights within the Organization, they can do these things: \begin{itemize} \tightlist \item Appoint other Users to be Organization Administrators \item Appoint other Users to be Organization Owners \item Remove the memberships of other Organization Administrators or Owners \end{itemize} Organization Administrators can't make these Role assignments and can't manage the memberships of other Organization Administrators or Owners. \noindent\hrulefill \section{Editing Organizations}\label{editing-organizations} To edit an Organization, go to the Users and Organizations section of the Control Panel and click the \emph{Organizations} tab. All active Organizations are listed. Click the \emph{Actions} button next to an Organization. This shows a list of actions you can perform on this Organization. \begin{itemize} \item \emph{Edit} lets you specify details about the Organization, including addresses, phone numbers, and email addresses. You can also create a Site for the Organization. \item \emph{Manage Site} lets you create and manage the public and private pages of the Organization's Site. This only appears after a Site has been created for the Organization. \item \emph{Assign Organization Roles} lets you assign Organization-scoped Roles to Users. By default, Organizations are created with three Roles: Organization Administrator, Organization User and Organization Owner. You can assign one or more of these Roles to Users in the Organization. All members of the Organization automatically get the Organization User Role so this Role is hidden when you click Assign Organization Roles. \item \emph{Assign Users} lets you search and select Users to be assigned to this Organization as members. \item \emph{Add User} adds a new User and assigns the User as a member of this Organization. \item \emph{Add Organization} lets you add a child Organization to this Organization. This is how you create hierarchies of Organizations with parent-child relationships. \item \emph{Delete} removes this Organization. Make sure the Organization has no Users in it first. You'll be prompted for confirmation that you want to delete the Organization. If there are Users in the Organization or if there are sub-Organizations, you must remove the Users and delete the sub-Organizations before deleting the parent Organization. \end{itemize} If you click the Organization name you can view both a list of Users who are members of this Organization and a list of all the sub-Organizations of this Organization. \section{Organization Types}\label{organization-types} By default, Liferay DXP only includes the \emph{Organization} type. Configure the existing type or add additional types using the aptly named Organization Type entry in System Settings. There are two main reasons to configure Organization types: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Organizations usually correlate to real-life hierarchical structures. Calling them by their real names is helpful for administrators and Users. In the Major League Baseball (MLB) example, \emph{League}, \emph{Division}, and \emph{Team} Organization types are useful. \item Enforce control over which Organizations can be top level Organizations and the type of sub-Organization allowed for each parent Organization type. For example, MLB would not allow Division Organization types to be sub-Organizations of Team Organizations. \end{enumerate} \begin{figure} \centering \includegraphics{./images/orgs-organization-type.png} \caption{Create new organization types through the System Settings entry called Organization Types.} \end{figure} Check out the configuration options that configure the default \emph{Organization} type and then configure an additional type. To add another Organization type called \emph{League}, enter these options into the configuration form: \begin{description} \tightlist \item[Name: \emph{League}] Adds League to the list of Organization types that appear in the Add Organization menu. \item[Country Enabled: \emph{True}] Enables the Country selection list field on the form for adding and editing League types. \item[Country Required: \emph{False}] Specifies that the \emph{Country} field is not required when adding a League. \item[Rootable: \emph{True}] Enables Leagues as a top level Organization. Limit League to sub-Organization status by excluding this property. \item[Children Types: \emph{Division}] Specifies Division as the only allowable sub-Organization type for the League parent type. \end{description} Once you configure additional Organization types and click Save, you'll find your new type(s) available for selection in the Add Organization form. \begin{figure} \centering \includegraphics{./images/orgs-add-custom-organization.png} \caption{Custom configuration types are available in the Add Organization form.} \end{figure} Users can join or be assigned to Sites when they share a common interest. Users can be assigned to Organizations when they fit into a hierarchical structure. User groups provide a more ad hoc way to group users than sites and Organizations. You'll look at them next. \chapter{Roles and Permissions}\label{roles-and-permissions} If a \emph{Role} were to win a Grammy or an Oscar or some other ego-feeding popularity contest, it better remember to thank all its \emph{permissions} groupies during the acceptance speech, because they're the ones doing the real work. The Role is just the pretty face, so to speak. Roles collect permissions that define a particular function, according to a particular scope. Roles collect permissions, and Users are assigned to Roles. \noindent\hrulefill \textbf{Note:} Roles are assigned to Users, but it's tedious to assign each User to a Role intended for lots of Users. Recall that Users are grouped in Sites, Organizations, and User Groups. Implicitly assign regular scoped permissions to Users by assigning a Role directly to one of these User groupings. \begin{figure} \centering \includegraphics{./images/roles-assignees.png} \caption{Assign Users to a role, directly or by their association with a Site, Organization, or User Group.} \end{figure} \noindent\hrulefill Take a Message Board Administrator Role, for example. A Role with that name should have permissions relevant to the specific Message Board portlets delegated to it. Users with this Role inherit the permissions collected underneath the umbrella of the Role. In addition to regular Roles, Site Roles, and Organization Roles, there are also Teams. Teams can be created by site administrators within a specific Site. The permissions granted to a Team are defined and applied only within the Team's site. The permissions defined by regular, Site, and Organization Roles, by contrast, are defined at the global level, although they are applied to different scopes. \begin{description} \tightlist \item[Regular role] Permissions are defined at the global level and are applied at the global scope. \item[Site role] Permissions are defined at the global level and are applied to one specific Site. \item[Organization role] Permissions are defined at the global level and are applied to one specific Organization. \item[Team] Permissions are defined within a specific Site and are assigned within that specific Site. \end{description} \noindent\hrulefill \textbf{Note:} Some permissions cannot be handled from the control panel. Asset-level permissions (for instance, permission to edit an individual blog post, or view a folder in the Documents and Media library) are managed from the individual asset. See \href{/docs/7-2/user/-/knowledge_base/u/widget-permissions}{Widget Permissions} for details. \noindent\hrulefill \section{Deleting Asset Containers}\label{deleting-asset-containers} A Web Content Folder contains Web Content articles. The Web Content Folder is an asset container, and the Web Content Article is an asset. It's possible to give a Role permission to delete an asset container without giving the Role permission to delete individual assets. In that case, beware: if a Role assignee deletes an asset container with individual assets in it, the individual assets themselves are deleted as well. Besides Web Content Folders, examples of asset containers include Bookmarks Folders, Message Boards Categories, Wiki Nodes, and Documents and Media Folders. You might not need to create a Role for a certain functionality. Liferay provides many pre-configured Roles for your convenience. \section{Default Liferay Roles}\label{default-liferay-roles} In the Roles Application appears a list of all the Roles in Liferay, by scope. These are some of the pre-configured regular Roles: \begin{itemize} \tightlist \item Guest: The Guest role is assigned to unauthenticated users and grants the lowest-level permissions. \item User: The User role is assigned to authenticated Users and grants basic permissions (mostly \emph{Add to Page} permissions for their own Sites). \item Power User: The Power User Role grants more permissions than the User Role. It's an extension point for distinguishing regular Users from more privileged Users. For example, you can set things up so that only Power Users have personal sites. \item Administrator: The administrator Role grants permission manage the entire portal, including global portal settings and individual Sites, Organizations, and Users. \end{itemize} These are some of the pre-configured site roles: \begin{itemize} \tightlist \item Site Member: The Site Member Role grants basic privileges within a Site, such as permission to visit the Site's private pages. \item Site Administrator: The Site Administrator Role grants permission to manage \emph{almost} all aspects of a Site including site content, site memberships, and site settings. Site Administrators cannot delete the membership of or remove roles from other Site Administrators or Site Owners. They also \emph{cannot} assign other Users as Site Administrators or Site Owners. \item Site Owner: The Site Owner Role is the same as the Site Administrator Role except that it grants permission to manage \emph{all} aspects of a Site, including permission to delete the membership of or remove Roles from Site Administrators or other Site Owners. They \emph{can} assign other users as Site Administrators or Site Owners. \end{itemize} These are some of the pre-configured organization roles: \begin{itemize} \tightlist \item Organization User: The Organization User role grants basic privileges within an Organization. If the Organization has an attached Site, the Organization User Role implicitly grants the Site member Role within the attached Site. \item Organization Administrator: The Organization Administrator Role grants permission to manage \emph{almost} all aspects of an Organization including the Organization's Users and the Organization's Site (if it exists). Organization Administrators cannot delete the membership of or remove Roles from other Organization Administrators or Organization Owners. They also \emph{cannot} assign other Users as Organization Administrators or Organization Owners. \item Organization Owner: The Organization Owner Role is the same as the Organization Administrator Role except that it grants permission to manage \emph{all} aspects of an Organization, including permission to delete the membership of or remove Roles from Organization Administrators or other Organization Owners. They \emph{can} assign other Users as Organization Administrators or Organization Owners. \end{itemize} \noindent\hrulefill \textbf{Tip:} It's easy to overlook the differences between owner type roles and administrator type roles for Sites and Organizations. Site and Organization administrators \emph{cannot} remove the administrator or owner Role from any other administrator or owner, and they \emph{cannot} appoint other Users as site or organization administrators or owners. In contrast, site and organization owners \emph{can} do those things. \noindent\hrulefill Roles, and the permissions granted with their assignment, are foundational components in Liferay. Understanding their uses and configuration enhances your ability to configure Liferay DXP to suit your organizational needs. \chapter{Managing Roles}\label{managing-roles} You manage Roles and Permissions in the Control Panel (\emph{Control Panel} → \emph{Users} → \emph{Roles}). There you'll find an application for creating Roles, granting them permissions, and assigning Users to them. Roles can be scoped by portal, Site, or Organization. Defining a Role's permissions is a topic deserving its own article. Read \href{/docs/7-1/user/-/knowledge_base/u/defining-role-permissions}{here} about defining a role's permissions. \begin{figure} \centering \includegraphics{./images/roles-app.png} \caption{The Roles application lets you add and manage roles for the global (Regular), Site, or Organization scope.} \end{figure} \section{Creating Roles}\label{creating-roles} Determine the scope of the Role you must create. Roles can be scoped globally (Regular Roles), to a specific Site (Site Roles), or to an Organization (Organization Roles). To create a Role: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the tab for the proper Role scope, then click the \emph{Add} (\includegraphics{./images/icon-add.png}) button. \item Enter a title and description. The title field is required but the description is optional. \item Enter a Key, if desired. This required field provides a key that can be used to refer to the Role programmatically. It's auto-populated with the title text, but you can override it if desired. \item Click \emph{Save}. \end{enumerate} Now the Role is present in the database and ready for further configuration. \section{Assigning Users to a Role}\label{assigning-users-to-a-role} Assign users to a Role in the Assignees tab of the Add/Edit Role form. Roles are assigned to Users, Sites, Organizations, or User Groups. Here's how to assign the User Group Manager Role created in the last section to Users: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Assignees tab of the Add/Edit Role form, click the second level tab for \emph{Users}. \item Click the Add button (\includegraphics{./images/icon-add.png}). \item Select the Users you want to add to the Role and click \emph{Add}. \end{enumerate} If assigning a group, note that all Users assigned to that group inherit the Role as well. That's a good start, but your Role isn't worth the database row it occupies without defining permissions for the Role. Read the next article to learn how. \chapter{Defining Role Permissions}\label{defining-role-permissions} Roles collect permissions, so when Users are given a Role, they receive all the permissions defined by the Role. If you create a Role with permission to access something in the Control Panel, keep in mind that the \emph{View Control Panel Menu} permission is automatically granted. Consider a Role called User Group Manager. Define the permissions for the User Group Manager Role so that assigned Users can add Users to or remove Users from any User Group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Control Panel and then click on \emph{Users} → \emph{Roles}. \item On the Regular Roles screen, click \emph{Add} (\includegraphics{./images/icon-add.png}). \item After naming your Role, click \emph{Save}. \item Click on the \emph{Define Permissions} tab. \item Drill down in the menu on the left to \emph{Control Panel} → \emph{Users} → \emph{User Groups}. \item Under the \emph{General Permissions} heading, flag \emph{Access in Control Panel} and \emph{View}. This lets user group managers access the User Groups Control Panel portlet and view existing User Groups. \item Since you want User Group managers to be able to view User Groups and assign members to them, also check the \emph{Assign Members} and \emph{View} permissions under the \emph{Resource Permissions} → \emph{User Group} heading. \item There's one last necessary permission you might not think of in association with this Role. In \emph{Control Panel} → \emph{Users} → \emph{Users and Organizations}, User Group managers need \emph{View} permission on the User resource. Grant this permission. \item Click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/roles-define-permissions.png} \caption{When defining permissions on a Role, the Summary view provides a list of permissions that have already been defined for the role. The area on the left side of the screen lets you drill down through various categories of permissions.} \end{figure} Now the User Group Manager Role has all the permissions necessary for adding Users to User Groups. After all, User Group managers can view User Groups, assign members, and access User Groups in the Control Panel. The permission to view Users in the Control Panel was necessary because you must view Users to assign them as members of a Role. Without this permission, User Group managers see an empty list if they try to add Users to a Role. \begin{figure} \centering \includegraphics{./images/roles-no-users-found.png} \caption{Users assigned to the User Group Manager Role can't find any users to add unless they have view permissions on the User resource.} \end{figure} \noindent\hrulefill \textbf{Note:} The Roles application in the Control Panel is not the only place where permissions are configured. You can configure a Role's permissions on a resource at a more granular level. For example, from a particular application instance, click its \emph{Options} (\includegraphics{./images/icon-options.png}) menu and select \emph{Permissions}. There you can configure permissions for the resource that overlap with those configured in the Control Panel's Roles application. However, permissions granted or removed in the Control Panel override those made at the more granular level. \noindent\hrulefill There are three categories of permissions: \emph{Control Panel}, \emph{Site Administration}, and \emph{User}. By default, Users can manage their User accounts via the permissions belonging to the User category. Site Administrators can access the site administration tools belonging to the Site Administration category. Portal Administrators can access the entire Control Panel. For custom Roles, you can mix and match permissions from as many categories as you like. The permissions in the Site Administration → Applications categories govern the content that can be created by portlets such as the Wiki and Message Boards. If you pick one of the portlets from this list, you see options for defining permissions on its content. For example, if you pick Message Boards, you see permissions for creating categories and threads or deleting and moving topics. Site application permissions affect the application as a whole. Using the Message Boards as an example, an application permission might define who can add the Message Boards portlet to a page. \begin{figure} \centering \includegraphics{./images/roles-message-board-content-permissions.png} \caption{You can fine-tune which actions are defined for a role within a specific application like the Message Boards.} \end{figure} The Control Panel permissions affect how the Control Panel appears to the User in the Control Panel. The Control Panel appears differently for different Users, depending on their permissions. Some Control Panel portlets have a Configuration button, and you can define who gets to see that. You can also fine-tune who gets to see various applications in the Control Panel. If you want to change the scope of a permission, click the \emph{Change} link next to the gear icon next to the permission and then choose a new scope. After you click \emph{Save}, you'll see a list of all permissions currently granted to the Role. From the Summary view, you can add more permissions or go back to the Role Application default view by clicking on the \emph{Back} (\includegraphics{./images/icon-back.png}) icon. Sometimes you might find that a certain permission grants more or less access than what you expected---always test your permissions configurations! \section{Delegating Social Activities Configuration}\label{delegating-social-activities-configuration} There's a permission that allows Site administrators to delegate responsibility for configuring social activities to other Users. To add this permission to a Role, click \emph{Actions} next to the desired Role and select \emph{Define Permissions}. Find the \emph{Site Administration} → \emph{Configuration} → \emph{Social Activity} permissions category. Flag all of the permissions and then click \emph{Save}: \begin{itemize} \tightlist \item Access in Site Administration \item Configuration \item Permissions \item Preferences \item View \end{itemize} Once these permissions are granted, Role assignees can manage the site's Social Activities. Roles allow portal administrators to define various permissions in whatever combinations they like. This gives you as much flexibility as possible to build the Site you have designed. \chapter{Managing User Data}\label{managing-user-data} Internet users are increasingly and justifiably concerned about how their personal data is processed by the systems they use. The enforcement of GDPR is a crystallization of these concerns into legislative action. Companies processing the personal data of EU residents must adopt appropriate measures to protect User data. Of course, legal requirements like those in GDPR only explain one reason for companies to develop policies for ensuring their users' right to privacy. The market demands site owners show higher levels of responsiveness to User inquiries into how their data is stored and processed. Liferay is aware of the need for functionality to address User data management, and added two important features toward this end: \begin{description} \tightlist \item[\href{/docs/7-2/user/-/knowledge_base/u/sanitizing-user-data}{Erase and/or anonymize data associated with a User}] Administrative Users go through a step by step process, choosing to erase certain pieces of data and anonymize others. \item[\href{/docs/7-2/user/-/knowledge_base/u/exporting-user-data}{Export a User's personal data}] Export ZIP files containing the data associated with a User. \end{description} These features are tools that get you closer to meeting two of GDPR's technically challenging requirements, the \emph{right to data portability} and the \emph{right to be forgotten}. \noindent\hrulefill \textbf{Note:} It is Liferay's sincerest hope that through the User Management functionality of Liferay DXP, companies processing the personal data of their website's users can satisfy the requirements of GDPR. However, the tools discussed here and anywhere else in the documentation, including those directly aimed at addressing GDPR requirements (as in this article) do not guarantee compliance with the legal requirements of GDPR. Each company or individual whose website processes user personal data and is under the jurisdiction of GDPR must carefully determine the precise steps necessary to ensure they are fully compliant with GDPR. \noindent\hrulefill \section{Anonymizing Data}\label{anonymizing-data} Deleting User data is the safest way to honor \emph{right to be forgotten} requests. When User data must be preserved, automatic anonymization of the data is in order. Users being anonymized must have their identifiers (for example, User ID and User Name) removed from content they've interacted with. However, portal content usually requires this information for its applications to work properly. Therefore, the User's identifiers must be replaced by something, or someone. Meet the new User, \emph{Anonymous Anonymous}, identity swapper \emph{extraordinaire}. This deactivated User is dedicated to be the User whose identifiers are assigned to anonymized content. This identity swap is an important step in the anonymization process, but additional manual intervention may be necessary to truly achieve anonymization. \begin{figure} \centering \includegraphics{./images/users-anonymized-content.png} \caption{Anonymized content is presented with the User Anonymous Anonymous's identifying information.} \end{figure} Here at Liferay, we've grown fond of \emph{Anonymous Anonymous}. If you'd rather start from scratch or assign an existing User to be the Anonymous User, get rid of \emph{Anonymous Anonymous} and configure your own Anonymous User. The anonymous user is programmatically created for each instance the first time an Administrator clicks \emph{Delete Personal Data} from a User's Actions menu (\includegraphics{./images/icon-actions.png}). If you haven't yet done that, no Anonymous User exists. The easiest way to set up a new User as the Anonymous User is to edit an existing Anonymous User configuration, passing in the new Anonymous User's User ID. To edit an existing configuration: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Configuration → System Settings → Users → Anonymous User. \item Edit the existing configuration, providing a different User ID. Get the User ID from Control Panel → Users → Users and Organizations. Click on the User and find the User ID in the Information screen of the Edit User application. \item Click \emph{Update}. \end{enumerate} To create a new Anonymous User: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/user/-/knowledge_base/u/adding-editing-and-deleting-users\#adding-users}{Create a User} use for data anonymization. Alternatively, you can use an existing User. \item If there's already an Anonymous User configured for the instance, there are two ways to remove it: Delete the User entirely. Deleting the User simultaneously deletes its configuration as the Anonymous User. Go to Control Panel → Users → Users and Organizations. If it's an active User, first deactivate, then delete the User. The default Anonymous Anonymous User is deactivated by default. Simply delete the User in this case. Click the Actions button (\includegraphics{./images/icon-actions.png}) and select \emph{Delete}. If you don't want to delete the User, just delete the User's configuration as the Anonymous User. Go to Control Panel → Configuration → System Settings → Users → Anonymous Users. \item Add a new Anonymous User configuration. Click the \emph{Add} button. \item Fill out the two fields, Company ID and User ID. Get the Company ID from Control Panel → Configuration → Virtual Instances. The Instance ID and Company ID are the same. Get the User ID from Control Panel → Users → Users and Organizations. Click on the User and find the User ID in the Information screen of the Edit User application. \end{enumerate} There can only be one Anonymous User configured for each instance. \begin{figure} \centering \includegraphics{./images/users-anonymous-config.png} \caption{Assign your own Anonymous User from Control Panel → Configuration → System Settings → Users → Anonymous User.} \end{figure} \section{Manual Anonymization}\label{manual-anonymization} Anonymizing just the User's identification fields is often not enough. If a User named Ziltoid Omniscient complains about The Lunar Resort's coffee in a Message Boards Message and in it signs the post with \emph{Supreme Leader of Ziltoidea 9}, anonymizing this post would remove the User's name (Ziltoid Omnisicent) and replace it with Anonymous Anonymous, but searching the Internet for \emph{Ziltoidea 9} quickly reveals that the post was written by \href{https://en.wikipedia.org/wiki/Ziltoid_the_Omniscient}{Ziltoid the Omniscient}. There can be user-entered personal data within the content of an application. You must manually edit such content to remove identifying details. \begin{figure} \centering \includegraphics{./images/users-partial-anonymization.png} \caption{Even though this Message Boards Message (a comment on a blog post in this case) is anonymized, it should be edited to remove User Associated Data from the content of the message.} \end{figure} \chapter{Sanitizing User Data}\label{sanitizing-user-data} One of the technically challenging requirements of the General Data Protection Regulation (GDPR) is \emph{the right to be forgotten}. The purpose of this article is not to go into the details of this requirement, but to show you how the personal data erasure functionality can assist you in satisfying this requirement. A simple way to think of what it means to be \emph{forgotten} by software is to consider a scenario where a new portal administrator is hired immediately after a User's right to be forgotten request has been honored. The new portal administrator has access to all of the Site's content and administration capabilities. Despite this, the administrator must not be able to glean information that could lead her to knowing the identity of the User whose personal data was erased. Conceptually, forgetting a User means two things, at a minimum: \begin{itemize} \tightlist \item Erasing the User's identifying information from the system. In Liferay DXP, this entails removing the User from database tables and search indexes. \item Erasing or anonymizing content the User has interacted with so it cannot be tracked to a real person. \end{itemize} Users can already be deactivated and then deleted, so why add new functionality? Deleting removes the User from the table of Users in the database. The User's information is preserved in other locations, however. In a standard User deletion scenario, all of a User's personally created content is still assigned to the User and her identifiers (User ID and User Name) still appear in the UI next to content associated with her. This unintentional preservation of user-identifying data is inadequate for satisfying some of the GDPR requirements and is the primary reason why the data erasure functionality was added in 7.0. \noindent\hrulefill \textbf{Note:} Personal data erasure can help companies in their attempts to satisfy the requirements of GDPR. Using the data erasure tool described here provides no guarantee of compliance with the legal requirements of GDPR. Each company or individual whose website processes user personal data and is under the jurisdiction of GDPR must carefully determine the precise steps necessary to ensure they are fully compliant with GDPR. \noindent\hrulefill To begin sanitizing a user's data, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Users → Users and Organizations. \item Click the Actions button for a User (\includegraphics{./images/icon-actions.png}) and select \emph{Delete Personal Data}. If you have not deactivated the user, you will be asked to do so. The User's Personal Data Erasure screen appears. \end{enumerate} \section{The Personal Data Erasure Screen}\label{the-personal-data-erasure-screen} You can browse all data the user has posted on the system. Click \emph{Personal Site} to browse data from that site. \begin{figure} \centering \includegraphics{./images/users-data-erasure-personal.png} \caption{From here, you can browse all data the user posted on his or her personal Site.} \end{figure} Click \emph{Regular Sites} to browse any data posted in regular Liferay sites. \begin{figure} \centering \includegraphics{./images/users-data-erasure-regular.png} \caption{Choose Regular Sites to browse all data posted by the user on administratively-created Sites.} \end{figure} To review the user's data, click the item. For example, Pepper seems to have posted a blog entry on her personal Site. Clicking that entry reveals the title of that blog entry. \begin{figure} \centering \includegraphics{./images/users-data-erasure-blog.png} \caption{Pepper's blog entry might need review.} \end{figure} To review any entry, click it. You're brought to the edit mode of the application (in this case, Blogs), where you can make any changes to the content that are necessary. To manage (anonymize or delete) all the items for an application at once: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Actions button (\includegraphics{./images/icon-actions.png}) for the application. \item If you're sure all items for an application can be safely deleted, choose \emph{Delete}. \item If you're sure simple anonymization is good enough for all of an application's items, choose \emph{Anonymize}. \end{enumerate} Use the interface to browse through the Sites, applications, and data. \section{Delete the User}\label{delete-the-user} Once all data is reviewed, deleted, edited, and/or anonymized as appropriate, delete the User. A dialog box pops up automatically when you're finished. This step is simple: Click \emph{OK}. \begin{figure} \centering \includegraphics{./images/users-delete-user.png} \caption{To finish the data erasure process, delete the User.} \end{figure} Now the User's data is anonymized or deleted, and the User is also deleted. \chapter{Exporting User Data}\label{exporting-user-data} User Management practices must account for the EU's General Data Protection Regulation. One of its tenets is that Users have a right to \emph{data portability}. Data portability means that a User has the right to receive their personal data in a machine-readable format. \noindent\hrulefill \textbf{Note:} Personal data export can help companies in their attempts to satisfy the requirements of GDPR. Using the export tool described here provides no guarantee of compliance with any GDPR requirement. Each company or individual whose website processes user personal data and is under the jurisdiction of GDPR must carefully determine the precise steps necessary to ensure they are fully compliant with GDPR. \noindent\hrulefill The Control Panel's User Management system now natively supports the export of a User's personal data to a ZIP file for download. The data format for the files containing the data is XML. \section{Exporting and Downloading}\label{exporting-and-downloading} To export user data, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Users} → \emph{Users and Organizations}. \item Find the User and click the Actions button (\includegraphics{./images/icon-actions.png}), then click \emph{Export Personal Data}. This opens the User's Export Personal Data screen. \item If there are no existing export processes shown, there's only one thing to do: click the \emph{Add} button (\includegraphics{./images/icon-add.png}). The tool for exporting the User's data appears. \begin{figure} \centering \includegraphics{./images/users-export-data.png} \caption{The Export Personal Data tool lets you export all or some of the User's data.} \end{figure} \item Most of the time you want to export all the available data. Click \emph{Select Items}, and all applications containing User data are selected in the UI. \item Click \emph{Export}. You're taken back to the User's Export Personal Data screen, but now there's an export process in the list. \begin{figure} \centering \includegraphics{./images/users-export-processes.png} \caption{Once User data is successfully exported, the export process is displayed in the User's Export Personal Data list.} \end{figure} \item Download the data. Click the Actions button (\includegraphics{./images/icon-actions.png}) for the process and select \emph{Download}. \end{enumerate} \section{Examining Exported Data}\label{examining-exported-data} So what does the exported data look like? \begin{verbatim} com.liferay.message.boards.model.MBMessage messageId statusByUserId statusByUserName userId userName subject body Great list. I was thinking of bringing the family, but I don't actually believe humans have ever been to the moon, so I guess it would be silly to book a trip! LOL!

    ]]>
    \end{verbatim} In this example, User Jane Slaughter made a Message Boards Message post, and her User information was recorded in the \texttt{MBMessage} model's database table. This actually corresponds with a comment on a Blogs Entry: \begin{figure} \centering \includegraphics{./images/users-mbmessage.png} \caption{A Comment on a blog post is User Associated Data.} \end{figure} Exporting User data informs Site owners and Users about how much personal data the sight may have. \chapter{User Groups}\label{user-groups} A User Group is a list of Users created for a specific purpose. User Groups can be created across the hierarchical boundaries of \href{/docs/7-2/user/-/knowledge_base/u/organizations}{Organizations}. For example, an administrator could create a Teachers User Group for adding all members to multiple Sites, assign them all to a \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Regular Role}, and create a common set of profile pages for all teachers in the User Group. User Groups are integrated with Roles, Sites, Site Templates, and permissions. This flexibility means that there are many different use cases for User Groups. The articles in this section show you how to work with User Groups to serve the most common use cases. User Groups are most often used in these scenarios: \begin{itemize} \item \textbf{Manage Site membership:} Grant Site membership to all Users in a User Group. Using the previous example, the Teachers user group could be added as a member of the Sites \emph{University Employees} and \emph{Students and Teachers Collaboration}. All users in that User Group would become members of those Sites. \item \textbf{Manage user personal pages:} Provide predefined public or private pages to the users in the user group. For example, the Teachers user group could be created to ensure the home page on all teachers' personal Sites has the same layout and applications. \item \textbf{Collect permissions:} Assign Roles and permissions to a group of Users that don't share an organization. For example, in a university's portal, a user group could be created to group all teachers independently of their departments (organization). This would make it easier to assign one or several Roles at once to all the teachers. \end{itemize} Read on to learn how to manage User Groups. \chapter{Creating a User Group}\label{creating-a-user-group} Follow these steps to create a user group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and select \emph{Control Panel} → \emph{Users} → \emph{Users Groups}. Any existing user groups appear here in a table. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This opens the \emph{New User Group} form. \item Give your user group a name and description. \item If you want to create My Profile and/or My Dashboard pages for the user group's members, select a Site Template to use from the \emph{My Profile} and \emph{My Dashboard} selector menus. \item Click \emph{Save}. The new user group then appears in the table. \end{enumerate} Note that new User Groups don't have any Users. The next section shows you how to add members to a user group. \begin{figure} \centering \includegraphics{./images/new-user-group.png} \caption{The New User Group form.} \end{figure} \begin{figure} \centering \includegraphics{./images/user-groups-table.png} \caption{The user group you just created now appears in the table.} \end{figure} \section{Assigning Members to a User Group}\label{assigning-members-to-a-user-group} Follow these steps to add existing users to a user group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you're not already there, open the Menu (\includegraphics{./images/icon-menu.png}) and select \emph{Control Panel} → \emph{Users} → \emph{Users Groups}. The available user groups appear here in a table. \item Click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Assign Members} for the user group you want to add users to. The group's existing users appear in a table. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This opens a list of the users you can select. \item Select one or more users from the list, then click \emph{Add}. This adds the selected users to the group, and returns you to the table containing the group's users. The users you added now appear in the table. \end{enumerate} \begin{figure} \centering \includegraphics{./images/user-group-add-users.png} \caption{Select the users to add to the user group.} \end{figure} \chapter{User Groups and Site Membership}\label{user-groups-and-site-membership} User Groups are used to manage Site membership. When you assign a User Group to a Site, the group's Users become members of that Site. This is one of the main use cases for User Groups. Follow these steps to assign a user group to a Site: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}), select the Site you want to work in, then open its Site Administration menu. \item In the Site Administration menu, select \emph{People} → \emph{Memberships}. This opens the Site Memberships screen. \begin{figure} \centering \includegraphics{./images/site-memberships.png} \caption{Select \emph{Memberships} from the People menu.} \end{figure} \item In Memberships, select the \emph{User Groups} tab. This tab displays any User Groups currently assigned to the Site. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}), select any User Groups you want to assign to the Site, then click \emph{Done}. The user groups you selected now appear in the User Groups tab. \end{enumerate} \begin{figure} \centering \includegraphics{./images/user-groups-site-memberships.png} \caption{The User Groups tab in Memberships shows the User Groups currently assigned to the Site.} \end{figure} \chapter{User Group Sites}\label{user-group-sites} Each User has a personal Site that consists of public (Profile) and private (Dashboard) pages. A \emph{User Group Site} determines the base pages of the User Group members' personal Sites. If the User Group Site pages are added to a User's Profile pages, then the User Group Site is a public Site, accessible to anyone with the URL (\texttt{http://www.{[}sitename{]}.com/web/{[}username{]}}). If the User Group Site pages are added to the user's Dashboard pages, then the User Group Site is a private Site. A mixed approach can also be used, where both private and public pages are added for the User Group Site. If Users belong to multiple User Groups, all the pages from those User Group Sites are made part of their personal Sites. \href{/docs/7-2/user/-/knowledge_base/u/creating-a-user-group}{When creating a user group}, you can create the user group Site via the \href{/docs/7-2/user/-/knowledge_base/u/building-sites-from-templates}{Site Templates} available for selection in the \emph{My Profile} and \emph{My Dashboard} selector menus. You can also create a User Group Site later, either manually or via a Site Template. \section{Creating User Group Sites From Site Templates}\label{creating-user-group-sites-from-site-templates} Follow these steps to create a User Group Site from a \href{/docs/7-1/user/-/knowledge_base/u/building-sites-from-templates}{Site Template}, for a User Group that already exists: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and select \emph{Control Panel} → \emph{Users} → \emph{User Groups}. User groups appear in a table. \item Click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Edit} for the User Group you want to create a Site for. This opens a form that you can use to edit the User Group. Note that this is the same form that appears when \href{/docs/7-2/user/-/knowledge_base/u/creating-a-user-group}{creating a user group}. \item To use a Site Template to create a public profile for the Users on their \emph{My Profile} Site, select that Site Template from the \emph{My Profile} menu. To use a Site Template to create private pages for the Users on their \emph{My Dashboard} Site, select that Site Template from the \emph{My Dashboard} menu. Note that you can also do both. \item Click \emph{Save}. \end{enumerate} Now, when one of the group's Users navigates to their \emph{My Profile} or \emph{My Dashboard} Sites, the content of those Sites reflect the Site Template(s) you selected. User Group Site pages function similarly to regular Site Template pages, with an important exception: User Group Site pages aren't copied for each user. They're shown dynamically along with any custom pages that Users may have on their personal Site. For this reason, Users can't modify pages inherited from the User Group. If needed, the User Group administrator can define certain areas of a page as customizable, like with regular Sites. This lets Users add and configure widgets in the specified area of the page. This flexibility lets you achieve almost any desired configuration for a User's personal Site without having to modify it directly. When Users are assigned to a User Group, they'll immediately have access to the User Group's Site pages from their personal Sites. \noindent\hrulefill \textbf{Note:} Site Templates have an option that propagates changes made to the Site Template. If you use a Site Template with this option enabled, the User Group Sites update automatically when that template changes. If you disable this option but enable it again later, the template's pages are copied to the Users' Sites, overwriting any changes they may have made. For more information on the automatically propagating Site Template changes, see \href{/docs/7-2/user/-/knowledge_base/u/building-sites-from-templates}{Site Templates}. \noindent\hrulefill \section{Creating User Group Sites Manually}\label{creating-user-group-sites-manually} You can create a User Group's Site manually, instead of basing it on a Site Template. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and select \emph{Control Panel} → \emph{Users} → \emph{Users Groups}. User groups appear here. \item Click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Manage Pages} for the user group you want to create a Site for. This opens the \emph{Pages} window. Note that this is the same window you use for \href{/docs/7-2/user/-/knowledge_base/u/creating-pages}{creating pages}. \item Create the public and/or private pages that you want to use for the Users' \emph{My Profile} and/or \emph{My Dashboard} Sites. Public pages you create here become pages on users' \emph{My Profile} Site, while private pages become pages on users' \emph{My Dashboard} Site. \end{enumerate} When you return to User Groups in the Control Panel, you can access a User Group's public and/or private pages via these links in the User Group's \emph{Actions} button (\includegraphics{./images/icon-actions.png}): \begin{itemize} \tightlist \item \textbf{Go to Profile Pages:} Opens the User Group's public \emph{My Profile} page(s) in a new browser window. \item \textbf{Go to Dashboard Pages:} Opens the User Group's private \emph{My Dashboard} page(s) in a new browser window. \end{itemize} In the new window, you can add more pages and portlets and configure Site settings. \section{Legacy User Group Sites Behavior}\label{legacy-user-group-sites-behavior} Since the inheritance of User Group Site pages is now dynamic, even if there are hundreds of thousands of Users, even millions, there isn't an impact in performance. Versions of Liferay Portal and Liferay DXP prior to 7.0 required User Group pages be copied to each User's personal Site. If you long for the old days, or if you're upgrading from an older version and must keep that behavior, enable it by adding the following line to your \texttt{portal-ext.properties} file: \begin{verbatim} user.groups.copy.layouts.to.user.personal.site=true \end{verbatim} When this property is set to \texttt{true}, the template pages are copied to a User's personal Site once, and then may be modified by the User. This means that if changes are made to the template pages later, they only affect Users added to the User Group after the change is made. Users with administrative privileges over their personal Sites can modify the pages and their content if the \emph{Allow Site Administrators to Modify the Pages Associated with This Site Template} box has been checked for the template. When a User is removed from a User Group, the associated pages are removed from the User's personal Site. If a User is removed from a group and is subsequently added back, the group's template pages are copied to the User's Site a second time. Note that if a User Group's Site is based on a Site Template and an administrator modifies the User Group's Site Template after users have already been added to the group, those changes only take effect if the \emph{Enable propagation of changes from the Site Template} box for the User Group was checked. \chapter{Configuring User Group Permissions}\label{configuring-user-group-permissions} Administrators commonly create User Groups so the group's Users can take some specific action in a Site. This is done by assigning the permissions for that action to a Role, and then assigning that Role to the User Group. This grants the Role's permissions to the User Group's Users. Follow these steps to grant permissions to Users in a User Group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item \href{/docs/7-2/user/-/knowledge_base/u/creating-a-user-group}{Create the User Group}. \item \href{/docs/7-2/user/-/knowledge_base/u/user-groups-and-site-membership}{Assign the User Group to a Site}. \item \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Create the Site Role and define its permissions}. \item Assign the Role to the User Group. \end{enumerate} For instructions on the first three steps, click those links above. This article shows you how to assign a Role to a User Group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}), select the Site to work in, then open its Site Administration menu. \item In the Site Administration menu, select \emph{People} → \emph{Memberships}. This opens the Memberships screen. \begin{figure} \centering \includegraphics{./images/site-memberships.png} \caption{Select \emph{Memberships} from the Site Administration menu.} \end{figure} \item In Memberships, select the \emph{User Groups} tab. This tab displays User Groups currently assigned to the Site. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for the User Group you want to assign to a Role, and select \emph{Assign Site Roles}. This opens the Assign Site Roles dialog. \begin{figure} \centering \includegraphics{./images/user-groups-site-role.png} \caption{Select \emph{Assign Site Roles} for the user group.} \end{figure} \item In the Assign Site Roles dialog, select the Role from the list and click \emph{Done}. \end{enumerate} \chapter{Editing User Groups}\label{editing-user-groups} You can access User Groups from \emph{Control Panel} → \emph{Users} → \emph{User Groups}. Managing membership is the most common action you'll likely perform on a User Group. \begin{figure} \centering \includegraphics{./images/user-groups-table.png} \caption{The user groups appear in a table.} \end{figure} Follow these steps to add/remove users to/from a User Group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the User Group's name or description. Alternatively, you can click the User Group's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Assign Members}. This presents a list of the User Group's users. \item To remove a User from the User Group, click the \texttt{X} button next to that User. To remove multiple Users at once, check each User's checkbox and then click the trash icon (\includegraphics{./images/icon-trash.png}) that appears in the Management Bar above the User list. \item To add Users to the User Group, click the \emph{Add} button (\includegraphics{./images/icon-add.png}). In the dialog that appears, select the Users and click \emph{Add}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/user-groups-users.png} \caption{The list of Users lets you manage the User Group's membership.} \end{figure} Other options are available in each User Group's Actions button (\includegraphics{./images/icon-actions.png}): \textbf{Edit:} Modify the User Group's name or description, or choose Site templates to use for the \href{/docs/7-2/user/-/knowledge_base/u/user-group-sites}{User Group's Sites}. \textbf{Permissions:} Assign permissions for viewing and managing the User Group. \textbf{User Group Pages Permissions:} Assign permissions for managing the User Group's Site pages. \textbf{Manage Pages:} Manually manage the User Group's Site pages. See the \href{/docs/7-2/user/-/knowledge_base/u/user-group-sites\#creating-user-group-sites-manually}{documentation on user group Sites} for details. \textbf{Assign Members:} Add/remove Users to/from the User Group. This is described in detail above. \textbf{Delete:} Remove the User Group. Note that you can't delete a User Group that contains Users. You must first remove the Users from the group. If your User Group has public and private Site pages, the options \emph{Go to Profile Pages} and \emph{Go to Dashboard Pages} also appear in your User Group's Actions menu. Clicking one of these links opens that Site in a new browser window. See the \href{/docs/7-2/user/-/knowledge_base/u/user-group-sites}{documentation on user group Sites} for details. \begin{figure} \centering \includegraphics{./images/user-groups-actions.png} \caption{The Actions menu for a user group.} \end{figure} \chapter{Password Policies}\label{password-policies} Password policies enforce password rules to help users specify secure passwords. Use the default policy that ships with Liferay (modified or as is), or create your own policies. Password policies can be assigned to Users or Organizations, or set as the default policy used throughout a virtual instance. \section{Adding and Configuring Password Policies}\label{adding-and-configuring-password-policies} To add or edit password policies, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Control Panel} → \emph{Users} → \emph{Password Policies}. There's already a default password policy in the system. \item Edit this the same way you edit other resources: click \emph{Actions} (\includegraphics{./images/icon-actions.png}) and then click \emph{Edit}. \item To add a new policy, click the \emph{Add} (\includegraphics{./images/icon-add.png}) button. Provide the \emph{Name} (required), \emph{Description}, and specific configuration options for your new password policy. \end{enumerate} \begin{figure} \centering \includegraphics{./images/password-policy-add.png} \caption{You can create new password policies to suit your needs.} \end{figure} There are several configuration categories. \begin{description} \tightlist \item[\textbf{Password Changes}] Allow or disallow users to change their passwords, and set a time limit on the validity of password reset links. \item[\textbf{Password Syntax Checking}] If enabled, require users to use a certain syntax when choosing a password. You can disallow dictionary words, set a minimum length, and more in this section. \item[\textbf{Password History}] If enabled, decide how many passwords to keep in the history, preventing users from reusing an old password. \item[\textbf{Password Expiration}] Decide whether to expire passwords after a specified time. If enabled, specify how long passwords are valid, when and whether to send a warning, and how many times the user can log in after the password is expired before needing to set a new password (called a \emph{Grace Limit}). \item[\textbf{Lockout}] If enabled, set a maximum number of failed authentication attempts before the account is locked, how long the number of attempts is stored, and the lockout duration. \item[\textbf{Self Destruct}] If enabled, set the time after lockout before Liferay self destructs catastrophically, sending the world into apocalyptic chaos, out of which self-aware robots arise and recolonize the world, enslaving the surviving remnant of humanity for their own nefarious purposes. We recommend you keep this disabled. \end{description} Just making sure you were paying attention; that last one doesn't actually exist. Once you configure the policy, click \emph{Save} to add it to the list of ready-to-use password policies. \section{Assigning Users to a Password Policy}\label{assigning-users-to-a-password-policy} To use the default password policy, you don't have to do anything: like its name suggests, it's the default. If you create a new password policy, however, you must assign users to it. To do this click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Assign Members}. \begin{figure} \centering \includegraphics{./images/password-policy-assign-members.png} \caption{Assign members to new password policies to make them take effect.} \end{figure} Choose whether to assign users directly or to assign organizations to the password policy, then click \emph{Add} (\includegraphics{./images/icon-add.png}). Once assignments are saved, the password policy is in effect. Did you know you can change the default password policy and configure it using Liferay's \texttt{portal.properties} file? \section{Default Policy Properties}\label{default-policy-properties} The Default Password Policy is set as the default and configured in Liferay's \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Passwords}{portal.properties} file. Find the properties that start with \texttt{passwords.default.policy}. To make changes, including changing the default policy, add whichever properties and values you choose to modify in your \texttt{portal-ext.properties} file, as usual. Restart the application server and your changes take effect. \begin{verbatim} # # Set the properties of the default password policy. # ... passwords.default.policy.name=Default Password Policy ... \end{verbatim} As you can see, Password Policies give you a simple yet powerful way to set password rules. \chapter{Auditing Users}\label{auditing-users} You've just finished lunch and are ready to get back to work. You have a Site you use to manage your project and before you left, you were about to create a folder in your Documents and Media library for sharing some requirements documentation. Sitting down at your desk, you navigate to the repository and attempt to create the folder. \emph{You do not have permission to perform this action}, Liferay DXP helpfully tells you. ``\emph{What?}'' you blurt in surprise. ``This is \emph{my} project!'' ``Ah, you too?'' asks a co-worker from over the cube wall. ``I lost access to a wiki I was updating just a few minutes ago. I was about to enter a support ticket for it.'' ``Forget the ticket. Let's go see the admin now,'' you say. And off you go, two floors down and to the far end of the building where, as you approach, you can already hear stress in the admin's voice as he tries to reassure someone on the phone. ``Yes, Mr.~Jones. Yes, I'll fix it.'' (\emph{Jones? The president of the company?} goes through your mind.) ``I'll get on it right away, Mr.~Jones. It was just a mistake; I'll fix it. Thank you, Mr.~Jones,'' and he hangs up the phone. ``Problems?'' you ask the admin, whose name is Harry. He does look rather harried. ``Yeah, Tom,'' he says. ``Somebody changed a bunch of permissions---it wasn't me. I'm assuming you and Dick are here because of the same problem?'' ``Yup,'' you say. ``I lost access to a document repository folder.'' ``And I lost access to a wiki,'' Dick says. ``It was probably due to some Site membership change. Let's take a look at the Audit app in the Control Panel and see what happened.'' Sometimes you need to know what Users are doing and exactly who is doing it. If you're a DXP subscriber, you can find this out with the Audit app. In combination with some settings in \texttt{portal-ext.properties}, the Audit app shows you all the activity that occurs on your server. You can quickly find out what changes were made and by whom. If you've delegated permission granting to any group of people, this is an essential feature you're likely to use. \chapter{Viewing Audit Events}\label{viewing-audit-events} The Audit app shows activities in your Liferay DXP installation. Access it by navigating to \emph{Control Panel} → \emph{Configuration} → \emph{Audit}. The app displays a searchable list of captured events. You can browse the list, but searching it is typically faster. This figure shows that John Watson logged in and performed some actions on the site. Click an entry to view details about any of these events. \begin{figure} \centering \includegraphics{./images/audit-list-events.png} \caption{The Audit app displays the events it captures in a searchable list.} \end{figure} \begin{figure} \centering \includegraphics{./images/audit-detail.png} \caption{Click an event in the list to show its details. The details for this event show that John Watson updated his user account's \texttt{prefixId} from \texttt{1} to \texttt{4}. The \texttt{prefixId} represents a name prefix like Dr., Mr., Mrs., or Ms.} \end{figure} As you can see, depending on how many users you have, this list can get populated very quickly. That's why page view events aren't displayed by default. They'll clutter up your audit report, since they'll definitely be the most frequent event. \textbf{Note:} You can add page view events to your audit report, but keep in mind that doing so adds LOTS of events. If you're a glutton for this kind of punishment, add this property to your \texttt{portal-ext.properties} file: \begin{verbatim} audit.message.com.liferay.portal.kernel.model.Layout.VIEW=true \end{verbatim} Liferay DXP's code refers to pages as \emph{layouts}. Setting this property to \texttt{true} therefore records audit events for page views. It's turned off by default because this is too fine-grained for most installations. Once you've added the property, restart your server. \section{Finding Audit Events}\label{finding-audit-events} Finding what you want in a big list of events is like searching for a needle in a haystack. This is why the Audit app has a robust search mechanism. By default, there's only a single search field. Clicking the \emph{magnifier} icon next to the search bar, however, reveals an advanced search dialog broken out by various fields you can use in your search. Here are the available search options: \textbf{Match:} Search for matches to \emph{all} the fields you've specified or \emph{any} single field. \textbf{User ID:} The user ID to search for. This is usually the User who performed some action you'd like to audit. \textbf{User Name:} The user name to search for. This is often easier than searching for a user ID, especially if you don't have access to the database containing the user ID. \textbf{Resource ID:} The ID of the resource that was modified or viewed in this audit record. \textbf{Class Name:} The name of the resource that was modified or viewed in this audit record. For example, you could search for user resources to see if someone modified a user's account. \textbf{Resource Action:} An action performed on the resource. This could be any of these: \texttt{add}, \texttt{assign}, \texttt{delete}, \texttt{impersonate}, \texttt{login}, \texttt{login\_failure}, \texttt{logout}, \texttt{unassign}, or \texttt{update}. \textbf{Session ID:} The session ID to search for. You can use this to correlate a session ID from your web server logs with activity in Liferay DXP. \textbf{Client IP:} The IP address of the client that performed the activity you wish to audit. \textbf{Client Host:} The host name of the client that performed the activity you wish to audit. \textbf{Server Name:} The name of the server in which the activity occurred. If you're using a cluster, each member of the cluster can be individually queried. \textbf{Server Port:} The server port in which the activity occurred. You need this if you run a vertical cluster of multiple VMs on the same machine. \textbf{Start Date:} The low end of the date range you wish to search for. \textbf{End Date:} The high end of the date range you wish to search. For example, to check if someone unassigned a User from a particular Role, you might search for a resource name of \emph{user} and a resource action of \emph{unassign}. Once you have the search results, you can click any of the returned records to see that record's detail page. \begin{figure} \centering \includegraphics{./images/audit-unassign-search.png} \caption{Searching for audit events is easy with the Audit app's advanced search form. You can specify various search criteria to find the types of events you want.} \end{figure} \begin{figure} \centering \includegraphics{./images/audit-unassign-detail.png} \caption{This record shows that the default administrative user removed the Power User Role from the User Test Test.} \end{figure} As you can see, the Audit app shows you what's happening as Users make changes. Use this information to troubleshoot problems, determine ownership of particular actions, or, as Harry (from the story in the introduction) is about to do, find out who made permission changes they weren't supposed to make. \chapter{Configuring Audits}\label{configuring-audits} Audits are enabled by default. The Audit app reports audit events, but you can also report them in Liferay DXP's logs or console, enable them for scheduled jobs, or disable them entirely. There are two main ways to configure Liferay DXP: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Edit a configuration via the Control Panel. This saves the configuration to the database. \item Edit a configuration via an OSGi configuration file (\texttt{.config} file) in your \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. \end{enumerate} These methods apply to each of the audit configuration options explained below. \section{Reporting Audit Events in Liferay's Logs and Console}\label{reporting-audit-events-in-liferays-logs-and-console} Follow these steps to use the Control Panel to configure the reporting of log events in Liferay DXP's log and console: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} and select \emph{Audit} from the \emph{Security} section. \item In the \emph{SYSTEM SCOPE} column on the left, select \emph{Logging Message Audit Message Processor}. \item Select the \emph{Enabled} checkbox to report audit events in Liferay DXP's log. \item Select the \emph{Output to Console} checkbox to report audit events in the console. \item In the \emph{Log Message Format} selector menu, select the format for the audit events (CSV or JSON). \item Click \emph{Save} when you're finished. \end{enumerate} Alternatively, you can make the same configuration via an OSGi configuration file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a file called \texttt{com.liferay.portal.security.audit.router.configuration.LoggingAuditMessageProcessorConfiguration.config}. \item Add these properties to the file: \begin{verbatim} enabled="true" logMessageFormat="CSV" #logMessageFormat="JSON" outputToConsole="true" \end{verbatim} Note that these are the same options set in the Control Panel. Edit them as you see fit. \item Deploy the file to the \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. Note that the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home folder} is typically the application server's parent folder. \end{enumerate} Regardless of your configuration approach, you must also extend Liferay DXP's \texttt{log4j-ext.xml} file to configure Log4j (Liferay DXP's logging implementation) to log messages produced by the appropriate class to the appropriate file. To do so, create a \texttt{portal-log4j-ext.xml} file in \texttt{{[}Liferay\ Home{]}/tomcat-{[}version{]}/webapps/ROOT/WEB-INF/classes/META-INF} with this configuration: \begin{verbatim} \end{verbatim} This configures Log4j to record INFO level messages from the class \texttt{com.liferay.portal.security.audit.router.internal.LoggingAuditMessageProcessor} to a file called \texttt{audit.yyyy-MM-dd.log} in the \texttt{{[}Liferay\ Home{]}/logs} folder. Adjust the audit file properties or log level to your liking. \section{Configuring Audit Events for Scheduled Liferay Jobs}\label{configuring-audit-events-for-scheduled-liferay-jobs} By default, scheduled jobs don't trigger audit events. Follow these steps to enable them via the Control Panel: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}, and select \emph{Infrastructure} from the \emph{Platform} section. \item In the \emph{SYSTEM SCOPE} column on the left, select \emph{Scheduler Engine Helper}. \item Select the checkbox for \emph{Audit scheduler job enabled} and click \emph{Save}. \end{enumerate} Alternatively, you can make the same configuration via an OSGi configuration file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a file called \texttt{com.liferay.portal.scheduler.configuration.SchedulerEngineHelperConfiguration.config}. \item Add this property to the file: \begin{verbatim} auditSchedulerJobEnabled=true \end{verbatim} \item Deploy the file to the \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. Note that the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home folder} is typically the application server's parent folder. \end{enumerate} Auditing scheduled jobs is a smart choice if there's a chance someone with a dubious competence level would try to schedule jobs, as you'll find out below in the conclusion of our story. \section{Enabling or Disabling Audit Events Entirely}\label{enabling-or-disabling-audit-events-entirely} Audit events are enabled by default. Follow these steps to disable them via the Control Panel: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} and then click \emph{Audit} in the \emph{Security} section. \item Uncheck the \emph{Enabled} box. Note that when auditing is enabled, you can adjust the audit message max queue size from its default value. \end{enumerate} Alternatively, you can enable or disable auditing via an OSGi configuration file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a file called \texttt{com.liferay.portal.scheduler.configuration.SchedulerEngineHelperConfiguration.config}. \item Add these properties to the file. You can adjust their values as desired: \begin{verbatim} enabled="true" auditMessageMaxQueueSize="200" \end{verbatim} \item Deploy the file to the \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. Note that the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home folder} is typically the application server's parent folder. \end{enumerate} \section{The End of the Story}\label{the-end-of-the-story} ``Okay,'' says Harry, ``let's fire up the audit system and see if we can figure out what happened.'' You and Dick stand behind Harry's chair and watch as he enters a query into a form on the Audit app. After clicking \emph{search}, the screen fills up with audit events. ``Wow, that's a lot of unassign events.'' Harry says. ``And look who the culprit is,'' he adds sarcastically. ``Who's Melvin Dooitrong?'' Dick asks. ``That's my new intern,'' Harry says. ``He's gonna be sorry.'' Harry pushes out his chair and walks down the row of cubes to the end, where a kid no more than 20 years old with disheveled hair sits, earbuds in his ears. ``Hey Melvin,'' Harry says as Melvin turns around to face him. ``Didn't I ask you to move that set of users from Site membership to Organization membership?'' ``Yeah,'' Melvin says, ``I did that already.'' ``How'd you do it?'' ``It was going to take a while to do it manually, so I wrote a script and executed it in the scripting host,'' Melvin replies, matter-of-factly. ``You did, did you? Well, guess what? Your script removed \emph{everybody} from \emph{all} Sites.'' ``\emph{What?}'' ``Yeah, and now you're going to start adding them back, one by one, manually, starting with Mr.~Jones\ldots.'' Tom and Dick back away slowly from Melvin's cube as Harry and Melvin continue to have their---let's call it a discussion. One thing is clear: they're having a better day than Melvin is. \chapter{Web Experience Management}\label{web-experience-management} Experience: consider that word for a moment. Not the type of experience you gain with repetition, but the contact or encounter you have with something. Suppose you're buying a new phone. It's easy to look at the phone with the biggest screen or the fastest processor and say that it's the best, but what will your experience be? The ``best'' phone might not be the fastest or the biggest; it might be the phone with the longest battery life or the most comprehensive suite of integrated apps. Or it might not be any of those things. Experience isn't always something that you can quantify with specs or features. Liferay takes the experience factor very seriously when it comes to site and content management. The Web Experience Management suite is focused on providing the best experience for users building websites. When it comes to web experience, just like with your phone, everyone is looking for something different. Some smartphone users might love watching videos during their commute with a big beautiful screen, while on the other side, some people might be more excited about a small sleek phone with a great battery life that fits easily in their pocket and simply does all the basic communication they need. Liferay's Content Management is the big beautiful phone and the sleek utilitarian one all in one. Marketers will find easy to use tools to build content without having to write any code or peak under the hood. Developers will find powerful tools like Structures and Templates that enable them to create dynamic content. And designers will love how Fragments and Content pages provide a way to perfectly realize their designs. \chapter{Authoring Content: Structured and Inline Content}\label{authoring-content-structured-and-inline-content} The primary goal of Content Management isn't to show off the flashiest new features or follow all the latest trends in design, but to provide you with the tools you need to create digital content that communicates your message clearly and effectively. With this in mind, Liferay DXP offers two core approaches to help you accelerate and simplify creating and organizing content: Structured Web Content and Inline Content. \section{Structured Web Content}\label{structured-web-content} If you've entered content into a CMS before, you may be familiar with the process of filling content into various fields like this: \begin{itemize} \tightlist \item Title \item Abstract \item Text Body \end{itemize} This is an example of Structured Content. Structured Content is created within a predefined content structure and then added to pages as needed. The structure defines the fields and then a template defines its styles. The content is then saved, ready to be added to a page later. The structure defines what kind of content you are creating and provides different types of fields that can be used. A developer could create a format for publishing articles that contains a \textbf{Title}, \textbf{Header Image}, \textbf{Body Text}, and a \textbf{Key Quotation}. The template defines how the elements of the structure are rendered. You could style the elements in the structure with the \textbf{Title} as large bold text, the \textbf{Header Image} as a full page width block above the title, the \textbf{Text} as standard text, and the \textbf{Key Quotation} as large font italics with a thin border that displays within the main text section. A content writer or marketer could then create any number of articles, all having a uniform style based on the structure. In Liferay DXP, those articles could be added to pages across the site, or displayed dynamically with tools like the Asset Publisher and Web Content Display Pages. \section{Inline Content}\label{inline-content} Inline Content is content that is created directly within a page. Rather than filling in fields to create content that's added to a page later, you have a completed page design where you edit the text and image content. Content Pages start with a design which is then created with Fragments. Inside the Fragments, a developer can define where text and images can be placed or edited. Marketers and content writers are then free to write or add images within the page and publish it. With content pages, basic HTML and CSS define the primary design, while JavaScript and Liferay specific tags can add dynamic behavior to the Content Page. After a developer creates the page and a content writer or marketer provides the content, the content exists inline within the page, and is published with it. \section{What's Best for Your Use Case?}\label{whats-best-for-your-use-case} When you step back to look at the big picture, what you see are two different paradigms for building pages: content pages, where the content is built into the page; and widget pages, where content and other features can be added, removed, and rearranged as desired. Often it is helpful to have reusable elements or content that can be moved around a page or placed anywhere on a site. For example, you might have a content based banner which you want to be able to drop onto any various pages with different layouts and styles, or you might have content that uses the same template to create similar items for different pages. Structured Content on widget pages is the tool you need to quickly create what you need and manage these cases. Widget pages with structured content are great for some cases, for example: \begin{itemize} \item Portal pages, where you provide users a gateway into several different services or providing aggregated information. \item Pages where the primary focus is widgets. \item Pages that are based around structured content. \end{itemize} In other cases, you need to create a page as a complete unit. For example, you have a series of marketing driven landing pages that must match a specific design and have associated content intended for use on that page or with that campaign. Content Pages provide the best tool for quickly bringing a design to life and empowering marketing with inline content. Content Pages are useful for pages like: \begin{itemize} \item Landing pages \item Front pages that provide marketing information or a direct path into the website. \item Pages with multiple variations and small graphical or textual changes across a large number of pages. \end{itemize} Most sites need a little bit of both. Read on to learn more about building sites with Liferay DXP and how Content Pages and Structured Content can help you do that. \chapter{Building a Site}\label{building-a-site} A site is a set of pages where content or applications are published. Sites can be independent or serve as an associated organization's website. You can create as many different sites as you like within the context of a single Liferay instance. You'll start with a tour of the site management user interface. Then you'll create a custom Lunar Resort Example instance and explore ways to create sites and pages for that Liferay instance. Finally, you'll learn how to change various settings for sites and pages to meet your needs. To begin building a site, continue on to the next section. \chapter{Site Management}\label{site-management} You can have many Sites on one Liferay instance, which work together to create one complete website, or you can simply have one Site which contains all of your pages and content---or anything in between. In this section you'll look at the interface for creating and managing Sites, create a Site, and learn how to use Site Templates for more efficient Site creation. \chapter{Understanding Site Management}\label{understanding-site-management} Whether you're building a large corporate web Site or a small Site for facilitating collaboration among team members, supporting different kinds of collaboration and social scenarios is a must. Liferay's Sites provide three membership types: \textbf{Open:} Users can become members of the Site at any time. \textbf{Restricted:} Users can request Site membership but Site administrators must approve requests for users to become members. \textbf{Private:} Users cannot join the Site or request Site membership. Site administrators must manually select users and assign them as Site members. In addition to these memberships, when a Site is associated with an organization, all the users of that organization are automatically considered members of the Site. You can view all the available open and restricted Sites by adding the My Sites application to a page and accessing the \emph{Available Sites} tab. You can request access to any of the Sites you're not already a member of by selecting the Site's \emph{Options} button (\includegraphics{./images/icon-actions.png}) and clicking \emph{Join}. \section{Site Scope}\label{site-scope} Members of a Site can be given additional privileges in the Site by using permissions. It is also possible to assign different roles within the Site to different members. This can be done through \emph{Site Roles}, which are defined equally for all Sites or \emph{Teams} which are unique for each Site. These concepts are discussed later. Liferay DXP separates Site-scoped information from the Control Panel by placing it in the Site menu. From this menu, you can select the specific Site to work on. The Site Administration panel is available for your Site, which includes Build, Content, Categorization, Recycle Bin, Members, Configuration, and Publishing. \begin{figure} \centering \includegraphics{./images/web-content-site-content.png} \caption{Your Site's content resides in the Site Administration menu.} \end{figure} \section{Site Hierarchies}\label{site-hierarchies} Sites can also be organized hierarchically, just like Organizations. The difference between Sites and Organizations, of course, is that Sites organize pages, content, application data, and users (via Site memberships) whereas organizations only group users. Content sharing is available for Sites within the same hierarchy. For instance, if a parent Site has a document type called \emph{Lunar Presentation} and all its child Sites should have a copy, the parent Site's administrator can enable content sharing to share the document type automatically with its child Sites. Also, content sharing privileges can be set to let every Site administrator share content across Sites they manage. You can share the following content across Sites: \begin{itemize} \tightlist \item Web Content Structures \item Web Content Templates \item Document Types \item Vocabularies and Categories \item Widget Templates \item Data Definitions (Dynamic Data Lists) \end{itemize} Please refer to the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Sites\%20Admin\%20Portlet}{Sites Admin Portlet} section of Liferay's \texttt{portal.properties} file for a list of relevant configurable properties. For example, the \texttt{Sites.content.sharing.with.children.enabled} property can disable content sharing between Sites and child Sites, disable it by default while allowing Site administrators to enable it per Site, or to enable it by default while allowing administrators to disable it per Site. The Sites Directory application is a configurable app that shows a hierarchy of Sites and child Sites. It enables users to navigate to any of the displayed Sites. To use this app to display Site hierarchies, add it to a page, open its Configuration window, and under Display Style, select \emph{List Hierarchy}. The My Sites Directory application is similar to the Sites Directory application, except that it lists only the Sites a user belongs to. Each child Site in the hierarchy has its own administrator, and the Site Administrator role permissions do not flow down to child Sites in the hierarchy. If a Site Administrator creates a child Site, he or she has the same permissions in that child Site. This is not, however, because of inheritance. It is only because creating a Site makes you the Owner of that Site. A Site Administrator or a parent Site has no default role in any child Sites created by other Site Administrators. If you wanted a user to have administrative access to all Sites in a Site/child Site hierarchy, you must create a role based on the Site Administrator role that has the permission \emph{Manage SubSites}. The Site Map application helps users navigate a Site. A Site administrator can configure a root page and a display depth. Just as Sites can have hierarchies, so can the pages within a Site. The display depth of the Site Map application determines how many levels of nested pages to display. \begin{figure} \centering \includegraphics{./images/site-directory-site-map.png} \caption{The Site Map application lets users navigate among pages of a Site organized hierarchically.} \end{figure} \section{Site Members}\label{site-members} Another useful administrative application is the Site Members application. This enables administrators to survey all the users, organizations, and user groups that reside in the Site. Similarly, Liferay provides the Portal Directory application, which functions the same as the Site Members app, but globally scoped for all Sites in the instance. \section{Page Sets}\label{page-sets} Sites have two categories of pages called page sets. There are two kinds of page sets: public pages and private pages. A Site can have only public pages, only private pages, or both. Private pages can only be accessed by Site members. Public pages can be accessed by anyone, including users who haven't logged in. It's possible to restrict access to pages at the page set level or at the level of individual pages through the permissions system. Public pages and private pages have different URLs and can have different content, applications, themes, and layouts. Building a corporate intranet is a typical use case for Sites. A corporate intranet could have Sites for all the organizations in the company: Sales, Marketing, Information Technology, Human Resources and so on. But what about the corporate health and fitness center? That's something everybody in the company, regardless of organization, may want to join. This makes it a good candidate for an open and independent Site. Similarly, the home page for a corporate intranet should probably be placed in an open independent Site so any member of the instance can access it. For other kinds of websites, you may want to use independent Sites to bring users together who share a common interest. If you were building a photo sharing website, you might have independent Sites based on the types of photos people want to share. For example, those who enjoy taking pictures of landscapes could join a Landscapes Site and those who enjoy taking pictures of sunsets could join a Sunsets Site. There is always one default Site, which is also known as the main Site of the instance. This Site does not have its own name but rather takes the name of the instance. By default the instance name is \emph{Liferay} but this value can be changed through the configuration of the setup wizard. The instance name can also be changed at any time through the Control Panel within \emph{Configuration → }Instance Settings*. \chapter{Adding Sites}\label{adding-sites} Sites can be created through the Control Panel by a Liferay administrator. The Control Panel provides an administrative interface for managing your Liferay instance. There are four main sections of the Liferay Control Panel: Users, Sites, Apps, and Configuration. In this section, you'll learn how to use the Control Panel to manage Sites. For information about the Apps, Users, and Configuration sections of the Control Panel, see the \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{Using the Liferay Marketplace}, \href{/docs/7-2/user/-/knowledge_base/u/managing-users}{Managing Users}, and \href{/docs/7-2/user/-/knowledge_base/u/system-wide-settings}{System Wide Settings} sections, respectively. \noindent\hrulefill \textbf{Tip:} If you're signed in as an administrator, you can access all Sites by navigating to the Site Administration menu from the Control Panel. To manage a single Site, navigate to the Site by going to the Menu and clicking the \emph{Site Selector} button (\includegraphics{./images/icon-compass.png}) from the Sites dropdown menu and selecting the appropriate Site name. Once finished, the Site administration options (i.e., Navigation, Content, Members, etc.) for that Site are available. \noindent\hrulefill Now, you'll add a Site for the Lunar Resort. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Control Panel and select \emph{Sites} → \emph{Sites}. \item Click the Add icon (\includegraphics{./images/icon-add.png}) at the top right of the page. \item Select a \emph{Blank Site}. Any available Site templates appear for you to select. Site templates provide a preconfigured set of pages, applications, and content that can be used as the basis of a Site's public or private page set. To create a Site from scratch, select \emph{Blank Site}. Otherwise, select the name of the Site template you want to use. If you opt to create a Site from a Site template, you have to choose whether to copy the Site template's pages as your new Site's public or private page set. If other Site templates are created, they will appear in the Add menu as they become available. \item Name your Site ``The Lunar Resort'' \end{enumerate} After you enter the name, you will be prompted to enter additional information about the Site and configure certain Site settings. \textbf{Name:} names the Site you wish to create. You also have the option to translate the name for many different languages. This can be done by selecting the language flag under the Name field, and inserting the name in the selected language. Liferay saves the name translation for each language and displays the translated Site name when that specific language is selected for the instance. If a name translation is not provided, the default instance language's name is displayed. \textbf{Description:} describes the Site's intended function. The description can also be translated to other languages; see the Name description for more information on translating the Site's description. \textbf{Active:} determines whether a Site is active or inactive. Inactive Sites are inaccessible but can be activated whenever a Site administrator wishes. \textbf{Membership Type:} can be open, restricted, or private. An open Site appears in the My Sites app and users can join and leave the Site whenever they want. A restricted Site is the same except users must request membership. A Site administrator must then explicitly grant or deny users' requests to join. A private Site does not appear in the My Sites app and users must be added to it manually by a Site administrator. \textbf{Allow Manual Membership Management:} determines whether to allow or disallow users to be manually added or removed from the Site. By default, manual Site membership management is enabled. This allows administrators to manually assign users to the Site. It also allows users to join open Sites or request membership from restricted Sites using the My Sites app. For organization Sites, manual Site membership management is disabled, by default. This causes organization members to be automatically assigned membership following the organization's membership policy. Also, because manual membership management is disabled for organization Sites, by default, the \emph{Users} section of \emph{Sites} is unavailable. To activate the \emph{Users} functionality for your organization Site, you'll need to check \emph{Allow Manual Membership Management} after creating the organization Site by navigating to its \emph{Site Settings} menu. \noindent\hrulefill \textbf{Note:} It's possible for Site memberships to be handled automatically by a membership policy. The membership policy can check various pieces of information from each user, such as their first names, last names, birthdays, job titles, organizations, and user groups. Using this information, the Site membership policy can automatically assign members to the Site. If your Site will implement a membership policy, your Site administrators can disallow manual membership management for their Site. When the Allow Manual Membership Management option is disabled, the \emph{Members} section of Site Administration (Site Memberships and Site Teams) is hidden, even from administrators. \noindent\hrulefill \textbf{Parent Site:} lets you select a parent Site for the Site that's being created. Sites can be organized hierarchically. Using hierarchical Sites provides a simplified way to manage Site memberships and Site content sharing. For organizations that have attached Sites, the organization hierarchy should match the Site hierarchy. When you select a parent Site, an additional option appears: \emph{Limit membership to members of the parent Site}. If this option is enabled, the Site's membership policy performs a check so that you can only assign members to the current Site if they're already members of the parent Site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Set the \emph{Membership Type} as \emph{Restricted}. \item Leave the remain defaults and click \emph{Save}. \end{enumerate} When creating a blank Site or organization Site, the Site is not immediately viewable. This is because Sites without a page are impossible to view. Therefore, before you can view your Site, you must first create a page for it. To add a page for your temporarily invisible Site, navigate to the \emph{Navigation} option from Site Administration. Then add a public page. After adding your Site's first page, it renders and your Site is viewable. For more information about adding pages, see the \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-pages}{Creating and Managing Pages} section. You can also categorize your Site template using tags and categories by selecting the \emph{Categorization} menu from the bottom of the page. To learn more about using tags and categories in Liferay, see the \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{Organizing Content with Tags and Categories} section. Lastly, at the top of the page is an additional tab named \emph{Social}. This tab manages whether users of your Site can mention other users. You'll learn about mentioning users later in the Social Collaboration sections. When creating a Site from a Site template, you're asked if you want to copy the pages from the template as public pages or as private pages. By default, the Site is linked to the Site template and changes to the Site template propagate to any Site based on it. A checkbox appears for unlinking the Site template if the User has permission to do so. Once the Site has been created, you should configure its settings to fit your needs. You can learn more about Site Settings in \href{/docs/7-2/user/-/knowledge_base/u/configuring-sites}{Configuring Sites}. \chapter{Adding Pages to Sites}\label{adding-pages-to-sites} In the previous section, you learned how to create sites. You may have gathered from that section that sites aren't particularly useful without pages. In fact, sites primarily exist for the sake of organizing pages and content, so now you'll learn about the different types of pages in Liferay, and how to select the best tools based on your use cases. You'll also learn how to manage pages and use various configuration options. Before diving into page creation, you should understand the two major page types provided in 7.0: \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{\emph{Content Pages}}: This new page type is flexible, especially for non-technical users. You can build pages using content created from pre-defined fragments, which themselves can contain widgets. \href{/docs/7-2/user/-/knowledge_base/u/adding-applications-to-a-page}{\emph{Widget Pages}}: Liferay DXP's traditional page type is made up of one or more widgets. There are some use cases (particularly if a page's sole purpose is to host an application) to prefer widget pages. You should always opt for Content Pages, unless there's a concrete reason otherwise. Content Pages offer many key features of Widget Pages plus more. Some key features of Content Pages include \href{/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization}{personalized Experiences} and \href{https://help.liferay.com/hc/en-us/articles/360034856751-A-B-Testing}{A/B Testing} Furthermore, Content Pages are easier to use and are more powerful for non-technical users compared to Widget Pages. Why would someone prefer Widget Pages? Widget Pages were once the only page type available in earlier versions of Liferay DXP, so they're more familiar than Content Pages. Additionally, there are still a few things that Widget Pages provide that are not possible with Content Pages: \begin{itemize} \item \emph{Developing an advanced custom layout}: Using Content Pages, authors can create their own page layouts. This prevents developers from creating pre-selectable, custom layouts with FreeMarker like Widget Pages allowed for. Though Content Pages let you create a layout visually (a more user-friendly approach), the programmatic approach of Widget Pages allows for more advanced capabilities. \item \emph{User-Customizable columns}: This was a rarely used feature of Widget Pages that is not provided in Content Pages. If your page requires a user-customizable column, you must use a Widget Page. \item \emph{Using Staging with Page Variations}: Content Pages do not support Staging's Page Variations. This avoids possible confusion with the similar capability to create variations of a page that are used in personalization and A/B Testing. \item \emph{Widget permissions}: You can configure widget permissions on a Widget Page. This is not yet possible for Content Pages; it's planned for a future release. \item \emph{Widget Look \& Feel}: On Widget Pages you can access the \href{/docs/7-2/user/-/knowledge_base/u/look-and-feel-configuration}{Look and Feel} tool for widgets, offering fine-grained control over its CSS. This is not available for widgets on Content Pages, since the look and feel of your content is defined in the theme or by using Fragments. \end{itemize} Continue on to learn more about creating pages! \chapter{Creating Pages}\label{creating-pages} After you create a Site, you can add new pages and maintain them. You can do everything you need with pages from Site Administration. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item If you're not currently on the Site you want to edit, click the \emph{Site Selector} button (\includegraphics{./images/icon-compass.png}) next to your current Site name in the Menu and select your desired Site. \item Go to \emph{Site Administration} → \emph{Site Builder}. \item Click on \emph{Pages} \end{enumerate} \begin{figure} \centering \includegraphics{./images/managing-site-pages.png} \caption{The Pages screen lets you edit your Site pages as a whole.} \end{figure} From here, you'll create pages and page templates. \noindent\hrulefill \textbf{Note:} Pages are always part of page sets, and page sets are always associated with Sites. Even users' personal pages are part of their personal Sites. All pages belong to one of two types of page sets: public pages and private pages. By default, anyone can access public pages, even non-logged in users (guests). Only users who are members of the Site that owns the pages can access private pages. This means the private pages of an organization's Site are viewable only by Site members and members of the organization. \noindent\hrulefill From \emph{Pages} you can do several things: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the (\includegraphics{./images/icon-add.png}) button in the top right corner to add a new page. \item Click options, icons, manage page, or page set settings. \item Create child pages by clicking the \texttt{+} button next to an existing page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/site-pages-breakdown.png} \caption{Understanding the options on Site Pages.} \end{figure} Adding a child page creates child pages in the hierarchy below the page you've selected. You can nest pages as deep as you like. \noindent\hrulefill \textbf{Note:} You're not forced to define the page hierarchy in a page's friendly URL. Therefore, child page friendly URLs are not required to include their parent page. For example, a parent page named Parent and its child page named Child could have the URLs \emph{SITE\_URL/parent} and \emph{SITE\_URL/child}, respectively. The default friendly URL given to a page is based only on the page name and not the hierarchy. If you wish to modify a generated friendly URL, you can do so by following the \href{/docs/7-2/user/-/knowledge_base/u/individual-page-settings\#name-and-friendly-url}{Friendly URL} configuration section. \noindent\hrulefill Once you've clicked the \texttt{+} icon to add a page, you're asked to select the type of page you are creating. There are two top options followed by other page types: \textbf{Widget:} Creates a page with a layout template that defines a number of rows and columns for adding widgets to your page. \textbf{Content:} Creates a Content Page with inline editing based on Fragments. Below those you have other options: \textbf{Full Page Application:} Creates a page that displays a single full page application. \textbf{Page Set:} Creates a container for subpages that is not actually a page itself. \textbf{Link to a Page of this Site:} Links to a page within the same Site. This is often used to make a page available in multiple parts of a Sites hierarchy. \textbf{Panel:} A page containing any number of applications as selected by an administrator, but only one is displayed at a time. Users select the portlet they want to use from a menu on the left side of the page, and the selected portlet takes up the entire page. \textbf{Embedded:} Displays content from another website inside your instance. An administrator can set a URL from the page management interface and that page appears in the context and within the navigation of your Liferay instance. To embed an external website, you must provide the protocol in the URL (e.g.~\texttt{https://www.liferay.com/}). \textbf{Link to URL:} Creates a link to any URL. This could be an external page or a link across Sites in the same Liferay instance. To the left, under Collections, you can choose to view the basic page types or a collection of page templates. By default, only \emph{Global Templates} appears, but additional collections you create appear here as well. \begin{figure} \centering \includegraphics{./images/page-types-adding.png} \caption{You must select a page type when adding pages.} \end{figure} After you've added a page, it may be difficult to track what kind of page you're currently viewing. The page type appears at the top of the page to help you determine the administration options you have and where you need to go to configure the page. \begin{figure} \centering \includegraphics{./images/page-type-guide.png} \caption{Here are three different page types as they're displayed in the heading.} \end{figure} Now that you know the basics of adding pages, you can start working on the Lunar Resort Site. If you're not currently on the right Site, navigate to Site Administration in the Menu, select the compass icon next to the current Site name, and select the Site you wish to edit. If you must ever modify the page you've created for your Site, select \emph{Configure} from the Options menu for the page from \emph{Pages}. When configuring a specific page, you have more options than when you were creating a new page. You can also read \href{/docs/7-2/user/-/knowledge_base/u/configuring-sites}{Configuring Sites}. There are also configuration options that are only available for individual pages or page groups only. You'll learn about options available for both instances. Next, you'll look at creating the main page types you'll use in Liferay DXP. \chapter{Creating Content Pages}\label{creating-content-pages} Content Pages provide a new paradigm for creating pages in Liferay DXP. They empower marketers and content creators to build pages that can be easily managed and have their content edited in-line and on the fly. With Content Pages, you build pages from reusable, extensible elements called \textbf{Page Fragments}. Page Fragments contain things like images, content, or functionality and are added to a page to be edited and configured to match your design. Web Developers can create Page Fragments to expand the options you have available for building pages. Some Fragments are completely static elements, like an image or text. These can be useful for building a landing page, or realizing a complicated design vision. Other Fragments have a number of editable fields which enable flexibility and experimentation when creating the perfect page for your needs. \section{Creating Page Fragments}\label{creating-page-fragments} The first thing you need to create a page, are the raw materials: Page Fragments. Fragments are organized into Collections which describe some aspect of the purpose of the Fragments and help keep them organized. For example, if you have a number of Fragments associated with a certain marketing campaign, you could create a Collection for that campaign, so that you have all of those Fragments readily available when you're working on a page for that campaign. With all of the Fragments and Collections that are included in 7.0, many projects can be completed using only the out-of-the-box Fragments and capabilities. In many cases, though, you need to develop your own Fragments. You can learn about the Fragment creation process and editing interface in the \href{/docs/7-2/user/-/knowledge_base/u/creating-page-fragments}{Creating Page Fragments} article. Developing Fragments is covered in more detail in the \href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments}{Page Fragment development guide}. \section{Building Content Pages}\label{building-content-pages} After you've got your materials, you need to arrange them on a page. Content Pages are built with Page Fragments and Widgets. Widgets work just like they always have, drag them onto the page where you want to use them. Fragments are intended to be reusable, so each Fragment is like a single puzzle piece that can fit in many different puzzles. Using the tools you have available, you can create stunning pages through an intuitive, empowering interface. In the \href{/docs/7-2/user/-/knowledge_base/u/building-content-pages}{Building Content Pages guide} you will create Content Pages using a variety of the features and capabilities of Content Pages. \section{The Content Page Interface}\label{the-content-page-interface} There are many features of the Content Page Interface---too many to cover in a practical exercise. For a complete overview of every nook and cranny of the Content Page management interface see the \href{/docs/7-2/user/-/knowledge_base/u/content-page-management-interface}{Content Page Interface reference}. \section{Personalizing Content Pages}\label{personalizing-content-pages} When you create Content Pages, you can create different \textbf{Experiences} for users based on User Segments. You can create a unique Experience on any Content Page for any existing User Segment. For more information, see the \href{/docs/7-2/user/-/knowledge_base/u/content-page-personalization}{Content Page Personalization guide}. \chapter{Content Page Elements}\label{content-page-elements} Content Pages, like Widget Pages, are built by dragging and dropping elements onto the page and then configuring the way those elements appear. There are three kinds of elements: \textbf{Sections} are fragments that define a space to place other elements. A section fills the entire width of the page. Sections can be thought of as \emph{complete} Fragments that serve a purpose by themselves. A large banner image with a text overlay is an example of something you might build as a section. \begin{figure} \centering \includegraphics{./images/content-page-section-example.png} \caption{A Section named \emph{Banner} being displayed while editing a Content Page.} \end{figure} \textbf{Layouts} are special Sections that define spaces where you can add fragments or widgets. Each layout you add fills the width of the page. You can add any number of layouts to the page. \begin{figure} \centering \includegraphics{./images/content-page-layout-example.png} \caption{A 3 Column and 1 Column layout stacked on top of each other.} \end{figure} \textbf{Components} are small design elements or pieces that add functionality to the page. A component might be an image with formatting or a block of text with styling pre-applied. Components must be added to the page inside a Layout. If you add a component outside an existing Layout, a one column layout is automatically added to contain the Component. While Sections should be complete by themselves, Components work together to build pages piece by piece. \begin{figure} \centering \includegraphics{./images/content-page-component-example.png} \caption{Here are several of Liferay's out of the box components arranged in the layout you saw previously.} \end{figure} Liferay DXP ships with a plethora of Layouts, Sections, and Components to use to build pages, and a \href{/docs/7-2/frameworks/-/knowledge_base/f/creating-fragments}{web developer can create their own Fragments} to add to these. \section{Editable Elements}\label{editable-elements} Fragments can have editable elements. After a Fragment has been added to a page, you can click on an editable area to provide your own text, image, or links in place of the default defined in the Fragment. You can also map these elements to content. You can set the \emph{Content} for the element (web content article, document, or blog) and choose its applicable \emph{Field} to display (e.g., title, author name, tags, etc.). You can configure this by selecting the element's \emph{Map} button (\includegraphics{./images/icon-map.png}). \noindent\hrulefill \textbf{Note:} Many mapping improvements were released in Liferay DXP 7.2 SP1+ and Liferay Portal 7.2 GA2+. For example, mapping editables to text/URL fields of existing content and mapping Fragment background images to image fields of existing content. You can also map \href{/docs/7-2/user/-/knowledge_base/u/custom-fields}{custom fields}. To ensure you leverage the latest editable element mapping features, upgrade to these versions. \noindent\hrulefill For more information on developing these elements, see \href{/docs/7-2/reference/-/knowledge_base/r/fragment-specific-tags}{Fragment Specific Tags}. Now you'll learn about each editable type. \section{Editable Text}\label{editable-text} Editable text can be plain or rich text. Plain text has no special styling. Rich text enables text styles, typographical emphasis, alignment, and list formatting. \begin{figure} \centering \includegraphics{./images/content-page-rich-text-editor.png} \caption{The rich text editor provides a simple WYSIWYG interface with a number of formatting options.} \end{figure} \section{Editable Images}\label{editable-images} Editable image elements allow replacing the image URL or an image from your Documents and Media library. You can provide a link target for the image. To edit an image from the Content Page editor, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the image you want to replace. \item Click \includegraphics{./images/icon-edit.png}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/fragment-image-editor.png} \caption{Editing an image allows you to enter a URL, select an image from Documents and Media or set a link for the image.} \end{figure} From here, you can click \emph{Select} to upload an image from Docs and Media or define an image URL. Click \emph{Clear} to reset the image. You can also specify an image description. You can also specify a background image for a layout from Section Builder. Click the Layout, select \emph{Layout Background Image}, and define the image to display. \noindent\hrulefill \textbf{Note:} Mapping a Layout background image is available in Liferay DXP 7.2 SP1+ and Liferay Portal 7.2 GA2+. \noindent\hrulefill For more information on developing editable images, see \href{/docs/7-2/reference/-/knowledge_base/r/fragment-specific-tags\#making-images-editable}{Making Images Editable}. You can also define a link for your image. You'll learn about this next. \section{Editable Links}\label{editable-links} Editable links can be associated with entities that redirect you to a content type or Page (e.g., buttons). To edit a link from the content page editor, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the link or button that you want to edit. \item Click on \includegraphics{./images/icon-edit.png} to edit the link text. \item Click on \includegraphics{./images/icon-link.png} to edit the link properties. \item Click on \includegraphics{./images/icon-map.png} to edit the link mapping (described earlier). \end{enumerate} From the Link Properties popup, you can define the following link options: \emph{Manual:} defines a manual link or map it to an existing content field. \begin{itemize} \tightlist \item \emph{URL:} sets the link's URL. \item \emph{Target:} set the link's behavior. \end{itemize} \emph{From Content Field:} \begin{itemize} \tightlist \item \emph{Content:} sets the content type. \item \emph{Field:} sets the field to display for the selected content. \end{itemize} Some of the content fields include \begin{itemize} \tightlist \item Categories \item Tags \item Display Page URL \item Description \item Publish Date \item Summary \item Title \item Last Editor Name \item Author Name \item Basic Web Content \end{itemize} For more information on developing editable links, see \href{/docs/7-2/reference/-/knowledge_base/r/fragment-specific-tags\#creating-editable-links}{Creating Editable Links}. Next you'll learn about the Content Page Editing Interface. \chapter{Content Page Management Interface}\label{content-page-management-interface} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Unlike Widget Pages, Content Pages can only be edited through the \emph{Site Builder} and cannot be edited live on the page. Any edits that you make to a page are saved as a draft until you publish the page. Subsequent changes after the initial publication are again saved as a draft, without affecting the live page, until the page is published again. To create a Content Page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Management} → \emph{Site Builder} → \emph{Pages}. \item Click \includegraphics{./images/icon-add.png}. \item On the next page, select \emph{Content Page} and provide a name for the page. You will be brought to the Content Page management interface. \begin{figure} \centering \includegraphics{./images/content-page-edit-blank-page.png} \caption{Each Content Page starts as a blank page.} \end{figure} \end{enumerate} To edit an existing Content Page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages}. \item Click \emph{Actions} (\includegraphics{./images/icon-staging-bar-options.png}) → \emph{Edit} next to the Content Page you want to edit. \end{enumerate} You can also get to this page by selecting the \emph{Edit} button (\includegraphics{./images/icon-edit-pencil.png}) from the Control Menu if you're viewing the published Content Page. On this page you can view a preview of your page, add Fragments and Widgets, and manage the configuration for the page or any Fragments and Widgets currently residing on the page. Your tools for building the page are all found on the right side of the page. From top to bottom, the options are \begin{itemize} \tightlist \item \hyperref[sections]{Sections} \item \hyperref[section-builder]{Section Builder} \item \hyperref[widgets]{Widgets} \item \hyperref[page-structure]{Page Structure} \item \hyperref[look-and-feel]{Look and Feel} \end{itemize} \section{Sections}\label{sections} When you open \emph{Sections} you see a list of Collections available. Initially, you only have the \textbf{Basic Sections} Collection which is included with the product. You can open the Collection and drag Sections directly onto the page. \begin{figure} \centering \includegraphics{./images/content-page-sections-editor.png} \caption{\emph{Sections} contains Fragments that fully define spaces on your page.} \end{figure} Once a section is added to a page, you can edit its background color, background image, and spacing. Since these options are available to marketers and administrators editing a page, the options are limited, and the color palette can be set by the Fragment developer. \begin{figure} \centering \includegraphics{./images/content-page-sections-config.png} \caption{The Section managment tool provide powerful tools, but with the training wheels still on.} \end{figure} \section{Section Builder}\label{section-builder} In Section Builder, you start with \emph{Layouts} and \emph{Basic Components}. Add Layouts to the page to provide a spaces where you can add Components. \begin{figure} \centering \includegraphics{./images/content-page-section-builder-editor.png} \caption{\emph{Sections Builder} contains \emph{Component} Fragments which are intended to be combined to create Sections.} \end{figure} \section{Widgets}\label{widgets} The Widgets section functions just like the \emph{Add} menu on a Widget Page. The full list of available widgets is displayed, and you can add them to the page. \begin{figure} \centering \includegraphics{./images/content-page-widget-editor.png} \caption{The Widgets section provides a list of Widgets that can be added inside of a Layout.} \end{figure} The main difference is that only the main configuration options are available for widgets on Content Pages. Various other configurations like \emph{Look and Feel} are only available for widgets on Widget Pages. \section{Page Structure}\label{page-structure} Page Structure provides a high level view of every Fragment and every field within each Fragment on the page. \begin{figure} \centering \includegraphics{./images/content-page-page-structure-editor.png} \caption{\emph{Page Structure} shows you a hierarchy of your page.} \end{figure} Clicking on a field in Page Structure will highlight it on the page. On large complicated page, this helps you keep on top of where everything is and also access items that might be hard to click on directly. \section{Look and Feel}\label{look-and-feel} Click the \emph{Look and Feel} icon (\includegraphics{./images/icon-look-and-feel.png}) to change the theme or manage other options for the page. These options are fully documented in \href{/docs/7-2/user/-/knowledge_base/u/creating-pages}{Creating Pages}. \section{Comments}\label{comments} You can also comment on any page fragments. This allows discussion and collaboration for teams creating content pages. Comments are disabled by default, but administrators can enable them from \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Pages} → \emph{Content Page Editor}. Select the \emph{Comments Enabled} checkbox and click \emph{Update}. This enables content page comments for all instances. To control this on an instance-by-instance basis, navigate to the same setting in \emph{Instance Settings} (instead of System Settings). \begin{figure} \centering \includegraphics{./images/enable-content-page-comments.png} \caption{Administrators can enable comments for content pages.} \end{figure} If comments are enabled, you can access them via the \emph{Comments} icon (\includegraphics{./images/icon-comments.png}). The comments appear for the selected fragment. You can take the following actions in the comments UI for a fragment: \begin{itemize} \tightlist \item Add new comments and reply to any existing ones. \item Resolve comments by clicking the checkbox for each. Resolving a comment hides it from view, unless \emph{Show Resolved Comments} is selected. \item Edit and delete your own comments via the Actions button (\includegraphics{./images/icon-actions.png}) for each. \end{itemize} If you de-select a fragment or enter the comments UI without a fragment selected, a list of the fragments on the page appears with the number of comments for each. Selecting a fragment then shows its comments. \begin{figure} \centering \includegraphics{./images/content-page-comments.png} \caption{When creating content pages, you and your team can comment on any fragments.} \end{figure} \section{The Title Bar}\label{the-title-bar} The title bar provides navigation back to the Main Menu, a link to page configuration, and the ability to search for other pages. The title bar is covered in more detail in \href{/docs/7-2/user/-/knowledge_base/u/creating-pages}{Creating Pages}. \begin{figure} \centering \includegraphics{./images/content-page-edit-title-bar.png} \caption{The title bar has several tools built into it.} \end{figure} Great! Now you know how to use the content page interface! \chapter{Building Content Pages}\label{building-content-pages-1} To demonstrate Content Page capabilities, create a \emph{Landing Page} for a space shuttle tour based on this design: \begin{figure} \centering \includegraphics{./images/content-page-design-mockup.png} \caption{You have lots of flexibility when arranging Fragments on a page.} \end{figure} \section{Creating a Content Page}\label{creating-a-content-page} To create the page, follow these general steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Add the Fragments you need to define the basic structure of the page. \item Edit the text for the current page. \item Edit the images. \item Publish the page. \end{enumerate} You'll complete this process next. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From Site Administration for your Site, go to \emph{Site Builder} → \emph{Pages}. \item Click the (\includegraphics{./images/icon-add.png}) and select \emph{Content Page}. \item Set the \emph{Name} as \emph{Space Landing Page} and click \emph{Save}. Now you're in Content Page creation. As you work, a draft of the page is automatically saved, but you must click \emph{Publish} to make it available for use. \item Open \emph{Sections} → \emph{Basic Sections} and drag a \emph{Banner Center} Fragment onto the page. \item Open \emph{Section Builder} → \emph{Layouts} and add a 2 Column layout below that. \item Open \emph{Basic Components}. \item Add a \emph{Card} to the left column of the layout. \item Add a \emph{Paragraph} to the right side, and then an \emph{Image} below that inside the same column. \end{enumerate} \begin{figure} \centering \includegraphics{./images/content-page-creation-step-1.png} \caption{You have lots of flexibility when arranging Fragments on a page.} \end{figure} Now that the structure is defined, start editing the text and images. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click in each text box and edit the text to be relevant to your goal of directing potential customers to space shuttle tours. \begin{figure} \centering \includegraphics{./images/content-page-creation-step-2.png} \caption{Edit the text and formatting as you see fit.} \end{figure} \item Click on the main banner image, and then on the \includegraphics{./images/icon-edit-pencil.png} icon. \item Select one of the many space flight images that are surely filling up your Documents and Media library, upload a new one, or specify a URL. \end{enumerate} \begin{figure} \centering \includegraphics{./images/content-page-creation-step-3.png} \caption{Add some images, and the big picture comes together.} \end{figure} Anytime during Fragment creation, you can remove, duplicate, or configure the Fragment. \begin{figure} \centering \includegraphics{./images/content-page-fragment-options.png} \caption{Add some images, and the big picture comes together.} \end{figure} Configuring a Fragment lets you modify the default options for a provided Fragment. This also lets you duplicate a Fragment and configure duplicates differently, so you can reuse base Fragments instead of developing new ones. When duplicating a Fragment, its configuration and editable elements are also copied. \noindent\hrulefill \textbf{Note:} Fragment configuration and duplication are available in Liferay DXP 7.2 SP1+ and Liferay Portal GA2+. \noindent\hrulefill This is looking good so far, but the one difference between the design mockup and the final result is that the background was black for the original. To finish it up, change the background color for the section to black. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the bottom Section and some additional icons will appear. \begin{figure} \centering \includegraphics{./images/content-page-section-editor.png} \caption{You can change the background color, image, or edit spacing and padding for a section. You can also remove it.} \end{figure} \item Click on \emph{Background Color} \includegraphics{./images/icon-color.png}, and select Black. \item Finally, publish your page. \end{enumerate} In just a few minutes, you used the power of Content Pages and Fragments to go from nothing to perfectly recreating a page design. To take it to the next level, head over to the \href{/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization}{Segmentation and Personalization guide}. \chapter{Propagation of Changes}\label{propagation-of-changes} If you make an update to a Page Fragment or Content Page Template, it doesn't automatically propagate changes, but you can access the \emph{Usages and Propagation} page to selectively propagate changes. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the Site Administration menu, go to \emph{Site Builder} → \emph{Page Fragments} \item Select the Collection containing the changed fragment. \item Open the \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) for the Fragment and select \emph{View Usages}. \end{enumerate} The \emph{Usages and Propagation} page shows a list of every Page, Page Template, and Display Page that uses the selected Page Fragment. You can then selectively propagate Fragment changes to any or all of the pages listed. You can use the various filters and selection options to apply updates to pages quickly. \begin{figure} \centering \includegraphics{./images/fragment-usages-and-propagation.png} \caption{Viewing the Usages and Propagation page.} \end{figure} \noindent\hrulefill \textbf{Note:} Beginning in Liferay DXP 7.2 SP1+ and Liferay Portal 7.2 GA2+, you can propagate changes from global Fragments to their usages on child Sites. See \href{/docs/7-2/user/-/knowledge_base/u/creating-page-fragments\#creating-and-managing-fragments}{Creating and Managing Fragments} for more information on global Fragments. \noindent\hrulefill To update a page or template, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the page or pages you want to update by checking the box next to the page name. \item Click the \emph{Propagate} icon ( \includegraphics{./images/icon-propagate.png}) After you propagate changes, visit any affected page to verify there were no unexpected side effects of the changes. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Developers or others testing a Fragment can enable Fragment change propagation from the Control Panel. You can learn more about this [here](/docs/7-2/frameworks/-/knowledge_base/f/managing-fragments-and-collections#propagating-fragment-changes-automatically). It's recommended to only leverage this functionality during testing, as automatic propagation on the production environment can cause unintended consequences. \end{verbatim} \noindent\hrulefill \begin{verbatim} Changes to existing `editable` fields are not propagated since this overwrites content currently in content pages. To force propagation to content in an `editable` field, a developer must change the field ID. Any content created in that field no longer appears in the Content Page when the changes are propagated, but it remains in the database and can be retrieved using the old ID. \end{verbatim} Next you'll learn how to create your own Page Fragments. \chapter{Creating Page Fragments}\label{creating-page-fragments-1} Fragments enable you to create rich content. With all of the included Fragments and Collections, many projects can be completed using just what ships with Liferay DXP. Sometimes, though, you must create your own Fragments. Fragments are built using HTML, CSS, and JavaScript. For a deeper dive into creating a custom Fragment, see \href{/docs/7-2/frameworks/-/knowledge_base/f/creating-fragments}{Developing Fragments}. In this article, you'll learn about the Page Fragments interface. \section{Creating and Managing Fragments}\label{creating-and-managing-fragments} To navigate to the Page Fragments interface, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Go to Site Administration. Make sure the Site where you want to work is selected. If you prefer creating a Fragment that's available for all Sites, navigate to the \emph{Global} Site and create your Fragment there. Global Fragments are inherited by child Sites, so they can only be edited from the Global Site. Any resources the Global Fragment references (e.g., image) from the Global Site is copied to a Site that leverages the Fragment. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Creating Global Fragments is available in Liferay DXP 7.2 SP1+ and Liferay Portal 7.2 GA2+. To expose this feature in the initial releases of those versions, however, you must create a `.config` file. Create the `com.liferay.fragment.web.internal.configuration.FragmentGlobalPanelAppConfiguration.config` file and add the `enabled=B"true"` property. Then copy it to your Liferay DXP instance's `osgi/configs` folder. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \tightlist \item Select \emph{Site Builder} → \emph{Page Fragments} \end{enumerate} \begin{figure} \centering \includegraphics{./images/empty-fragments-page.png} \caption{Here is the Page Fragments page with no custom Fragments or Collections created.} \end{figure} Fragments are organized in \emph{Collections}. The main Page Fragments page shows available Collections (out-of-the-box Fragment Collections to start), allows Import and Export, and enables you to create Collections. You can also manage the organization and display of Fragments and Collections you have created. \noindent\hrulefill \textbf{Note:} You cannot edit a default Fragment. If you'd like to provide an edited version of a default Fragment, you can copy it to your custom Collection and edit it there. To do this, navigate to the default Fragment Collection and click the Fragment's \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Copy To} button. Then select the Collection to copy the default Fragment to. Copying default Fragments is available in Liferay DXP 7.2 SP1+ and Liferay Portal 7.2 GA2+. \noindent\hrulefill To create a Fragment, you must first create a Collection. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{New} → \emph{Collection} to add a Collection. \item Give the Collection a \emph{Name} and \emph{Description} and click \emph{Save}. \end{enumerate} Collections help you organize Fragments, and can be used to differentiate between different types of Fragments or Fragments used by different groups or departments. Next, create a Fragment inside the Collection you created. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the Collection you created. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) to create a Fragment. \item Choose either \emph{Section} or \emph{Component}. \item Give it a \emph{Name} and click \emph{Save}. \end{enumerate} The Fragment development environment appears. Each pane in the editor has a different function: \begin{itemize} \tightlist \item The top left pane is for entering HTML. \item The top right pane is for entering CSS. \item The bottom left pane is for entering JavaScript. \item The bottom right pane provides a live preview as you work in the other panes. \end{itemize} \begin{figure} \centering \includegraphics{./images/fragments-editor.png} \caption{The Fragments editor provides an environment for creating all the parts of a Fragment.} \end{figure} In addition to standard HTML, CSS, and JavaScript, developers can also embed widgets and provide fields containing text and images that can be edited during the final Content Page publication process. Fragment development is covered in depth in \href{/docs/7-2/frameworks/-/knowledge_base/f/creating-fragments}{Developing Fragments}. You can also include resources in your Fragment Collection that your Fragments can reference. This is helpful when exporting/importing Fragments: their resources are automatically included. If they're stored somewhere else (e.g., Documents and Media), you must export/import them separately. Click the \emph{Resources} tab for your Collection and add resources (e.g., image, document, etc.) there. \begin{figure} \centering \includegraphics{./images/fragment-resources-tab.png} \caption{The Resources tab can be selected from the Fragment Collection.} \end{figure} Once added, you can reference resources from your Fragment's code without worrying about their availability. You can learn more about doing this in \href{/docs/7-2/frameworks/-/knowledge_base/f/including-default-resources-in-fragments}{Including Default Resources in Fragments}. Next you'll learn how to import and export Page Fragments. \chapter{Exporting and Importing Fragments}\label{exporting-and-importing-fragments} Often you'll want to reuse or re-purpose code from Fragments. To do this, export your Fragment Collections. \section{Exporting Fragments}\label{exporting-fragments} There are two ways to export Fragments: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Export a Collection or set of Collections. \item Export some Fragments outside of a Collection. \end{enumerate} To export a single Collection, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Build} → \emph{Page Fragments}. \item Next to \emph{Collections} click \emph{Actions} (\includegraphics{./images/icon-actions.png}) and select \emph{Export}. \item Select a Collection or multiple Collections to be exported. Each collection exports in a separate file. \begin{figure} \centering \includegraphics{./images/collections-export.png} \caption{Select Collections to export.} \end{figure} \end{enumerate} Each Collection \texttt{.zip} contains all Collection data and Fragments. To export individual Fragments, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the Collection that you want to export. \item To export all Fragments in the Collection without exporting any Collection data, click \emph{Actions} (\includegraphics{./images/icon-actions.png}) \emph{Export} next to the Collection name. A \texttt{.zip} file is generated and downloaded automatically. \begin{figure} \centering \includegraphics{./images/fragments-export-individual.png} \caption{Exporting all of the Fragments in a Collection.} \end{figure} \item To export a single Fragment, click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Export} next to the Fragment. A \texttt{.zip} file is generated and downloaded automatically. \end{enumerate} Note that if you export a single Fragment or a group of Fragments without a collection, they must be imported into an existing Collection. You can also export Global Collections and single Fragments from your Site. Now it's time to import your Fragments to where you need them. \section{Importing}\label{importing} You can import a Collection that was created in Liferay DXP, a Collection created using external tools, or Page Fragments without a collection. When you first import Page Fragments, they aren't available for use until you have approved them for use. This is to ensure that there are no errors in any imported fragments before they are added to a page. See \href{/docs/7-2/frameworks/-/knowledge_base/f/page-fragments-desktop-tools\#importing-and-exporting-fragments}{Developing a Fragment Using Desktop Tools} for more information on creating and importing Fragments using other tools. \section{Importing Collections}\label{importing-collections} To import a collection, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Build} → \emph{Page Fragments}. \item Next to \emph{Collections}, click \emph{Actions} (\includegraphics{./images/icon-actions.png}) and select \emph{Import}. \begin{figure} \centering \includegraphics{./images/collections-import.png} \caption{Importing and exporting Collections is accessed from a single menu.} \end{figure} \item On the next screen, click \emph{Choose File} and select the file you want to import. \item If you want to replace an existing collection, make sure the box is checked for \emph{Overwrite Existing Files}. \item Click \emph{Import} and the collection is uploaded. \end{enumerate} \section{Importing Individual Page Fragments}\label{importing-individual-page-fragments} You can also import a single Page Fragment or a set that was exported outside of a collection. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the root level of the Fragments page, click on an existing Collection where you want to import the Fragments. \item From inside the Collection click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button in the top right corner of the page. \item Select \emph{Import}. \item Drag-and-drop or click \emph{Select} to upload the Fragments \texttt{.zip}. \item Click \emph{Import} \end{enumerate} The Fragments are imported into the Collection. Exporting and importing fragments is the preferred way to share code or bring it into your Site. \chapter{Using Widget Pages}\label{using-widget-pages} Widget Pages are composed of \emph{widgets}. A widget is any application that you can add to a page. Widget Pages are constructed by adding widgets to the page and filling them with content. A widget could be a wiki display or a dynamic publishing tool like the Asset Publisher. The content you display with widgets could be long-form text or an image gallery, or anything in between. In this section, you'll learn to create widget pages and build content with them. \chapter{Creating Widget Pages}\label{creating-widget-pages} Widget Pages are the classic type of page in Liferay DXP. They're simple to create and fill with content or functionality. You create a blank page, define a layout, and then add widgets to the layout. Widgets can display content or provide some tool or function for Users. \section{Adding a Widget Page}\label{adding-a-widget-page} When you first start Liferay DXP you get a widget page by default as your home page. To create a new widget page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages}. \item Click the \emph{Add} icon ( \includegraphics{./images/icon-add.png}) in the top right and select \emph{Public Page} to add a new page. \item Select \emph{Basic Pages} if it is not selected by default. \item Select the \emph{Widget Page} type. \item Name the page \emph{Community} and click \emph{Save}. \item On the next screen, you can select a Layout Template or manage other options. Leave the defaults and click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/creating-community-page.png} \caption{Create a page called \emph{Community} with two columns.} \end{figure} Creating a page by default also adds it to any Navigation Menus that are configured to have new pages added to them. If you don't want a new page added to a specific Navigation Menu that is listed during page creation, uncheck the box for that menu. Your new page is now added to the navigation. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the logo in the top left of the page to go back to your Site's front page. The page you just created appears in the main navigation. \begin{figure} \centering \includegraphics{./images/community-page-created.png} \caption{Your page has been added to the navigation automatically.} \end{figure} \item Click on \emph{Community} to go to the page. \end{enumerate} Currently the page is empty. Next you'll add some widgets to give it functionality. \section{Adding Widgets to a Page}\label{adding-widgets-to-a-page} To add a widget to a page, go to the page and click the \emph{Add} button (\includegraphics{./images/icon-add-widget.png}) from the top menu and select the \emph{Widgets} tab. You can either browse through the categories of available widgets until you find the one you want, or you can search for widgets by name. Once you've found a widget, click the \emph{Add} button to add it to the current page. Once there, you can drag it to a new position. Alternatively, you can drag the widget directly from the Widgets menu to a specific location on the page. Follow the steps below to add some Collaboration apps to the Lunar Resort Site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the top menu, select \emph{Add} → \emph{Widgets}. \item In the menu that appears, expand the \emph{Collaboration} category. \item Drag the \emph{Blogs Aggregator} widget onto the right column of your page. \item Next, drag the \emph{Wiki} app to the left column. \end{enumerate} See how easy it is to add applications to your pages? You've added the Wiki app and Blogs Aggregator app to a page. \begin{figure} \centering \includegraphics{./images/app-layout-design.png} \caption{Your page layout options are virtually limitless with a slew of application and layout combinations.} \end{figure} If the default layout options provided aren't enough, you can create your own. For more information about developing custom layout templates, see the tutorial \href{/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator}{Layout Templates with the Liferay Theme Generator}. Next, you'll look at creating reusable templates for widget pages. \chapter{Creating Widget Pages from Templates}\label{creating-widget-pages-from-templates} Page templates provide pre-configured pages to reuse. There are two types of page templates in 7.0: Widget Page templates consist of a portlet layout and configuration. Content Page templates are constructed from Fragments. You can read about \href{/docs/7-2/user/-/knowledge_base/u/building-content-pages}{Content Page Templates in this article}. Three sample layout page templates are installed by default: \begin{itemize} \item \textbf{Search:} Contains a search bar and configuration to display various facets. \item \textbf{Wiki:} Provides a page with three applications related to authoring a wiki. \item \textbf{Blog:} Provides a page with three applications related to blogging. \end{itemize} \begin{figure} \centering \includegraphics{./images/default-page-templates.png} \caption{The Blog page template is already available for use along with the Search and Wiki page templates.} \end{figure} To add a new widget page template, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Builder} → \emph{Pages}. \item Select the \emph{Page Templates} tab. \item Click \emph{New} and create a collection named \emph{Lunar Resort Templates}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select \emph{Widget Page Template}. \item Enter a name. \item Click \emph{Save}. \end{enumerate} The editing page for the template appears. You can add widgets to the page or access page configuration now. The changes you make are instantly applied to the template. If you want to edit the template again, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go back to the \emph{Page Templates} tab. \item Click the \emph{Actions} icon (\includegraphics{./images/icon-actions.png}). \item Click \emph{Configure}. \end{enumerate} Note that after a new page template has been created, the default permissions only allow the creator to use the page template. To give other users access to it, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Use the Actions menu for the template and select \emph{Permissions}. \item In the matrix of Roles and permissions, check the \emph{View} permission for the Roles needed to see the page template in the list of available page templates when creating a new page. \end{enumerate} If you want any user who can create a page to be able to use the page template, check the \emph{View} permission for the \emph{User} Role. To use your template to create a new page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Site Administration and select the \emph{Pages} option from the \emph{Site Builder} menu dropdown option. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}). \item Inside the Lunar Resort collection, select the page template that you created. \item Enter the name of your page and click \emph{Add}. \end{enumerate} Pages based on templates can inherit changes from the page template: \begin{figure} \centering \includegraphics{./images/automatic-application-page-template-changes.png} \caption{You can choose whether or not to inherit changes made to the page template.} \end{figure} By default, when a Site administrator creates pages based on a page template, future changes to the template are automatically propagated to those pages. Site administrators can disable this behavior by disabling the \emph{Inherit Changes} selector. Occasionally, propagation for page templates fails due to unintended errors. To learn how to manage a failed page template propagation, visit the \href{/docs/7-2/user/-/knowledge_base/u/propagating-changes-from-site-templates-to-sites}{Propagating Changes from Site Templates to Sites} article. If staging has been enabled, changes to the page template are automatically propagated to the staged page. These changes must still be approved before the page is published to live. For this reason, the automatic propagation of page template changes to the staged page cannot be turned off and the \emph{Inherit Changes} selector does not appear. You can read more about staging in the \href{/docs/7-2/user/-/knowledge_base/u/staging}{Stagingl} articles. \section{Sharing Widget Page Templates}\label{sharing-widget-page-templates} When importing pages to a new site or environment, you must also import templates associated with those pages. Generally templates are included automatically when an associated page is exported, but if not you can export the template collection separately so the page can be imported to the new environment. To export page templates, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Management} → \emph{Site Builder} → \emph{Pages}. \item Select the \emph{Page Templates} tab. \item At the top right of the page, click \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Export/Import}. \item On the \emph{Export/Import} page you can choose to export configuration data and change which Collections and templates are being exported. \item When you're done configuring the export, click \emph{Export} and save the exported LAR file. \item On the target environment, go to \emph{Site Management} → \emph{Site Builder} → \emph{Pages} and select \emph{Page Templates}. \item At the top right of the page, click \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Export/Import}. \item Select the \emph{Import} tab. \item Upload the LAR with your template data. If the LAR contains additional content you don't want to import, you can deselect it. \end{enumerate} Once the template has been imported, the page can be imported normally to your new environment. For more information on exporting/importing content, visit the \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Sites and Content} article. \chapter{Building a Responsive Site}\label{building-a-responsive-site} Now more than half of all page views in the world come from mobile devices like phones and tablets. That means that if your pages don't look good on mobile devices, your pages don't look good for more than half the people looking at them. Liferay DXP can provide the best experience possible no matter what device you're using. Most of the heavy lifting for mobile optimization comes from your developers. Themes must be designed to be responsive. Web Content Structures and Templates and Page Fragments must be created with code that transitions gracefully to fit smaller screens on mobile devices. But there's work to do for marketers and administrators as well. \chapter{Built-in Mobile Support}\label{built-in-mobile-support} Out of the box, there are several features that help make your pages look just as good and have the same functionality on mobile devices as they do on a desktop: \begin{itemize} \item Liferay Widgets and custom widgets that use Liferay's UI frameworks automatically scale to fit the screen size. \begin{figure} \centering \includegraphics{./images/widget-adjustment.png} \caption{A widget adjusts its size.} \end{figure} \item UI elements like the navigation and Product Menu automatically adjust to remain usable on smaller screens. \begin{figure} \centering \includegraphics{./images/navigation-adjustment.png} \caption{The main navigation adjusts its size.} \end{figure} \item When the screen width is low, Liferay combines columns so that all content remains legible. \item For web developers, Liferay's theme tools provide a number of tools to help ensure optimum mobile performance. \end{itemize} For most business users, this means that all you need to do to display pages on Mobile device is to create a page. However, you also have tools available to verify that everything displays as intended. The Device Simulator (\includegraphics{./images/icon-simulation.png}) is a powerful tool that shows you how pages look on different devices. \section{Using the Device Simulator}\label{using-the-device-simulator} When creating a page or reviewing a page before it is published, one of your most important tools is the Device Simulator found in the top right corner of every page. The simulator lets you view the current page in a number of resolutions based on different display types. There are three predefined options: \textbf{Desktop:} Fixes the width to display the page at full size. \textbf{Tablet:} Puts your page in a box as if it is being displayed on a tablet. It also activates some of Liferay's built-in mobile features. \textbf{Mobile:} Puts your page in an even smaller box to demonstrate how the page looks to your average smartphone user. \begin{figure} \centering \includegraphics{./images/device-simulation.png} \caption{The Simulation panel defines multiple screen sizes.} \end{figure} There are also two options available to display \textbf{AutoSize:} Provides another way to view the default behavior where the page shrinks and grows based on the width of the browser window. \textbf{Custom:} Lets you enter a specific size for testing and fixes the height and width of the display. Because modern mobile browsers are built on the same technology as desktop browsers, the behavior you see in the simulator should match the experience of users on mobile devices. In addition to making sure the basic layout looks good and that all functionality remains, it's also important to make sure that automatic features---like how columns are combined at lower resolutions---don't have unintended effects. \section{Designing Mobile Friendly Pages}\label{designing-mobile-friendly-pages} Liferay DXP provides the tools you need, but building pages that provide a good experience across all kinds of devices still means working across all levels of web development and publishing. Theme developers must create themes that use Liferay's frameworks to scale content well across all kinds of displays. Designers must have multiple screen sizes in mind when designing pages. And before anything it published it must be thoroughly reviewed to make sure that it provides the best experience to all of your users. Now that you've learned about Liferay's tools for making your website mobile friendly, let's look at your options for adapting to different types of mobile devices. \chapter{Mobile Device Rules}\label{mobile-device-rules} With mobile device rules, you can alter what gets displayed based on the device being used to access Liferay DXP. For instance, you can configure the look and feel of pages accessed by mobile device users differently from those accessed by users on a computer. Whole Sites or individual pages can be configured for mobile device families. A family describes a group of devices. You can set rules that describe a category of devices, such as all Android devices or all iOS devices. You can define as many rules in a family as you need to classify all the devices for which you'd like to define actions. Families can be prioritized to determine which one applies to a given page request. \chapter{Creating Mobile Device Rules}\label{creating-mobile-device-rules} To configure mobile device rules, you need a way to find out the characteristics of the device. While some of the characteristics are provided by the device, most are not. For this reason, there are databases that contain information about thousands of devices. These databases make it possible to learn device details from the device type. Liferay DXP's Mobile Device Rules can connect to device databases so that you can use their device characteristics in your rules. \noindent\hrulefill \textbf{Important:} For the features described in this article to work, you must install the \href{https://web.liferay.com/marketplace/-/mp/application/92831494}{Liferay Mobile Device Detection Lite (LMDD)} app from Liferay Marketplace. This app provides the device detection database that's required to detect which mobile devices are accessing it. \noindent\hrulefill You can develop plugins that integrate with other device databases. Even if you don't have a device database, you can still set up mobile device rules. They won't, however, be effective until a database is deployed, because the portal won't have enough information about the devices being used to make page requests. To access the Mobile Device Families administrative page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Product Menu}. \item Use the \emph{Site Selector} (\includegraphics{./images/icon-compass.png}) to choose the Site that you want to define Mobile Device Rules for. \item Select \emph{Configuration} → \emph{Mobile Device Families}. \end{enumerate} You can also add families for all Sites by navigating to the Control Panel → \emph{Sites} → \emph{Sites} → \emph{Global}. The Mobile Device Families administrative page displays a list of defined families and lets you add more. To add rules, you must first add a family. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Add} button (\includegraphics{./images/icon-add.png}) to add a \emph{New Device Family}. \item Enter a \emph{Name} and \emph{Description}. \item Click \emph{Save}. \item Click on the name of the Mobile Device Family to access the rules page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/mobile-device-families.png} \caption{Create a Mobile Device Family so you can create rules.} \end{figure} The rules defined for a family, along with the priorities of the families selected for a particular Site or page, determine which family's actions are applied to a given request. From the New Classification Rule page for a specific rule set, you can add a rule by specifying an operating system, rule type, physical screen size, and screen resolution. Remember that you can add as many rules to a family as you need in order to classify the devices on which you'd like to take actions. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the \emph{Add} button (\includegraphics{./images/icon-add.png}) to add a new rule. \item Enter a \emph{Name} and \emph{Description}. \item Select the classifications you want for this rule from \emph{Operating System and Type}, \emph{Physical Screen Size}, and \emph{Screen Resolution}. \item Click \emph{Save}. \end{enumerate} You'll notice after saving the classification rule that it's characterized as a \emph{Simple Rule}. Only Simple Rules are included with Liferay DXP, but the rules are designed to be extensible, and additional rule types can be added by your developers. \begin{figure} \centering \includegraphics{./images/mobile-device-editing-rule.png} \caption{Select the operating system and device type for your rule.} \end{figure} Once you've created some mobile device families and added some rules to them, you're ready to create some actions. The actions defined for a family determine what happens to a request when the device is detected and the family has been found to apply. You can add families to a Site, individual page, or page set from their respective configuration pages. To do it for a Page Set: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Builder} → \emph{Pages} in your Site. \item Click on \emph{Configure} (\includegraphics{./images/icon-gear.png}) for the Public Pages. \item Select the \emph{Advanced} tab and open the \emph{Mobile Device Rules} option in the bottom menu. \item Click \emph{Select} to open the list of families that can be applied. \end{enumerate} From the same page, you can access the configuration for an individual page, or you can configure Mobile Device Rules for an entire Site from \emph{Configuration} → \emph{Site Settings}. You can select multiple families for a particular Site or page and order them by priority. The families are checked in decreasing order of priority: the actions defined by the first family that applies are executed. \begin{figure} \centering \includegraphics{./images/mobile-device-selection.png} \caption{You can select a mobile device family to apply for a Site or page.} \end{figure} \chapter{Mobile Device Actions}\label{mobile-device-actions} After you've created families and applied rules to define those families, you can associate specific actions that occur when a user visits that Site on a device. To add actions to a selected rule group: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Configure} (\includegraphics{./images/icon-gear.png}) for the page, page set, or Site where you have configured a device family. The configuration page appears. \item Click the \emph{Advanced} tab. \item Decline using the same mobile device rules from the public pages by setting the option to \emph{No}. The device family section appears. \begin{figure} \centering \includegraphics{./images/configure-mobile-device-rule-for-page.png} \caption{Disable using public pages device rules.} \end{figure} \item Click \emph{Select} to open the Device Families page. \item In the \emph{Mobile Device Rules} section, click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Manage Actions} next to the device family that you wish to add an action for. \item Click \emph{Add Action}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/manage-mobile-actions.png} \caption{Getting to the Manage Actions page.} \end{figure} By default, there are four kinds of actions that can be configured for mobile families: \textbf{Layout Template Modification:} Changes the way portlets are arranged on pages delivered to mobile devices. For example, you could have pages with more complex layouts automatically switch to a simpler template if it detects a mobile device---even if the resolution is theoretically high enough to support the standard layout. \textbf{Theme Modification:} Selects a specific theme for different mobile device families. You'd have to have a mobile version of your Site's theme that is automatically applied when a device hits your page. \textbf{URL Redirect:} Sends mobile users to any URL. This can be used to direct mobile users to a mobile app download or a mobile version of the page. \textbf{Site Redirect:} Sends mobile users to a different Site on your portal. In some cases, mobile content could be created on a mirror of your Site. \noindent\hrulefill \textbf{Tip:} 7.0 was designed from the ground up to be responsive and adapt to any device that might be accessing it. Before creating new themes or forcing a layout template change, you should test how the Site behaves using Liferay DXP out of the box. Certain features, like URL Redirects, can be disruptive and frustrating for users if used improperly. \noindent\hrulefill Like mobile device rules, mobile device actions are extensible. Your developers can define custom actions in addition to the four actions provided by default. To review, if you want to configure an action or actions that take place when mobile device requests are received, take the following steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a mobile device family to represent the group of devices for which to define an action or actions. \item Define one or more rules for your family that describe the group of devices represented by your family. \item Apply your family to an entire page set of a Site (all the public pages of a Site or all the private pages) or to a single page. \item Define one or more actions for your family that describe how requests should be handled. \end{enumerate} \section{Mobile Device Rules Example}\label{mobile-device-rules-example} Now you'll look at an example of using mobile device rules. Suppose you want to create a rule so that when a Site is accessed by an Android device, a different layout is used. To set this up, you must follow the same four steps described above. First create the Mobile Device Family: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Mobile Device Families} page of \emph{Site Administration}. \item Click \emph{Add Device Family} (\includegraphics{./images/icon-add.png}). \item Enter \emph{Android} for the \emph{Name}. \item Click \emph{Save}. \end{enumerate} Next create a rule for the family: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Mobile Device Families} page, click on \emph{Android}. \item Click \emph{Add Classification Rule} (\includegraphics{./images/icon-add.png}). \item Name the rule \emph{Rule 1}. \item Under \emph{Operating System} select \emph{Android} (you can hold or to select multiple items). \item Under \emph{Device Type} select \emph{All}, \item Click \emph{Save}. \begin{figure} \centering \includegraphics{./images/example-classification-rule.png} \caption{Create the Classification rule.} \end{figure} \end{enumerate} As with the previous example, you only need one rule to describe your device family. Now you must apply the rule to some pages. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Builder} → \emph{Pages} in Site Administration. \item Click on the \emph{Configure} icon for the \emph{Public Pages} \item Go to the \emph{Advanced} tab. \item Under \emph{Mobile Device Rules}, select the \emph{Android} device family. \end{enumerate} Now you must define an action for your Android rule group to use a different layout. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Actions} → \emph{Manage Actions} for the \emph{Android} rule. \item Click \emph{Add Action}. \item Enter the name \emph{Layout Template Modification}, and select the \emph{Layout Template Modification} action type. \item Select the \emph{1 Column} layout template. \item Click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/example-mobile-action.png} \caption{Create the Actions for Android.} \end{figure} Now the Liferay Site's pages are presented to Android users with the 1 Column layout template. Mobile Device Rules are a powerful way to manage the way pages and content appear on the various devices that access your Site. But remember to consider the power of modern devices and the experience of your users, and use this great power responsibly---to help users have a great experience on your website and to not interrupt or negatively impact that experience on whatever device they're using. \chapter{Using Full Page Applications}\label{using-full-page-applications} Full Page Applications are the ideal way to display a Message Board, Wiki, or other application that demands a full page. \section{Configuring the Page}\label{configuring-the-page} Creating a Full Page Application starts just like creating any other type of page. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages}. \item Click the (\includegraphics{./images/icon-add.png}) icon. \item Select the \emph{Full Page Application} page type. \item Give your page a \emph{Name} and click \emph{Save}. \item Set the \emph{Full Page Application} to \emph{Wiki} and click \emph{Save}. \begin{figure} \centering \includegraphics{./images/full-page-app-configure.png} \caption{The Full Page Application configuration page.} \end{figure} \item Finish the page configuration and click \emph{Save}. Out of the box, you can set the Blogs, Wiki, Media Gallery, Message Boards, RSS, Hello Soy Portlet, Documents and Media, Form, or Application Authorization Request to be the sole application for the page. Developers can make their applications Full Page Applications. \item Click \emph{Go to Site} in the Site Administration menu, and then click on your page. \end{enumerate} Now the page is configured to display the Wiki and only the Wiki. No other widgets can be added to the page, and the Wiki app cannot be removed. \begin{figure} \centering \includegraphics{./images/single-page-app-wiki.png} \caption{The Wiki displayed as a Full Page Application.} \end{figure} Note that all of the applications that can be added to the page are non-instanceable and the content of whichever application you select is based on the instance for that site. So if you already had data in your Wiki it appears on this page. If you want to configure the application to be scoped to this specific page, you can configure that through the application's settings. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the page, click the (\includegraphics{./images/icon-options.png}) button for the Wiki and select \emph{Configuration}. \item From the \emph{Wiki - Configuration} page, select the \emph{Scope} tab. \item Open the \emph{Scope} menu and select \emph{Space Wiki}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/configuring-scope.png} \caption{Configuring the scope.} \end{figure} Now the Wiki is scoped for this page, and doesn't share data with the Site or globally scoped Wiki. \chapter{Managing Site Navigation}\label{managing-site-navigation} Liferay provides powerful tools for creating and organizing pages. You can have anything from a simple, flat Site navigation to a complex hierarchy with tree of sub-pages nested down many levels. 7.0 lets you create Navigation Menus separate from your page hierarchy. Now you have the freedom to leave one-off marketing landing pages out of the navigation or create multiple navigation menus: a main menu, secondary menus, footer menus, and custom menus for anything that you can dream up. Menus can differ by page: landing pages can show a simple list of frequently visited pages, and the rest can appear in secondary navigation. You can also create specific menus for different landing pages to direct users to content that is relevant to them. Go to \emph{Site Builder} → \emph{Pages} to view the existing pages or create new pages. The Site hierarchy as displayed on \emph{Pages} is the main reference for the organization of pages on that Site. While Navigation Menus can customize their organization and what appears and what doesn't appear, this menu is always the primary reference for the pages on your Site. \chapter{Page Hierarchy}\label{page-hierarchy} Using the Page Hierarchy, you create public and private pages and organize those pages in whatever order or structure that you see fit. \section{Creating a Page}\label{creating-a-page} New pages are created on the \emph{Site Builder} → \emph{Pages} page in Site Administration. Pages can be created as \emph{Public Pages} which anyone can view, or \emph{Private Pages} which can only be viewed by Site Members. To create a new page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} for the Site you want to work on, then \emph{Site Builder} → \emph{Pages}. \begin{figure} \centering \includegraphics{./images/default-nav-pages.png} \caption{In the default site, initially only the \emph{Home} and the hidden \emph{Search} pages exist in the Public Pages Hierarchy.} \end{figure} \item Click \emph{Add} (\includegraphics{./images/icon-add.png}) and select \emph{Public Page}. \item Select \emph{Widget Page}. \item Set the \emph{Name} as \emph{About Us}. \item Click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/page-hierarchy-menu.png} \caption{When you create a page, by default it is added to the site hierarchy.} \end{figure} Now that the page is created, it appears in the hierarchy, and you can move or organize its position there. \section{Organizing Pages}\label{organizing-pages} Drag and drop pages to reorder their position in the page hierarchy (and subsequently the default navigation that users see), and to nest them as subpages. The page at the top of the list is the \emph{Home} page that users see automatically when visiting your Site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Go to Site} to view the organization of the navigation menu. You can see the order of the pages matches the order of the pages from Site Administration. \begin{figure} \centering \includegraphics{./images/navigation-practical1.png} \caption{You can see the order of pages in Site Administration vs.~how they appear on the site.} \end{figure} \item Drag the \emph{About Us} page above the \emph{Welcome} page in the list. It automatically becomes the \emph{Home} page. \item Click \emph{Go to Site} to see how this affects your menu. \begin{figure} \centering \includegraphics{./images/navigation-practical2.png} \caption{\emph{About Us} is now the home page, and \emph{Welcome} is second in the nav.} \end{figure} \item Drag \emph{About Us} on top of \emph{Welcome} to nest it. \item Click \emph{Go to Site} one more time to see how nested pages appear. \end{enumerate} \begin{figure} \centering \includegraphics{./images/navigation-practical3.png} \caption{\emph{About Us} is now nested under \emph{Welcome} and appear when you mouse-over \emph{Welcome}.} \end{figure} As you've just demonstrated, organizing pages in the default menu is simple, but very powerful. \section{Public and Private Pages}\label{public-and-private-pages} As noted above, Private Pages work just like Public Pages, except they can be viewed only by registered members of a Site. In the default configuration, Public Pages are at the URL \texttt{{[}web-address{]}/}\textbf{web}\texttt{/{[}site-name{]}} while Private Pages are at \texttt{{[}web-address{]}/}\textbf{group}\texttt{/{[}site-name{]}}. Other than the membership distinctions, Public and Private Pages share the same behavior. \section{Page Options}\label{page-options} While managing the default menu, you can also access page options. Clicking on the \emph{Options} icon \href{./images/icon-options.png}{Option} accesses several configuration tools: \textbf{View} goes to the selected page on the Site. \textbf{Configure} goes to page configuration. \textbf{Copy Page} creates a new page in the current Site that duplicates the selected page. \textbf{Permissions} opens the Permissions dialog. \textbf{Orphan Widgets} clears data related to widgets that have been removed from the page. \textbf{Delete} deletes the page and all its data. Creating and managing pages using the page hierarchy is simple but very powerful. If you need more navigation options, however, Navigation Menus provide more flexibility. \chapter{Creating and Managing Navigation Menus}\label{creating-and-managing-navigation-menus} To better understand Navigation Menus, it's time to create a new menu. \section{Creating a Navigation Menu}\label{creating-a-navigation-menu} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Navigation Menus}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to add a new menu. \item Give your menu a name and click \emph{Save}. \end{enumerate} On the next page appears a number of elements that you can add to a menu. \textbf{Page}: Select an existing page from the current Site to add to the navigation menu. \textbf{Submenu}: Create a second level of menu navigation. \textbf{URL}: Create a link to any page anywhere by providing a URL. The link appears just like any other option in your menu. Click on \emph{Page} and you see a view of all the current pages on the current Site. Select a page and click \emph{Add} to add that page to the menu. \noindent\hrulefill \textbf{Note:} When you click on a page, you select that page. Multiple pages can be selected by clicking on each page one at a time. To deselect a page, click on the page again. \noindent\hrulefill Now you see the menu management screen. From here, you can drag and drop menu elements to rearrange or nest them. You can also manage options for this menu by clicking the gear icon in the top right. Let's add another item to the menu. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \texttt{+} icon. \item Select \emph{Submenu} in the menu that pops up. \item Name your menu \emph{External Links}. \item Click \emph{Add}. \end{enumerate} Click the \texttt{+} button again and select \emph{URL}. You're prompted to enter a page name and URL. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enter a \emph{Name}. \item Enter the \emph{URL} for an external Site. \item Click \emph{Add}. \end{enumerate} Drag the URL item onto the \emph{External Links} submenu. This nests the URL item in the submenu. \begin{figure} \centering \includegraphics{./images/basic-nav-menu.png} \caption{Menus can have a standard page, a submenu, and a URL link in the submenu.} \end{figure} Now that you can see how menus work, you can learn the details. \section{Managing Menus}\label{managing-menus} After you create a menu, more configuration options appear on the main Navigation Menus page. \textbf{Title}: Your menu's name. \textbf{Add New Pages}: Determines if new pages added to the main navigation are added to the menu automatically. \textbf{Marked As}: Can be set as \emph{Primary Navigation}, \emph{Secondary Navigation}, or \emph{Social Navigation}. \textbf{Author}: The user that created the menu. \textbf{Create Date}: When the menu was created. Theme and Fragment developers primarily use the menu types to determine how a menu should be styled. \textbf{Primary Navigation} is the main navigation for a page. \textbf{Secondary Navigation} is a second level of navigation, possibly a sidebar or a separate menu within a page. \textbf{Social Navigation} is for menus that contain links for sharing content on social media or similar tasks. \section{Modifying Menus}\label{modifying-menus} Next click on the options menu at the far right of your new navigation menu: \begin{figure} \centering \includegraphics{./images/nav-menu-options.png} \caption{Menus with a standard page, a submenu, and a URL link in the submenu are created for different reasons.} \end{figure} \textbf{Edit}: Add, remove, or organize menu items. \textbf{Rename}: Change the name of your menu. \textbf{Permissions}: Define who can view, update, delete, and manage the permissions for the menu. \textbf{Mark As}: Change the menu type. \textbf{Delete}: Deletes the menu. Next you'll learn about the tools for displaying menus on pages. \chapter{Displaying Navigation Menus}\label{displaying-navigation-menus} You can display Navigation Menus in different ways on your Site. You may want to configure different display styles for a main menu, sidebar, and footer menu all on one page. \section{Navigation Menu Widget}\label{navigation-menu-widget} The Navigation Menu widget lets you add navigation wherever you need it. You can place the widget on a page and then select a menu and style for the menu you are displaying. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to a Widget Page, open the Add menu on the right side of the page and add the \emph{Content Management} → \emph{Navigation Menu} to the page. \item Open the \emph{Configuration} menu. \end{enumerate} From here you can configure three main categories: \begin{itemize} \item The Navigation Menu to be displayed \item The styling of the menu \item What level of navigation to display \end{itemize} \begin{figure} \centering \includegraphics{./images/nav-widget-configuration.png} \caption{Configuring the Navigation Menu Widget.} \end{figure} \section{Choosing a Navigation Menu}\label{choosing-a-navigation-menu} The Navigation Menu Widget has two ways to select a menu. You can choose to \emph{Select Navigation} or \emph{Choose Menu}. \textbf{Select Navigation:} Select from the three main menu types: \emph{Primary Navigation}, \emph{Secondary Navigation}, and \emph{Social Navigation}. \textbf{Choose Menu:} Choose any menu that was created for that Site. Once you select a menu, you must choose how to display it. \section{Display Template}\label{display-template} The \emph{Display Template} option lets you select an \href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget Template} for Navigation Menus. There ten included by default: \textbf{List Menu:} Displays all the items in a vertical list. \textbf{Pills Horizontal:} Displays the items horizontally and uses a button style for highlighting. \textbf{Pills Justified:} Like Pills Horizontal, but pads the items to fill out the horizontal space. \textbf{Pills Stacked:} A vertical version of the pills style. \textbf{Tabs:} Displays the items like navigation tabs. \textbf{Tabs Justified:} Navigation tabs that fill horizontal space. \textbf{Bar Minimally Styled:} A lightweight version of the default display that you see in the embedded menu on your page. \textbf{Bar Minimally Justified Styled:} Like Bar Minimally Styled with horizontal padding. \textbf{Bar Default Styled:} The default embedded menu. \textbf{Split Button Dropdowns:} Displays each item as a button with a dropdown for multiple navigation levels. You can also add your own custom templates. \section{Menu Items to Show}\label{menu-items-to-show} \emph{Menu Items to Show} configures which pages at what level from the menu are displayed in the widget. You can choose the starting level, how many levels deep to display, and how to display sub-levels. \textbf{Start with Menu Items In:} Choose to start at a specific level of the navigation or a level relative to the current level (above or below). \textbf{Sublevels to Display:} Select the number of levels to display in the navigation, from \textbf{1} down to \textbf{Infinite}. \textbf{Expand Sublevels:} Choose if hovering your mouse over the navigation reveals navigation levels one at a time automatically or reveal all the levels at once. Now you can see how there are a variety of customizations and configurations available for navigation menus that you can implement for your Site. \begin{figure} \centering \includegraphics{./images/navigation-menu-examples.png} \caption{Navigation menus give you many ways to help users navigate your Site.} \end{figure} \chapter{Building Sites from Templates}\label{building-sites-from-templates} Site Templates create a single Site structure that can be used for any new Site. They are created and administered from the Control Panel. In addition to creating multiple Sites with the same design, you can also use them to manage changes across multiple Sites with propagation of changes. Site templates can contain multiple pages, each with its own theme, layout template, applications, and app configurations. Site templates can also contain content just like actual Sites. This allows administrators to use Site Templates to create new Sites that are each created with the same default pages, applications, and content. After they've been created, these Sites and their pages can be modified by Site administrators. Using Site templates can save Site administrators a lot of work even if each Site that was created from a given Site Template ends up being very different. \chapter{Creating a Site Template}\label{creating-a-site-template} Suppose you need to create the following three private Sites for the Lunar Resort's internal use: Engineering, Marketing, and Legal. These should be accessible only to members of these respective departments. You could design each Site separately, but you can save yourself time if you create a Site template instead. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Control Panel and click \emph{Sites} → \emph{Site Templates}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and enter the name \emph{Department} for your template. \item Leave the \emph{Active} and \emph{Allow Site administrators to modify pages associated with this Site template\ldots{}} boxes checked. \item Click \emph{Save} to create your Site template. \end{enumerate} The \emph{Active} box must be checked for your template to be usable. If your template is still a work in progress, uncheck it to ensure that no one uses it until it's ready. Checking \emph{Allow Site administrators to modify pages associated with this Site template\ldots{}} allows Site administrators to modify or remove the pages and apps that the template introduces to their Sites---if you want the templates to be completely static, you should uncheck this. Now it's time to edit your Site template. This example, includes four pages. \noindent\hrulefill \textbf{Note:} This section assumes knowledge of Liferay DXP 7.2 page management. For more information on how to create and manage pages in Liferay DXP 7.2, see the \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-pages}{Adding Pages to Sites article}. \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Options} icon (\includegraphics{./images/icon-options.png}) and select \emph{Manage}. This brings you to the \emph{Pages} page for the Site Template. You already have a home page. Create three more pages. \item Create a \emph{Full Page Application} page named \emph{Documents}. \item Click \emph{Options} (\includegraphics{./images/icon-actions.png}) → \emph{Configure} and set the \emph{Full Page Application} to \emph{Documents and Media}. \item Create a page using the Global Page Template \emph{Wiki} and name it \emph{Wiki}. \item Create a widget page named \emph{Message Boards}. \item Click \emph{Go to Site} in the menu to the left to go to the pages you just created. \item On the \emph{Home} page add the Activities, Announcements, and Calendar apps. \item On the \emph{Message Boards} page add the Message Boards and Tag Cloud apps. \end{enumerate} The changes you made to your Site template above are completed in real time, so there's no \emph{Save} button. \begin{figure} \centering \includegraphics{./images/editing-site-template.png} \caption{You can see the name of the Site template you're currently editing.} \end{figure} Next, you'll use your Site template to create the Engineering, Marketing and Legal Sites. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Control Panel and click on \emph{Sites} → \emph{Sites}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) → \emph{Department}. \item Enter \emph{Engineering} for the Site name. \item Check the \emph{Create default pages as private (available only to members). If unchecked, they will be public (available to anyone)} option since the Engineering Site is intended for internal use only. \item Click \emph{Save}. \item In the next section, set the Membership Type to \emph{Private}. Recall that private Sites don't appear in the My Sites application so that regular users won't even know that the Engineering Site exists. Also, the only way users can be added to a private Site is via an invitation from a Site administrator. \item Leave the \emph{Active} selector enabled so that your Site can be used immediately. \item Check the \emph{Create default pages as private (available only to members). If unchecked, they will be public (available to anyone).} option since the Engineering Site is intended for internal use only. \item Leave the \emph{Enable propagation of changes from the Site template} box enabled so that the Engineering Site receives updates if the Department Site template is modified. \item Click \emph{Save} to create your Engineering Site. \item Repeat these steps to create the Marketing and Legal Sites. \end{enumerate} The new Sites have all the pages and apps you created in the Site template. To view the pages of the new Sites, click \emph{Sites} → \emph{Sites} in the Control Panel and then click \emph{Actions} → \emph{Go to Private Pages} next to one of your new Sites. Using Site templates streamlines the Site creation process for administrators, making it easy to create Sites quickly. Now each department of the Lunar Resort has its own Calendar, Documents and Media Library, Wiki, and Message Boards on their Sites. Although the pages and apps of each department's Site are the same, each Site will quickly be filled with department-specific information as users add and share content within the Sites. Also, Site administrators can add new pages, apps, and content to their Sites, further differentiating each department's Site from the others. \chapter{Managing Site Templates}\label{managing-site-templates} To get started, click on \emph{Site Templates} in the Sites section of the Control Panel. Here, you can add, manage, or delete Site templates. You can also configure the permissions of Site templates. As long as a Site is linked to the Site template it was created from, changes to the Site template's pages, apps, and app configurations are propagated to the Site. Changes to a Site template's content, however, are not propagated to existing Sites that are linked to the Site template. You'll learn about the propagation of changes between Site templates and Sites in more detail in the section on Site template use cases below. To manage a Site Template's pages, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on \emph{Site Templates} in the Control Panel. \item Select the \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and then \emph{Manage} for an existing template. \end{enumerate} If you open the main Menu on the left side of your screen (if necessary), the Site Template is selected in the Site Administration dropdown menu. You're provided similar options as a regular Site, including \emph{Build}, \emph{Content}, \emph{Configuration}, and \emph{Publishing}. By default, the Manage Interface opens \emph{Build} → \emph{Pages}. From here, you can add or remove pages from a Site Template or select themes and layout templates to apply to the Site Template. You can also configure each page to have any theme, any layout template, and any number of applications, just like a page of a regular Site. As with Site pages, you can organize a Site Template's pages into hierarchies. When you create a Site using a Site template, the configuration of pages and apps is copied from the template to the Site. By default, all changes made to the Site template are automatically copied to Sites based on that template. \noindent\hrulefill \textbf{Tip:} If you want to publish a piece of web content to many Sites and ensure modifications are applied to all, don't use Site template content for that purpose. Instead, place the content in the global scope and then reference it from a \emph{Web Content Display} application in each Site. \noindent\hrulefill The Content section offers separate repositories for content related apps based on your Site Template. For instance, by clicking \emph{Polls} from the Content section, you can create a poll question that is only available for that specific Site template. Assets created within your template's Content section can only be accessed by Sites using the template. The Configuration section includes Widget Templates and Mobile Device configuration options for your Site Template. Also, nested in the Configuration section is the \emph{Site Template Settings}. This edits the template's name and description while also offering boolean options for activating your Site template and allowing Site administrators to modify pages associated with your template. The following figure displays the form shown when editing the \emph{Department} template's settings: \begin{figure} \centering \includegraphics{./images/site-template-settings.png} \caption{Site templates have several configurable options including the option to allow Site administrators to modify pages associated with the Site template.} \end{figure} By default, the following Site templates are provided: \begin{itemize} \item \textbf{Intranet Site:} Provides a preconfigured Site for an intranet. The Home page displays the activities of the members of the Site, search, a language selector, and a list of the recent content created in the intranet. It also provides two additional pages for Documents and Media and external News obtained through public feeds. \item \textbf{Community Site:} Provides a preconfigured Site for building online communities. The Home page of a \emph{community Site} provides message boards, search, a display of a poll and statistics of the activity of community members. The Site will also be created with a page for a wiki. \end{itemize} Now that you know the basics for creating and managing your Site templates, you can learn about propagating changes next. \chapter{Propagating Changes from Site Templates to Sites}\label{propagating-changes-from-site-templates-to-sites} Site Template administrators can add, update, or delete Site Template pages. Changes made to a Site Template can be propagated to Sites whose page sets are linked to the Site Template. When you create a Site based on a Site Template with the \emph{Enable propagation of changes from the Site template} box checked this link is created. To configure propagation of changes: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the Site from the Sites dropdown in the Menu by selecting the \emph{Site Selector} button (\includegraphics{./images/icon-compass.png}). \item Navigate to the \emph{Configuration} → \emph{Settings} page and uncheck or recheck the \emph{Enable propagation of changes from the Site template} checkbox. \end{enumerate} In this section, you'll learn about the propagation of changes from Site templates to Sites and discuss the options available to Site administrators and Site template administrators. \section{Site Template Page Behavior}\label{site-template-page-behavior} If a Site's page set has been created from a Site template and the propagation of changes from the Site template is enabled, Site administrators can add new pages but cannot remove or reorder the pages imported from the Site Template. If a Site has both pages imported from a Site template and custom Site pages, the Site Template pages always appear first in the Site page hierarchy; custom pages added by Site administrators appear after the Site template pages. Only Site template administrators can remove, reorder, or add Site template pages. Site administrators can add or remove custom Site pages. They can also reorder custom Site pages as long as they're all positioned after the Site template pages. Site template administrators cannot add, remove, or reorder custom Site pages. \noindent\hrulefill \textbf{Note:} Pages containing a fragment (e.g., \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{Content Pages}) cannot propagate changes after a Site is \emph{first} created based on a site template. \noindent\hrulefill If a Site administrator changes a page that was imported from a Site Template and refreshes the page, the following Information icon (\includegraphics{./images/icon-control-menu-information.png}) appears in the Control Menu with the following message: \begin{verbatim} This page has been changed since the last update from the Site template. No further updates from the Site template will be applied. \end{verbatim} \begin{figure} \centering \includegraphics{./images/site-template-update-message.png} \caption{You can click the Information icon to view important information about your Site template.} \end{figure} \section{Merging and Resetting Changes}\label{merging-and-resetting-changes} If the Site administrator clicks the \emph{Reset Changes} button, changes are propagated from the Site template page to the corresponding Site page that was imported from the Site template. Clicking the \emph{Reset Changes} button makes two kinds of updates to a page. First, changes made by Site administrators to the Site page are undone. Second, changes made by Site template administrators to the Site template page are applied to the Site page. Note: clicking the \emph{Reset Changes} button only resets one page. If multiple Site pages have been modified and you'd like to re-apply the Site template pages to them, you'll need to click the \emph{Reset Changes} button for each page. Site template administrators can set preferences for apps on Site template pages. When a Liferay administrator creates a Site from a Site template, the app preferences are copied from the Site template's apps, overriding any default app preferences. When merging Site template and Site changes (e.g., when resetting), app preferences are copied from Site template apps to Site apps. Only global app preferences or local app preferences which don't refer to IDs are overwritten. In some cases, merging Site template and Site changes fails. For example, if pages from a Site template cannot be propagated because their friendly URLs are in conflict, Liferay DXP could try to continuously merge the Site changes. Instead of entering into an infinite loop of merge fails, Liferay DXP stops the merge after several unsuccessful attempts. Liferay DXP, however, doesn't stop there: your merge is temporarily paused, you're given an indication of the current merge fail, and then you have the opportunity to fix your merge conflicts. After you've squared away your conflict, navigate to your Site's \emph{Site Administration} → \emph{Configuration} → \emph{Site Settings} and click the \emph{Reset and Propagate} button. \begin{figure} \centering \includegraphics{./images/friendly-url-propagation-failure.png} \caption{This type of warning is given when there are friendly URL conflicts with Site template pages.} \end{figure} The \emph{Reset and Propagate} button resets the merge fail count and attempts to propagate your Site changes again. This process gives you the opportunity to detect and fix a merge fail when problems arise. This helpful process can also be done with page template merges, which follows similar steps. Site administrators can also add data to Site template applications. For example, Site template administrators can add the Wiki app to a Site template page and use the Wiki to create lots of articles. When a Liferay administrator creates a Site from a Site template, data is copied from the Site template's apps to the Site's apps. The preferences of the Site's apps are updated with the IDs of the copied data. For example, if a Site is created from a Site template that has a Wiki app with lots of wiki articles, the wiki articles are copied from the Site template's scope to the Site's scope and the Site's Wiki app is updated with the IDs of the copied wiki articles. \noindent\hrulefill \textbf{Important:} App data, fragment-based pages, related resources, and permissions on resources are only copied from a Site template to a Site when that Site is \emph{first} created based on the template. No changes made to these entities are propagated to the Site after the Site is created. Neither are such changes propagated to a Site by the \emph{Reset} or \emph{Reset and Propagate} features. \noindent\hrulefill For example, consider a Site template administrator who includes a Message Boards app as part of a Site template. They even create Message Board categories and configures permissions over the actions of the categories. The first time a Site is created based on the Site template, the categories (app data) and related permissions are copied to the Site. If the Site template administrator adds, removes, or deletes some categories, however, such changes \emph{aren't} propagated to the Site. Now that you've learned how Site templates work, you'll learn how to share Site templates. \chapter{Sharing Site Templates}\label{sharing-site-templates} If you want to export a Site that uses Site or Page Templates to a different environment (through a LAR file or remote publication), the templates must be exported and imported manually in advance or the import fails. To export a Site using a Site Template, use the following process: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Control Panel} → \emph{Sites} → \emph{Site Templates} menu. \item Click the \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and then \emph{Manage} for the Site template you want to export. \item Open the Site Template's management section and click on \emph{Publishing} → \emph{Export}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) to create a new Custom Export. \item Select the content and pages you want to export and click \emph{Export}. \item Click on the \emph{Download} icon for the template that you exported. \item In your target environment, go to \emph{Control Panel} → \emph{Sites} → \emph{Site Templates} and create a new Site template. \item Click \emph{Actions} → \emph{Import} for that Site template and upload the LAR file containing your Site template's content. \end{enumerate} Now the template can be used normally in the new environment. For more information on exporting/importing content, visit the \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Pages and Content} article. \chapter{Configuring Sites}\label{configuring-sites} Just like there's more than one way to cook and an egg --- or eat an egg for that matter --- there's more than one way to build a site. Liferay is created with that in mind, and beyond just creating content and pages, Liferay has a wealth of configuration options and tools available to help you create the site that meets your needs and the needs of your users. In this section, you'll explore all of Liferay's options for configuring sites, and gain a deeper understanding of why you might want to use various options and configurations. \chapter{Configuring Site Settings}\label{configuring-site-settings} General settings range from core configuration, like your Site's Membership Type, to finer details like Documents and Media indexing options. \section{Details}\label{details} \emph{Details} provides the same menu you filled out when first creating your Site. This allows an administrator to change the description and membership type of a Site. \section{Membership Options}\label{membership-options} The membership type can be set as open, restricted, or private based on the privacy needs of the Site. Users can join and leave an open Site at will. To join a restricted Site, users must be added by the Site administrator, but they can request membership through the Sites section of the Control Panel. A private Site works like a restricted Site but is hidden from users who aren't members. \section{Site Hierarchies}\label{site-hierarchies-1} Sites can be organized into hierarchies. At the bottom of the Details sub-section is the Parent Site section. When you select the parent Site for the Site you're currently on, a checkbox appears for limiting membership to members of the parent Site. \section{Pages}\label{pages} Under Pages you can view your Site's Public or Private Pates, if any exist. If they don't exist, a \emph{Site Templates} selector appears for creating pages with a Site Template. \begin{figure} \centering \includegraphics{./images/selecting-site-template.png} \caption{Selecting a Site Template.} \end{figure} \section{Categorization}\label{categorization} \emph{Categorization} helps administrators organize the Site and allows for users to easily find your Site and its content through search and navigation. For more information on using tags and categories, visit the \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{Organizing Content with Tags and Categories} section. \section{Site URL}\label{site-url} The \emph{Friendly URL} option lets you set your Site's URL paths. Friendly URLs are used for both public and private pages. The public Site base URL is \texttt{https://localhost:8080/web}, and the private one is \texttt{https://localhost:8080/group}. Each friendly URL must be unique. For example, setting the friendly URL of your default Site to \texttt{/lunar-resort} makes your Site's public home page's URL \texttt{https://localhost:8080/web/lunar-resort/home}. The private Site's URL is thus \texttt{https://localhost:8080/group/lunar-resort/home}. Note that if you add a friendly URL for your instance's home page, you should update your instance's Home URL field so that page requests to \texttt{http://localhost:8080} redirect properly: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configuration} → \emph{Instance Settings} → \emph{Instance Configuration} → \emph{General} in the Control Panel. \item Under \emph{Navigation}, in the \emph{Home URL} field enter your home URL (i.e. \emph{/web/lunar-resort/home}). \end{enumerate} Once you've entered this setting, page requests to \texttt{localhost:8080} redirect to the friendly URL of your Liferay instance's new home page. You can also configure Virtual Hosts, which connects a domain name to a Site, under \emph{Site URL}. You can use this to define a domain name (i.e., \texttt{www.lunar-resort.com}) for your Site. This can be a full domain or a subdomain. You can use this to host a number of web sites as separate Sites on one Liferay server. \begin{figure} \centering \includegraphics{./images/settting-virtual-hosts.png} \caption{When configuring virtual hosts, the public and private pages of a site can be configured to different domains.} \end{figure} For instance, if you set this up for the Lunar Resort's development network, users in that Site would access \texttt{developers.lunar-resort.com}, provided that the Lunar Resort instance's network administrators created the domain name and pointed it to the Liferay server. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item With your provider, set the DNS name \emph{developers.lunar-resort.com} to point to your Liferay instance's IP address. \item In the Virtual Host tab for the Developers Site, set the URL to \emph{http://developers.lunar-resort.com} \end{enumerate} This helps users quickly access their Site without having to recall an extended URL. The \emph{Site URL} option is listed under the General tab. \section{Documents and Media}\label{documents-and-media} Below that is \emph{Documents and Media}, which lets you enable/disable Directory Indexing. If on, Site administrators can browse your Site's Documents and Media files and folders. For example, a Site administrator of a Site called \emph{Lunar Resort} can browse documents at \texttt{http://localhost:8080/documents/lunar-resort} if this option is enabled. \section{Site Template}\label{site-template} If you created your Site using a Site Template, this section appears and displays information about the link between the Site Template and the Site. Specifically, you can see which Site Template was used and whether or not it allows modifications to the pages inherited from it by Site administrators. To learn more about Site Templates and how to create your own, see \href{/docs/7-2/user/-/knowledge_base/u/building-sites-from-templates}{Building Sites from Templates}. \section{Asset Auto Tagging}\label{asset-auto-tagging} \emph{Asset Auto Tagging} lets you enable or disable the use of any Asset Auto Tagging rules on your site. See \href{link}{Asset Auto Tagging} to learn more about setting up auto tagging features. \section{Custom Fields}\label{custom-fields-1} \emph{Custom Fields} only appears if you've created them in Control Panel → \emph{Configuration} → \emph{Custom Fields}. For more information on Custom Fields, see \href{/docs/7-2/user/-/knowledge_base/u/setting-up}{Custom Fields}. \chapter{Social Settings and Languages}\label{social-settings-and-languages} The Social tab provides options for managing the social interactions on your Site. Languages lets you configure language options and change the default language options for the Site. \section{Ratings}\label{ratings} The \emph{Ratings} option lets you select the ratings type to use for applications like Documents and Media, Web Content, Comments, etc. Ratings types include Stars, Likes, and Thumbs. \section{Mentions}\label{mentions} At the bottom of the page is \emph{Mentions}, which lets you enable/disable Mentioning functionality, which is used to \emph{mention} (notify and/or draw attention to) friends and colleagues by entering the ``@'' character followed by their user names. See the \href{/docs/7-2/user/-/knowledge_base/u/mentioning-users}{Mentioning Users} article for more information. \section{Languages}\label{languages} The \emph{Languages} tab lets you configure the language options for your Site. \begin{figure} \centering \includegraphics{./images/site-language.png} \caption{In the Languages tab, you can configure the site to use the instance's default language or another supported language.} \end{figure} You can use the default language or define another supported language as the default for your Site. \chapter{Advanced Site Settings}\label{advanced-site-settings} Advanced Settings relate to security (like User Roles) or require external configuration (like creating a Google Analytics account) to use. \section{Default User Associations}\label{default-user-associations-1} \emph{Default User Associations} configures Site roles and teams that newly assigned Site members have by default. If you'd like to learn more about creating roles and/or teams, visit the \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions} and \href{/docs/7-2/user/-/knowledge_base/u/creating-teams-for-advanced-site-membership-management}{Creating Teams for Advanced Site Membership Management}. sections. \section{Analytics}\label{analytics-1} Liferay DXP includes built-in support for Google Analytics for analyzing traffic on your Site. Google Analytics provides a snippet of code which you add to your pages enable tracking. Adding this code to every page on a Site would be tedious, especially if it's a large Site with a lot of user-generated content. There are two ways to mitigate this problem: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item A web developer can hard-code the tracking code into a theme, which embeds it on every page. \item An administrator can enter the tracking code in Site settings. \end{enumerate} To use option \#2: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configuration} → \emph{Settings} → \emph{Advanced}. \item Expand the \emph{Analytics} section. \item Enter your Google Analytics ID. \item Click \emph{Save}. All the pages in the Site you selected now have the Google Analytics code and can be tracked. \end{enumerate} \begin{figure} \centering \includegraphics{./images/maintaining-google-analytics.png} \caption{To set up Google Analytics: sign up, receive an ID, and then enter it into the Google Analytics ID field.} \end{figure} To enable a different analytics service: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configuration} in the Control Panel. \item Go to \emph{Instance Settings} → \emph{Analytics}. \item Enter the name of any additional service you want to add in the \emph{Analytics} field provided. \item Once you have entered the name, go to the \emph{Site Settings} → \emph{Advanced} → \emph{Analytics} page for the Site where you wish to add analytics. \item Copy the JavaScript tracking code provided by your analytics platform into the corresponding field for your service. \end{enumerate} Now all pages on the selected Site contain the tracking script and send analytics data to your analytics platform. \section{Maps}\label{maps} The \emph{Maps} option configures the maps API provider used by your Liferay instance when displaying geolocalized assets. Geolocalized assets can be displayed for documents, web content articles, DDL records, etc. Maps is available under the Advanced tab. You can read more about Geolocation in \href{/docs/7-2/user/-/knowledge_base/u/geolocating-assets}{Geolocating Assets}. \section{Recycle Bin}\label{recycle-bin} The \emph{Recycle Bin} option enables or disables the Recycle Bin for your Site. You can also regulate the age (in minutes) for which content is able to be stored in the Recycle Bin until it is permanently deleted. For a full explanation of the Recycle Bin, see \href{/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets}{Restoring Deleted Assets}. \section{Content Sharing}\label{content-sharing} If you select the \emph{Content Sharing} tab from the Advanced tab, you can configure whether sub-Sites can display content from this Site. Administrators of this Site's sub-Sites can use all structures, templates, categories, widget templates, and more from this parent Site. Even if you initially allowed content sharing between the parent Site and its sub-Sites, you can disable this option and immediately revoke content sharing from all sub-Sites. You can manage this globally by navigating to the Control Panel → \emph{Configuration} → \emph{Instance Settings} → \emph{Content \& Data} → \emph{Sharing} → \emph{Content Sharing}. First, you can choose if Site administrators can display content in Sites from other Sites they administer. For example, suppose that a certain User is a Site administrator of two Sites: \emph{Engineering} and \emph{Marketing}. The checkbox in the Content Sharing section of Instance Settings determines if the Site administrator can display content from the Marketing Site in the Engineering Site and vice versa. You can also choose if child Sites can display content from parent Sites and configure the defaults. There are three options: \textbf{Enabled by Default}: Child Sites can display content from parent Sites by default, but this can be disabled by a Site administrator. \textbf{Disabled by Default}: Child Sites cannot display content from parent Sites by default, but this can be enabled by a Site administrator. \textbf{Disabled}: Child Sites cannot display content from parent Sites, and this behavior cannot be changed by a Site administrator. That covers your Site's advanced settings. You're now equipped to manage all aspects of your Site's configuration. \chapter{Customizing Personal Sites}\label{customizing-personal-sites} By default, newly created users are granted a personal Site. \begin{itemize} \item Users function as Site administrators of their personal Sites. \item Personal Sites are fully customizable but cannot have more than one member. \item Users can have publicly available content on their Site's Public Pages. This is often used for a user blog. \item Users can also have Private Pages where they can keep personal information or use Documents and Media to have their own private file repositories. \end{itemize} You can disable personal Sites by adding the following properties to your \texttt{portal-ext.properties} file: \begin{verbatim} layout.user.public.layouts.enabled=false layout.user.private.layouts.enabled=false \end{verbatim} \noindent\hrulefill \textbf{Note:} The public and private page sets of personal Sites are handled separately. You can leave one page set enabled while disabling the other. \noindent\hrulefill If you initially had user personal Sites enabled for your instance but then disabled them, existing personal Sites remain on your Liferay instance until the next time users log in, at which point they're removed. You can allow users to create personal Sites but not have them automatically created for new users. To do this, add the following properties to your \texttt{portal-ext.properties} file: \begin{verbatim} layout.user.public.layouts.auto.create=false layout.user.private.layouts.auto.create=false \end{verbatim} If the properties \texttt{layout.user.public.layouts.enabled}, \texttt{layout.user.private.layouts.enabled}, \texttt{layout.user.public.layouts.auto.create}, and \texttt{layout.user.private.layouts.auto.create} are all set to \texttt{true}, which is the default, users have personal Sites and public and private pages are created automatically for new users. There are a number of portal properties you can use to customize the automatically created pages. You can customize the names of the default pages, the applications that appear on the pages, the themes and layout templates of the default pages, and more. Please refer to the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Default\%20User\%20Public\%20Layouts}{Default User Public Layouts} and \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Default\%20User\%20Private\%20Layouts}{Default User Private Layouts} sections of the \texttt{portal.properties} file for details. \noindent\hrulefill \textbf{Note:} By default, users can modify the pages and applications on their personal Sites. Administrators, however, can customize the modifiable portions of personal Sites through Liferay DXP's permissions system by removing permissions from Roles. To disallow all Liferay users from modifying something, remove the relevant permission from the User Role. \noindent\hrulefill Great! Now you know how to customize a personal site! \chapter{Importing/Exporting Sites and Content}\label{importingexporting-sites-and-content} Export/Import lets you backup and restore your Site and app data as a LAR (Liferay Archive). There are two primary places Export/Import is used: Sites and apps. You can learn more about exporting/importing app data in the \href{/docs/7-2/user/-/knowledge_base/u/exporting-importing-widget-data}{Exporting/Importing Widget Content} section. In this section, you'll learn how to export and import content for Sites. \section{Backing Up and Restoring Pages and Their Content}\label{backing-up-and-restoring-pages-and-their-content} In \emph{Site Administration} → \emph{Publishing}, you can find the \emph{Export} and \emph{Import} option for pages. If you click on \emph{Export}, you see an interface for exporting your public or private pages. The Export feature exports your Site's data as a single LAR file. Similarly, \emph{Import} is a similar interface for importing public or private pages from a LAR file. When importing data into a Site, you should use a newly created Site to avoid conflicts between the existing data and the data being imported. When exporting Site data, you can specify exactly what data should be included in the LAR: \begin{itemize} \tightlist \item Site pages (you can select exactly which ones) \item Page settings \item Theme \item Theme settings \item Logo \item Application configurations \item Application content \item Archived setups \item User preferences \end{itemize} A LAR file can be imported into a Site on another Liferay server. You can take content from a Site in one environment (say, a development or QA environment) and move it all to a Site on another server with LARs. You can use LARs to import data onto production servers, but you should not make this a regular occurrence. If you want to regularly move pages from one server to another, you should use Liferay DXP's staging environment. See the \href{/docs/7-2/user/-/knowledge_base/u/staging}{Staging} section for more details. You can export LARs to use them as a backup. If you ever have to restore your Site, you must only import the latest LAR file. However, please be careful! If there's content that exists both in the LAR and in the Site that's importing the data, there may be a conflict, and data could be corrupted. If you want to restore a Liferay Site using a LAR file, delete the Site entirely, create a new Site with the same name as the old one, and then import the LAR file into the new Site. This way, there's no chance for there to be a data conflict. Some naming collisions are handled automatically. For example, a collision occurs if the LAR you're importing and the Site both have a page with the same friendly URL. Liferay DXP resolves the collision by adding a number to the end of the friendly URL and incrementing until there's no collision. Similarly, if importing a LAR into a Site causes a category name collision, the imported categories are automatically renamed. \noindent\hrulefill \textbf{Note:} LAR files are version dependent. You can't import a LAR file that was exported from one version of Liferay into a Liferay server that's running a different version of Liferay. Also, note that periodically exporting LARs is \emph{not} a complete backup solution; please refer to the \href{/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation}{Backing up a Liferay Installation} section for information on backing up Liferay. \noindent\hrulefill \section{Page Export Example}\label{page-export-example} Here's how the export process works: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go the \emph{Site Administration} → \emph{Publishing}. \item Click \emph{Export}. \item Click \emph{Add} (\includegraphics{./images/icon-add.png}). A \emph{New Custom Export} page loads, so you can choose the pages and content you want to export from your Site. \item Enter \emph{Lunar Resort Version 1} for the \emph{Title}. \item Under \emph{Pages}, select public or private pages and the settings you want to export. \item Under the \emph{Content} category, select \emph{All}. Note that if you select one of the \emph{Choose} radio selectors or \emph{Change} links, you're given checkboxes for options to choose. The applications' content can also be selected for export, including the Documents and Media Library, Message Boards, and Web Content assets. You can even export the theme you're using. Finally, you can select whether the permissions for your exported pages and content are included. \begin{figure} \centering \includegraphics{./images/export-page-templates.png} \caption{You can configure your export options manually by selecting pages, content, and permissions.} \end{figure} \item Click \emph{Export}. \end{enumerate} Once you click \emph{Export}, the menu automatically switches to the \emph{Processes} tab, where you see the status of your exported LAR file. You can select the \emph{Download} icon (\includegraphics{./images/icon-download.png}) to download the export to your local machine. Once you have the file, you can copy it to a backup location for safekeeping or import it into another installation of Liferay. If you must rebuild or wish to revert back to this version of your Site, you can import this file by clicking the \emph{Import} button from the Publishing menu, browsing to it, and selecting it. You can also drag a LAR file inside the dotted area, which also executes the import process. \section{Export Templates}\label{export-templates} Instead of manually customizing an export process every time you export pages/content, you can use an Export Template. This provides you the convenience of storing export process settings so they can be reused. If you export pages frequently and usually select the same options to export, you can create an export template to export with your standard options. To create an export template, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the \emph{Options} icon (\includegraphics{./images/icon-options.png}) from the top right corner of the screen and select \emph{Export Templates}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Assign the template a \emph{Name} and \emph{Description}. \item Fill out the configuration options for your export process. \item Click \emph{Save}. \end{enumerate} Your template is now available to use from the \emph{Export Templates} menu. To use the template, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) next to the template. \item Select \emph{Export}. This automatically fills the fields and options for exporting pages and their content. \item Give the export a name. \item Click \emph{Export} and your LAR file is generated. \end{enumerate} \chapter{Styling Apps and Assets}\label{styling-apps-and-assets} Widget Template define custom display templates used to render widgets. For example, you may want to show blog entries horizontally instead of vertically, or list your assets in the asset publisher application in different sizes. In this section, you'll learn about the capabilities of widget templates and how to create and configure them. \chapter{Styling Widgets with Widget Templates}\label{styling-widgets-with-widget-templates} Suppose you're customizing the Lunar Resort Site and want to allow users to use Facebook or Twitter to communicate with other interested travelers. You can add this functionality to an existing widget using widget templates: launch a template editor, create a custom template, and configure your app to host that template. Widget templates let you re-skin your widget and give you ultimate control over its appearance and functionality. \section{Creating a Widget Template}\label{creating-a-widget-template} Here's the process of creating a widget template: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From Site Administration, click the \emph{Site Selector} button (\includegraphics{./images/icon-compass.png}) to choose the Site where you want to create the widget template. \item Open \emph{Site Builder} → \emph{Widget Templates}. \end{enumerate} If you selected the Global context, this page shows a list of sample templates available for your apps. These sample templates differ from the default templates already configured in the apps. If you choose a Site to host your template, you must create a custom template for that Site's apps. \begin{figure} \centering \includegraphics{./images/context-selector.png} \caption{The Site Administration dropdown menu lets you choose the context in which your widget template resides.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \tightlist \item Click the \emph{Add} (\includegraphics{./images/icon-add.png}) button, and you're prompted to select the type of template to create. \end{enumerate} \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} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Enter the name and, optionally, open \emph{Details} to provide a description and a small image to use. You can select the language type for your template. \item Within \emph{Details} select a scripting language to use. You can use FreeMarker or Velocity. FreeMarker is recommended. \item Use the \emph{Script} section to create the widget template. \item Click \emph{Save} when done. \end{enumerate} \section{The Template Editor}\label{the-template-editor} On the left side of the template editor is a palette of common variables used for making templates. This is a great reference when creating your template. To place one of the variables into the template editor, position your text cursor where you want it placed, and click the variable name. Each variable also has a tooltip which displays a detailed description. Because there are multiple kinds of widget templates, there are also different variables for each widget template. Thus, each template has a different set of variables only applicable for that specific template. \begin{figure} \centering \includegraphics{./images/adt-script-editor.png} \caption{Liferay offers a versatile script editor to customize your widget template.} \end{figure} You can also use the autocomplete feature to add variables to your template. It can be invoked by typing \emph{\$\{} which opens a drop-down menu of available variables. By clicking one of the variables, the editor inserts the variable into the editor. You can also embed same-type templates into other templates. For example, suppose you have an existing Wiki widget template and would like to create another similar Wiki widget template. Instead of starting from scratch, you can import the existing Wiki widget template into your new one and build off of it. In other words, you can utilize widget templates as generic templates which allow for reusable code to be imported by Velocity or FreeMarker templates in the system. \section{Configuring Widget Templates}\label{configuring-widget-templates} After you've saved your widget template, you can manage it through its \emph{Actions} (\includegraphics{./images/icon-actions.png}) button. This provides several options: \begin{itemize} \tightlist \item \emph{Edit}: lets you modify the widget template's setup properties. \item \emph{Permissions}: lets you manage the permissions \emph{Update}, \emph{Permissions}, \emph{Delete}, and \emph{View} for the widget template. \item \emph{Copy}: creates a copy of the widget template. \item \emph{Delete}: deletes the widget template. \end{itemize} Additionally, your widget template generates a static URL and a WebDAV URL. These values access the XML source of your template. You can find these URLs by clicking the widget template from the menu and expanding the \emph{Details} section. With the WebDAV URL, Site administrators can add, browse, edit, and delete widget templates on a remote server. If you want to learn more about what the WebDAV URL can do, visit the article on \href{/docs/7-2/user/-/knowledge_base/u/desktop-access-to-documents-and-media}{WebDAV access}. \noindent\hrulefill \textbf{Note:} Embedding widgets into widget templates, although possible, is not recommended because this could cause conflicts with other widgets or unexpected behavior (e.g., embedding a widget that aggregates data to the breadcrumb). If embedding a widget into a widget template is your only option, make sure it does not interfere with other widgets. \noindent\hrulefill Next you must configure the widget to use the new widget template: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Configuration} page for the widget you want to modify and open its \emph{Display Settings}. \item Under \emph{Display Template}, select your widget template from the drop-down menu. \end{enumerate} Also, you can manage Site-specific display templates for your app: do this by clicking the \emph{Manage Display Templates for {[}SPECIFIC\_SITE{]}} link next to the \emph{Display Template} drop-down menu. A window appears with a list of your configured templates only available for your Site with options to add new templates or edit existing templates. \begin{figure} \centering \includegraphics{./images/adt-configuration.png} \caption{In the \emph{Configuration} menu of an app, you can edit and manage available widget templates.} \end{figure} \chapter{Widget Template Example}\label{widget-template-example} Now that you know the general functions of widget templates, you'll create your own. This brief demonstration will show you just how easy, yet powerful, widget templates can be for your Liferay instance. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the Media Gallery widget to a page by navigating to \emph{Add} (\includegraphics{./images/icon-add.png}) → \emph{Widgets} → \emph{Content Management} → \emph{Media Gallery}. \item Click the widgets's \emph{Add} button (\includegraphics{./images/icon-add.png}) → \emph{Multiple Files Upload} and select two custom photos to display. Then click \emph{Save}, and navigate back to the main application screen. \item Notice the default format of the pictures. To change the display template for this widget, navigate to \emph{Options} (\includegraphics{./images/icon-app-options.png}) → \emph{Configuration}. \item From the \emph{Display Template} drop-down menu, select \emph{Carousel}. Then click \emph{Save}. \begin{figure} \centering \includegraphics{./images/adt-carousel.png} \caption{After applying the Carousel widget template, your pictures are displayed as a carousel slideshow.} \end{figure} The Media Gallery application is transformed into a carousel slideshow. At this time, it's perfectly natural to be experiencing ``I can conquer the world'' feelings, just as Liferay's mascot, Ray, exudes in the image above. widget templates have that kind of power to transform your site into an enjoyable and convenient home for users. \end{enumerate} Customizing the user interface of Liferay DXP's bundled widgets provides the ultimate customization experience for Liferay users. \chapter{Setting a Default Widget Template}\label{setting-a-default-widget-template} You can change the widget template for an individual widget through its own configuration, but to configure the default widget template for all widgets of that type, you must go to \emph{System Settings}. In the System Settings you can find a configuration for every widget in Liferay DXP. Any widget that supports widget templates has a \emph{Display Style Group ID} and a \emph{Display Style} option. \begin{figure} \centering \includegraphics{./images/adt-system-settings.png} \caption{The widget template configuration in System Settings lets you change the display style.} \end{figure} \begin{itemize} \item \textbf{Display Style Group ID:} The Site ID where the widget template is located. For Global templates use \emph{0} for the ID. \item \textbf{Display Style:} The widget template's key. \end{itemize} To enter a Display Style, you first need the \emph{Template Key} for the template you want to use. To get the Template Key, go to the \emph{Application Display Template} list for a given Site and retrieve it from the widget template listing. Then enter the display style as \texttt{ddmTemplate\_{[}template-key{]}}. \section{Default Widget Template Example}\label{default-widget-template-example} For example, configure the Language Selector widget templates like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Product Menu}. \item Using the \emph{Site Selector}, select the \emph{Global} site. \item Go to \emph{Site Builder} → \emph{Widget Templates} \item Create a \emph{Language Selector Template}. \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button for the new widget template. \item Open \emph{Details} and find the \emph{Template Key} - \texttt{LANGUAGE-ICON-FTL} \end{enumerate} \begin{figure} \centering \includegraphics{./images/adt-template-key.png} \caption{System Settings shows where you can find the Template Key.} \end{figure} Now that you have the ID, you can change the template from System Settings. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item Find \emph{Localization} under the \emph{Platform} heading and select \emph{Language Selector} from the options on the left. \item In the \emph{Display Style} field, enter \texttt{ddmTemplate\_LANGUAGE-ICON-FTL}. \end{enumerate} Now any Language Selector widgets are added to a page use the new defaults. This doesn't affect widgets already added to a page and configured. \begin{figure} \centering \includegraphics{./images/adt-new-default.png} \caption{You can see the new default configuration.} \end{figure} \chapter{Customizing Page Options}\label{customizing-page-options} Every page has options to give it a unique configuration. You can handle that configuration at the individual page level or configure a Page Set of public or private pages all at once. When you configure options across Sites, Page Sets, and individual pages, there is a hierarchy that flows down. For example, you can set a theme at the Site level that applies to all pages within a Site. You could then set a different theme for the Private Pages set. You can even set a different theme for a specific page that is different than the master site configuration or the configuration for its page set. When configuring pages, be aware of your context, so you don't configure an option that you intended for the whole site just on one page---or change the whole site's configuration, when you meant to change it for only a specific page. \chapter{Configuring Page Sets}\label{configuring-page-sets} To configure options for the entire Page Set, select the \emph{Configure} icon next to the Page Set in \emph{Pages}. Options configured for the Page Set apply to all its pages. Page Set options override options set at the Site level, and customizations to an individual page override those for the Page Set. \begin{figure} \centering \includegraphics{./images/configure-page-set.png} \caption{Selecting the Page Set configuration option.} \end{figure} \chapter{Configuring Page Sets}\label{configuring-page-sets-1} Page Set configuration starts with the \emph{Look and Feel} tab. Here you have an interface for choosing a theme for the current Site. \begin{figure} \centering \includegraphics{./images/page-set-look-and-feel.png} \caption{The Look and Feel page set tab.} \end{figure} \section{Themes}\label{themes} Themes can transform the entire look of your Site. They are created by developers and are easily installed using Liferay Marketplace. \begin{figure} \centering \includegraphics{./images/look-and-feel-pages.png} \caption{The Look and Feel interface allows you to choose a theme for the current site.} \end{figure} You can apply themes to the entire Site or to individual pages. For the Site, go to \emph{Pages} → the Site (public or private), and click the Gear icon. For individual pages, click \emph{Configure} → \emph{Define a specific look and feel for this page} option under the page's \emph{Look and Feel} category. See the \href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{Themes} section for information on creating and developing your own custom themes. \begin{figure} \centering \includegraphics{./images/define-a-specific-look-and-feel.png} \caption{You can define a specific look and feel for a page.} \end{figure} Many themes include more than one color scheme, which keeps the existing look and feel while giving the Site a different flavor. The Color Schemes option is not available for the default theme. There are a few more configurable settings for your Page Set look and feel. You can switch the bullet style between dots and arrows and you can choose whether or not to show maximize/minimize application links by default. The \emph{CSS} section lets you enter custom CSS for tweaking your theme. \section{Using a Custom Logo for a Site}\label{using-a-custom-logo-for-a-site} By default, the Liferay logo is used for your Site pages' logo. If you want to use your own logo for a specific Site, select the \emph{Logo} tab from the \emph{Configure} interface and browse to the location of your logo. Make sure your logo fits the space in the top left corner of the theme you're using for your website. If you don't, you could wind up with a Site that's difficult to navigate, as other page elements are pushed aside to make way for the logo. In the logo tab, you can also choose whether or not to display the Site name on the Site. If you check the box labeled \emph{Show Site Name}, the Site name appears next to the logo. This option is enabled by default and cannot be disabled if the \emph{Allow Site Administrators to set their own logo} option is disabled in \emph{Instance Settings}. Removing the Site name is not available for the default Site---you can configure this only for new Sites and user pages. \chapter{Advanced Page Set Options}\label{advanced-page-set-options} There are some powerful options that should only be used by those with a firm command of the technology, or they could have major unintended side effects. You can find these options under the \emph{Advanced} tab. \section{Executing JavaScript in Site Pages}\label{executing-javascript-in-site-pages} At the top of the \emph{Advanced} tab is a JavaScript editor. Code entered here is executed at the bottom of every page in the Site. If your Site's theme uses JavaScript (as is usually the case), it's best to add custom JavaScript code to the theme and \emph{not} here. This way, all your Site's JavaScript code remains in one place. This may be useful if your Site's theme does \emph{not} use JavaScript. In this case, you can place \emph{all} of your Site's JavaScript here. \section{Merge Public Pages}\label{merge-public-pages} If you have more than one Site on a specific Liferay instance, one of those Sites will be the \emph{Default Site} where visitors will be directed if they access the instance's root URL. By default, visitors will only see the pages of that Site in the navigation. To have another Site's public pages appear in the primary navigation for the Default Site, check the box to \emph{Merge public pages} for that site. Be careful as adding too many pages to the main navigation can make it become unwieldy very quickly. \section{Rendering Pages for Mobile Devices}\label{rendering-pages-for-mobile-devices} \emph{Mobile Device Rules} lets you configure your page set to have specific behaviors for specific mobile devices or types. Mobile device rules are inherited from your Public Pages, but you can define specific rules per page. You can edit the Look and Feel of specific pages for mobile devices, including the theme. This is explained in \href{/docs/7-2/user/-/knowledge_base/u/mobile-device-rules}{Mobile Device Rules}. \section{Robots}\label{robots} The \emph{Robots} option lets you configure \texttt{robots.txt} rules for the domain: both its public and private pages. The \texttt{robots.txt} file provides instructions to search engines and other tools that are automatically crawling and indexing your Site. Common entries here include defining some pages not to be indexed. \section{Notifying Search Engines of Site Pages}\label{notifying-search-engines-of-site-pages} The \emph{Sitemap} option generates a sitemap you can send to some search engines so they can crawl your Site. It uses the industry standard sitemap protocol. Select a search engine link to send the sitemap to it. It's only necessary to do this once per Site. If you're interested in seeing what is sent to the search engines, select the \emph{preview} link to see the generated XML. \chapter{Configuring Individual Pages}\label{configuring-individual-pages} After you've configured your Page Set, you can reconfigure some options at the individual page level. When you decide to customize a single page, options that were not available when initially creating a page appear. You can customize a page by navigating to \emph{Pages} under \emph{Build} menu and selecting \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Configure} next to the page you want to edit from the navigation tree. Alternatively, you can click the \emph{Configure} icon on the top right of any page. \chapter{Individual Page Settings}\label{individual-page-settings} On the Configure page are four tabs: General, SEO, Look and Feel, and Advanced. Options selected here have no effect on the rest of the Site; just the page you've selected. Many of these options are the same as those that configure the complete page set, so you can view more details in the \href{/docs/7-2/user/-/knowledge_base/u/configuring-page-sets}{Configuring Page Sets} article. Note that many of the options are localizable, so you can provide translations based on the user's locale. \section{General}\label{general-2} The \emph{General} tab lets you configure the basic information and design for the page. You can change the \emph{Name}, \emph{Friendly URL}, and \emph{Page Layout}. \section{Name and Friendly URL}\label{name-and-friendly-url} The \emph{Name} is the title that appears in the browser's title bar, and how the page is identified in the navigation. The \emph{Friendly URL} defines the page's link. It is a best practice to have the URL match the name of the Page, so these two should generally be updated together. \section{Page Layout}\label{page-layout} For Widget Pages, you can select a Layout Template that defines droppable locations for widgets. Layout templates define a number of sections with columns and rows. Widgets added to a section expand (or contract) horizontally to fill the space and can be stacked vertically. \begin{figure} \centering \includegraphics{./images/page-select-layout.png} \caption{Setting a layout template for your page.} \end{figure} \section{Categorization and SEO}\label{categorization-and-seo} Managing your page's content drastically improves your page's organization and user experience. The Site page's configuration options offers some opportunities to organize page content. \section{Categorization}\label{categorization-1} The \emph{Categorization} tab shows the categorization options. These tools help administrators organize the page so users can find your page and its content through search and navigation. For more information on using tags and categories, see \href{/docs/7-1/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{Organizing Content with Tags and Categories}. \section{SEO}\label{seo} \emph{SEO} provides several ways to optimize the data the page provides to an indexer that's crawling the page. You can set the various meta tags for description, keywords and robots. There's also a separate Robots section for telling robots how frequently the page is updated and how it should be prioritized. If the page is localized, you can select a box to generate canonical links by language. If you want to set some of these settings for the entire Site, you can specify them from the Sitemaps and Robots tabs of the Manage Site Settings dialog box (see below). Each asset (web content article, blog entry, etc.) has a unique URL. From the search engine's point of view, this makes your pages rank higher since any references to variations of a specific URL are considered references to the same page. You can also configure the page to use a custom canonical URL. To do so, set the \emph{Use Custom Canonical URL} toggle to \emph{YES}, then enter your desired canonical URL in the field that appears. You can define a custom canonical URL for each language. If there's no value for a specific language, the canonical URL for that language is controlled by the global/instance-level setting. \begin{figure} \centering \includegraphics{./images/canonical-url-page.png} \caption{Enter the custom canonical URL that you want to use for the page.} \end{figure} You can also configure canonical URLs at the global and instance levels. \noindent\hrulefill \textbf{Note:} Any custom canonical URLs set for individual pages take precedent over the global and instance level settings. \noindent\hrulefill \textbf{Global:} \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Pages} → \emph{SEO} \textbf{Instance:} \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Pages} → \emph{SEO} Navigate to the level (global/instance) on which you want to configure canonical URLs, then choose one of the following in the \emph{Canonical URL} menu: \textbf{Use Default Language URL (default):} When a user visits a page in any supported language, the default language's URL is used as the canonical URL. \textbf{Use Localized URL:} The page's localized URL is used as the canonical URL. \begin{figure} \centering \includegraphics{./images/canonical-url-system.png} \caption{You can also configure canonical URLs at the global and instance levels.} \end{figure} \section{Look and Feel}\label{look-and-feel-1} \emph{Look and Feel} lets you set a page-specific theme. You can inherit what you already have configured for your Page Set's theme, or you can define a theme per page. See \href{/docs/7-2/user/-/knowledge_base/u/page-set-look-and-feel}{Customizing the Look and Feel of Site Pages} for more details. \section{Advanced Settings}\label{advanced-settings} \emph{Advanced Settings} contains options useful for specific cases. Some of these are the same as the options available at the Site or Page Set level, but \emph{Custom Fields}, \emph{Embedded Widgets}, and \emph{Customization Settings} are unique to the individual page configuration. \section{Query String}\label{query-string} You can set a query string to provide parameters to the page. This can become useful to web content templates. You can set a target for the page so that it pops up in a particularly named window or appears in a frameset. And you can set an icon for the page that appears in the navigation menu. \section{Custom Fields}\label{custom-fields-2} \emph{Custom Fields} lets you edit the custom fields you already have configured for the \emph{Page} resource. If you don't have any custom fields configured in your Site, this option doesn't appear. In this case, navigate to the Control Panel → \emph{Custom Fields} located under the \emph{Configuration} tab. These are metadata about the page and can be anything you like, such as author or creation date. For more information on Custom Fields, see \href{/docs/7-1/user/-/knowledge_base/u/setting-up}{Custom Fields}. \section{Embedded Widgets}\label{embedded-widgets} This option only appears if you have embedded one or more widgets on the page. Widgets can be embedded on a page via web content template or fragment. To learn more about this, see \href{/docs/7-2/user/-/knowledge_base/u/adding-templates}{Adding Templates}. You can embed a widget on a page layout or theme programmatically. If you're interested in learning more about this, visit the \href{develop/tutorials/-/knowledge_base/7-2/embedding-portlets-in-themes}{Embedding Portlets in Themes} tutorial. \section{Customization Settings}\label{customization-settings} This configuration option in the \emph{Advanced} tab lets you mark specific sections of the page you want users to be able to customize. You can learn more about page customizations in \href{/docs/7-2/user/-/knowledge_base/u/personalizing-pages}{Personalizing Pages}. \section{JavaScript}\label{javascript} This shows a JavaScript editor for code that's executed at the bottom of your page. If your Site's theme uses JavaScript (as is usually the case), it's best to add custom JavaScript code to the theme instead. This way, all your Site's JavaScript code remains in one place. This configuration option is also available for Page Sets like Public Pages and Private Pages. Visit \href{/docs/7-2/user/-/knowledge_base/u/advanced-page-set-options\#executing-javascript-in-site-pages}{Executing JavaScript in Site Pages} for more information on doing this for Page Sets. \section{Mobile Device Rules}\label{mobile-device-rules-1} Apply rules for how this page should render for various mobile devices here. Create them by navigating to Site Administration menu and selecting \emph{Configuration} → \emph{Mobile Device Families}. \chapter{Personalizing Pages}\label{personalizing-pages} Administrators can designate pages or sections of Widget Pages as customizable. When a user visits such a page, a notification appears stating that the user can customize the page. Users can make customizations only in the sections of pages designated by administrators. Customizations are based on the rows and columns of a page layout. Page customizations are only visible to the user who made the customizations. By default, Site members can make page customizations but non-Site members and guests can't. \section{Enabling Page Customizations}\label{enabling-page-customizations} To enable page customizations as an administrator, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Configure Page} from the \emph{Options} button next to the Page you want to let Site members modify. \item Select the \emph{Advanced} tab at the top of the page and expand the \emph{Customization Settings} area. \item Click the \emph{Customizable} selector button to activate customizations. \begin{figure} \centering \includegraphics{./images/page-customizations.png} \caption{To enable page customizations, click on the \emph{Configure Page} button next to the page, expand the \emph{Customization Settings} area, and click on the \emph{Customizable} button.} \end{figure} \item Select the sections of the page that should be customizable. \item Enable one or more of the \emph{Customizable} sections so Site members can customize sections of the page. Regions that you've designated as customizable are colored blue. \end{enumerate} When Site members visit your customizable page, they see an extended Control Menu with a notification saying \emph{You can customize this page}. Site members can toggle whether to view or hide the customizable regions. If you toggle the selector to view customizable regions, the regions on the page are color-coded to help distinguish customizable vs.~non-customizable sections of the page. \begin{figure} \centering \includegraphics{./images/color-coded-customizable-regions.png} \caption{Customizable regions are colored green and non-customizable regions are colored red.} \end{figure} \section{Customization Permissions}\label{customization-permissions} Administrators must grant users permission to customize pages under the Site section. This can be achieved by assigning permission to a Role, then assigning this Role to the appropriate users. For example, if you want users to be able to customize your customizable pages, assign the \emph{Customize} permission to the Role \emph{User}. If you want Site members to be able to customize their Sites' customizable pages, accept the default setting. By default, the \emph{Customize} permission is assigned to the Role \emph{Site Member}. The \emph{Customize} permission also lets users customize the look and feel of apps and import or export app settings. \section{Customizing Pages}\label{customizing-pages} With customization active, Site members can access the Add menu from the top right side of the screen when viewing their customizable page, which lets them add apps to the customizable sections of the page. If they click \emph{View Page without my customizations}, the Add menu disappears. Users can make two kinds of customizations to customizable regions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item They can configure applications within the customizable regions. \item They can add apps to or remove apps from the customizable regions. \end{enumerate} \emph{Reset My Customizations} from the \emph{Options} button restores a user's customized page to match the default page, discarding their customizations so they can start anew. \begin{figure} \centering \includegraphics{./images/customizable-regions.png} \caption{Customizable areas are highlighted green when organizing apps on the page.} \end{figure} Users can't change a non-instanceable app's configuration inside a customizable region since those apps are tied to the Site where they've been added. \section{Viewing Customized Pages}\label{viewing-customized-pages} Site members can also choose between viewing their customized page and viewing the default page by selecting the \emph{Options} button (\includegraphics{./images/icon-options.png}) from the Control Menu and clicking the \emph{View Page without my customizations} or \emph{View My Customized Page}. Administrators of customizable pages have the same two views as Site members: the \emph{default page} view and the \emph{customized page} view. Changes made to the default page affect all users, whereas changes made to the customized page affect only the administrator who made the changes. Changes made by administrators to non-customizable sections in the default view are immediately applied for all users. Changes made by administrators to customizable sections, however, do \emph{not} overwrite users' customizations. \section{Customization Example}\label{customization-example} As an administrator, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configure} for the \emph{Welcome} page. \item Go to the \emph{Advanced} tab and activate Customizations. \item Set the main column of the Welcome page of the Lunar Resort Site to be customizable. \end{enumerate} As a regular user, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Welcome} page. \item Click \emph{Add} → \emph{Widgets}. \item Locate the \emph{Language Selector} widget and add it to the page. \end{enumerate} The Language Selector application lets users select their language to view a translation of your Site into their native language. After closing the Configuration dialog box of the Language Selector app, the customized Welcome page looks like this: \begin{figure} \centering \includegraphics{./images/customized-portal-homepage.png} \caption{In this example, the user added the Language app, and changed the display style from icons to a select box.} \end{figure} \chapter{Changing Page Permissions}\label{changing-page-permissions} Public pages are just that: public. They can be viewed by anybody, logged in or not. And private pages are only private from non-members of the Site. If someone has joined your Site or is a member of your organization, that person can see all the private pages. If you want to further protect some content, you can modify the permissions on individual pages in either page group so only certain users can view them. Here's how to create a page only administrators can see: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to your Site's Site Administration dropdown and select \emph{Site Builder} → \emph{Pages} → \emph{Private Pages}. \item Create a page called \emph{Admin Tips}. \item Click \emph{Configure} from the Options button dropdown for the page in the left menu. \item Select \emph{Permissions} from the \emph{Options} icon (\includegraphics{./images/icon-options.png}) in the top right corner of the screen. \item Uncheck the \emph{View} and \emph{Add Discussion} permissions next to the Site Member role. \item Click the \emph{Save} button. \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-page-permissions.png} \caption{The Permissions offer a plethora of options for each role.} \end{figure} Congratulations! You've changed the permissions for this page so only Site administrators can view it. Any users you add to this Role can now see the page. Other users, even members of this Site, don't have permission to see it. Pages are as flexible as pages you'd create manually without Liferay. Using a point and click interface, you can define your Site any way you want. You can create and remove pages, export and import them, set their layouts, define how they are indexed by search engines, and more. \chapter{Configuring Widgets}\label{configuring-widgets} Just like siblings have common features inherited from their parents, widgets that ship with Liferay DXP also share common features. These include look and feel, exporting/importing app data, communication, sharing, permissions, scoping, and configuration templates. These features work together to facilitate information flow within Liferay DXP and provide an enhanced experience for your users. You'll start with look and feel configuration options. \chapter{Look and Feel Configuration}\label{look-and-feel-configuration} To access the look and feel configuration menu of any widget, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Options} (\includegraphics{./images/icon-app-options.png}) in the top right corner of the widget. \item Select \emph{Look and Feel Configuration}. \end{enumerate} \emph{Look and Feel Configuration} has six tabs: \begin{itemize} \tightlist \item General \item Text Styles \item Background Styles \item Border Styles \item Margin and Padding \item Advanced Styling \end{itemize} After making customizations, click \emph{Save} and refresh your page to apply your changes. If you don't like the effect of your changes, some tabs have a \emph{Reset} button to discard changes. \section{General Settings}\label{general-settings} On the General tab are the following options: \textbf{Use Custom Title} enables changes to your widget's title. The value in the title box is displayed on widget's decorator. The title is localizable, so you can provide translations of the title for different languages. \textbf{Application Decorators} gives you the choice between three decorators: \emph{Barebone}, \emph{Borderless}, and \emph{Decorate}. The Decorate application decorator is the default. Be careful about turning widget borders off; some themes assume widget borders are turned on and may not display correctly with them turned off. \begin{figure} \centering \includegraphics{./images/look-and-feel-portlet-configuration-menu.png} \caption{The General tab of the Look and Feel Configuration menu lets you define a custom widget title and select the widget contrast option using decorators.} \end{figure} \section{Text Styles}\label{text-styles} \emph{Text Styles} configures the format of the text that appears in the widget. The options include \textbf{Font:} Choose various fonts. You can set the text to bold, italics, or both. \textbf{Size:} Set the font size anywhere from 0.1 em to 12 em, with 0.1 em increments. 1 em is the default. \textbf{Color:} Set to any six digit hex color code. Click on the text box to open the color palette. \textbf{Alignment:} Set to \emph{Left}, \emph{Center}, \emph{Right}, or \emph{Justified}. \textbf{Text Decoration:} Set to \emph{Underline}, \emph{Overline}, or \emph{Strikethrough}. The default text decoration is \emph{None}. \begin{figure} \centering \includegraphics{./images/look-and-feel-text-styles.png} \caption{The Text Styles tab lets you configure the format of the text that appears in the widget.} \end{figure} \textbf{Word Spacing:} Set from -1 em to 0.95 em, with 0.05 em increments. 0 em is the default. \textbf{Line Spacing:} Set from 0 em to 12 em, with 0.1 em increments. 0 em is the default. \textbf{Letter Spacing:} Set from -10 px to 50 px, with 1 px increments. 0 px is the default. \section{Background Styles}\label{background-styles} The Background Styles tab specifies the widget's background color. When you select the text space, you're given a color palette to choose your background color or you can manually enter any six digit hex color code. \begin{figure} \centering \includegraphics{./images/look-and-feel-background-styles.png} \caption{The Background Styles tab lets you specify the widget's background color.} \end{figure} \section{Border Styles}\label{border-styles} The Border Styles tab, configures your widget's border width, style, and color. For each of these attributes, leave the \emph{Same for All} selector enabled to apply the same settings to top, right, bottom, and left borders. \begin{figure} \centering \includegraphics{./images/look-and-feel-border-styles.png} \caption{The Border Styles tab lets you specify a border width, style, and color for each side of the widget.} \end{figure} For border width, you can specify any \% value, em value, or px value. For border style, you can select Dashed, Double, Dotted, Groove, Hidden, Inset, Outset, Ridge, or Solid. For border color, you can enter any six digit hex color code, just like for the text color and background color. You can also use the color palette. \section{Margin and Padding}\label{margin-and-padding} The Margin and Padding tab specifies margin and padding lengths for the edges of your widget. Just like for border styles, leave the \emph{Same for All} selector enabled to apply the same settings to each side (top, right, bottom, and left) of the widget. \begin{figure} \centering \includegraphics{./images/look-and-feel-margin-and-padding.png} \caption{The Margin and Padding tab allows you to specify margin and padding lengths for the sides of your widget.} \end{figure} For both padding and margin, you can specify any \% value, em value, or px value. \section{Advanced Styling}\label{advanced-styling} The Advanced Styling tab displays current information about your widget, including your widget's Liferay ID and CSS classes. \begin{figure} \centering \includegraphics{./images/look-and-feel-advanced-styling.png} \caption{The Advanced Styling tab displays your widget's Liferay ID and allows you to enter CSS code to customize the look and feel of your widget.} \end{figure} You can also enter custom CSS class names for your widget and custom CSS code. Clicking the \emph{Add a CSS rule for just this portlet} or \emph{Add a CSS rule for all portlets like this one} links adds the CSS code shells into your custom CSS text box. If you check the \emph{Update my styles as I type} box, your CSS code is applied dynamically to your widget so you can see the effects of your edits. Next, you'll learn about communication between widgets. \chapter{Exporting/Importing Widget Data}\label{exportingimporting-widget-data} You may need to export data from a specific widget instance, without regard to content on the rest of the Site. There are many widgets that let you export or import their data individually: \begin{itemize} \tightlist \item Blogs \item Bookmarks \item Dynamic Data Lists \item Forms \item Knowledge Base \item Message Boards \item Web Content \item Wiki \item And more \end{itemize} Exporting widget data produces a \texttt{.lar} file that you can save and import into another widget of the same type. To import widget data, you must select a \texttt{.lar} file. Be careful not to confuse widget-specific \texttt{.lar} files with Site-specific \texttt{.lar} files. See the \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Pages and Content} article for information on importing/exporting Site page data. There are two ways to export/import widget content. You can navigate to the widget's administrative area located in the Product Menu, or you can visit the widget on its page. Both export/import menus work the same, but the administrative area may hold content different from its widget counterpart (e.g., Web Content Admin in Product Menu and Web Content Display widget do not offer same content for export/import), so be wary of your selection. To export or import data from the widget's administrative area, follow the steps below. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the widget's designated area in the Product Menu. For example, if you plan to export Web Content data, navigate to \emph{Content} → \emph{Web Content}. \item Click the \emph{Options} button (\includegraphics{./images/icon-options.png}) from the top right of the page and select \emph{Export/Import}. \item Select the \emph{Export} or \emph{Import} tab to begin configuring the respective process. \end{enumerate} \begin{figure} \centering \includegraphics{./images/admin-app-export-import-feature.png} \caption{You can access a widget's administrative \emph{Export/Import} feature by selecting its Options menu.} \end{figure} To export or import data from a widget, follow the steps below: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Ensure the widget you're exporting/importing from is available on a page. You can add widgets from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} menu. \item Select the widget's \emph{Options} button (\includegraphics{./images/icon-app-options.png}) and select \emph{Export/Import}. \item Select the \emph{Export} or \emph{Import} tab to begin configuring the respective process. \end{enumerate} \begin{figure} \centering \includegraphics{./images/widget-export-import-feature.png} \caption{You can access a widget's \emph{Export/Import} feature by selecting its Options menu.} \end{figure} Now that you know how to navigate to the \emph{Export/Import} menus, you can explore the export process. \section{Exporting Widget Data}\label{exporting-widget-data} To export widget data, create a new export process by selecting the \emph{New Export Process} tab (default). You have several export options to configure. First, you can choose to export your widget's configuration settings. This exports your customized settings from your widget's \emph{Options} → \emph{Configuration} menu. For some widgets, the configuration export might also include content. For example, a Web Content Display widget that shows a web content article also exports the article when exported, even though no content is selected. This applies when publishing a Web Content Display widget too; the configured article is published with the widget. Next, you can select a \emph{Date Range} of content that you want to export. Content added to your widget within your specified date range is included in the \texttt{.lar} file. The following date range choices are available: \textbf{All:} Publishes all content regardless of its creation or last modification date. \textbf{Date Range:} Publishes content based on a specified date range. You can set a start and end date/time window. The content created or modified within that window of time is published. \textbf{Last\ldots:} Publishes content based on a set amount of time since the current time. For example, you can set the date range to the past 48 hours, starting from the current time. By checking the \emph{Content} box, you can choose specific content you want to export. When you check the \emph{Content} box, more options appear, letting you choose specific kinds of metadata to include. For example, if you have a wiki page with referenced content that you don't want, check the \emph{Wiki Pages} checkbox and uncheck the \emph{Referenced Content} checkbox. Another option is the selection of content types. Two familiar content types in your Liferay instance are \emph{Comments} and \emph{Ratings}. If you want to include these entities in your \texttt{.lar} file, select \emph{Change} and select them from the checklist. For more information on managing content types, see the \href{/docs/7-2/user/-/knowledge_base/u/managing-content-types-in-staging}{Managing Content Types in Staging} article. Next, you can choose to export individual deletions. This lets delete operations performed for content types be exported to the LAR file. Finally, you can choose whether to include permissions for your exported content. The permissions assigned for the exported widget window are included if you enable the \emph{Export Permissions} selector. After you've exported your widget's data, switch to the \emph{Current and Previous} tab to view ongoing export processes and the history of past exports. You can also download the exported \texttt{.lar} file from this tab. \section{Importing Widget Data}\label{importing-widget-data} To import widget data, you can select the LAR using your file explorer or by dragging and dropping the file between the dotted lines. \begin{figure} \centering \includegraphics{./images/import-menu.png} \caption{When importing widget data, you can choose a LAR file using the file explorer or drag and drop the file between the dotted lines.} \end{figure} Your LAR file is uploaded and displayed to you for review. Click \emph{Continue}. Now that you've uploaded and confirmed your LAR file, you're given a similar screen to what you'd be offered during export. Several of these options are covered in great detail in the \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Pages and Content} tutorial. There are some additional options available: \emph{Update Data} and \emph{Authorship of the Content}. Here's options and descriptions for each section: \textbf{Update Data} \textbf{Mirror:} All data and content inside the imported LAR is newly created the first time while maintaining a reference to the source. Subsequent imports from the same source updates entries instead of creating new entries. \textbf{Mirror with overwriting:} Same as the mirror strategy, but if a document or an image with the same name is found, it is overwritten. \textbf{Copy as New:} All data and content inside the imported LAR is created as new entries within the current Site every time the LAR is imported. \textbf{Authorship of the Content} \textbf{Use the Original Author:} Keep authorship of imported content whenever possible. Use the current user as author if the original one is not found. \textbf{Use the Current User as Author:} Assign the current user as the author of all imported content. Once you've selected the appropriate options, select \emph{Import} and your widget's data is imported and ready for use. \chapter{Communication Between Portlet Widgets}\label{communication-between-portlet-widgets} Portlet widgets can communicate with each other using public render parameters and events. Public render parameters are easy to use and can be quite powerful. Some Liferay portlets provide a configuration UI to help you get the most out of this communication mechanism. To access this UI, open your portlet's configuration window by clicking on the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and selecting \emph{Configuration}. Then click on the \emph{Communication} tab. \begin{figure} \centering \includegraphics{./images/app-communication-tab.png} \caption{You can configure portlets to communicate with each other using public render parameters.} \end{figure} \noindent\hrulefill \textbf{Note:} If your widget isn't a portlet, this feature isn't available. \noindent\hrulefill The screenshot above is for the Wiki, which has six public render parameters: \texttt{categoryId}, \texttt{nodeId}, \texttt{nodeName}, \texttt{resetCur}, \texttt{tag}, and \texttt{title}. For each of these parameters, you can configure the portlet to ignore the values coming from other portlets or read the value from another parameter. Why might it be useful to ignore the values for certain parameters that come from other portlets? Consider a common use case for the Wiki application. The Wiki portlet is often used along with the Tags Navigation portlet so that when a user clicks on a tag of the latter, the Wiki shows a list of pages with that tag. An administrator may want the Wiki to show the front page always independently of any tag navigation done through other portlets. Ignoring the values of the parameter coming from other widgets let this happen. Reading the value of a parameter from another portlet is an advanced but very powerful option that allows portlets to communicate with each other even if their developers didn't intend them to. For example, imagine that the Wiki is used to publish information about certain countries, and there's another portlet that allows browsing countries for administrative reasons. The second portlet has a public render parameter called \emph{country} with the name of the country. You'd like the Wiki to show the information from the country that's selected in the administration portlet. This can be achieved by setting the value of the title parameter of the Wiki portlet to be read from the country parameter of the administration portlet. Cool, isn't it? \chapter{Sharing Widgets with Other Sites}\label{sharing-widgets-with-other-sites} You can share widgets with other Sites by embedding an instance of a widget running on your Site into another website, such as Facebook. This opens up a whole new avenue of exposure to your web site that you would not have had otherwise. In fact, this is how all those Facebook games work. \begin{figure} \centering \includegraphics{./images/collaboration-app-configuration-sharing.png} \caption{The Sharing tab in your widget's Configuration menu lets you share your widget in a variety of ways.} \end{figure} To share one of your widgets, open the \emph{Configuration} dialog box from the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select the \emph{Sharing} tab. There are five sub-tabs under sharing: Any Website, Facebook, OpenSocial Gadget, and Netvibes. \section{Any Web Site}\label{any-web-site} Copy and paste the provided snippet of JavaScript code into the web site where you want to add the widget. That's all you need to do. When a user loads the page on the other website, the code pulls the relevant widget from your Site and displays it. \section{Facebook}\label{facebook} You can add any widget as a Facebook app. To do this, you must first get a developer key. A link for doing this is provided to you in the Facebook tab. You must create the application on Facebook and get the API key and canvas page URL from Facebook. Once you've done this, you can copy and paste their values into the Facebook tab. Save the configuration and navigate back to the Facebook tab in Liferay DXP. You're given the Callback URL, which you can copy and paste into Facebook. When opening your app in Facebook, the correct callback URL is used to render the application. You can also enable the \emph{Allow users to add {[}application-name{]} to Facebook}. Then you can navigate to your app's Options menu and select \emph{Add to Facebook}. \section{OpenSocial Gadget}\label{opensocial-gadget} OpenSocial comprises a container and a set of APIs for social networking and other web applications. Liferay DXP can serve up applications to be used as OpenSocial Gadgets on any OpenSocial-compatible pages. To serve a Liferay widget on an OpenSocial platform, copy and paste the provided gadget URL and add it to the appropriate configuration page of the OpenSocial platform you're using. Your Liferay instance serves that widget directly onto that platform's page. The URL provided is unique to the specific instance of the widget, so you could serve multiple instances of the same widget as different OpenSocial Gadgets. From the Sharing tab in the Configuration menu, you can also enable the selector \emph{Allow users to add {[}application-name{]} to an OpenSocial platform}. Click \emph{Save} and revisit the \emph{Options} button of your widget. A new button appears named \emph{Add to an OpenSocial Platform}. When selecting this new button, the URL is provided for sharing the widget to an OpenSocial platform. \section{Netvibes}\label{netvibes} Netvibes offers a similar environment where users can log in, create their own personal dashboard, and add customizable widgets to it. To set up Netvibes support for a widget, enable the \emph{Allow users to add {[}application-name{]} to Netvibes pages} selector. You can then use the provided URL to create a custom Netvibes widget based on the instance of the Liferay widget that you're using. Next, you'll learn how to set permissions for Liferay applications. \chapter{Widget Permissions}\label{widget-permissions} All of Liferay's widgets support Liferay DXP's robust, fine-grained permissions system. Some higher level permissions can be configured in the permissions tab of the widget's configuration dialog box. You can grant Roles permission to \begin{itemize} \tightlist \item Add a display template \item Add the widget to a page \item Configure the app \item Modify the widget's permissions \item Modify the widget's preferences \item View the widget \end{itemize} \begin{figure} \centering \includegraphics{./images/widget-permissions.png} \caption{Viewing the permissions configuration for a widget.} \end{figure} To set these permissions, go to the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and click select \emph{Permissions}. This shows you a table of Roles. Use the check boxes to grant certain permissions to different Roles. Click \emph{Save} after you've made your selections. Beyond this, specific permissions are generally defined for specific widgets. For example, Message Boards contains a \emph{Ban User} permission. This makes no sense in the context of most other widgets. You'll go over permissions for specific widgets in the sections for those widgets. Next, you'll learn about widget scopes. \chapter{Widget Scope}\label{widget-scope} As you learned earlier, Roles can be scoped by the instance, by Site, or by an Organization. A Role only takes effect within its scope. For example, a Message Boards Administrator Role with complete access to the Message Boards has different permissions based on the Role's scope. If it's a global Role, members have permission to administer message boards across the entire installation. If it's a Site Role, members only have permission to administer message boards within the Site where they've been assigned the Role. For Organizations with Sites, Site Roles are automatically assigned to Organization members based on the Organization Roles they have. For an Organization-scoped Message Boards administrator Role, members only have permission to administer message boards within the Site of the Organization that assigned the Role to them. You've also heard the word \emph{scope} refer to the data set of a widget. By default, when a widget is added to a page in a Site, it is \emph{scoped} for that Site. This means its data belongs to that Site. If the widget is added to a page in a different Site, it employs a completely different data set. This enables you to place a Message Boards widget in one Site with one set of categories and threads, and place another Message Boards widget in different Site with a different set of categories and threads. Scoping by Site means that you can only have one Message Boards widget per Site. If you add one Message Boards widget to a page in a Site and add another Message Boards widget to a different page in the same Site, the second Message Boards widget contains exactly the same data as the first. This is because, by default, the Message Boards widget is scoped by Site. Most of Liferay DXP's other widgets also default to being scoped by Site. To avoid this limitation, many Liferay widgets can be scoped by page. The data sets of page-scoped widget serve a single page, not an entire Site. If you set the scope of a widget to \emph{page} instead of \emph{Site}, you can add any number of these widgets to different pages, and then they have different sets of data. Then you can have more than one message board per Site if you wish. Most widgets, however, default to the ``native'' configuration, and have their scopes set to the Site where they are placed. Unless otherwise noted, all widgets support scoping by instance (global), Site (default), or page. This grants you some flexibility in how you want to set up your Liferay instance. You can configure the scope of a widget's content with just a few simple steps. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}). \item Select \emph{Configuration}. \item Select the \emph{Scope} tab. \item Use the drop-down menu to set the scope. \end{enumerate} \begin{figure} \centering \includegraphics{./images/changing-widget-scope.png} \caption{You can change the scope of your widget's content by navigating to its Configuration menu.} \end{figure} Once you've created a new scope for widgets, a button (\includegraphics{./images/icon-cog.png}) with a drop-down menu appears in the \emph{Content \& Data} menu for you to select which scope to manage content for. You can choose the default scope or any new scopes you created for your widgets. Your selection changes the content that appears when you manage each type. \begin{figure} \centering \includegraphics{./images/widget-scope-drop-down-menu.png} \caption{Use the drop-down menu under Content \& Data to determine which scope to manage content for.} \end{figure} That's all it takes to change the scope for a particular widget. By setting the scope to the current page, you can add as many of these widgets to a Site as you want, provided they are all added to separate pages. Another useful feature of Liferay's widgets is Configuration Templates. \chapter{Configuration Templates}\label{configuration-templates} Once you've configured a widget, Configuration Templates can save those settings in a reusable template. If someone goes in and changes the settings of a particular widget, it then becomes easy to revert those changes back to the original configuration template. Configuration templates are only available for widgets placed on a page. Applications available from the Product Menu do not provide configuration templates. To create a configuration template, click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) from the menu in the widget's title bar and select \emph{Configuration Templates}. If widget's current settings are the ones you want to save, click the \emph{Save Current Configuration as Template} button. If not, change the settings until it's configured the way you want it, and then click the button. \begin{figure} \centering \includegraphics{./images/configuration-template.png} \caption{Create a configuration template to save your app's configuration settings.} \end{figure} There is only one field to fill out. Enter a name for your template and click \emph{Save}. You should now see your configuration in the list. If you ever need to revert the app to these archived settings, you can click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Apply} next to the configuration template you want to apply. Unless otherwise noted, all widgets in Liferay DXP support this feature. This is particularly useful for widgets that have a lot of configuration options, such as the Message Boards application. \section{Summary}\label{summary} You've now explored the configuration options available for Liferay widgets. You learned how to customize your widgets, export/import data, communicate between widgets, take advantage of different scopes, and save configuration settings. You also examined the different uses of social applications like Facebook and Netvibes for your Liferay widgets. In all, Liferay DXP gives you an abundance of options to leverage the full capability of your widgets. \chapter{Managing Members in Your Site}\label{managing-members-in-your-site} Users and Sites are important concepts. Sites are where all your content and pages are stored, and Users access and create that content. While user management is covered in depth in our \href{/docs/7-2/user/-/knowledge_base/u/managing-users}{User Management tutorial}, there are other user configuration options specific to Site Management: \begin{itemize} \tightlist \item Adding members to Sites administratively \item Adding members to Sites automatically \item Creating Teams of Site members for various functions \end{itemize} This section of tutorials shows you how to manage and configure these Site options for Users. \chapter{Adding Members to Sites}\label{adding-members-to-sites} In \href{/docs/7-1/user/-/knowledge_base/u/adding-sites}{Adding Sites} you learned the difference between Site Membership Types and about public and private pages within a Site. Now you'll learn how to add users manually to Sites and how to provide options for self management. For review, there are a few key reasons why Site Membership management is important: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Only Site Members can view a Site's Private Pages. \item Site Members have more permissions than guests for many widgets like Message Boards and Wikis that enable them to create content and collaborate on your Site. \item Site Members can be associated with Roles that grant Site privileges. \end{enumerate} \section{Administrating Site Membership}\label{administrating-site-membership} Administrators can manage Site members from that Site's \emph{Site Membership} page. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open \emph{Site Administration} and select the Site that you want to manage members for. \item Click on \emph{People} → \emph{Memberships} \end{enumerate} From here you can manage Site Memberships, Organization, and User Group associations. You can learn more about those in the \href{/docs/7-2/user/-/knowledge_base/u/users-and-organizations}{Users and Organizations tutorial}. Here you see a list of all of the current Users of the Site and you can add or remove user memberships from the Site. \begin{figure} \centering \includegraphics{./images/orgs-add-organization-site.png} \caption{The current members of the Site as displayed on the \emph{Site Memberships} page.} \end{figure} \section{Adding Members to a Site}\label{adding-members-to-a-site} Follow these steps to make an existing user a member of the Site: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{New} (\includegraphics{./images/icon-add.png}) button in the top right of the screen. \item Use \emph{Filter and Order} or the \emph{Search} function to locate the User you want to add to the Site. \item Select the User(s) you wish and click \emph{Done}. \end{enumerate} On the \emph{Assign Users to This Site} screen, all Users eligible to be added to the Site appear. Deactivated Users do not appear. Site members also appear, but with a greyed-out checkbox. \begin{figure} \centering \includegraphics{./images/assign-users.png} \caption{The list of users available to add to the current Site. Note that the current members are visible but cannot be added or removed here.} \end{figure} \section{Removing User Membership from a Site}\label{removing-user-membership-from-a-site} There are two ways to remove a user from a Site. You can remove an individual member like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) icon for the user that you want to remove. \item Select \emph{Remove Membership}. \item In the pop-up that appears, confirm the removal. \end{enumerate} \begin{figure} \centering \includegraphics{./images/remove-user.png} \caption{Selecting to remove a user.} \end{figure} To remove several users at once, you can do this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the checkbox for each user that you want to remove. \item In the menu at the top of the page, click the \texttt{X} icon to remove the users from the Site. \item In the pop-up that appears, confirm the removal. \end{enumerate} Removed Users lose access to the Site's private pages and membership in any Site Roles or Teams they had. \section{Assigning Site Roles}\label{assigning-site-roles} Roles grant permissions in Liferay DXP. Roles can be assigned for the entire instance or just for one specific Site or Organization. Site Roles assign permissions for a specific Site. You can use the same interface options that you used to remove Users from the Site to assign them to Site Roles. If you select a User or Users and click \emph{Assign Site Roles} (either through the Actions menu or the menu at the top), you are taken to the \emph{Assign Site Roles} screen. From here: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the Roles that you want to assign to the selected users. \item Click \emph{Done}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/assigning-site-roles.png} \caption{Assigning Site Roles.} \end{figure} Site Roles are created at a global level, but when they're assigned they only provide privileges for the specific Site where they were assigned. Since Roles are created at a global level, they cannot be created by Site Administrators (since Site Administrators only have Administrator privileges for their Site). \textbf{Teams} allow Site Administrators to assign permissions to groups of Users within their Sites. Next, you'll look at more configuration for managing members of your Site. \chapter{Creating Teams to Empower Site Members}\label{creating-teams-to-empower-site-members} If you have an \emph{ad hoc} group of Users who perform the same set of tasks in a Site, you can organize them into Site Teams, and then assign the team permissions for various site-specific functions. Site Teams are the preferred method for collecting permissions within a single Site. Some common functions to assign a Site Team include \begin{itemize} \tightlist \item Moderating site Wiki content \item Managing Message Boards threads \item Writing blogs \item Editing a specific page in the site \end{itemize} If your Site has Message Boards, you might want to enable a subset of the Site's members to moderate the categories and threads, and perhaps to ban abusive/offensive posters. To do this, you could create a Site Team named \emph{Lunar Resort Message Board Moderators}, define the team's permissions in the Message Boards application, and assign the desired Site members to the team. The permissions assigned to a Site Team only apply to that Site. The two key features of Teams are that they are limited to their Sites and that they empower Site Administrators to manage permissions for their Sites since Site Administrators cannot create new Roles. \noindent\hrulefill \textbf{Note:} To create and apply permissions for a group of users to use across multiple Sites or Organizations in your Liferay instance, consider aggregating the Users into a \href{/docs/7-2/user/-/knowledge_base/u/user-groups}{User Group} and assigning the User Group permissions via \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles}. \noindent\hrulefill To create a team within a Site, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Site Administration page of your Site. \item Select \emph{People} → \emph{Teams}. It's important to note that configuring other Site membership groupings, such as \emph{Users}, \emph{Organizations}, and \emph{User Groups} can be done in the \emph{Site Memberships} app, which is also in the Members tab. You can visit \href{/docs/7-2/user/-/knowledge_base/u/managing-users}{User Management} for more information on how Site memberships work. \item Finally, click the \emph{Add Team} icon (\includegraphics{./images/icon-add.png}). \begin{figure} \centering \includegraphics{./images/creating-a-team.png} \caption{Creating teams within your site can foster teamwork and collaboration, as team permissions enable team members to access the same resources and perform the same types of tasks.} \end{figure} \item Enter a name and a description and click \emph{Save}. Your new team shows in the list. \item To add members, click on the team name link and then select \emph{Add Team Members}. \end{enumerate} To manage a team's permissions, click the \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and select \emph{Permissions} for that team. Setting permissions for the team assigns those permissions to all the team's members. Only administrators who can edit/manage the team can manage team permissions. If you created a team whose task is to moderate the Message Boards, for example, you'd want to give the team all the permissions they'd need. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Content \& Data} → \emph{Message Boards}. \item Select \emph{Home Category Permissions} from the \emph{Options} icon (\includegraphics{./images/icon-options.png}) in the top right of the screen. \item Find the Team in the Role column and select the appropriate permissions. \end{enumerate} \begin{figure} \centering \includegraphics{./images/site-team-permissions-message-boards.png} \caption{The Lunar Resort Message Board Moderators Site Team has unlimited permissions on the Message Boards application.} \end{figure} That's it! It's easy to give groups of Site Users permissions to perform their tasks. These tutorials have introduced you to Liferay DXP Site management. You've learned how to use Liferay DXP to create multiple Sites with different membership types. You've also seen how easy it is to create and manage Sites and to create and manage pages within Sites. Next, you'll begin working with web content. \chapter{Managing Content}\label{managing-content} There are two primary ways to manage and display content in Liferay DXP \begin{itemize} \tightlist \item Web Content \item Content Sets \end{itemize} The Web Content framework helps users who are not web developers publish content with a simple point and click interface, while enabling developers to create complex templates with dynamic elements. A Content Set defines a list of content, and then that list can be displayed. Continue on to learn more about managing content! \chapter{Managing Web Content}\label{managing-web-content} Web Content Management (WCM) helps users who are not web developers publish content with a simple point and click interface, while enabling developers to create complex templates with dynamic elements. Once these templates have been deployed into Liferay DXP, your non-technical users can use them to manage complex content as easily as they would manage basic content. It has these components: \textbf{Web Content Editor:} A complete HTML editor for modifying fonts, adding color, inserting images, and much more. \textbf{Structure Editor:} Define fields for structured content and more advanced designs. \textbf{Template Editor:} Import template script files or create your own template to inform the system how to display the content within the fields determined by the structure. \textbf{Web Content Display:} Place web content on pages in your Site. \textbf{Asset Publisher:} Aggregate and display different types of content together in one view. This is covered in more detail in \href{/docs/7-2/user/-/knowledge_base/u/publishing-assets}{Publishing Assets}. \textbf{Scheduler:} Schedule when content is reviewed, displayed or removed. This is covered in more detail in \href{/docs/7-2/user/-/knowledge_base/u/scheduling-web-content-publication}{Scheduling Web Content Publication}. \textbf{Workflow Integration:} Run your content through a review process. This is covered in more detail in the \href{/docs/7-2/user/-/knowledge_base/u/workflow}{Workflow} section. \textbf{Staging:} Use a separate staging server or stage your content locally so you can keep your changes separate from the live site. This is covered in more detail in the \href{/docs/7-2/user/-/knowledge_base/u/staging}{Staging} section. These tools streamline the content creation process for end users and are also integrated with Liferay's services so advanced template developers can use them to query for data stored elsewhere on your website. To demonstrate Liferay DXP's Web Content Management features, you'll create and manage content on Liferay for the ambitious (and fictitious) \emph{Lunar Resort} project. The Lunar Resort project specializes in facilitating lunar vacations. It provides space shuttle transportation from the Earth to the Moon and back, offers the use of a state-of-the-art recreational facility enclosed by a large, transparent habitat dome, and even rents out lunar rovers. \chapter{Publishing Basic Web Content}\label{publishing-basic-web-content} Web Content is one of many different kinds of assets, along with blog posts, wiki articles, message board posts, and other kinds of content. Like all of these assets, Liferay DXP handles Web Content using an asset framework that includes categories, tags, comments, ratings and more. Please see \href{/docs/7-2/user/-/knowledge_base/u/publishing-content-dynamically}{Publishing Content Dynamically} for more information on Liferay's asset framework. To start working with Web Content, publish some basic material using Web Content Management's WYSIWYG editor. Then you can cover the editor's features in greater depth and learn how to publish the content you create to a page. \chapter{Creating Web Content}\label{creating-web-content} To start you'll create and publish some simple content using the WYSIWYG editor to the home page of the Lunar Resort's web site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} and click the \emph{Site Selector} button (\includegraphics{./images/icon-compass.png}). Content is created in whichever Site is selected, so always make sure that you're working on the right Site. Content can also be created as Global so that any Site can access it. \begin{figure} \centering \includegraphics{./images/site-page-scopes.png} \caption{You can choose where to create content by navigating to the Site Administration menu and selecting your Site and page scope.} \end{figure} \item Select the \emph{Lunar Resort} Site. \item Open the \emph{Content \& Data} section and click on \emph{Web Content}. Here you can create web content and organize it into folders. \item Click \emph{Add} (\includegraphics{./images/icon-add.png}) → \emph{Basic Web Content} to create a new web content article. \begin{figure} \centering \includegraphics{./images/web-content-add-menu.png} \caption{By default, \emph{Basic Web Content} is the only article type available. The next tutorial covers how to create new types.} \end{figure} \item Type \emph{Welcome to the Lunar Resort} in the top \emph{Title} field. \item In the \emph{Summary} field under the Basic Information tab on the right side, give a short description of the Lunar Resort's facilities (be creative). \item In the \emph{Content} field, add the body of your web content article, which you'll dive into next. \item Click \emph{Publish}. \end{enumerate} That's all it takes to create Web Content, but there's much more under the hood of the web content editor that you'll learn about next. \chapter{Using the Web Content Editor}\label{using-the-web-content-editor} In the previous article, you created a simple web content article, but the Web Content editor can do much more than make plain text articles. \section{Basic Editor Functions}\label{basic-editor-functions} To explore these options, go back to the article you just created and make it better: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item On the \emph{Web Content} page in \emph{Site Administration}, click on the title of the article. \item Highlight the text you entered in the \emph{Content} field. A number of controls appear. These let you style the text or provide a link. \item Click on the arrows where it says \emph{Normal} to open the \emph{Styles} dropdown and select the \emph{Heading 1} style. \end{enumerate} Next, you'll add an image to the article. Whenever you place your cursor in the \emph{Content} area, the \emph{Add} icon ( \includegraphics{./images/icon-wysiwyg-add.png}) appears. If you click on it, controls for inserting an image, video, table, or horizontal line (\includegraphics{./images/icon-content-insert-controls.png}) appear. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Add} (\includegraphics{./images/icon-wysiwyg-add.png}). \item Select the icon that depicts a mountain silhouette to insert the image. \item In the image file selector, select an image to add to the article. Select an image from your computer or from the Site's Documents and Media repository. If you select one from your Documents and Media repository, you can access the \href{/docs/7-2/user/-/knowledge_base/u/editing-images}{image editor} to make changes specifically for your article. \end{enumerate} \begin{figure} \centering \includegraphics{./images/image-editor-preview-window.png} \caption{You can access the image editor through the item selector window.} \end{figure} After adding an image to the web content article, click it to bring up controls (\includegraphics{./images/icon-wysiwyg-image-controls.png}) for formatting it. You can also make it a link. The same way you inserted an image in to the article, you can also insert a table. Click the table to access edit controls, which let you designate the first row and/or column as table headers, and also enable you to add rows, columns, and cells. In addition to images and tables, you can insert a horizontal line as a separator between between sections. You can also add video by providing a URL. \section{Editing the Article Source}\label{editing-the-article-source} If you need to work directly with the HTML, you can switch to source view. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item With your cursor in the \emph{Content} field, select the \emph{Source} icon ( \includegraphics{./images/icon-wysiwyg-source.png}) to switch. \item Click the regular mode icon ( \includegraphics{./images/icon-text.png} ) to go back once you're done editing HTML. \end{enumerate} The HTML editor highlights syntax, and you can switch between a dark and light theme by choosing the moon and sun icons. In HTML mode, click on the \emph{Fullscreen} icon (\includegraphics{./images/icon-enlarge.png}) to access a dual pane view that shows your HTML code on the left and a preview pane on the right. You can arrange the HTML and preview panes horizontally or vertically. \begin{figure} \centering \includegraphics{./images/web-content-editor-html.png} \caption{You can view how your HTML would render by using the preview pane.} \end{figure} You can exit the enlarged editor by clicking the \emph{Done} button at the bottom of the screen. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a few short sentences announcing the grand opening of the Lunar Resort. \item Click \emph{Save as Draft}. Be sure to save your content frequently, because it is not auto-saved. \end{enumerate} The content can be localized in whatever language you want. You'll learn more about localizing your content later. \section{Web Content Options}\label{web-content-options} The right-side menu of the New Web Content form provides options for customizing your web content. It's organized into two tabs: \emph{Properties} (basic configuration properties) and \emph{Usages} (Where the web content is used on the site). Note that the \emph{Usages} tab is only visible if you're editing existing web content that's been added to a page. \begin{figure} \centering \includegraphics{./images/wcm-menu.png} \caption{New web content can be customized in various ways using the menu located to the right of the editor.} \end{figure} The available properties are listed below: \textbf{Basic Information:} Provide a summary for the web content article. \textbf{Default Template:} Customize the web content article's template if it has one. To learn more about web content templates, see \href{/docs/7-2/user/-/knowledge_base/u/designing-uniform-content}{Designing Uniform Content}. \textbf{Display Page Template:} Select a display page template to enhance the styling and formatting of your web content. For example, if you had a news site with different sections---Sports, Technology, Culture---you could create a display page for each section with unique banners, formatting, embedded widgets, or other features. By selecting a display page, you would ensure that content appears on the page with the appropriate features. You'll work through an example of creating a display page in the \href{/docs/7-2/user/-/knowledge_base/u/display-pages-for-web-content}{Display Pages for Web Content} tutorial. If a display page template is configured, you can preview what it will look like with the \emph{Preview} (\includegraphics{./images/icon-preview.png}) button located next to the selected display page template. \textbf{Featured Image:} Set the image that is used for the web content article's previews. You can set this image from a URL or your computer. If you don't want a feature image, choose \emph{No Image}. \textbf{Metadata:} Organize web content articles by selecting tags, categories, and priority. To learn more about tags and categories, see \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{Organizing Content with Tags and Categories}. \textbf{Friendly URL:} Set the friendly URL where the article can be viewed alone. If a specific display page is set, the URL links to it. \textbf{Schedule:} Customize the date and time your content publishes and/or expires. To learn more about scheduling content, see \href{/docs/7-2/user/-/knowledge_base/u/scheduling-web-content-publication}{Scheduling Web Content Publication}. \textbf{Search:} Disabling search for your article removes it from end users' search results. Administrators can still search for it from \emph{Site Administration} → \emph{Content \& Data} → \emph{Web Content}, and the article can still be added to pages. \textbf{Related Assets:} Determine relationships between the web content article and other assets, even if they don't share any tags and aren't in the same category. You can connect your content to any asset that implements the Related Assets feature. To learn more about defining content relationships and publishing links to those related assets, see \href{/docs/7-2/user/-/knowledge_base/u/defining-content-relationships}{Defining Content Relationships}. \begin{figure} \centering \includegraphics{./images/related-assets-link.png} \caption{This blog entry has links to two Related Assets: an article and a message board thread.} \end{figure} \textbf{Permissions:} Customize who has access to the content. By default, content is viewable by Anyone (Guest Role). You can limit viewable permissions by selecting any Role from the drop-down or in the list. You can customize permissions in more detail by selecting the \emph{More Options} link below the drop down button. If your permissions are ignored, you must activate the Web Content Article permissions in your System Configuration. This is enabled by default: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item Search or browse for \emph{Web Content}. \item Check the box labeled \emph{Article View Permissions Check Enabled} under the \emph{Virtual Instance Scope} → \emph{Web Content} tab. \item Click \emph{Save}. \end{enumerate} Once it is activated, any permissions you set in the article's configuration are checked before displaying the article. \chapter{Publishing Web Content}\label{publishing-web-content} In the previous sections, you created and edited an article. Now it's time to publish it. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Welcome} page of the Lunar Resort Site. \item Select the \emph{Add} button (\includegraphics{./images/icon-add.png}) from the top Control Menu and select the \emph{Widgets} tab. \item Find \emph{Web Content Display} and add it to the page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/add-web-content-display.png} \caption{Add the Web Content Display app to a page to begin displaying your new web content article.} \end{figure} You can drag a widget to the position on the page where you want your content to appear. You can have as many Web Content Display widgets on a page as you need, which gives you the power to lay out your content exactly the way you want it. Now select the content to display: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Select Web Content to make it visible} in the bottom of the widget. \item Click \emph{Select} under \emph{Web Content}. \item Click on the article that you want to display If your content does not immediately appear in the list, you can search for the content by title, description, user name, or Site (click the drop-down arrow to see all the options). \end{enumerate} Selecting a web content article displays the Web Content Display's configuration page, where you can choose the User Tools and Content Metadata to be published in the widget. These two entities have the following options to choose from, by default: \begin{itemize} \tightlist \item \textbf{User Tools} \begin{itemize} \tightlist \item \emph{Translations} \item \emph{Print} \end{itemize} \item \textbf{Content Metadata} \begin{itemize} \tightlist \item \emph{Related Assets} \item \emph{Ratings} \item \emph{Comments} \item \emph{Comment Ratings} \end{itemize} \end{itemize} If you have enabled OpenOffice/LibreOffice integration, you can also enable document conversion for your content. Then users can download your content in their format of choice. To enable OpenOffice/LibreOffice integration, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Connectors} and check the \emph{Server Enabled} box. Back in the Web Content Display's configuration page, conversion options are available under the \emph{User Tools} list. \begin{figure} \centering \includegraphics{./images/web-content-choosing-web-content.png} \caption{Publishing web content is a snap. At a minimum, you only have to select the content you wish to publish. You can also enable lots of optional features to let your users interact with your content.} \end{figure} \textbf{Translations:} Shows the available locales for your content. If you're working on the page for a particular language, you can select the translation of your content that goes with your locale. \textbf{Print:} Opens the content in a separate browser window with just the content---no navigation or other widgets. By default, guests cannot leave comments on web content. If you want to allow guests to comment on your web content article, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Control Panel → \emph{Users} → \emph{Roles} \item Select \emph{Guest} → \emph{Define Permissions}. \item From the left menu, select \emph{Site Administration} → \emph{Content \& Data} → \emph{Web Content}. \item Navigate down to the Web Content Article heading and select the \emph{Add Discussion} checkbox. Click \emph{Save}. \end{enumerate} Guests can now post comments on your web content article! You may decide you want one, some, or none of these features, which is why they're all implemented as simple selector buttons to be enabled or disabled at need. Once you've selected the features you want to include in your Web Content Display widget, click \emph{Save} and close the configuration window. \section{Editing Published Content}\label{editing-published-content} If you must edit published content, you can do it directly from the Web Content Display app or from Site Administration. To edit it from the Web Content Display app, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the \emph{Options} button (\includegraphics{./images/icon-app-options.png}) from the widget's top panel. \item Select \emph{Edit Web Content} to launch the editor. Select \emph{Edit Template} to launch the template editor for the web content article's template if it has one. \end{enumerate} If you edit the article from Site Administration, you can also view the article's history and use the diff tool to compare versions. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Content \& Data} → \emph{Web Content} from the Product Menu. \item Next to the article, click \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and select \emph{View History}. This shows you \end{enumerate} This shows you all the article's versions and modified/display dates. The diff tool compares these versions and highlights the differences between them. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Actions} next to a version of the article you'd like to compare. \item Select \emph{Compare to\ldots{}}. \item Select the version with which to compare it. The tool provides color coded highlighting to emphasize additions and deletions between the two articles. \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-diff-feature.png} \caption{Comparing web content articles is a great feature to use during the Workflow process.} \end{figure} Whenever you publish updates to a web content article that's already being displayed, the content is immediately updated unless you have a workflow enabled (see \href{/docs/7-2/user/-/knowledge_base/u/workflow}{Workflow} for details). \chapter{Other Content Options}\label{other-content-options} Here are some options and tools that you can use to enhance your content and user experience. \section{Localizing Content}\label{localizing-content} When you create a new web content article, you can choose a default language. First, you must change the system configuration to enable the option to change the default language. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item Locate \emph{Web Content} → \emph{Administration} by scrolling or using the search bar. \item Check the box labeled \emph{Changeable Default Language}. \item Click \emph{Save}. \end{enumerate} You must now add translations for any languages you need. Adding translations works like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open a web content article. \item Click the flag icon with a country code on it next to any localizable web content field. \item Select a language from the list. \end{enumerate} When you select a language, all fields in the article switch to the new language. To create the new translation, fill in the fields in the selected language and publish the article. \begin{figure} \centering \includegraphics{./images/web-content-translation.png} \caption{Adding a translation to an article works like adding the default translation.} \end{figure} \noindent\hrulefill \textbf{Note:} To view localizable fields in a given language, you must have your Portal set to that language. This includes friendly URLs for the web content as well. When you navigate to the localized friendly URL (e.g.~\texttt{http://localhost:8080/web/guest/-/espanol}), the web content is always displayed in the current language. You can change the language with the \href{/docs/7-2/user/-/knowledge_base/u/personalizing-pages\#customization-example}{Language Selector app}. \noindent\hrulefill You can modify the language translation list by inserting \texttt{locales.enabled=} followed by your preferred languages in your \texttt{portal-ext.properties} file. For example, \texttt{locales.enabled=ar\_SA,nl\_NL,hi\_IN} offers \emph{Arabic (Saudi Arabia)}, \emph{Dutch (Netherlands)}, and \emph{Hindi (India)}. \noindent\hrulefill \textbf{Warning:} If you switch your Site's default language (e.g., via friendly URL), but do not have the necessary translations for localizable fields, your Site's language values are used from the old default language. Therefore, you should change the default language of your Site \emph{only} when you have translated values for all localizable entities. \noindent\hrulefill When you create a new web content structure, each field you create has a \emph{Localizable} checkbox displayed next to it. This enables you to control what can and can't be changed in the translation process. For example, if you don't want images or content titles to be changed when the content is translated, you can make sure those fields aren't localizable. When you follow the steps above to localize content, only fields within the structure that had the \emph{Localizable} box checked appear within the translation window. \section{Xuggler for Embedding Video}\label{xuggler-for-embedding-video} Xuggler is a tool which generates video previews and makes it possible to embed videos from your Documents and Media library in web content and elsewhere on the site. To enable Xuggler, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Control Panel}. \item Click on \emph{Configuration} → \emph{Server Administration} → \emph{External Services}. \item Scroll to the bottom and click \emph{Install} in the \emph{Xuggler} section. This downloads the necessary libraries and prompts you to restart the server to enable Xuggler. \item After the server restarts, you can enable Xuggler from the same page. \end{enumerate} Once Xuggler has been installed and enabled, you can embed a video or audio file in a web content article the same way you added images previously. \begin{figure} \centering \includegraphics{./images/web-content-audio-video.png} \caption{If you've installed and enabled Xuggler from the \emph{Server Administration} → \emph{External Tools} section of the Control Panel, you can add audio and video to your web content!} \end{figure} \section{XML Format Downloads}\label{xml-format-downloads} Tools like the \href{/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme}{Resource Importer} and Site Initiators can be deployed to build a site almost instantly. Before you can use them to import Web Content, however, you first need to have the content exported individually in XML format. To export the content, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Content \& Data} → \emph{Web Content}. \item Start editing the article you want to download. \item Click the \emph{Options} icon (\includegraphics{./images/icon-options.png}) in the top right of the page and select \emph{View Source}. \end{enumerate} This displays the raw XML source of the article. You can copy this content to save into an XML file locally. \begin{figure} \centering \includegraphics{./images/web-content-download.png} \caption{The \emph{View Source} button is available from the \emph{Options} button.} \end{figure} \section{Subscribing to Content}\label{subscribing-to-content} An administrator or web content writer can subscribe to an article or folder to follow changes being made to it. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Content \& Data} → \emph{Web Content} for your Site. \item Click \emph{Actions} (\includegraphics{./images/icon-app-options.png}) → \emph{Subscribe} next to the article or folder you want to follow. \end{enumerate} Anytime an asset that you follow is modified, you receive an email notifying you of the change. \begin{figure} \centering \includegraphics{./images/web-content-subscribe.png} \caption{Click the Subscribe icon in the web content entity's \emph{Options} menu to begin receiving web content notifications.} \end{figure} That's pretty much all there is to basic content creation. Whole sites have been created this way. But if you want to take advantage of the full power of Liferay DXP's WCM, you'll want to use structures and templates or Fragments. You'll cover these topics next. \section{Organizing Structure Names}\label{organizing-structure-names} By default, when you select a structure to add a new Web Content article, the structures are ordered by their IDs, not their names. This can be confusing, but---never fear---there's a configuration property to sort them alphabetically. \begin{figure} \centering \includegraphics{./images/web-content-default-order.png} \caption{The default ordering for Web Content Structures can yield confusing results.} \end{figure} To enable this property for Site Administration, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configuration} → \emph{System Settings} → \emph{Web Content} → \emph{Web Content Administration}. \item Check the box labeled \emph{Journal Browse by Structures Sorted by Name}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-admin-alphabetical.png} \caption{Web Content Administration will now display structures in alphabetical order.} \end{figure} You can also set this property for the Web Content Display widget. To enable this property for the Web Content Display, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Configuration} → \emph{System Settings} → \emph{Web Content} → \emph{Web Content Display}. \item Check the box labeled \emph{Sort Structures by Name}. \end{enumerate} After this option is checked, the structures are sorted alphabetically. Note that enabling this property can degrade performance with large structure libraries. \chapter{Designing Structured Content}\label{designing-structured-content} If you've ever launched a web site, you know that as it grows, you can experience growing pains. This is the case especially if you've given lots of people access to the site to make whatever changes they need to make. Without preset limitations, users can display content in any order and in any manner they desire (think huge, flashing letters in a font nobody can read). Content can get stale, especially if those responsible for it don't maintain it like they should. And sometimes, content is published that should never have seen the light of day. Thankfully, Web Content Management helps you handle all of these situations. You can use \emph{Structures} to define which fields are available to users when they create content. These can be coupled with \emph{Templates} that define how to display that content. Content won't get stale, because you can take advantage of the \href{/docs/7-2/user/-/knowledge_base/u/scheduling-web-content-publication}{Scheduling} feature to determine when content is displayed and when it's removed. Additionally, you can configure Liferay DXP's built-in \href{/docs/7-2/user/-/knowledge_base/u/workflow}{Workflow} system to set up a review and publishing process so only what you want winds up on the live site. This gives you what you need to run everything from a simple, one-page web site to an enormous, content-rich site. All of this starts with structures. \chapter{Creating Structured Web Content}\label{creating-structured-web-content} Structures are the foundation for web content. They determine which fields are available to users as they create new items for display. Structures not only improve manageability for the administrator, they also make it much easier for users to add content quickly. For example, say you're managing an online news magazine. All your articles must contain the same types of information: a title, a subtitle, an author, and one or more pages of text and images that comprise the body of the article. With only basic content creation, you'd have no way to make sure your users entered a title, subtitle, and author. You might also get articles that don't match the look and feel of your site. If titles are supposed to be navy blue but they come in from your writers manually set to light blue, you must spend time reformatting them before they are published. Structures enforce a format for your content so your writers know exactly what a complete article or mapped display page needs. Using structures, you can provide a form for your users which spells out exactly what is required and can be formatted automatically using a template or a \href{/docs/7-2/user/-/knowledge_base/u/display-pages-for-web-content}{display page}. You create a structure by adding form controls such as text fields, text boxes, HTML text areas, check boxes, select boxes and multi-selection lists. You can add specialized application fields such as an Image Uploader or Documents and Media right onto the structure. Positioning elements is accomplished by drag-and-drop, making it easy for you to prototype different orders for your input fields. Additionally, elements can be grouped together into blocks which can then be repeatable. Display Page creators can then map these fields to \href{/docs/7-2/user/-/knowledge_base/u/content-page-elements\#editable-elements}{editable page fragments} to use custom styles and formatting. Alternatively, template writers can write a template which loops through these blocks and presents your content in innovative ways, such as in sliding navigation bars, content that scrolls with the user, and more. Next you'll learn how you can create and edit structures through the Manage Structures interface. \chapter{Editing Structures}\label{editing-structures} To start, go to the \emph{Structures} page. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From \emph{Site Administration} go to \emph{Content \& Data} → \emph{Web Content}. \item Open the \emph{Structures} tab. \end{enumerate} This page shows you all the web content structures in this Site. You can add new web content structures, edit existing ones, manage the templates associated with a structure, edit the permissions of a structure, and copy or delete structures. \begin{figure} \centering \includegraphics{./images/manage-structures.png} \caption{Structures are not pre-installed. You have to make your own.} \end{figure} \noindent\hrulefill Note: When you copy a structure, Liferay DXP generates a unique ID for the copied structure, but every other attribute of the copied structure, including the name, is the same as that of the original. When you copy web content structure, enter a new name for it to avoid confusing it with the original. During the copy process, you're prompted to choose whether to copy any detail templates or list templates associated with the structure. For information on detail templates and list templates, please refer to \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists}. \noindent\hrulefill \emph{Basic Web Content}, which you used in previous exercises, lives at the \emph{Global} scope so that it is available to all Sites. This structure and template are used automatically if a custom structure and template are not added. \section{Structure Fields}\label{structure-fields} Now, create a new Structure: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Add} (\includegraphics{./images/icon-add.png}). \item Give your Structure a name. \end{enumerate} Structures are essentially a set of fields organized in a certain way. The interface on this page provides an easy way to add and organize whatever fields you need. Each element that you add has three icon options that you can click: \textbf{Settings:} (\includegraphics{./images/icon-wrench.png}) Changes the name and label and set other information about the field, like whether or not it is required. \textbf{Delete:} (\includegraphics{./images/icon-trash.png}) Removes the field from the structure. \textbf{Duplicate:} (\includegraphics{./images/icon-wysiwyg-add.png}) Duplicates the field and all its settings and iterates the \emph{Name} to avoid conflicts. Web content structures can inherit characteristics from other structures. A child structure inherits all the parent's fields and settings. You can use this to make a similar structure to one that already exists. For example, if you have \emph{Sports Article} and you want to create \emph{In-depth Sports Article}, set \emph{Sports Article} as the parent and the \emph{In-dept Sports Article} inherits all its fields, letting you add new ones for more in-depth information. \noindent\hrulefill \textbf{Note:} Due to import/export operations, it's possible to have both a global and a Site-scoped structure with the same \texttt{structureKey}. If this happens, the Site-scoped structure takes precedence, and you can't access the global structure from that Site. \noindent\hrulefill You can also manually customize a structure's XML in \emph{Source} mode. By default the \emph{View} mode is selected, but you can click the \emph{Source} tab to switch. This method is for more experienced developers. Take a moment to add, delete, and rearrange different elements. \begin{figure} \centering \includegraphics{./images/web-content-structure-editor.png} \caption{The structure editor gives you many options to customize your Web Content.} \end{figure} The following fields can be in structures: \textbf{Boolean:} Adds a checkbox onto your structure, which stores either \texttt{true} (checked) or \texttt{false} (unchecked). Template developers can use this as a display rule. \textbf{Color:} Adds a way to choose a color. \textbf{Date:} Adds a pre-formatted text field that displays a date picker to assist in selecting the desired data. The format for the date is governed by the current locale. \textbf{Decimal:} Similar to \emph{Number}, except that it requires a decimal point (.) be present. \textbf{Documents and Media:} Adds an existing uploaded document to attach to the structure. Can also upload documents into the Document Library. \textbf{Geolocation:} Adds a map that displays a configured location. The geolocation system can work in two ways: letting the system know your current location (especially useful on mobile devices) and giving the user directions to a another place. \textbf{HTML:} An area that uses a WYSIWYG editor to enhance the content. \textbf{Image:} Adds the browse image application into your structure. You can select an image from the Documents and Media library or upload an image from your computer's storage. If uploading an image from your personal computer to the web content article, it is only available for that article. \textbf{Integer:} Similar to \emph{Number}, except that it constrains user input to whole numbers. \textbf{Link to Page:} Inserts a link to another page in the same site. \textbf{Number:} Presents a text box that only accepts numbers as inputs, but puts no constraints on the kind of number entered. \textbf{Radio:} Presents the user with a list of options to choose from using radio button inputs. \textbf{Select:} Presents a selection of options for the user to choose from using a combo box. Can be configured to allow multiple selections, unlike \emph{Radio}. \textbf{Separator:} Adds a horizontal line between fields. \textbf{Text:} Used for items such as titles and headings. \textbf{Text Box:} Used for the body of your content or long descriptions. \textbf{Web Content:} Provides a way to select a web content article. These fields provide all you need to model any information type you would want to use as web content. Liferay customers have used structures to model everything from articles, to video metadata, to wildlife databases. \chapter{Configuring Structure Fields}\label{configuring-structure-fields} There are many options available for configuring each structure field. Some of them relate to how the fields are displayed or how users interact with them, but probably the most important field configuration is the \textbf{Name}. When you create a new field, it has a random name generated that looks like \texttt{TextField4882}. In most cases, you should change this to something that is more memorable and more descriptive. If you create a matching template, you don't want to have to remember if \texttt{TextField4882} was the field for entering an applicant's name or annual salary. Practice this now. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your structure, add an \emph{HTML} element. \item Hover over the field and select the \emph{Configuration} icon (\includegraphics{./images/icon-wrench.png}). \item Change the \emph{Field Label} value to \emph{Instructions} and the \emph{Name} value to \emph{steps}. Now your template writer has a variable by which he or she can refer to this field. \end{enumerate} Here's a list of all the configurable settings available for a structure's fields: \textbf{Type:} Lists the type of field placed in the definition. This is not editable but is available to reference from a template. \textbf{Field Label:} Sets the text that can be displayed with the field. This is the human-readable text that the user sees. \textbf{Show Label:} Select \emph{Yes} to display the Field Label. \textbf{Required:} Select \emph{Yes} to mark the field required. If a field is required, users must enter a value for it in order to submit content using this structure. \textbf{Name:} The name of the field internally, automatically generated. Since this is the variable name that you can read the data from in a template or display page, you should enter a descriptive name. \textbf{Predefined Value:} When a user creates a new web content article based on a structure that has predefined values for various fields, the predefined values appear in the form as defaults for those fields. \textbf{Tip:} Each field can have a small help icon, with a tooltip attached that displays helpful information. If you want to provide text for the tooltip, you may enter it here. \textbf{Indexable:} Select \emph{Yes} to permit your field to be indexed for search. \textbf{Localizable:} Select \emph{Yes} to permit localization for this field. \textbf{Repeatable:} Select \emph{Yes} to make your field repeatable. Users can then add as many copies of this field as they need. For example, if you're creating a structure for articles, you might want a repeatable Author field in case you have multiple authors for a particular article. \textbf{Multiple:} Select \emph{Yes} to enable a multi-selection list (only available for the Select field). \textbf{Options:} Changes the options available for selection. You can add and remove options as well as edit each individual option's display name and value (only available for Radio and Select fields). \textbf{Style:} Changes the line separator's style (only available for Separator). \section{Structure Default Values}\label{structure-default-values} You can define Structure Default Values for repeatable values in content created from that structure. They can also set defaults for Liferay's standard asset fields (like tags, categories, and related assets) and the content of the structure fields, while also setting a default template for displaying the structure data. \begin{figure} \centering \includegraphics{./images/structure-actions.png} \caption{You can edit default values via the \emph{Actions} button of the Manage Structures interface.} \end{figure} Returning to the newspaper scenario again, suppose you want all sports articles to have the same display page (sports page), the same categories, or the same set of tags. Instead of adding them for each article or wondering if your users are adding them to every web content article, you can add these characteristics once for every sports article by creating default values for the structure. Creating default values is not part of creating a new structure, so make sure you have an existing structure. To edit a structure's default values: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Content \& Data} → \emph{Web Content} and click on the \emph{Structures} tab to see the structures list. \item Find the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for the desired structure and select \emph{Edit Default Values} from the menu to view a window like the one below. This form manages the structure settings. It duplicates the function of the \emph{Predefined Value} field setting (see above), but is much more convenient for setting or editing a large number of defaults at once. \end{enumerate} \begin{figure} \centering \includegraphics{./images/structure-default-values.png} \caption{You can define values for your structure fields and the standard asset metadata fields.} \end{figure} If you prefer to reset all your structure's default values, you can select the \emph{Reset Values} button. This resets all values for the structure's fields and all properties shown in the right panel. \noindent\hrulefill \textbf{Note:} The \emph{Reset Values} button is available for Liferay Portal 7.2 GA2+ and Liferay DXP 7.2 SP1+. \noindent\hrulefill Every new web content you create with this structure is preloaded with the data you inserted. Next, you'll learn about assigning permissions. \chapter{Structure Settings}\label{structure-settings} After you have created a structure and configured its fields, there is additional configuration to consider including permissions and remote access to managing structures. \section{Assigning Permissions}\label{assigning-permissions} Permissions on structures can be set just like any other \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{permission}. Most users should not be able to edit structures. Structures can be coupled with templates or used on display pages, which require some web development knowledge to create. This is why only trusted developers should be able to create structures. Users, of course, should be able to view structures. The \emph{View} permission enables them to make use of the structures to create content. \begin{figure} \centering \includegraphics{./images/web-content-structure-permissions.png} \caption{You're able to assign structure permissions via the \emph{Actions} button.} \end{figure} The best practice for structure permissions is to grant or deny them based on Roles. \section{WebDAV URL}\label{webdav-url} The WebDAV URL feature is available for web content structures and templates so users can upload and organize resources from both a web interface and the file explorer of their desktop operating system. With the WebDAV URL, site administrators can add, browse, edit, and delete structures and templates on a remote server. After you complete your structure, you can access the WebDAV URL by re-opening the structure or template and clicking the \emph{Details} section. If you'd like the see WebDAV in action, see \href{/docs/7-2/user/-/knowledge_base/u/desktop-access-to-documents-and-media}{WebDAV Access}. \noindent\hrulefill \textbf{Note:} Some operating systems require a WebDAV server to be class level 2 (to support file locking) before allowing files to be read or written. The Documents and Media library uses a class level 2 WebDAV server but Web Content structures and templates do not. This means that Liferay DXP's Document and Media library supports WebDAV file locking but Web Content structures and templates do not. However, on operating systems which require WebDAV servers to be class level 2, it's possible to avoid the restriction by using third-party WebDAV clients (e.g., \href{http://cyberduck.ch}{Cyberduck}). \noindent\hrulefill Now that you understand how structures work, you're ready to understand another key aspect of Liferay DXP's web content management system: templates. \chapter{Designing Web Content with Templates}\label{designing-web-content-with-templates} While templates aren't required to display web content (i.e.~you can use a \href{https://portal.liferay.dev/docs/7-2/user/-/knowledge_base/u/display-pages-for-web-content}{Display Page Template} to map the structure fields), developers can create templates to display the elements of the structure in the markup they want. In essence, templates are scripts that tell Liferay DXP how to display content in the structure. Changes to the structure require corresponding changes to any templates that use it, because new or deleted fields produce errors on the page. Unless a template is generic and meant to be embedded in other templates, a template must have a matching structure. Liferay DXP only supports creating web content templates in FreeMarker (FTL) by default. FreeMarker is the preferred, recommended language, and what you'll use in the next example. \chapter{Adding Templates with Structures}\label{adding-templates-with-structures} To better understand templates, now you'll create a structure and an associated template. First create the structure: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Content \& Data} → \emph{Web Content} from Site Administration page and open the \emph{Structures} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Name the structure \emph{News Article} and add the following fields: \end{enumerate} \noindent\hrulefill \begin{verbatim} Field Type |  Field Label |  Name | \end{verbatim} \noindent\hrulefill --------- \textbar{} ---------- \textbar{} ---------- \textbar{} Text \textbar{} ~Title \textbar{} ~\texttt{title} \textbar{} Text Box \textbar{} ~Abstract \textbar{} ~\texttt{abstract} \textbar{} Image \textbar{} ~Image \textbar{} ~\texttt{image} \textbar{} HTML \textbar{} ~Body \textbar{} ~\texttt{body} \textbar{} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{4} \tightlist \item Click \emph{Save}. \end{enumerate} Now create the template and connect it to the structure. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Web Content} page, go to the \emph{Templates} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Enter the name \emph{News Article}. \item Open \emph{Details} and make sure FreeMarker is selected as the script language (it's the default). \item Click \emph{Select} under \emph{Structure}. \item Choose the \emph{News Article} structure. \item In the \emph{Script} area, find the \emph{Fields} label on the left and click on \emph{Title}, \emph{Abstract}, \emph{Image} and \emph{Body} into the editor area. It should look like this: \begin{verbatim} ${title.getData()} ${abstract.getData()} <#if image.getData()?? && image.getData() != ""> ${image.getAttribute( ${body.getData()} \end{verbatim} \item Next, add heading and \texttt{\textless{}p\textgreater{}} tags and align the image to center to style your elements like this: \begin{verbatim}

    ${title.getData()}

    ${abstract.getData()}

    <#if image.getData()?? && image.getData() != ""> ${image.getAttribute(

    ${body.getData()}

    \end{verbatim} \item Click \emph{Save}. \end{enumerate} To finish it up, add some content: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Web Content} tab. \item Click on the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select \emph{News Article}. \item Insert some content and publish! \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-structures-templates-completed.png} \caption{The Lunar Resort News Article is shaping up!} \end{figure} Awesome! You created your own web content template! \chapter{Embedding Widgets in Templates}\label{embedding-widgets-in-templates} You can also embed widgets in web content templates. Core apps and custom apps, instanceable or non-instanceable can be embedded in web content templates. Below is an example of embedding a Language widget in FreeMarker: \begin{verbatim} <@liferay_portlet_ext["runtime"] portletName="com_liferay_portal_kernel_servlet_taglib_ui_LanguageEntry" /> \end{verbatim} \noindent\hrulefill \textbf{Warning:} The \texttt{theme} variable is no longer injected into the FreeMarker context. For more information about why the theme variable was removed for Liferay DXP 7.0 and suggestions for updating your code, visit the \href{/docs/7-0/reference/-/knowledge_base/r/breaking-changes\#taglibs-are-no-longer-accessible-via-the-theme-variable-in-freemarker}{Taglibs Are No Longer Accessible via the theme Variable in FreeMarker} breaking change entry. \noindent\hrulefill In addition to embedding widgets in templates, you can embed a template within another template. This allows for reusable code, JavaScript library imports, scripts, or macros. Below is an example of embedding a template in FreeMarker: \begin{verbatim} <#include "${templatesPath}/[template-key]" /> \end{verbatim} The \emph{Template Key} can be found when editing a previously published template. \begin{figure} \centering \includegraphics{./images/adt-template-key.png} \caption{You can find the Template Key when view the Edit page for a template..} \end{figure} \chapter{Using Taglibs in Templates}\label{using-taglibs-in-templates} Liferay's taglibs are also accessible to web content administrators developing in FreeMarker. There is no need to instantiate these taglibs within your FreeMarker template; they're already provided for you automatically. You can access these taglibs by indicating the TLD's file name with underscores. When you're using Liferay DXP's template editor, you can find variables on the left side of the template editor. To place one of the variables onto the template editor, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Position your cursor where you want the variable placed. \item Click the variable name. \end{enumerate} If the variable name doesn't give you sufficient information on the variable's functionality, you can hover your pointer over it for a more detailed description. \begin{figure} \centering \includegraphics{./images/web-content-templates-create.png} \caption{You can hover your pointer over a variable for a more detailed description.} \end{figure} The interactive template editor is available for the FreeMarker, Velocity, and XSL languages. Depending on which language you select, the variable content changes so you're always adding content in the language you've chosen. Another cool feature for the template editor is the autocomplete feature. It can be invoked by typing \emph{\$\{} which opens a drop-down menu of available variables. By clicking one of the variables, the editor inserts the variable into the template editor. \noindent\hrulefill \textbf{Note:} The \texttt{utilLocator}, \texttt{objectUtil}, and \texttt{staticUtil} variables for FreeMarker are disabled by default. These variables are vulnerable to remote code execution and privilege escalation, and should be used with caution, if enabled. \noindent\hrulefill After you've saved your template, Liferay DXP provides a WebDAV URL and static URL. These values access the XML source of your structure. You can find these URLs by returning to your template after it's been saved and expanding the \emph{Details} section. For more information on WebDAV and the uses of the WebDAV URL, reference the \href{/docs/7-0/user/-/knowledge_base/u/publishing-files\#desktop-access-to-documents-and-media}{WebDAV Access} section. Now that you've created a handsome template and know how to use the template editor, it's time to decide who the lucky people are that get to use your new template. \chapter{Assigning Template Permissions}\label{assigning-template-permissions} Structures and Templates provide direct access to Liferay's APIs which makes them powerful, but it also means that they can be dangerous in the wrong hands. Only trusted users should be given access. The recommended practice is to create two Roles with access to structures and templates: \begin{itemize} \item \textbf{Content Developers} get full permission to create and edit structures and templates. \item \textbf{Content Creators} only need permission to view the structures and templates so they can use them to create content. \end{itemize} When creating the Roles, define them to have global permission for all structures and templates across the entire instance or only for specific Sites. For more information on creating Roles, see the \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions} article. \section{Assigning Permissions for Individual Templates}\label{assigning-permissions-for-individual-templates} You can also control access to specific templates separately. To determine who can view and interact with a template, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Templates} tab. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for a template that you created and select \emph{Permissions}. \end{enumerate} Here permissions for a template can be set for Roles or Teams. Use this option to provide access to templates on a case by case basis for users that shouldn't have access to templates on a larger level granted by a Role. Whether your Site is small and static or large and dynamic, Liferay's Web Content Management system enables you to plan and manage it. With tools such as the WYSIWYG editor, structures and templates, you can quickly add and edit content. With Web Content Display, you can rapidly select and configure what content to display. You'll find that managing your site becomes far easier when using Liferay DXP's Web Content Management system. \chapter{Display Page Templates for Web Content}\label{display-page-templates-for-web-content} Display Page Templates provide a new level of control over the look and feel of your content. The templates empower marketers and designers to create stunning designs for Web Content. They use both Page Fragments and Web Content to provide an easy way to create beautiful layouts for displaying articles. One great way to use Display Pages Templates is to create standardized formats for articles. If you examine the content on many writing platforms, each article follows a similar format like this: \begin{itemize} \tightlist \item Header Image \item Title \item Main Body \item Highlighted Quote \item Footer with links to related articles or other content \end{itemize} Display Pages Templates let you create standard, reusable formats like this to streamline the creation of attractive content. Read on to learn more! \chapter{Creating Display Page Templates}\label{creating-display-page-templates} Display Page Templates are created initially in much the same way as Content Pages. You select any number of page fragments and add them to the page. Display pages differ in that after you add the fragments, you can then map editable fields in those fragments to the fields of a web content article. You can learn more about creating Page Fragments in the \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{Creating Content Pages} article. Looking at the example of a template for a long form article, we can see how Display Page Templates utilize Page Fragments. The article can have an image, a title (simple style text), a main body (rich text), a highlighted quote (simple styled text), and then a standard footer. Your first step in creating the Display Page Template is to create a Page Fragment which has all those fields formatted the way you want them. Your fragment could have these fields: \begin{itemize} \tightlist \item Editable header \item Editable Image \item Editable rich text \item Editable plain text (with block-quote styling) \item Non-editable footer \end{itemize} To go along with this fragment, you could have a Web Content Structure with these fields: \begin{itemize} \tightlist \item Title (Text box) \item Image (Documents and Media image) \item Content (Web Content) \item Quote (Text area) \end{itemize} \begin{figure} \centering \includegraphics{./images/structure-to-fragment.png} \caption{Connecting structure fields to fragment data.} \end{figure} The Display Page Template maps the fields from the Web Content Structure to the fragment. When the Display Page Template is assigned for an article with that Structure, it appears on a display page with the formatting from the fragment. \chapter{Display Page Template Example}\label{display-page-template-example} Create a new Display Page Template: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Builder} → \emph{Pages} in Site Administration. \item Go to the \emph{Display Page Templates} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Name your Display Page \emph{Lunar Resort Display Page Template}, open the \emph{Content Type} dropdown and select \emph{Web Content Article} and \emph{Basic Web Content} for the Subtype, and click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/display-page-asset-type.png} \caption{Selecting the Asset type and Subtype.} \end{figure} \begin{figure} \centering \includegraphics{./images/create-display-page.png} \caption{The Display Page Template creation interface.} \end{figure} To build the Display Page Template, you can add any number of Fragments---with and without editable content---to the page to build your design. Fragments with editable content can have their editable fields mapped to be filled by a Web Content article. You can also base it on a specific Web Content Structure. \begin{figure} \centering \includegraphics{./images/display-page-with-fragments.png} \caption{Editing a Display Page Template with some Fragments added.} \end{figure} Notice that the example has an editable title and text body, with a static footer containing graphics and links. After you've added some fragments to the page, you can map them like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Section Builder} → \emph{Basic Components} tab in the right menu. \item Add a \emph{Heading 1} and \emph{Text} components to the appropriate areas. \item Click on an editable text area and click the \emph{Map} button (\includegraphics{./images/icon-map.png}) in the dialog that appears. \item Select a field to map the editable fragment to. The mapped field highlights purple to indicate that it's mapped. \begin{figure} \centering \includegraphics{./images/display-page-map-field.png} \caption{Mapping the editable fragments to structure fields.} \end{figure} \item Click \emph{Publish} at the top of the page to save your work. \end{enumerate} You now have a Display Page Template with static graphics and a text area that's replaced with whatever content you add to it. \noindent\hrulefill \textbf{Note:} You can map any data or metadata from a Web Content Article or Structure to a Display Page Template. For the Basic Web Content type, this includes structure-defined fields like Summary, Title, and Content, as well as metadata fields like Publish Date, Categories, and Tags. In a user-defined structure, all user selected fields appear here as well. Custom fields are also available for display if they apply to the content type selected. \noindent\hrulefill For more information on the right panel and the Fragment options available to you, see \href{/docs/7-2/user/-/knowledge_base/u/content-page-elements}{Content Page Elements} and \href{/docs/7-2/user/-/knowledge_base/u/content-page-management-interface}{Content Page Interface}. Content Pages and Display Page Templates share the same Page builder tools. \section{Publishing with Display Page Templates}\label{publishing-with-display-page-templates} Now create a short article to display with this display page template: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Content \& Data} → \emph{Web Content} in Site Administration. \item Add a Basic Web Content article. \item Name it \emph{Thoughts About Space} and fill in some short content. \item Open the \emph{Display Page Template} section and under the dropdown, select \emph{Specific Display Page Template}. Then click \emph{Select}. \item Select the \emph{Lunar Resort Display Page Template} and click \emph{Done}. \item Click \emph{Publish}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/display-page-creating-content.png} \caption{Selecting the Asset type and Subtype.} \end{figure} When published, you can view the content at its Friendly URL (you can find the Friendly URL while editing a Web Content article under \emph{Friendly URL}) or when you click on the content in an Asset Publisher with \emph{Asset Link Behavior} set to \emph{View in Context}. When editing the article, you can preview what the display page template will look like with the \emph{Preview} (\includegraphics{./images/icon-preview.png}) button located next to the selected display page template. \begin{figure} \centering \includegraphics{./images/display-page-in-context.png} \caption{Selecting the Asset type and Subtype.} \end{figure} You can go back and edit the display page template by navigating to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages} → \emph{Display Page Templates} and clicking \emph{Actions} (\includegraphics{./images/icon-staging-bar-options.png}) → \emph{Edit} next to the display page template you want to edit. If you're viewing the published display page, you can also select the \emph{Edit} button (\includegraphics{./images/icon-edit-pencil.png}) from the Control Menu. Awesome! You now know how to create and configure a display page using display page templates. \chapter{Managing Content Sets}\label{managing-content-sets} A Content Set is exactly what it sounds like: a set of content items. In short, an administrator can define a list of content, and then that list can be displayed. The way that the Content Set is displayed is determined by the method that is used to display it. For example, if the Content Set is being used by a smartwatch app, it could be displayed as a simple list of titles, and selecting a title would cause the full article to display on a connected mobile device. The same Content Set could be displayed in a web browser with the full content of each article. \noindent\hrulefill \textbf{Note:} In previous versions of Liferay DXP, you used the Asset Publisher to define and display either static lists of assets or dynamic lists based on criteria like tags, categories, or asset type. In 7.0 Content Sets take the core idea of defining different types of asset lists and expand it. Content Lists are created outside of the context of a specific application or widget and can be used and re-used across different channels and applications. \noindent\hrulefill \section{Creating and Displaying Content Sets}\label{creating-and-displaying-content-sets} Content Sets are created through the Site Administration interface. All the features for creating and managing Content Sets are contained here. They are displayed using Liferay's widgets or your own custom applications. Read our guides for information on \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets} and \href{/docs/7-2/user/-/knowledge_base/u/displaying-content-sets}{Displaying Content Sets} \section{Content Set Personalization}\label{content-set-personalization} Content Sets can have variations driven by Liferay DXP's Personalization engine. After you create a Content Set, if you have at least one User Segment created, you can create a personalized experience of the Content Set for that Segment. To learn to harness the power of experience personalization for Content Sets, see \href{/docs/7-2/user/-/knowledge_base/u/content-set-personalization}{Content Set Personalization}. \section{Converting Asset Publisher Configurations to Content Sets}\label{converting-asset-publisher-configurations-to-content-sets} You may have already gone through a great deal of work to create a perfect, curated list of content through the Asset Publisher, but now you want to display that list elsewhere without duplicating your work. You can do that with Content Sets. Read the \href{/docs/7-2/user/-/knowledge_base/u/converting-asset-publisher}{Converting Asset Publisher Configuration to Content Sets guide} article to learn more. \chapter{Creating Content Sets}\label{creating-content-sets} Content Sets are created by content administrators through the Content Sets interface in Site Administration. Content Sets can use either Manual or Dynamic selection, and you can create any number of Content Sets, and display them through the Asset Publisher or custom applications. Content Sets can also have \href{/docs/7-2/user/-/knowledge_base/u/content-set-personalization}{personalized variations} which provide different experiences for different users based on criteria that you specify. The criteria management is shared with the Asset Publisher, so for more information on each option, see the official \href{/docs/7-2/user/-/knowledge_base/u/publishing-content-dynamically}{Asset Publisher Documentation}. \section{Creating a Manual Content Set}\label{creating-a-manual-content-set} To demonstrate the creation of a Manual Content Set, create a Content Set that contains a number of images to be displayed on the Frontpage of the fictitious Space Program website. To prepare for this exercise, upload some appropriate images to \emph{Documents and Media} to use for the Content Set. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Content \& Data} and select \emph{Content Sets}. \begin{figure} \centering \includegraphics{./images/content-sets-empty-page.png} \caption{Content Sets is found in the Content \& Data section of Site Administration.} \end{figure} \item Click \includegraphics{./images/icon-add.png} and select \emph{Manual Selection}. \item Name your Content Set \emph{Space Program Images}. \end{enumerate} On the next screen, you can select the assets to include in the Content Set. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Select} → \emph{Basic Document}. \begin{figure} \centering \includegraphics{./images/content-sets-select-document.png} \caption{You can select the type of asset to add to the Content Set.} \end{figure} \item Now, check the boxes for each image that you want to add and click \emph{Add}. \end{enumerate} Now this Content Set can be displayed anywhere on the site where it was created. You can add or remove items from the set, and it will automatically update it whereever it is displayed. \section{Creating a Dynamic Content Set}\label{creating-a-dynamic-content-set} To demonstrate the creation of a Dynamic Content Set, create a Content Set that contains a number of varied assets that are tagged as ``trending.'' In order for this to work, you will need some number of existing assets with the appropriate tag. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Content Sets} page, click \includegraphics{./images/icon-add.png} → \emph{Dynamic Selection}. \item Enter \emph{Trending} for the name and click \emph{Save}. \end{enumerate} With Dynamic Content Sets, you can choose the \emph{Source}, \emph{Scope}, \emph{Filter}, and \emph{Ordering} for the items in the set. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Leave \emph{Source} as \emph{Any} and \emph{Scope} as \emph{Current Site} \item Open \emph{Filter}, make sure it is set to \emph{Contains Any of the following Tags}, and then enter ``trending'' in the \emph{Tags} box. \begin{figure} \centering \includegraphics{./images/content-set-trending-filter.png} \caption{Content Sets use the same filter system as the Asset Publisher.} \end{figure} \item Open \emph{Ordering} and set it to \emph{Order By}: \emph{Publish Date}, \emph{And Then By}: \emph{Title}. \item Click \emph{Save}. \end{enumerate} This will create a Content Set which will contain any items that are currently tagged as \emph{trending} and any future items with the \emph{trending} tag will be added to the Content Set automatically. Now that you have your Content Sets created, you can \href{/docs/7-2/user/-/knowledge_base/u/displaying-content-sets}{display them on a page}. \chapter{Displaying Content Sets}\label{displaying-content-sets} Content Sets are primarily displayed through the Asset Publisher. It is currently the only method to display them out of the box, but you can develop your own external applications or widgets to utilize Content Sets. In \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets} you created two Content Sets. Now display them on a page. \section{Configuring the Asset Publisher for Content Sets}\label{configuring-the-asset-publisher-for-content-sets} To display the Content Sets, start with a blank page, and then add the necessary Asset Publishers and configure them to display the Content Sets. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a new \emph{Home} page for your site as a Widget Page with a 1 column layout. If you're using a fresh Liferay DXP bundle, you can just remove the \emph{Hello World} widget from the sample \emph{Home} page. \item Open the \emph{Add} menu and add two \emph{Content Management} → \emph{Asset Publishers} to the page stacked vertically. \item Click \includegraphics{./images/icon-app-options.png} → \emph{Configuration} for the top Asset Publisher. \item Under \emph{Asset Selection} choose \emph{Content Set}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/content-set-asset-selection.png} \caption{The Asset Publisher has a number of options available for selecting its source for content.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{4} \item Open \emph{Select Content Set} and click \emph{Select}. \item Click on the \emph{Space Program Images} Content Set. \item Click \emph{Save}. \end{enumerate} Now the images will appear at the top of the page. You can manage the way the content is displayed---like what metadata appears---or even create a \emph{Widget Template} to style the content, but the items which display and the order in which they appear are determined by the Content Set. Now configure the bottom Asset Publisher with the other Content Set. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \includegraphics{./images/icon-app-options.png} → \emph{Configuration} for the bottom Asset Publisher. \item Under \emph{Asset Selection} choose \emph{Content Set}. \item Open \emph{Select Content Set} and click \emph{Select}. \item Click on the \emph{Trending} Content Set. \end{enumerate} \begin{figure} \centering \includegraphics{./images/content-set-select-set.png} \caption{Select the Content Set you want to use.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{6} \tightlist \item Click \emph{Save}. \end{enumerate} Again, you can manage various display settings, but the items which appear and their order are determined by the Content Set criteria. \begin{figure} \centering \includegraphics{./images/content-set-dynamic-results.png} \caption{You can see the results as the standard Asset Publisher output. You can create Widget Templates to add more style and pizzazz here.} \end{figure} \section{Adding Items to an existing Content Set}\label{adding-items-to-an-existing-content-set} To demonstrate both the management of both static and dynamic Content Sets, upload a new image, tag it, and add it to the static set manually. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Upload a new image, and under \emph{Categorization} tag it as \emph{trending}. \item Without lifting another finger, the image is added to the top of the \emph{Trending} Content List. \end{enumerate} \begin{figure} \centering \includegraphics{./images/content-set-dynamic-add.png} \caption{The result is dynamically added to the Content List wherever it is displayed.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item To add it to the manual set, go back to \emph{Site Administration} → \emph{Content \& Data} → \emph{Site Builder}. \item Click on \emph{Space Program Images} or select \includegraphics{./images/icon-options.png} → \emph{Edit} next to \emph{Space Program Images}. \item Next to \emph{Asset Entries} click \emph{Select} → \emph{Basic Document}. \item Select the new image and click \emph{Add}. \item Navigate back to the \emph{Home} page to see your image added to the list. \end{enumerate} Content Sets are a powerful feature which provide one place to easily define content and other assets to be displayed all over your site. Their reusability also means less repeated work involved in getting great content delivered to your users. \chapter{Converting Asset Publisher Configurations to Content Sets}\label{converting-asset-publisher-configurations-to-content-sets-1} In the previous two guides in this section, you've seen \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets} and \href{/docs/7-2/user/-/knowledge_base/u/displaying-content-sets}{Displaying Content Sets} demonstrated. Next, try out converting an existing Asset Publisher configuration to a Content Set. In this case, you have an Asset Publisher on a page, which is configured to display images tagged as \emph{trending} in reverse alphabetical order by title. This might not be too hard to reproduce in the \emph{Content Set} creator, but it's even easier to create the Content Set definition directly from the Asset Publisher. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \includegraphics{./images/icon-app-options.png} → \emph{Configuration} for the Asset Publisher. \item Click \emph{Create a content set from this configuration}. \begin{figure} \centering \includegraphics{./images/content-set-create-ap.png} \caption{You can generate a Content Set directly from the Asset Publisher configuration.} \end{figure} \item Enter the title and click \emph{Save}. \end{enumerate} And as quickly as that you have a new Content Set that you can use with Asset Publishers anywhere on the site. \begin{figure} \centering \includegraphics{./images/content-set-ap-added.png} \caption{The Content Set is added right alongside any existing sets.} \end{figure} Great! You converted your Asset Publisher configuration to a Content Set. \chapter{Organizing Content with Tags and Categories}\label{organizing-content-with-tags-and-categories} Tags and categories are two important tools you can use to help organize information in Liferay DXP. These tools help users to easily find the content they're looking for through search or navigation. Tagging and categorizing assets is easy. You can tag or categorize an asset at creation time or when editing an existing asset. If you click on the \emph{Metadata} section of the form when creating or editing an asset, you'll find an interface for adding tags and categories. If no categories are available to be added to the asset (e.g., if no categories have been created), the \emph{Select} option doesn't appear. \begin{figure} \centering \includegraphics{./images/web-content-categorization.png} \caption{Here is the Web Content application's metadata section.} \end{figure} \noindent\hrulefill \textbf{Note:} You'll notice in Figure 1 above that there is also a \emph{Priority} field for web content. This field is not related to categories and tags, but rather, specifies the order in which the web content article is listed when displayed in the Asset Publisher. To learn more about the Asset Publisher, see the \href{/docs/7-2/user/-/knowledge_base/u/publishing-assets}{Publishing Assets} section. \noindent\hrulefill The Menu (\includegraphics{./images/icon-menu.png}) contains interfaces for managing tags and categories for each site in Liferay DXP. Navigate to the Site Administration menu → \emph{Categorization}, and you'll find the \emph{Tags} and \emph{Categories} options. These options can be used to manage all your site's tags and categories. It is important that you both tag and categorize your content when you enter it. You'll take a closer look at tags and categories next. \chapter{Tagging Content}\label{tagging-content} Tags are an important tool that can help organize information and make it easier for users to find the content they want. Tags are all-lowercase words or phrases that you can attach to any content. Tagging content makes your search results more accurate and enables you to use tools like the Asset Publisher to display content in an organized fashion on a web page. There are two ways to create tags: through the administrative console in the Site Administration section of the Menu or on the fly as content is created. By default, tags can be created by regular users and users can apply them to any assets they have permission to create or edit. Only site administrators can access the \emph{Tags} application in the Content section of the Site Administration area of the Menu. Here, site administrators can create new tags and edit any existing site tags: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the site you want to create tags for and click \emph{Categorization} → \emph{Tags}. From this screen, you can view existing tags and create new ones. \item To create a new tag, click the \emph{Add Tag} icon (\includegraphics{./images/icon-add.png}) and enter a name for the tag. \end{enumerate} \begin{figure} \centering \includegraphics{./images/new-tag-interface.png} \caption{The Add Tag interface is very simple, only requiring the name of your tag.} \end{figure} The process for adding tags during content creation is similar. For example, to create tags for a new web content article: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Metadata} dropdown in a New Web Content menu. \item Add tags \emph{lunar}, \emph{moon}, and \emph{spectacular}. \end{enumerate} Once you've created the web content with these tags, the web content is associated with those tag words when they are searched or referenced. Tags are not the only instance-wide mechanism for describing content: the next tutorial describes categories. \chapter{Defining Categories for Content}\label{defining-categories-for-content} Categories are similar in concept to tags, but are designed for use by administrators, not regular users. Hierarchies of categories can be created, and categories can be grouped together in \emph{vocabularies}. While tags represent an \emph{ad hoc} method for grouping content, categories exist to allow administrators to organize content in a more official, hierarchical structure. Think of tags like the index of a book and categories like its table of contents. Both serve the same purpose: to help users find the information they seek. You can add properties to categories. Category properties are a way to add information to specific categories. You can think of category properties as tags for your categories. Structurally, category properties are just like tag properties: they are key-value pairs associated with specific categories that provide information about the categories. Adding vocabularies and categories is similar to adding tags: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the site where you want to create categories. \item Click \emph{Categorization} → \emph{Categories} to view the Categories application. \end{enumerate} \begin{figure} \centering \includegraphics{./images/vocabulary-list.png} \caption{After adding new vocabularies, you'll notice your vocabularies indicate the amount of categories existing beneath them.} \end{figure} Clicking on a vocabulary displays categories that have been created under that vocabulary. To create a new vocabulary, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the \emph{Add Vocabulary} button (\includegraphics{./images/icon-add.png}). \item Enter a name and, optionally, a description. \item Click \emph{Save}. \end{enumerate} By default, the \emph{Allow Multiple Categories} option is enabled. This allows multiple categories from the vocabulary to be applied to an asset. If the box is disabled, only one category from the vocabulary can be applied to an asset. The \emph{Associated Asset Types} lets you choose which asset types the categories of the vocabulary can be applied to and which asset types are \emph{required} to have an associated asset from the vocabulary. Finally, you can configure the permissions of the vocabulary. By default, guests can view the vocabulary but only the owner can delete it, update it, or configure its permissions. Creating new categories is similar to creating new tags except that categories must be added to an existing vocabulary and they can only be created by site administrators. Once created, however, regular users can apply categories to any assets they have permission to create or edit. To create a new category: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Click the \emph{Add Category} icon (\includegraphics{./images/icon-add.png}). \end{enumerate} If you're already viewing a vocabulary: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to an existing vocabulary and select \emph{Add Category}. \item Enter a name for the new category and, optionally, a description. \item Click \emph{Save}. \end{enumerate} Just as with tags, you can configure the category's permissions, choosing which roles (guest, site member, owner) can view the category, apply it to an asset, delete it, update it, or configure its permissions. By default, categories are viewable by guests, and site members can apply categories to assets. Once you have created some vocabularies and categories, you can take advantage of the full capabilities of categories by creating a nested hierarchy of categories. To nest categories, select the \emph{Actions} button for the category you want to be the parent category. Then select \emph{Add Subcategory}, which adds a child category to the selected parent. After you've created a hierarchy of categories, they're available to apply to content: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on \emph{Web Content} in the \emph{Content \& Data} section of Site Administration and click \emph{Add} → \emph{Basic Web Content}. \item Click on \emph{Metadata} from the right-side menu and click \emph{Select} on the vocabulary you'd like to apply. A dialog box appears with your categories. \item Select relevant categories by checking the box next to them, and they'll be applied to the content. \end{enumerate} Suppose you're running a Lunar Resort shop called Lunar Fireworks and you have many web content articles describing the colors and types of fireworks you offer. The abundance of your articles is overwhelming, and as your shop grows, so too does the web content articles you're required to manage. You've decided to categorize your web content based on the color and type of firework, so the articles are easier to manage. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Site Administration → \emph{Categorization} → \emph{Categories} and create vocabularies \emph{Type} and \emph{Color}. \item Make sure both vocabularies are only used for web content articles by clicking the \emph{Associated Asset Types} dropdown and selecting \emph{Web Content Article}. \item Create categories \emph{Fire} and \emph{Smoke} for the Type vocabulary and \emph{Red}, \emph{Yellow}, and \emph{Blue} categories for the Color vocabulary. \item Now navigate to \emph{Content \& Data} → \emph{Web Content} in Site Administration and create an article called \emph{Red Rocket}. This is your best selling product, so make sure to give it a detailed explanation and an awesome picture. \item Select the \emph{Metadata} dropdown for your web content article and select the Type → Fire and Color → Red categories. \end{enumerate} When you publish your new web content article for your best selling product, it's organized by its type and color. Once you've organized all your articles, you'll always be able to reference the type and color of a firework, just in case you forget. There are a few other cool features for vocabularies and categories. A few of them were mentioned already when the \emph{Allow Multiple Categories} and \emph{Required} selectors for vocabularies and categories were discussed. The three new features are targeted vocabularies, single/multi-valued vocabularies, and separated widgets for every vocabulary. They're in the next tutorial. \chapter{Targeted Vocabularies}\label{targeted-vocabularies} You can decide which vocabularies can be applied to an asset type and which vocabularies are required for an asset type with Targeted Vocabularies. To configure these settings, go to the Categories application in Site Administration and select a vocabulary's \emph{Actions} → \emph{Edit} button. Select the \emph{Associated Asset Types} tab to reveal a dialog box like the one below. \begin{figure} \centering \includegraphics{./images/targeted-vocabularies.png} \caption{You can target vocabularies by checking the \emph{Allow Multiple Categories} selector and then selecting the Asset Types.} \end{figure} The default value for \emph{Associated Asset Types} is \emph{All Asset Types}. You can fine tune your choices by using the \emph{+} and \emph{-} buttons, which narrows the scope of the vocabulary to specific assets. In the screenshot above, notice that the vocabulary is configured to be available for Web Content articles and Blog entries, but it is not required. It is mandatory, however, for Bookmark entries. \section{Single and Multi-valued Vocabularies}\label{single-and-multi-valued-vocabularies} You can also decide if users can choose one or more categories from the same vocabulary to apply to an asset. If a vocabulary is single-valued you can only choose one. If it allows more, you can choose several categories from the vocabulary to apply to an asset. \begin{figure} \centering \includegraphics{./images/multi-valued-vocabularies.png} \caption{Multi-valued vocabularies allow multiple categories from the vocabulary to be applied to an asset. Single-valued vocabularies only allow one category from the vocabulary to be applied. Here, the \emph{Dining} and \emph{Nightlife} categories are selected to be applied but the \emph{Scenic Adventures} category is not.} \end{figure} You can configure the single-valued or multi-valued status of a vocabulary through the Categories application. Edit a vocabulary and deselect the \emph{Allow Multiple Categories} selector to create a single-valued vocabulary. Use the default option to create a multi-valued vocabulary. \section{Separated Entries}\label{separated-entries} A third feature of vocabularies and categories is that every vocabulary has its own separated entry. These entries appear in the Categorization section of the form for editing an asset, and they allow users to easily select appropriate categories for that asset. \begin{figure} \centering \includegraphics{./images/separated-entries.png} \caption{Vocabularies have their own entries, making it easy to select available categories.} \end{figure} It's important to use tags and categories with all your content, so that content is easier for users to find. Next, you'll learn how to geo-locate assets. \chapter{Geolocating Assets}\label{geolocating-assets} Geolocation adds the geographic coordinates where an asset was created as metadata to your assets. You can add geolocation metadata to your web content, Data Lists, and Documents \& Media. This feature is provided for you out-of-the-box. However, you must first enable it in your assets in order to use it. Let's examine how you can enable geolocation in your web content. \section{Geolocating Web Content}\label{geolocating-web-content} To use geolocation in your web content, you must create a \href{/docs/7-2/user/-/knowledge_base/u/designing-uniform-content}{structure and template} that includes a Geolocation field. \begin{figure} \centering \includegraphics{./images/geo-structure.png} \caption{Add a geolocation field to your structure to enable geolocation in your web content.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a structure with a Geolocation field like in the image above. \item Create a new template and select the structure you just created with the geolocation field. \item Scroll down to the \emph{Script} heading and locate the \emph{Fields} section. Here are \emph{Content} and \emph{Geolocation} snippets. \item Click on the snippets to add them to the template and \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-geolocation-template.png} \caption{Add the Content and Geolocation snippets to create your web content template quickly.} \end{figure} To set your location for the web content, you can share your location with the browser, type a specific address into the address bar on the map, or even drag the indicator and drop it in any point in the map and the address is automatically updated to reflect the new point. Once the web content is saved, the location is added as metadata to the web content. \begin{figure} \centering \includegraphics{./images/web-content-geo-create.png} \caption{You can enter your location in the address bar, move the indicator to a location, or share your location with the browser.} \end{figure} \noindent\hrulefill Note: Depending on your browser settings, you may need to configure it to share your location. \begin{figure} \centering \includegraphics{./images/share-location-dialog.png} \caption{Make sure your browser is configured to share your location.} \end{figure} \noindent\hrulefill \section{Geolocating Data Lists}\label{geolocating-data-lists} To use geolocation in your dynamic data lists, you must first create a data definition that includes a geolocation field. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Product Menu and navigate to \emph{Content \& Data} → \emph{Dynamic Data Lists}. \item Click the \emph{Options} menu and select \emph{Manage Data Definitions}. \item Click the \emph{Add} button to create a new data definition. \item Enter a name, optional description, and parent data definition if you have one. \item Scroll down and add a \emph{Geolocation} field to the data definition, along with any other fields you wish to add and \emph{Save}. \item Go back to the Dynamic Data Lists screen and click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to create a new list. \item Enter a name and optional description. \item Finally, click the \emph{Select} button and choose the newly created data definition. \end{enumerate} Now that your data list is complete, you can use the \href{/docs/7-2/user/-/knowledge_base/u/creating-data-lists}{Data List Display portlet} to display it. \section{Geolocating Documents and Media}\label{geolocating-documents-and-media} To enable geolocation in Documents and Media, you must first create a document type that includes geolocation metadata. You can add geolocation metadata as part of a Metadata Set or as part of the new document type. To add geolocation metadata as part of a Metadata Set: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Product Menu and navigate to \emph{Content \& Data} → \emph{Documents and Media}. Open the \emph{Options} menu, and select \emph{Metadata Sets}. \item Click the \emph{Add} (\includegraphics{./images/icon-add.png}) button and enter a name, optional description, and Parent Metadata Set if you have one. \item Scroll down and add a \emph{Geolocation} field, along with any additional fields you wish to have, and \emph{Save}. \end{enumerate} To create the new document type with geolocation: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Documents and Media}, open the \emph{Options} menu and select \emph{Document Types}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and enter a name and optional description. \item Scroll down to the Main Metadata Fields heading and add a \emph{Geolocation} field along with any other fields you wish to have for the document type. \item If you are using a Metadata Set, scroll down to the Additional Metadata Fields heading, click the \emph{Select Metadata Set} button. \item Choose your Metadata Set with the geolocation metadata and \emph{Save}. \item Navigate back to the \emph{Documents and Media} screen and click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select your newly created document type. \item Fill out the information for the document, and just as with the web content, your location is automatically obtained from the browser and added to your document. \end{enumerate} Once your assets are geolocation enabled, you can use the \href{/docs/7-2/user/-/knowledge_base/u/publishing-assets}{Asset Publisher} to display the location of the assets on a map, using the map display template. Check out the \href{/docs/7-2/user/-/knowledge_base/u/configuring-display-settings}{Configuring Display Settings} section to learn more. \begin{figure} \centering \includegraphics{./images/geo-map.png} \caption{The Asset Publisher can display your geolocated assets on a map.} \end{figure} \chapter{Publishing Content Dynamically}\label{publishing-content-dynamically} Most content types are Assets. In the \href{/docs/7-2/user/-/knowledge_base/u/creating-web-content}{Creating Web Content} articles, you examined the most common type of asset: web content. Other types of assets include blog posts, wiki articles, message board posts, bookmarks, and documents. Developers can define custom asset types that use the asset framework, which provides support for tags, categories, vocabularies, comments, ratings, and asset relationships. The Asset Publisher application displays assets. It has many configuration options which you'll cover in this chapter. By default, Asset Publisher displays abstracts (previews) of recently published assets with links to their full views. You can configure the Asset Publisher app to display a table of assets, a list of asset titles, or the full content of assets. You can also make it display only certain kinds of assets, and you choose how many items to display in a list. You might use Asset Publisher to display chosen content types, recent content, or content by tags and categories. This section covers the following topics: \begin{itemize} \tightlist \item Adding relationships between assets \item Publishing assets \item Publishing RSS feeds \item Restoring deleted assets \end{itemize} The first thing you'll learn about is tagging and categorizing content. \chapter{Defining Content Relationships}\label{defining-content-relationships} Related Assets are assets connected to other assets, even if they don't share any tags and aren't in the same category. Here you'll focus on how to define relationships between assets so when you begin publishing assets, the Related Assets widget can display those relationships. \section{Related Assets Widget}\label{related-assets-widget} By default, the Related Assets widget displays any related asset of the asset selected in the Asset Publisher. If you don't want to show every related asset, you can configure what content relationships to display. To do this, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Related Assets app and select the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the upper right corner of the application and click \emph{Configuration}. \item Under the \emph{Setup} → \emph{Asset Selection} tab, set the type of asset(s) to display using the \emph{Asset Type} menu. The default value is set to \emph{Any}. \item You can narrow the scope of the app to display any single category of asset type or select multiple assets from the menu. Filter options set minimum requirements for displaying assets by their categories, tags, and custom fields. Ordering and Grouping organizes assets using the same criteria. Display settings customize how the app shows assets: by title, in a table, by abstract, or full content. You can convert assets to different document types like ODT, PDF, and RTF. You can choose to show metadata fields such as author, modification date, tags, and view count. You can even enable RSS subscriptions and customize their display settings. \item When you're finished setting the Source and Filter options, click \emph{Save}. \end{enumerate} Now that you've configured the Related Assets widget to display specific content types, you must define the relationships for your assets. Here's a simple example of defining related assets for a web content article and then displaying those related assets. Suppose you own a gift shop at the Lunar Resort, and you want all your shop's assets to appear when an asset is clicked. You must define relationships between your content, so when an asset is clicked, its related assets are appear alongside the clicked asset. Here's how to do it: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a blog entry explaining your gift shop's new apparel and a photo of the moon, just so consumers are aware that you offer the \emph{only} gift shop on a desolate rock orbiting the Earth! \item Create a web content article describing your shop. Once you've given your article a title and some content, open the \emph{Related Assets} dropdown menu. Click \emph{Select}, choose \emph{Blogs Entry}, and select the blog you created. Click \emph{Select} again, choose \emph{Basic Document}, and select the photo of the moon. Click \emph{Publish} to publish your web content article. \item Now that those assets are created, you can relate the blog entry and photo to your web content article. Navigate to your article in Site Administration → \emph{Content \& Data} → \emph{Web Content}. \item You've now defined relationship with your three assets. Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) at the top of your page in the Control Menu, select \emph{Widgets}, and add the Related Assets and Asset Publisher widgets to the page. Don't panic: related assets don't appear until you select an asset in the Asset Publisher. \end{enumerate} \begin{figure} \centering \includegraphics{./images/related-assets-app-1.png} \caption{Select an asset in the Asset Publisher to see its related assets displayed in the Related Assets application.} \end{figure} Once you select an asset, its related assets appear in the Related Assets app, as in the image above. If you want more detail, you can place two Related Assets widgets on the page and name one \emph{Related Blogs} and the other \emph{Related Photos}. \begin{figure} \centering \includegraphics{./images/related-assets-app-2.png} \caption{Related Assets applications can be configured to display specific content.} \end{figure} Next, you'll learn more about how to use the Asset Publisher. \chapter{Publishing Assets}\label{publishing-assets} As you create web content, remember that pieces of content are assets, just like message board entries and blog posts. Since the Asset Publisher publishes assets, it excels at publishing mixed content types like images, documents, blogs, and of course, web content. This helps in creating a more dynamic web site: you can place user-created wiki entries, blog posts, or message board messages in context with your web content. You'll examine some of the Asset Publisher's features next. \chapter{Querying for Content}\label{querying-for-content} The Asset Publisher works by querying for mixed types of content on the fly. Since you can control what and how content is displayed from one location, the Asset Publisher helps to ``bubble up'' the most relevant content to your users. To get to all the application's options, click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the application's menu. If you click the \emph{Configuration} option and then \emph{Setup} (if necessary), you can configure the Asset Publisher's settings from the following three areas: \begin{itemize} \tightlist \item Asset Selection \item Display Settings \item Subscriptions \end{itemize} Asset Selection configures which assets are displayed. You can set asset selection to \begin{itemize} \tightlist \item \emph{Dynamic} \item \emph{Manual} \item \emph{Content Set} \item \emph{Content Set Provider} \end{itemize} Dynamic displays assets based on certain rules or filters. For example, you can set the Asset Publisher to display only assets of a certain type or to which certain tags or categories have been applied. Manual asset selection only displays assets that have been explicitly selected by an administrator. For more information on Content Sets and their place in the Asset Publisher, see the \href{/docs/7-2/user/-/knowledge_base/u/content-sets}{Managing Content Sets} section. The Asset Publisher supports a scope that restricts both dynamic and manual asset selection. The Asset Publisher can only display assets from its configured scope. By default, the Asset Publisher app is scoped to the site of the page to which it was added. You can, however, customize the scope from the Asset Selection section of the Asset Publisher configuration window. To extend your Asset Publisher's scope, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Select} under Scope. \item Choose \emph{Global} to add the global scope or \emph{Other Site\ldots{}} to add the scope of another site. \end{enumerate} The Display Settings section of the Asset Publisher configuration window is for customizing how content is displayed. The Subscription section enables, disables, or configures email subscriptions and RSS subscriptions. In the following sections, you'll explore the available configurations for the Asset Selection, Display Settings, and Subscriptions sections of the Asset Publisher's configuration window. You'll start by learning how select content manually. You'll see that it's very similar to using the Web Content Display application except that you can select assets of any type, not just web content articles. \chapter{Selecting Assets}\label{selecting-assets} You can configure Asset Publisher to select assets manually or dynamically through various criteria. Within those options there is flexibility in what assets are displayed and how they are displayed. \section{Selecting Assets Manually}\label{selecting-assets-manually} To enable manual asset selection, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the widget's menu. \item Select \emph{Configuration}. \item In the Asset Publisher configuration, select \emph{Manual} from the select box beneath \emph{Asset Selection}. \end{enumerate} Now you must select a \emph{Scope} and specific \emph{Asset Entries} from that scope to display. You can configure multiple scopes, including the global scope, from which to select assets. \begin{figure} \centering \includegraphics{./images/web-content-asset-publisher-manual.png} \caption{Selecting assets in the Asset Publisher manually is similar to selecting assets in the Web Content Display application except that you can select assets of any type, not just web content. You can also add scopes to expand the list of assets that are available to be displayed in the Asset Publisher.} \end{figure} When selecting assets manually, a list of configured scopes appears under the Scope heading. You can configure the scope like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{X} button at the right to remove a scope from the list. \item Click the \emph{Select} button to add additional scopes to the Asset Publisher's configuration. \item After you've added a scope, a new Select button appears under the Asset Entries heading. A list of assets selected for display appears in the Asset Entries section. You can select assets to be displayed by clicking the appropriate \emph{Select} button. One button appears for each configured scope. By default, these are the available asset types: \begin{itemize} \tightlist \item Blogs Entry \item Bookmarks Entry \item Bookmarks Folder \item Calendar Event \item Basic Document \item Google Drive Shortcut \item Documents Folder \item Dynamic Data Lists Record \item Knowledge Base Article \item Message Boards Message \item Basic Web Content \item Web Content Folder \item Wiki Page \end{itemize} You can select any number of assets to be displayed. Note, however, that there's a display setting called \emph{Number of Items to Display} that determines the maximum number of items to display (or, if pagination is enabled, the maximum number of items to display per page). The Asset Publisher can mix and match different asset types in the same interface. \item When you're done selecting items to display, click \emph{Save}. Any selected assets are added to the list of assets that are displayed by the application. \end{enumerate} Once you have your content selected, you can configure the display types to configure how the content appears. You'll explore the display settings in more detail after we finish discussing how to select assets for display. Manual asset selection lets you select assets of various types from different scopes, but it can be time-consuming to update the assets that should be displayed. It's often more convenient to use the Asset Publisher to select content dynamically. \section{Selecting Assets Dynamically}\label{selecting-assets-dynamically} The Asset Publisher's default behavior is to select assets dynamically according a set of customizable rules. These rules can be combined so that they compliment each other to create a nice, refined query for your content. Assets are filtered by permissions automatically, no matter how complicated your asset selection rules are. You have the following rule types: \textbf{Scope:} Choose the sites containing the content that should be selected. This works the same way as with manual asset selection: assets can only be displayed if they belong to a configured scope. The following scope options are available: \begin{itemize} \tightlist \item \emph{Current Site} \item \emph{Global} \item \emph{Other Site} \end{itemize} The Other Site scope option is unavailable for Asset Publisher applications configured on a page template (e.g., Content Display Page). \textbf{Asset Type:} Choose the asset types you want, from all assets, to only one, or any combination in between. For example, you could choose only web content, only wiki entries, or any combination of multiple types. \textbf{Filter:} Add as many filters on tags or categories as you like. You can choose whether the content must contain or must not contain any or all of the tags or categories that you enter. \begin{figure} \centering \includegraphics{./images/web-content-asset-publisher-filter.png} \caption{You can filter by tags and categories, and you can set up as many filter rules as you need.} \end{figure} Once you've set up your filter rules for dynamically selecting content, you can decide how the content is displayed. If you've added custom User profile attributes, you can configure the Asset Publisher to display assets that match them. This setting retrieves assets that have matching categorization. These categories must be from the global context. For example, suppose a User has a custom field called \emph{Location} with the type \emph{Text}. If this attribute is set to \emph{Moon}, you could create a vocabulary called \emph{Location} and a category for the Location vocabulary called \emph{Moon}. Then you could categorize content with \emph{Moon} in the \emph{Location} vocabulary. With this organizational setup, adding an Asset Publisher and specifying \emph{Location} as the Asset Publisher's custom user attribute would only display content that had been categorized as \emph{Moon}. Pretty cool, right? See \href{/docs/7-2/user/-/knowledge_base/u/defining-categories-for-content}{Defining Categories for Content} for further information. In addition, you can use these advanced filters: \begin{itemize} \tightlist \item \textbf{Show only assets with} \textbf{\emph{Welcome}} \textbf{as its display page} displays only assets specifically configured for the \emph{Welcome} page. \item \textbf{Include tags specified in the URL?} lets you specify tags in the URL for the Asset Publisher to display. \end{itemize} The \emph{Ordering} section of the Asset Publisher precisely controls how content is ordered and grouped when displayed. You can order the assets displayed by Asset Publisher in ascending or descending order by the following attributes: \begin{itemize} \tightlist \item Title \item Create Date \item Modified Date \item Publish Date \item Expiration Date \item Priority \end{itemize} Say you have a series of ``How To'' articles that you want displayed in descending order based on whether the article was tagged with the \emph{hammer} tag. Or suppose you want a series of video captures to appear in ascending order based on a category called \emph{birds}. For these use cases, you can configure the ordering and grouping settings. You can also configure a second ordering. The second ordering is applied to any assets for which the first ordering wasn't sufficient. For example, if you ordered assets by title and there are multiple assets with the same title, the second ordering takes effect, perhaps the publication date. You can establish grouping rules as well as ordering rules. You can group assets by type or by vocabulary. For example, suppose you have a vocabulary called \emph{Membership Type} with two categories: \emph{Premium} and \emph{Regular}. If you group assets by Membership Type, all assets with the Premium category appear in one group and all assets with the Regular category appear in another group. Grouping rules are applied before any ordering rules: they're a way to divide up the displayed assets into separate lists. The ordering rules are applied separately to each group of assets. Note that ordering rules are only one way to control how your content appears. You can refine the display through many other display settings which you'll examine next. \noindent\hrulefill \textbf{Note:} The following actions have immediate effects in your Asset Publisher: \begin{itemize} \tightlist \item Change the value of the \emph{Asset Selection} option. \item Change the value of the \emph{Scope} option. \item Select, add, sort or delete asset entries (only when selecting assets manually). \end{itemize} \noindent\hrulefill Other changes happen after clicking \emph{Save}. Next you'll learn about the Asset Publisher's other configuration options. \section{Selecting a Content Set}\label{selecting-a-content-set} 7.0 adds the Content Set feature to create predefined lists of content to display in the Asset Publisher. To use a Content Set, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the Asset Publisher's menu. \item Select \emph{Configuration} from menu. \item Select \emph{Manual} from the select box beneath the \emph{Asset Selection} tab. \item Choose the Content Set that you want to use. \end{enumerate} For more information on using Content Sets, see \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets}. \chapter{Configuring Display Settings}\label{configuring-display-settings} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} From the Asset Publisher's configuration page, open the Setup tab's \emph{Display Settings} sub-tab. This section gives you precise control over the display of your assets. There are many options available to configure how you want your content to appear. Many of these, such as printing, flags, ratings, comments, comment ratings, and social bookmarks work the same way they do in the Web Content Display application. \textbf{Display Template}: This selector lets you choose an application display template to customize how the Asset Publisher displays assets. These templates are in every site by default: \begin{itemize} \tightlist \item \emph{Abstracts:} Shows the first 200-500 characters of the content, defined by the \textbf{Abstract Length} field. This is the default display template. \item \emph{Table:} Displays the content in an HTML table which can be styled by a theme developer. \item \emph{Title List:} Displays the content's title as defined by the user who entered it. \item \emph{Full Content:} This display template displays the entire content of the entry. \end{itemize} There's also the \emph{Rich Summary} and \emph{Map} display templates that belong to the global scope. The Rich Summary template provides a summary view of each asset along with a \emph{Read More} link to the article's full content. The Map template displays \href{/docs/7-2/user/-/knowledge_base/u/geolocating-assets}{geo-localized assets} in either a Google Map or an Open Street Map provider. The map provider can be configured in Instance Settings, and Site Settings in the Advanced section. \textbf{Abstract Length}: Select the number of characters to display for abstracts. The default is \texttt{200}. \textbf{Asset Link Behavior:} The default value is \emph{Show Full Content}, which displays the full asset in the current Asset Publisher. \emph{View in a Context} causes that asset to be displayed in the application where the asset belongs. For example, a blog entry is displayed in Blogs where it was created. See the section below on display pages for more information. \textbf{Number of Items to Display}: Select the maximum number of assets that can be displayed by the Asset Publisher. If pagination is enabled, this number represents the maximum number of assets that can be displayed per page. \textbf{Pagination Type}: This can be set to \emph{None}, \emph{Simple}, or \emph{Regular}. \emph{None} displays at most the number of assets specified in the \textbf{Number of Items to Display} property. \emph{Simple} adds Previous and Next buttons for browsing through pages of assets in the Asset Publisher. \emph{Regular} adds more options and information including First and Last buttons, a dropdown selector for pages, the number of items per page, and the total number of results (assets being displayed). \textbf{Show Add Content Button}: When selected, an \emph{Add New} button appears that lets users add new assets directly from the Asset Publisher application. This is checked by default. \textbf{Show Metadata Descriptions:} Enables Metadata descriptions such as \emph{Content Related to\ldots{}} or \emph{Content with tag\ldots{}} to be displayed with the published assets. \textbf{Show Available Locales:} Since content can be localized, you can have different versions of it based on locale. Enabling this option shows the locales available, so users can view the content in their languages. \textbf{Set as the Default Asset Publisher for This Page}: The Asset Publisher app is an instanceable app: multiple Asset Publishers can be added to a page and each has an independent configuration. The default Asset Publisher for a page is the one used to display web content associated with the page. \textbf{Show only assets with Home as its display page template:} Display assets that only exist on the Home page template. \textbf{Include tags specified in the URL:} Incorporate tags specified in the URL. \textbf{Enable \ldots{}}: Enable/disable the following options for displayed assets: \begin{itemize} \tightlist \item Print \item Flags \item Ratings \item Related Assets \item Subscribe \item Comments \item Comment Ratings \item View Count Increment \end{itemize} The Print option adds a \emph{Print} link to the full view of an asset displayed in the Asset Publisher. Clicking \emph{Print} opens a new browser window with a print view of the asset. Enabling flags, related assets, ratings, comments, comment ratings, or social bookmarks add links to the corresponding social features to the view full of the asset in the Asset Publisher. \noindent\hrulefill \textbf{Tip:} An alternate way to add flags, comments, and ratings to a page is through the \emph{Page Flags}, \emph{Page Comments}, and \emph{Page Ratings} applications. Just add the applications in the appropriate location near the asset that should have feedback. \noindent\hrulefill \textbf{Metadata:} Select various metadata types to be displayed (see below). For example, you can select tags and categories for display. Upon saving your configuration, the Asset Publisher displays tags and categories for each displayed asset. Then users can click on the tags and categories to filter the displayed assets manually. \begin{figure} \centering \includegraphics{./images/available-metadata-fields.png} \caption{You can configure the Asset Publisher to display various kinds of metadata about the displayed assets.} \end{figure} Next you'll learn about configuring subscriptions for email and RSS through the Asset Publisher. \chapter{Configuring Asset Publisher Subscriptions}\label{configuring-asset-publisher-subscriptions} The Asset Publisher application supports two kinds of subscriptions email subscriptions. To enable subscriptions, click the Asset Publisher's Options icon and select \emph{Configuration}. In the configuration window, open the Setup tab's Subscriptions tab. There you can enable/disable the \emph{Enable Email Subscription} selector. \begin{figure} \centering \includegraphics{./images/asset-publisher-email.png} \caption{An email subscription notifies users when new assets are published.} \end{figure} Enabling Email Subscription adds a \emph{Subscribe} link to the Asset Publisher. Users wishing to be notified of newly published assets can click on this link to be added to the subscription list. Liferay DXP periodically checks for new assets and sends emails to subscribed users informing them about the new assets. By default, Liferay performs this check every twenty-four hours. \chapter{Publishing RSS Feeds}\label{publishing-rss-feeds} RSS is a family of web feed formats used to publish frequently updated works such as blog entries and news articles. RSS allows users to stay up-to-date with your site's content without actually having to visit your site. Users can use their own RSS feed readers to aggregate content, and you can also use RSS to share and aggregate content across sites. Next, you'll see how to create RSS feeds from Asset Publisher configurations. \chapter{Configuring RSS Feeds}\label{configuring-rss-feeds} \noindent\hrulefill \textbf{Note:} RSS feeds are deprecated for Liferay DXP 7.2 and are disabled by default. To leverage RSS feeds, you must enable this feature. Go to the Control Panel → \emph{Configuration} → \emph{System Settings} → \emph{Web Content}. Under the \emph{System Scope} → \emph{Administration} tab, check the \emph{Show Feeds} box. For more information on deprecated apps, see \href{/docs/7-2/deploy/-/knowledge_base/d/deprecated-apps-in-7-2-what-to-do\#web-experience}{this article}. \noindent\hrulefill To manage a Liferay site's RSS feeds, navigate to your Site's Site Administration → \emph{Content \& Data} → \emph{Web Content}. Site administrators can use this Web Content menu option to manage their site's web content, including web content structures and templates, which you learned in the \href{/docs/7-2/user/-/knowledge_base/u/creating-web-content}{Creating Web Content} section. Site administrators can also use this option to manage their site's RSS feeds. To add a new feed: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Feeds} tab. \item Click the \emph{Add Feed} button. \item Enter a \emph{Name}, select a \emph{Target Page}, and select a \emph{Web Content Structure} for the feed. \end{enumerate} A feed's target page serves two purposes: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The site the target page belongs to determines which web content articles appear in the feed. For example, if the target page belongs to the Marketing site, only web content articles belonging to the Marketing site appear in the feed. \item The target page is the page where ``orphaned'' web content articles are displayed. Orphaned web content articles have been published in your Site but are not displayed in specific Web Content Display applications. Liferay RSS feeds can provide links to any published web content articles, both orphaned articles and articles that have been configured to be displayed in specific Web Content Display applications. For articles that have been configured to be displayed, the RSS feeds' links point to the Liferay page of that app. For orphaned articles, the RSS feeds' links point to the feed's target page. When users click on such links for orphaned articles, the full content of the orphaned article is displayed on the target page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/web-content-new-feed.png} \caption{To create a new RSS feed, you only need to specify a name, target page, and web content structure. Of course, you can also configure other features of the feed such as its permissions, web content constraints, and presentation settings.} \end{figure} To specify a target page, you must enter the target page's friendly URL. Note that friendly URLs don't include the host name. For example, the friendly URL of a public page called \emph{Welcome} belonging to a Site called \emph{Marketing} might look like this: \texttt{/web/marketing/welcome}. Optionally, you can specify a target portlet ID. This would be the portlet ID of a Web Content Display application on the target page in which orphaned web content should be displayed. The application must exist or else the content isn't displayed. The URL field contains the address of your RSS feed. It appears after you've actually created the feed by clicking \emph{Save}. The final two sections of the \emph{Add Feed} form are for customizing the web content articles that appear in your feed. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Web Content Constraints selects a web content structure to filter the articles that appear in your feed. This is useful since all web content articles are created using web content structures. \item Presentation Settings customizes additional details about your feed and how articles are displayed in your feed. Leave the Feed Item Content set to \emph{Web Content Description} if you want a description of each article to appear in your feed. Set it to \emph{Rendered Web Content: Use Default Template} if you want the full content of each article to appear in the feed. Customizing the Feed Type allows you to choose which web feed language to use for your feed. You can choose \emph{Atom 1.0} (the default), \emph{RSS 1.0}, or \emph{RSS 2.0}. Customize the \emph{Maximum Items to Display} to choose the maximum number of articles should appear in your feed at one time. Leave the Order By Column set to \emph{Modified Date} to have articles arranged in order from the last time they were published or modified. You can set the Order by Column to \emph{Display Date} if you want to have articles arranged in order from the time they were configured to be displayed in a specific Web Content Display application. Lastly, you can leave the Order by Type set to \emph{Ascending} to have the oldest articles at the top of the feed or you can set it to \emph{Descending} to have the newest articles at the top of the feed. \end{enumerate} When you're done configuring your RSS feed, click \emph{Save} to create your feed. Once one or more feeds have been created, they'll appear in a list in the Feeds tab. You can edit existing feeds using the same form used for creating them. The main difference is that when you edit an existing feed, the URL field is populated. Copy this URL into a new browser tab or window to test your feed. From the Feeds popup window, you can also customize the permissions of feeds or delete feeds. It's possible to completely disable RSS feeds at the instance level. You can do this by setting the \texttt{rss.feeds.enabled} property to \texttt{false} in your \texttt{portal-ext.properties} file. By default, it's set to \texttt{true}. If you keep the default, RSS enabled, you can make several other RSS property customizations. Please refer to the \href{https://docs.liferay.com/ce/portal/7.2-latest/propertiesdoc/portal.properties.html\#RSS}{RSS section} of your \texttt{portal.properties} file for details. \chapter{The RSS Publisher Widget}\label{the-rss-publisher-widget} The RSS Publisher widget displays RSS feeds. If you're looking for a web-based RSS reader, look no further: just add the RSS Publisher widget to one your personal Site's private pages, and \emph{voila}! You have your own personal RSS reader. You can select the RSS feeds the widget displays and how it displays them. The RSS Publisher widget can also be placed on Sites' public or private pages to make feeds available to guests or Site members, respectively. In these cases, make sure that only Site administrators have permission to customize the RSS widget and select feeds to be displayed. \begin{figure} \centering \includegraphics{./images/rss-widget-default-view.png} \caption{The RSS Publisher widget lets you display RSS feeds of your choosing.} \end{figure} \textbf{Note:} If you run your server behind a proxy, you must set the appropriate Java proxy settings (such as \texttt{http.proxyHost=} and \texttt{http.proxyPort=}) in your \texttt{setenv} script or in your \texttt{system-ext.properties}. Without these properties, the RSS Publisher widget can't access any RSS feeds. Note that the RSS Publisher widget is deprecated. In Liferay CE Portal 7.1 GA2+, and Liferay DXP 7.1 FP4+, the widget is available from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{News} menu. However, the widget is hidden in earlier releases of Liferay CE Portal 7.1 and Liferay DXP 7.1. In these releases, you must therefore make the widget visible via a configuration file. The next section shows you how to do this. \section{Using the RSS Publisher Widget}\label{using-the-rss-publisher-widget} You can add the RSS Publisher widget to a page from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{News} menu. Once you've done so, open the widget's Configuration menu by clicking on the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) at the top-right corner of the widget and selecting \emph{Configuration}. \begin{figure} \centering \includegraphics{./images/rss-widget-config.png} \caption{The RSS Publisher widget's configuration lets you customize how the widget displays RSS feeds.} \end{figure} \begin{figure} \centering \includegraphics{./images/rss-widget-config-feeds.png} \caption{You can also use the RSS Publisher widget's configuration to specify which feeds to display.} \end{figure} By default, the RSS Publisher widget displays one feed. In the \emph{Feeds} section, add or remove a feed via the plus or minus buttons, respectively. To add a feed, enter its URL and title in the respective fields. If you leave the \emph{Title} field blank, the feed's default title is used (the \emph{Title} field is for custom titles). In the top section, use the following toggles to enable/disable the display of the feed's details: \begin{itemize} \tightlist \item Show Feed Title \item Show Feed Published Date \item Show Feed Description \item Show Feed Image \item Show Feed Item Author \end{itemize} You can also select the number of entries and expanded entries that should be displayed per feed. Expanded entries show more of an article's actual content than regular entries. By default, each feed shows four entries per feed and eight expanded entries per feed. You can set the feed image alignment to control whether feed images appear to the right or left of the text. By default, the feed image alignment is set to \emph{Right}. \chapter{Restoring Deleted Assets}\label{restoring-deleted-assets} Have you ever had that life-altering experience where you deleted an important file and immediately regretted deleting it? The deed is usually followed by a palm to the forehead or a sick feeling. Good news! Liferay DXP is here to turn that frown upside down with the \emph{Recycle Bin} feature. With the Recycle Bin, the \emph{Move to the Recycle Bin} action replaces \emph{Delete} for certain asset types. Content is now temporarily stored in the Recycle Bin. This allows the content to be restored back to its original state. Recycled items can expire after a certain period of time, resulting in their permanent deletion. Before diving into how the Recycle Bin works, you'll look at how to configure it. \chapter{Configuring the Recycle Bin}\label{configuring-the-recycle-bin} The Recycle Bin supports instance-wide scope or site-specific scope. The instance-wide scope of the Recycle Bin is set by adding the \texttt{trash.enabled} property to your \texttt{portal-ext.properties} file. By default, the Recycle Bin is enabled instance-wide. You'll go into more detail for adding this property and several others to your properties file later in the section. First, you'll explore the UI and see what the Recycle Bin can do. First, you'll configure the Recycle Bin for site-specific scoping. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Choose the Site you'd like configure for the Recycle Bin from the Site Administration menu. \item Click \emph{Configuration} → \emph{Settings}. \item Next, select the top \emph{Advanced} tab and click \emph{Recycle Bin}. You'll notice a few configurable options. \textbf{Enable Recycle Bin:} enable and disable settings for the Recycle Bin's site-specific scope. \textbf{Trash Entries Max Age:} customize the number of minutes a file is kept in the Recycle Bin until its permanent deletion (default is 43200 minutes, or 30 days). \begin{figure} \centering \includegraphics{./images/recycle-bin-site-settings.png} \caption{The Recycle Bin offers several configurable options for your site.} \end{figure} \item When you've finished configuring your Recycle Bin settings, click \emph{Save}. \end{enumerate} \noindent\hrulefill \textbf{Note:} If you disable the Recycle Bin while it's still holding recycled items, the recycled items remain stored and reappear in the Recycle Bin if it is re-enabled. \noindent\hrulefill You can also configure the Recycle Bin via properties in the \texttt{portal.properties} file. Remember that it's a best practice not to edit the \texttt{portal.properties} directly, but to create a separate \texttt{portal-ext.properties} file containing the properties to override. There are some additional options not available in the GUI that you can set: \texttt{trash.search.limit=500}: set the limit for results used when performing searches in the Recycle Bin (default is 500). \texttt{trash.entry.check.interval=60}: set the interval in minutes for how often the trash handler runs to delete trash entries that have been in the Recycle Bin longer than the maximum age (default is 60). Also, as was mentioned earlier, there are properties to enable the Recycle bin instance-wide and set trash entries' maximum age. \texttt{trash.enabled=true}: set this property to \emph{false} to disable the Recycle Bin for all sites in the portal (default is \emph{true}). \texttt{trash.entries.max.age=43200}: set the number of minutes trash entries should be held before being permanently deleted. Visit the \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Trash}{portal.properties} file to view all of the configurable properties for the Recycle Bin. Next, you should make sure permissions are set properly for users who can handle/view the assets in the Recycle Bin. Users who had \emph{View} permissions on a document when it was recycled can also view that document in the Recycle Bin. Users who had \emph{Update} or \emph{Delete} permissions on a document when it was recycled can restore the document. Now that you've successfully configured the Recycle Bin, you'll look at how to use it. \chapter{Using the Recycle Bin}\label{using-the-recycle-bin} The Recycle Bin is temporary storage where assets go when you delete them. You can recycle several different types of assets: \begin{itemize} \tightlist \item Blogs \item Bookmarks \item Documents and Media \item Message Boards (and attachments) \item Web Content \item Wiki (and attachments) \end{itemize} \begin{figure} \centering \includegraphics{./images/recycle-bin-overview.png} \caption{The Recycle Bin provides a seamless administrative experience for deleting and removing content.} \end{figure} \noindent\hrulefill \textbf{Note:} Attachments added to Wiki and Message Board entries do not go to the Recycle Bin when they are deleted. They can be restored in a similar fashion from the \emph{Removed Attachments} menu within the application. \noindent\hrulefill To demonstrate using the Recycle Bin let's delete a web content article and then restore it. You'll run through two different methods of restoring the file. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Site Administration and select \emph{Content \& Data} → \emph{Web Content}. \item Select the \emph{Add} button (\includegraphics{./images/icon-add.png}) and click \emph{Basic Web Content}. \item Enter some text for the Title and Content and click \emph{Publish}. \item Click the article's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and click \emph{Move to the Recycle Bin}. Note that the \emph{Delete} button is not listed. Liferay DXP avoids the risk of accidental deletion of your files by funneling the content through the Recycle Bin. \item After deleting the file, a success message appears, offering an \emph{Undo} option. Click \emph{Undo}. The web content is retrieved from the Recycle Bin and stored in its original place. \item Click \emph{Move to the Recycle Bin} again. \item Go back to Site Administration and click Recycle Bin from the Content dropdown. \item Find your sample web content and click its \emph{Actions} button. \item You can restore or delete the content. Select \emph{Restore}. \begin{figure} \centering \includegraphics{./images/recycle-bin-restore.png} \caption{In the Recycle Bin, you have the option of restoring or permanently deleting the content.} \end{figure} \item Navigate back to the Web Content screen and notice that your sample web content is back to its original place. \end{enumerate} That covers the two general processes of sending and restoring content to/from the Recycle Bin. For other asset types, the Recycle Bin works similarly. Some applications, such as Web Content and Documents and Media, support folders for organizing content. You can also send folders to the Recycle Bin. Keep in mind that this sends any sub-folders of the deleted folder all the files it contains to the Recycle Bin. Folders are restored and deleted the same way as a single file. \emph{Delete} within the Recycle Bin is the permanent delete button. Once you select this, your file cannot be retrieved and is gone forever. There is also an \emph{Empty the Recycle Bin} option accessible from the (\includegraphics{./images/icon-options.png}) button at the top of the Recycle Bin screen. This permanently deletes all the files from the Recycle Bin. \section{Drag and Drop}\label{drag-and-drop} You can also drag and drop items into the Recycle Bin. While you're in the Control Panel, select an asset and drag it to the Recycle Bin portlet on the Control Panel menu. When you click and begin dragging the asset, a message appears near your cursor notifying you of the number of files ready to be moved, and the Recycle Bin is highlighted, showing you where the files can be dropped. After you drop the asset onto the Recycle Bin portlet, the asset is removed from its original location and transferred to the Recycle Bin. \begin{figure} \centering \includegraphics{./images/recycle-bin-drag.png} \caption{A quick and easy way of disposing your items is the drag and drop method.} \end{figure} Awesome! You now know how to use the Recycle Bin! \chapter{Recycle Bin Intelligence and Support}\label{recycle-bin-intelligence-and-support} Have you ever wondered what happens to file shortcuts if their linked assets are recycled? What if you restore a file that has the same name as another file currently stored in your site/instance? The Recycle Bin already knows how to handle these types of issues. When documents with shortcuts are moved to the Recycle Bin, the shortcuts are removed. This ensures that all your links and shortcuts work and cuts down on maintenance time and backtracking. Another important trait how recycled content is managed with the \href{/docs/7-2/user/-/knowledge_base/u/staging}{Staging} framework. Although you there is only one master Recycle Bin for all asset types, when staging is enabled a \emph{Staging} Recycle Bin is created. The original Recycle Bin, or \emph{Live} Recycle Bin, is still viewable while in staging; however, it is never used. During staging, everything you recycle is sent to the Staging Recycle Bin. This prevents staged and unstaged recycled content from mixing. For example, if you have an unstaged document currently on your live site you can enable staging and delete that document. If you were to turn staging off and return to the live site, without separate Recycle Bins, the live document would be both on your site and in the Recycle Bin! Because of this, the separate Staging Recycle Bin is necessary and only used during the staging process. When you publish your staged material, the Staging Recycle Bin content is transferred to the Live Recycle Bin. \noindent\hrulefill \textbf{Note:} The Staging Recycle Bin saves its contents until the staged material has been published to the live site. This means that you can turn the staging mode on and off without losing your recycled material. \noindent\hrulefill The Recycle Bin saves you time by letting you restore content that's been recycled. Instead of recreating or re-uploading content, you'll be tailoring your Liferay instance to fully leverage its capabilities. \chapter{Collaboration}\label{collaboration} Liferay DXP contains an expansive collaboration suite that empowers users to create content and communities that they couldn't create alone. A robust document management system is a key component of this suite. As users produce digital assets---documents, videos, audio---they can store and share them using the Documents and Media Library. Documents and Media supports file check in and check out to prevent conflicting edits from multiple users, and maintains a version history of those files. It also contains its own repository, and for added flexibility can connect to external repositories. Once files exist in Documents and Media, users can insert them in other content like blog posts and wiki articles. \begin{figure} \centering \includegraphics{./images/dm-images-in-admin.png} \caption{You can use the Documents and Media Library to manage and use documents in the portal.} \end{figure} The collaboration suite also contains apps that let users share information and create active communities. The Message Boards app gives users a platform for discussions. The Blogs app lets users publish their ideas using rich content. Notifications keep users informed of what's happening. Social networking apps let users connect and share in ways that bolster friendship and productivity. And this is just scratching the surface---there are many more apps that help users communicate, produce, and present. \begin{figure} \centering \includegraphics{./images/blog-entry-abstract.png} \caption{You can also make your blog entries look great.} \end{figure} The guides that follow show you how to leverage these features, and more, in detail. \begin{figure} \centering \includegraphics{./images/message-boards-category-threads.png} \caption{The Message Boards app is fantastic for facilitating discussions.} \end{figure} \chapter{Managing Documents and Media}\label{managing-documents-and-media} The Documents and Media library stores files on the server using the same type of structure that you use to store files locally. It accepts files of any kind, can serve as a virtual shared drive, and can mount and browse external repositories. You can organize documents using customizable document types and metadata sets and display them with automatic document preview generation. Its companion app, the Media Gallery, displays selected content from the Documents and Media library. It can render image, audio, and video files. Liferay Sync synchronizes Documents and Media folders with local folders on your devices, both your desktop machines and mobile devices. You'll get started with Documents and Media by exploring how to publish files. \chapter{Publishing Files}\label{publishing-files} As you create sites, you'll probably want to share files on them. The Documents and Media library (Document Library) lets you upload and publish all kinds of files on your sites. Pictures, videos, spreadsheets, slide presentations, and more can be stored in and shared from the Document Library. Document Library instances can be scoped to a portal instance, site, or page, so you can work with files where they're relevant. Here, you'll learn how to add files, display them, and collaborate on them. You'll learn how to use both the Documents and Media Library and the Media Gallery. And lastly, you'll learn how to collaborate on files from within several environments, including your browser and local desktop file system. \begin{figure} \centering \includegraphics{./images/dm-images-in-admin.png} \caption{These documents are awesome.} \end{figure} \begin{figure} \centering \includegraphics{./images/dm-media-gallery-slideshow.png} \caption{This slideshow rules.} \end{figure} \begin{figure} \centering \includegraphics{./images/dm-file-entry-details.png} \caption{Viewing a file's details is fun.} \end{figure} \chapter{Adding Files to a Document Library}\label{adding-files-to-a-document-library} This article covers the following topics to help you get started adding files to your Document Library: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Granting File Permissions and Roles:} Determine who can add, view, and update files. Doing this before adding files ensures that only those you wish can access your Document Library. \item \textbf{Adding Files:} Add specific types of files and their associated metadata to your Document Library. \end{enumerate} \section{Granting File Permissions and Roles}\label{granting-file-permissions-and-roles} You should carefully manage who can add, view, and update files. You can store files of all kinds for various purposes. For example, you may have one set of files intended for only specific site members and another intended for everyone, including guests. You can use \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions} to control access to Document Library files. The Document Library's folder permissions also help you organize files. Follow these steps to create a Role for managing files in your site's Documents and Media: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}) and navigate to \emph{Control Panel → Users → Roles}. \item Select the \emph{Site Roles} tab (or \emph{Organization Roles}, for an Organization Role) and then click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to begin creating a role. \item Give your Role a name and a description, then click \emph{Save}. \item Select your Role's \emph{Define Permissions} tab. In the Role's permission definition screen, navigate to \emph{Site Administration} → \emph{Content \& Data} → \emph{Documents and Media}. In the \emph{General Permissions} section, select \emph{Access in Site Administration} and click \emph{Save}. \begin{figure} \centering \includegraphics{./images/dm-define-role-permissions.png} \caption{It's often helpful to define a role for specific users to access Documents and Media from Site Administration.} \end{figure} \item Assign this Role to the Users that should manage media. For more information on this and other topics related to Roles, see \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions}. \end{enumerate} \section{Using the Add Menu}\label{using-the-add-menu} Follow these steps to add files to your site's Document Library: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click on your site's name, and navigate to \emph{Content \& Data} → \emph{Documents and Media}. The Documents and Media screen appears and displays the Documents and Media library's \emph{Home} (its root folder). As you add files and folders to the Document Library, they're listed here. \begin{figure} \centering \includegraphics{./images/dm-admin-home.png} \caption{The Documents and Media's \emph{Home} folder starts empty.} \end{figure} \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select the type of document to add to the Document Library. You can add documents, folders, and shortcuts much like you would on a desktop file system. You can even configure access to an entirely different repository. The Add menu's options are described below. \begin{figure} \centering \includegraphics{./images/dm-admin-add-menu.png} \caption{The Add menu lets you upload and add all kinds of documents to the library.} \end{figure} \item When you're finished selecting the file to upload and filling out any document type fields that are necessary, click \emph{Publish}. \end{enumerate} \textbf{File Upload:} Upload a file to the Documents and Media library. \textbf{Folder}: Create a new folder in the Documents and Media library's file system. \textbf{Multiple Files Upload:} Upload several files at once. You can apply a single description and document type to all the files. You can also \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{categorize and tag} the files, and assign them default permissions. \textbf{Repository}: Add access to an external repository. \textbf{Shortcut}: Create a shortcut to any document that you can view. You can set permissions on the shortcut to specify who can access the original document via the shortcut. Any additional items in the Add menu are \href{/docs/7-2/user/-/knowledge_base/u/document-types}{document types} described by a unique \href{/docs/7-2/user/-/knowledge_base/u/metadata-sets}{metadata set}. When you add a document belonging to a document type, a form appears that lets you pick the file to upload and enter the data defined by the document type's metadata set. \chapter{Creating Folders}\label{creating-folders} You'll need folders to organize all but the most limited set of files. Here, you'll learn how to work with folders in a Document Library: \begin{itemize} \tightlist \item \hyperref[adding-a-folder]{Adding a Folder} \item \hyperref[document-type-restrictions-and-workflow]{Document Type Restrictions and Workflow} \item \hyperref[setting-folder-permissions]{Setting Folder Permissions} \end{itemize} \section{Adding a Folder}\label{adding-a-folder} Follow these steps to add a folder: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click on your site's name, and navigate to \emph{Content \& Data} → \emph{Documents and Media} for your site. The Documents and Media screen appears and displays the Documents and Media library's \emph{Home} (its root folder). \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select \emph{Folder}. The New Folder form appears. \item In the New Folder form, name and describe your folder. Then expand the \emph{Permissions} section. \item In the Permissions section, set the folder's permissions. The \emph{Viewable by} menu lets you select who has View permission for the folder: \begin{itemize} \tightlist \item Anyone (the Guest role; this is the default option) \item Site Members \item Owner \end{itemize} Click the \emph{More Options} link to choose the other folder permissions for the Guest and Site Member roles. By default, site members can add files, subfolders, shortcuts, and subscribe to changes to the folder's files. Guests don't have any such permissions, which is typically what you want. \begin{figure} \centering \includegraphics{./images/dm-folder-permissions.png} \caption{Select your folder's permissions.} \end{figure} \item To finish creating the folder, click \emph{Save} after making your selections in the Permissions section. \end{enumerate} Upon creating the folder, it appears in your Document Library. Opening the folder's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) presents several options for managing the folder. The following sections describe some of these options. \begin{figure} \centering \includegraphics{./images/dm-folder.png} \caption{Your new folder appears in the Document Library.} \end{figure} \section{Document Type Restrictions and Workflow}\label{document-type-restrictions-and-workflow} After creating a folder, you can restrict what document types are allowed in it. You can also choose what \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow} (if any) to use for approving files added to or edited in the folder. Follow these steps to change a folder's document type restrictions and workflow: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the folder's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Edit}. \item Expand the \emph{Document Type Restrictions and Workflow} section. In this section, choose from the following options: \begin{itemize} \tightlist \item Use Document Type Restrictions and Workflow of the Parent Folder (the parent folder) \item Define Specific Document Type Restrictions and Workflow for this Folder (the current folder) \item Default Workflow for this Folder (the current folder) \end{itemize} \item Click \emph{Save} when you're finished. \end{enumerate} \begin{figure} \centering \includegraphics{./images/dm-restrictions-workflow.png} \caption{You can set the document type restrictions and workflow to use for a folder's files.} \end{figure} \section{Setting Folder Permissions}\label{setting-folder-permissions} When creating a folder, you can set some of its permissions via the new folder form. Fine tuning a folder's permissions, however, can only be done after creating the folder. Follow these steps to fine tune a folder's permissions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the folder's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Permissions}. The Permissions window appears. \item In the Permissions window, set the permissions you want to use for this folder. The permissions listed below are available for each role. \item Click \emph{Save} when you're finished setting permissions. \end{enumerate} Here are the permissions you can set: \textbf{Delete}: Move the folder to the Recycle Bin. \textbf{Permissions}: View and modify the folder's permissions. \textbf{Add Subfolder}: Create folders within the folder. \textbf{Add Shortcut}: Create a shortcut (link) to any file in the folder that the role is authorized to view. \textbf{Update}: Edit the folder's attributes and/or move the folder under a new parent folder. \textbf{Add Document}: Add a new file to the folder. \textbf{Subscribe}: Receive email notifications when files are added to or modified in the folder. Note that you can specify the email sender and template from the Documents and Media's \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Configuration} menu. \textbf{View}: View the folder. \textbf{Access}: Access the folder's contents. \chapter{Using the Documents and Media Management Bar}\label{using-the-documents-and-media-management-bar} The Documents and Media \emph{Management Bar} is where people who manage documents go to unwind after a long day at work. Just kidding. The Management Bar, as its name implies, contains tools for managing the files and folders in your Document Library. It appears above the files and folders in Documents and Media. \begin{figure} \centering \includegraphics{./images/dm-management-bar.png} \caption{The Management Bar is a great place to hang out if you're managing documents.} \end{figure} If you've added files or folders to your Document Library, then you're already familiar with the Management Bar's \emph{Add} button (\includegraphics{./images/icon-add.png}). The sections that follow describe the rest of the Management Bar. \noindent\hrulefill \textbf{Note:} If a Document Library contains more items than it can display at once, you can use the navigation tool that appears at the bottom of the window to switch your view to another page or configure the page to display more items per page. \noindent\hrulefill \section{View Types}\label{view-types} The \emph{View Types} button is to the left of the Add button. It lets you choose how to display the Document Library's items. The View Types button's icon depends on the selected view type: \textbf{Cards} (\includegraphics{./images/icon-view-type-cards.png}): Shows a card-like rendering of the item. If the item isn't an image, a generic image for the item's type is displayed. For files, each card also contains the file's suffix (e.g., JPG, PNG, etc.), timestamp, name, and \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow} status (e.g., Approved, Draft, etc.). \textbf{List} (\includegraphics{./images/icon-view-type-list.png}): Shows the same information as the Cards view type, in a list with small file renderings. \textbf{Table} (\includegraphics{./images/icon-view-type-table.png}): Shows the same information as the other view types, in a list with no file renderings. Also, the file information is in columns. The items in all view types have an Actions menu (\includegraphics{./images/icon-actions.png}). These actions are also available in when viewing each item separately. \begin{figure} \centering \includegraphics{./images/dm-images-in-admin.png} \caption{The Cards View type shows items in large card-like renderings.} \end{figure} \section{The Info Panel}\label{the-info-panel} To display an info panel with the current folder's details, click the \emph{Information} icon (\includegraphics{./images/icon-information-dm.png}). The info panel slides out from the right side of the screen and contains the folder's name and number of items. It also has these buttons: \textbf{Subscribe} (\includegraphics{./images/icon-star.png}): Get notifications about files added to or modified in the folder. \textbf{Actions} (\includegraphics{./images/icon-actions.png}): Lists actions you can perform on the current folder. \section{Finding and Arranging Items}\label{finding-and-arranging-items} The Management Bar also contains tools that help you locate and arrange items in the Document Library. The most prominent of these tools is the \emph{Search} bar, where you can find files by keywords. To the left of the Search bar, the Sort button (\includegraphics{./images/icon-sort.png}) arranges items in ascending or descending order. You can also arrange items via the \emph{Filter and Order} selector using these criteria: \textbf{All:} Shows all of the current folder's immediate subfolders and files (default). \textbf{Mine:} Shows all the current user's files (no matter their folder). \textbf{Document Type:} Shows the files of the selected document type. Upon choosing this option, you must select the document type you want from a popup. You can also select from the following criteria for ordering items: \begin{itemize} \tightlist \item Size \item Downloads \item Modified Date (default) \item Create Date \item Title \end{itemize} \section{Selecting Items}\label{selecting-items} The checkbox on the left-most side of the Management Bar selects all currently displayed items. Selecting multiple items lets you act on all of them at once. You can also select multiple items individually by using the checkboxes for each. When you select one or more items, the Management Bar changes to reflect the actions you can take on the selected items. \begin{figure} \centering \includegraphics{./images/dm-management-bar-actions.png} \caption{With items selected, the Management Bar changes.} \end{figure} Here are the actions you can take on the selected items: \begin{itemize} \tightlist \item Download (\includegraphics{./images/icon-download.png}) \item Move (\includegraphics{./images/icon-move.png}) \item Edit Tags \item Move to Recycle Bin (\includegraphics{./images/icon-trash.png}) \end{itemize} The Actions button (\includegraphics{./images/icon-actions.png}) contains all the actions displayed in the Management Bar, plus actions for file checkin and checkout. File checkout and checkin is explained in \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{Checking out and Editing Files}. \chapter{Viewing File Previews}\label{viewing-file-previews} File previews help users browse and find media efficiently. To view a preview of a file, click the file's name in the Document Library. If the file is an image, the image appears. If an app is installed that can render a preview of the file type, a representative image of the file appears (e.g., the opening frame of a video file or a presentation's first slide). If there are no such preview apps for the file, a generic image based on the file type appears. \begin{figure} \centering \includegraphics{./images/dm-file-entry-details.png} \caption{File previews let you view and manage a file.} \end{figure} \section{File Preview Apps}\label{file-preview-apps} Whenever possible, Liferay DXP generates previews of documents added to the Document Library. Out of the box, Java-based APIs generate previews. The only tool available that is 100\% Java and has a compatible license to be distributed with Liferay DXP is \href{https://pdfbox.apache.org}{PDFBox}. A separate thread generates a preview for PDFs when uploaded. This process may last only a few seconds for a small file. The larger the file, the longer it takes. While PDFBox provides a default implementation of image generation for document previews and thumbnails, you must install and configure additional tools to harness the full power of document previews. These tools include: \begin{itemize} \item \href{http://www.openoffice.org}{OpenOffice} or \href{http://www.libreoffice.org}{LibreOffice}: Using one of these in server mode lets you generate thumbnails and previews for supported file types (\texttt{.pdf}, \texttt{.docx}, \texttt{.odt}, \texttt{.ppt}, \texttt{.odp}, etc.), view documents in your browser, and convert documents. \item \href{http://www.imagemagick.org}{ImageMagick} (also requires \href{http://www.ghostscript.com}{Ghostscript}): Enables faster and higher-quality previews and conversions. \item \href{http://www.xuggle.com/xuggler}{Xuggler}: Enables audio and video previews, lets you play audio and video files in your browser, and extracts thumbnails from video files. \end{itemize} After installing these tools, you can configure them via portal properties in the Control Panel's Server Administration screen, or in a \texttt{portal-ext.properties} file. To learn how to use these tools, see \href{/docs/7-2/user/-/knowledge_base/u/setting-up}{Configuring Liferay DXP}. With these tools installed and configured, a customized viewer displays Documents and Media content, depending on the content type. For example, you can view a document with a customized viewer that lets you navigate through the document's pages. You can also view and play multimedia documents (audio or video). If the browser supports HTML5, the viewer uses the browser's native player. Otherwise it falls back to a Flash player. \section{Managing Files}\label{managing-files} You can also manage a file from its preview. The bar above the preview contains these buttons: \textbf{Info} (\includegraphics{./images/icon-information-dm.png}): Open/close the file's info panel. This panel contains more detailed information about the file. For more information on this, see \hyperref[the-info-panel]{The Info Panel}. \textbf{Share}: Share the file with other users. For more information, see \href{/docs/7-2/user/-/knowledge_base/u/sharing-files}{Sharing Files}. \textbf{Download}: Download the file. \textbf{Actions} (\includegraphics{./images/icon-actions.png}): Opens a menu that lets you perform these actions on the file: \begin{itemize} \item \textbf{Download} \item \textbf{Edit:} Modify the file's name, description, document type, categorization, and \href{/docs/7-2/user/-/knowledge_base/u/defining-content-relationships}{related assets}. You can even upload a new file to replace it. Note that modifying the file increments its version. \item \textbf{Edit with Image Editor:} Edit the image in the Image Editor. The Image Editor is explained in \href{/docs/7-2/user/-/knowledge_base/u/editing-images}{Editing Images}. \item \textbf{Checkout/Checkin:} Checkout prevents others from editing the document while you are working on it. Other users can still view the current version of the document, if they have permission. You can check in the document when you're done with it. \item \textbf{Move:} Relocate the file to a different parent folder. \item \textbf{Permissions:} Specify which actions each role can perform on the file. \item \textbf{Move to Recycle Bin:} Move the file from the Documents and Media library to the Recycle Bin. \item \textbf{Share} \end{itemize} Also note that the \emph{Options} menu (\includegraphics{./images/icon-options.png}) at the top-right of the screen contains the same actions as the Actions menu. The comments area (below the preview area) lets you comment on and subscribe to comments on the file. \section{The Info Panel}\label{the-info-panel-1} As mentioned above, clicking the \emph{Info} icon (\includegraphics{./images/icon-information-dm.png}) opens the info panel. The top of the info panel displays the file's name, version, and \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow status}. There are two tabs in the info panel: Details, and Versions. Details is selected by default and shows the following: \textbf{Owner:} The file's owner. \textbf{Download:} A button to download the file. \textbf{Latest Version URL:} A URL to access the newest version of the file. \textbf{WebDAV URL:} A WebDAV URL for accessing the file via a desktop. \textbf{Document Type:} The file's document type. \textbf{Extension:} The file's extension (e.g., JPG, PDF, etc.). \textbf{Size:} The file's size on disk. \textbf{Modified:} The user that last modified the file, and when it was last modified. \textbf{Created:} The user that created the file, and when it was created. \textbf{Ratings:} The file's average user rating. \textbf{Automatically Extracted Metadata:} Any and all metadata automatically extracted from the file. When adding new documents or viewing existing documents, a process is triggered automatically that extracts the file's metadata. The library used by this process is TIKA and it's included out of the box. Depending on your file's type and the metadata written with the file, you can find out all kinds of details. In the case of audio or video files, the media's duration is displayed. To instead view the file's version history, select the \emph{Versions} tab near the top of the info panel. The info panel then changes to list the different versions of the file and lets you view, download, remove, and revert to specific file versions. File version history actions are explained in \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{Checking Out and Editing Files}. \chapter{Editing Images}\label{editing-images} Editing and re-uploading images when you only need to apply simple edits is tedious. Docs \& Media contains a simple built-in image editor for exactly this reason. To access the image editor, locate the image you want to edit. Click the Actions icon (\includegraphics{./images/icon-actions.png}) and select \emph{Edit With Image Editor}. You can also access the image editor when selecting an image to insert in content (i.e., via an item selector). Anywhere an image is, you can edit it. For example, you can access the image editor via item selector preview windows in blog entries and web content articles. To do this, click the pencil icon (\includegraphics{./images/icon-edit-pencil.png}) in the bottom-right corner of the preview window. \begin{figure} \centering \includegraphics{./images/image-editor-docs-and-media.png} \caption{You can access the image editor through the Documents and Media repository.} \end{figure} \begin{figure} \centering \includegraphics{./images/image-editor-preview-window.png} \caption{You can also access the image editor through the item selector preview window.} \end{figure} If you edit and save the image via the Documents and Media repository, the file version is incremented a minor version (e.g., from version 1.0 to version 1.1). You can view the image's version history (and previous versions) by clicking the image, clicking its \emph{Info} button (\includegraphics{./images/icon-information-dm.png}), and then selecting the \emph{Versions} tab. In contrast, if you edit and save an image via an item selector, a copy of the image is created and saved to the Document Library. Liferay designed the image editor with quick editing in mind. It offers a minimal, user-friendly UI. The main toolbar consists of three buttons, each of which contains a subset of options: \begin{figure} \centering \includegraphics{./images/image-editor-tools.png} \caption{The image editor's UI is clear and to the point, offering only what you need.} \end{figure} \textbf{Transform} (\includegraphics{./images/icon-transform.png}) \begin{itemize} \tightlist \item \textbf{Rotate}: Rotate the image to the left or right, in 90 degree increments. \item \textbf{Resize}: Resize the image. If the lock is closed, the aspect ratio is locked and changing width or height automatically adjusts the other dimension to maintain the aspect ratio. When the lock is opened, the width and height can be changed individually, letting the aspect ratio change (this isn't recommended because the image can become distorted). \item \textbf{Crop}: Crop the image. \end{itemize} \textbf{Adjustment} (\includegraphics{./images/icon-adjustment.png}) \begin{itemize} \tightlist \item \textbf{Saturation}: Adjust the color saturation. The default value is 50. Values range from 0 (completely desaturated) to 100 (completely saturated). \item \textbf{Contrast}: Adjust the contrast. The default value is 50. Values range from 0 (no contrast) to 100 (full contrast). \item \textbf{Brightness}: Adjust the brightness. The default value is 50. Values range from 0 (completely black) to 100 (completely white). \end{itemize} \textbf{Filter} (\includegraphics{./images/icon-wand.png}): Apply a filter to the image. \begin{figure} \centering \includegraphics{./images/image-editor-filters.png} \caption{Select from a set of preset image filters.} \end{figure} Upon editing the image in the editor, you can click the \emph{Cancel} button to cancel the changes, or the \emph{Apply} button to apply them. Upon applying the changes, the history bar appears. It lets you undo, redo, or reset the changes. Use the Reset button with caution; it resets the image to its original state, reverting all changes made in the editor. \begin{figure} \centering \includegraphics{./images/image-editor-history-bar.png} \caption{The history bar lets you undo, redo, and reset changes.} \end{figure} \chapter{Publishing Files}\label{publishing-files-1} Once your Document Library contains files, you may want to publish them in your site. Here are some ways to publish files: \begin{itemize} \tightlist \item Show them in a Documents and Media app. \item Display them in a Media Gallery. \item Use the Asset Publisher. \item Insert them in an asset like a web content article or blog entry. \end{itemize} Here, you'll learn to use the Media Gallery. \section{Using the Media Gallery}\label{using-the-media-gallery} The Media Gallery publishes your media files in a simple gallery-like style. It shows a large thumbnail of each media file, lets the user download files, and has slideshow capabilities. A common way to use the Media Gallery is to create a separate page for displaying media and add a Media Gallery widget to it. This way, your media takes center stage. Follow these steps to create a page that contains a Media Gallery widget: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-pages}{Create a page} and navigate to it in your site. \item At the top-right of the screen, click the \emph{Add} icon (\includegraphics{./images/icon-add-app.png}) then navigate to \emph{Widgets} → \emph{Content Management} and select \emph{Add} next to \emph{Media Gallery} (alternatively, drag the Media Gallery onto your page). The Media Gallery widget appears on the page. \item Configure the Media Gallery widget to show your files. By default, it shows files from the Home folder of your site's Documents Library. To choose a different folder, click the widget's Options icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. The Configuration window appears and shows the \emph{Setup} tab. This tab contains these sections: \textbf{Display Settings:} Lets you show each file's actions, filter the media types to display, and choose a display template for your media. \textbf{Folders Listing:} Lets you select a Document Library folder to serve as the root folder from which to display files. The root folder you select becomes the highest-level folder the Media Gallery can access. For example, if you create a subfolder of a parent folder, and then set that subfolder as the Media Gallery's root folder, the Media Gallery can no longer access the parent folder. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note**: To access the Carousel display template in Media Gallery, your role must have View access for that template. Since the Carousel template is in the Global scope, a Global-scope administrator must grant the role permission to view the template. \end{verbatim} \noindent\hrulefill \begin{verbatim} ![ You can configure the Media Gallery to use any Documents and Media folder as its root folder.](./images/dm-select-root-folder.png) \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Configure the rest of the settings as desired in the Media Gallery app's other configuration tabs: \textbf{Communication:} Lists public render parameters the widget publishes to other widgets on the page. Other widgets can take action on these parameters. For each shared parameter, you can specify whether to allow communication via the parameter and select which incoming parameter can can populate it. \textbf{Sharing:} Embed the widget instance as a widget on on any website, Facebook, Netvibes, or as an OpenSocial Gadget. \textbf{Scope}: Specify the Document Library instance the widget uses: the current site's instance (default), the global instance, or the page's instance. If the page doesn't already have an instance of the widget, you can select \emph{Your Page (Create New)} to create a page-scoped instance for the widget to display. \item Click \emph{Save} when you're finished configuring the Media Gallery widget. \end{enumerate} The Media Gallery now shows your files, with images appearing as thumbnails. When you click a thumbnail, a slideshow appears showing the selected image. Below that image, thumbnails of the folder's other images are displayed. The slideshow continues until you click pause or view the last image. Closing the slideshow window returns you to the page. \begin{figure} \centering \includegraphics{./images/dm-media-gallery.png} \caption{The Media Gallery renders large thumbnail images of media files.} \end{figure} \begin{figure} \centering \includegraphics{./images/dm-media-gallery-slideshow.png} \caption{The Media Gallery's slideshow provides a nice way to view images.} \end{figure} \chapter{Checking Out and Editing Files}\label{checking-out-and-editing-files} When you check out a document in the Document Library, only you can make changes to it until you check it back in. This prevents conflicting edits on the same document by multiple users. When you check out a file, you can download it, replace it, move it to another Document Library folder, check it in, or cancel the checkout. Checking in a file also increments its version, which lets you keep track of changes. Unless you're using \href{/docs/7-2/user/-/knowledge_base/u/using-liferay-sync-on-your-desktop}{Liferay Sync} or a \href{/docs/7-2/user/-/knowledge_base/u/desktop-access-to-documents-and-media}{local drive mapped to the file's WebDAV URL}, follow these steps to edit a Document Library file from your machine: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Checkout the file by clicking its Actions icon (\includegraphics{./images/icon-actions.png}) → \emph{Checkout}. Upon checkout, the file's status changes to Draft and a lock icon appears on the file. \begin{figure} \centering \includegraphics{./images/dm-file-checked-out.png} \caption{The file on the right in this image is checked out.} \end{figure} \item Download the file by clicking its Actions icon (\includegraphics{./images/icon-actions.png}) → \emph{Download}. \item Edit the file locally. \item Return to the Documents and Media Library and click the file's Actions icon (\includegraphics{./images/icon-actions.png}) → \emph{Edit}. The file's edit screen appears. \item From the file's Edit screen, select the edited local file for upload. \item Click \emph{Save and Check In}. In the pop-up that appears, select whether your change is a major or minor version, add any version notes that you need, and click \emph{Save}. \end{enumerate} \noindent\hrulefill \textbf{Note}: If you edit a file without checking it out, the file's edit screen displays a toggle for \emph{Customize the Version Number Increment and Describe My Changes}. Setting this to \emph{YES} lets you specify the version increment's type and description. \noindent\hrulefill Follow these steps to access a file's version history: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the file in the Documents and Media Library. \item Click the file's \emph{Info} button (\includegraphics{./images/icon-information-dm.png}) at the top-right of the screen. This opens the file's info panel. \item Select the \emph{Versions} tab in the info panel. \end{enumerate} Each file version has an Actions menu (\includegraphics{./images/icon-actions.png}) that you can use to perform the following actions on that file version: \textbf{Download}: Download the selected version of the file to your machine. \textbf{View}: View the file entry screen for the selected version of the file. \textbf{Revert}: Restores the selected file version as a new major file version. Note that this option isn't available for the newest file version. \textbf{Delete Version}: Remove the file version from the Document Library. All other file versions remain intact. \begin{figure} \centering \includegraphics{./images/dm-file-version-history.png} \caption{The version history actions let you inspect, delete, and reinstate file versions.} \end{figure} \chapter{Sharing Files}\label{sharing-files} Liferay DXP's \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{role-based permissions system} defines which actions users can take on assets, including \href{/docs/7-2/user/-/knowledge_base/u/adding-files-to-a-document-library\#granting-file-permissions-and-roles}{files}. Administrators can let users collaborate on files by assigning the appropriate file permissions to a Role, and then assigning users to that Role. Similarly, non-administrative users can grant permissions to Roles for files they own. This Role-based permissions system sometimes falls short. For example, if a Role appropriate for file collaboration doesn't exist, an administrator must create it and manage its users and permissions. Non-administrative users can't create or manage Roles. Also, if a user wants to share a file with one other user, it's not practical for an administrator to create and manage a Role for only two users. Liferay DXP's sharing feature solves these problems by letting users share files directly with each other, without involving an administrator. This saves time and effort for everyone. After all, sharing is caring. \noindent\hrulefill \textbf{Note:} Administrators can disable sharing. For instructions on this, see \href{/docs/7-2/user/-/knowledge_base/u/configuring-sharing}{Configuring Sharing}. \noindent\hrulefill When you share, you grant some of your own permissions for that file to the receiving user. However, there are some important caveats: \begin{itemize} \tightlist \item You can only grant View, Comment, or Update permissions. For example, you can't grant Delete or Override Checkout permissions even if you have those permissions on the file. \item You can only grant permissions you have on the file. For example, you can't grant Update permission if you only have View and Comment permissions on the file. \item You must grant at least View permission. \item Traditional Role-based permissions always take precedence over sharing permissions. So although sharing can extend permissions, it can't remove those granted via Roles in the portal. \item By default, the Guest Role has Add Discussion permission. This overlaps with the Comment permission in sharing. Therefore, all users can comment on a file regardless of whether the Comment permission was granted via sharing. Administrators can change this by removing the Add Discussion permission from the Guest Role. \end{itemize} Also note that the receiving user must be part of the same instance, but doesn't have to be a member of the same Site. \section{Sharing Files in Documents and Media}\label{sharing-files-in-documents-and-media} To share a file, you must own that file or be an administrator. You must share files via the Documents and Media app in Site Administration or the Documents and Media widget on a page. Follow these steps to share a file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Using the Documents and Media widget on a page or in Site Administration, navigate to the file you want to share. To navigate to the Documents and Media app in Site Administration, open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click your Site's name, and go to \emph{Content \& Data} → \emph{Documents and Media}. To share a file via the Documents and Media widget on a page, actions must be enabled for the widget. Follow these steps to enable actions: \begin{itemize} \tightlist \item Select \emph{Configuration} from the widget's \emph{Options} menu (\includegraphics{./images/icon-app-options.png}). \item In the \emph{Setup} tab's \emph{Display Settings}, select \emph{Show Actions}. \item Click \emph{Save} and close the Configuration window. \end{itemize} \item Click the file's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Share}. This opens the Share dialog. Alternatively, click the file in Documents and Media and then click the \emph{Share} button at the top-right. This opens the same Share dialog. \begin{figure} \centering \includegraphics{./images/sharing-file.png} \caption{To share a file, you must fill out the Share dialog as these steps describe.} \end{figure} \item Enter the name or email address of the user you want to share the file with. To share the file with multiple users, enter each user's email address in a comma delimited list. \item To let receiving users also share the file, select \emph{Allow the document to be shared with other users}. Note, however, that administrators can share the file regardless of your selection here. \item Select the file permissions to grant to receiving users. Because you can only grant your own permissions for the file, some of these options may be unavailable: \begin{itemize} \tightlist \item \textbf{Update:} View, comment, and update. \item \textbf{Comments:} View and comment. \item \textbf{View:} View only. \end{itemize} If you enabled further sharing in the previous step, note that receiving users can only share the file with the permissions you grant here. \item Click \emph{Share}. \end{enumerate} \section{Working with Shared Files}\label{working-with-shared-files} You can access files shared with you in three places: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{The Documents and Media Library:} Files shared with you are visible in their existing Documents and Media locations. For example, if someone shares a file with you that resides in the Documents and Media Library's Home folder, then you can access the file in that folder. \item \textbf{The Notifications app:} When a file is shared with you, you get a notification in the Notifications app. Clicking the notification takes you to the file in Documents and Media. For information on notifications, see \href{/docs/7-2/user/-/knowledge_base/u/managing-notifications-and-requests}{Managing Notifications and Requests}. \begin{figure} \centering \includegraphics{./images/sharing-notifications.png} \caption{The Notifications app contains the notifications that are sent when a user shares a file with you.} \end{figure} \item \textbf{The Shared Content app:} This app lists all the content shared with you, and the content you shared. You can access this app from your user menu. Each file has an Actions button (\includegraphics{./images/icon-actions.png}) for performing permitted actions on the file (e.g., view, comment, update). \begin{figure} \centering \includegraphics{./images/shared-content-app.png} \caption{The Shared Content app lists the files shared with you, and the files you shared.} \end{figure} \end{enumerate} \section{Managing Shared Files}\label{managing-shared-files} After sharing a file, you can unshare it or modify its permissions on a per-user basis. This can only be done by Administrators, the file's owner, or any user with Update permission and permission to share the file. You can take these actions from the file's Info panel in Documents and Media. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the file in Documents and Media, then click the \emph{Info} button (\includegraphics{./images/icon-information-dm.png}) at the top-right. The file's Info panel slides out from the right. \item Click the \emph{Manage Collaborators} link. This shows a list of the users you shared the file with and their file permissions. \begin{figure} \centering \includegraphics{./images/sharing-info.png} \caption{Click \emph{Manage Collaborators} to open up the list of users you shared the file with.} \end{figure} \item Make any changes you want to the list of collaborators. To unshare the file with a user, click the \texttt{x} icon next to that user. You can also change the file permissions via the selector menu for each user. \item Click \emph{Save} and close the dialog. \end{enumerate} \begin{figure} \centering \includegraphics{./images/sharing-collaborators.png} \caption{The Collaborators dialog lets you unshare a file or change the file permissions for each user.} \end{figure} \chapter{Configuring Sharing}\label{configuring-sharing} Administrators can choose whether \href{/docs/7-2/user/-/knowledge_base/u/sharing-files}{file sharing} is enabled at the global, instance, and Site levels. \section{Global Configuration}\label{global-configuration} Sharing is enabled globally by default. To configure sharing globally, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Sharing}. \item Under \emph{SYSTEM SCOPE}, select \emph{Sharing}. \item Configure sharing via these settings: \textbf{Expired Sharing Entries Check Interval:} The interval in minutes for how often expired sharing entries are checked for deletion. \textbf{Enabled:} Whether sharing is enabled globally. \end{enumerate} \begin{figure} \centering \includegraphics{./images/sharing-system.png} \caption{Configure sharing globally.} \end{figure} When sharing is enabled globally, it's also enabled by default for all portal instances. You can change this from \emph{Virtual Instance Sharing} under \emph{VIRTUAL INSTANCE SCOPE}: \textbf{Enabled:} Whether sharing is enabled by default for all instances in the portal. \section{Instance Configuration}\label{instance-configuration} To enable or disable sharing on a per-instance basis, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Sharing}. \item Select \emph{Virtual Instance Sharing} under \emph{VIRTUAL INSTANCE SCOPE}. \item Check or uncheck the \emph{Enabled} checkbox to enable or disable sharing, respectively. \end{enumerate} \begin{figure} \centering \includegraphics{./images/sharing-instance.png} \caption{You can enable or disable sharing for each instance.} \end{figure} \section{Site Configuration}\label{site-configuration} To enable or disable sharing for a Site, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Site Administration (your site's menu) → \emph{Configuration} → \emph{Settings}. \item Select the \emph{General} tab. \item Expand the \emph{Sharing} section and use the toggle to enable or disable sharing for the Site. \end{enumerate} \begin{figure} \centering \includegraphics{./images/sharing-toggle.png} \caption{You can enable or disable sharing for each Site.} \end{figure} \chapter{Desktop Access to Documents and Media}\label{desktop-access-to-documents-and-media} You can access the Document Library from your desktop file manager via \href{https://en.wikipedia.org/wiki/WebDAV}{WebDAV}. WebDAV is a set of methods based on HTTP that let users create, edit, move, or delete files stored on web servers. WebDAV is supported by most major operating systems and desktop environments, including Linux, macOS, and Windows. Using your file manager via WebDAV doesn't bypass the functionality of the web interface---Liferay DXP increments the version numbers of files edited and uploaded via WebDAV. To access the Document Library folder from a file browser, you must use your log-in credentials and the WebDAV URL of the folder you want to access. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Documents and Media app that contains the folder you want to access. Click the folder's Actions icon (\includegraphics{./images/icon-actions.png}) and select \emph{Access from Desktop}. \begin{figure} \centering \includegraphics{./images/dm-access-from-desktop-action.png} \caption{Select \emph{Access from Desktop} to get the folder's WebDAV URL.} \end{figure} \item Copy the WebDAV URL and follow the instructions for your operating system: \textbf{Windows:} Map a network drive drive to the WebDAV URL. Enter your credentials when prompted. The Document Library folder appears in the network drive. From your file browser, you can now add, edit, move, or delete files in this folder. \textbf{macOS:} In the Finder, select \emph{Go} → \emph{Connect to Server}. In the Server Address field, enter the WebDAV URL of the folder you want to access, then click \emph{Connect} and enter your credentials when prompted. \textbf{Linux:} In your file manager, you must slightly modify the Document Library folder's WebDAV URL. For KDE's Dolphin, change the URL's protocol to \texttt{webdav://} instead of \texttt{http://}. For GNOME's Nautilus, change the URL's protocol to \texttt{dav://} instead of \texttt{http://}. Then press \emph{Enter} and enter your credentials when prompted. \end{enumerate} Now you can access the Document Library folder from your desktop file system. If you edit a file in this folder on your file system, the change also shows up in the same Document Library folder in the portal. What's more, the file's minor version is incremented due to the edit. \chapter{Linking to Google Drive™}\label{linking-to-google-drive} You can create Document Library files that link to files in Google Drive™ and Google Photos™. This lets you access your Google files from the Document Library. Note that this functionality isn't available by default. To enable it, you must complete these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Install the Liferay Plugin for Google Drive™ from Liferay Marketplace. \item Create and/or configure a Google project capable of communicating with your Liferay DXP instance. The \href{https://developers.google.com/picker/}{Google Picker API} must be enabled for this project. This API lets you select Google files to link to. You must also create the credentials the Google project needs to communicate with your Liferay DXP instance. \item Configure your portal to communicate with your Google project. \end{enumerate} This article shows you how to complete these steps and finishes with an example of linking to a Google file from the Document Library. \noindent\hrulefill \textbf{Note:} You can also use Google Docs™ for online file creation and editing. This doesn't require a plugin and is covered in a \href{/docs/7-2/user/-/knowledge_base/u/online-file-creation-and-editing-with-google-docs}{separate section of the documentation}. \noindent\hrulefill \noindent\hrulefill \textbf{Important:} The Liferay Plugin for Google Drive™ is a Labs application available for Liferay CE Portal and Liferay DXP. Labs apps are experimental and not supported by Liferay. They're released to accelerate the availability of useful and cutting-edge features. This status may change without notice. Use Labs apps at your own discretion. \noindent\hrulefill \section{Install the App}\label{install-the-app} First, you must install the the Liferay Plugin for Google Drive™ from Liferay Marketplace. This app is available via the following links for Liferay CE Portal and Liferay DXP: \begin{itemize} \tightlist \item \href{https://web.liferay.com/marketplace/-/mp/application/105847499}{Liferay Plugin for Google Drive - CE} \item \href{https://web.liferay.com/marketplace/-/mp/application/98011653}{Liferay Plugin for Google Drive - DXP} \end{itemize} If you need help installing apps from Marketplace, see the documentation on \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{using Marketplace}. \section{Configure Your Google Project}\label{configure-your-google-project} Follow these steps to create and/or configure your Google project so it can communicate with your Liferay DXP instance: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \href{https://console.developers.google.com}{Google API Console}. If you don't have a suitable project, \href{https://support.google.com/googleapi/answer/6251787?hl=en&ref_topic=7014522}{create a new one}. \item Enable the Google Picker API for your project. For instructions, see the Google API Console documentation on \href{https://support.google.com/googleapi/answer/6158841}{enabling and disabling APIs}. \item Create an OAuth 2 client ID in your Google project. For instructions, see the Google API Console documentation on \href{https://support.google.com/googleapi/answer/6158849}{setting up OAuth 2.0}. Enter these values when creating your client ID: \begin{itemize} \tightlist \item \textbf{Application type:} Web application \item \textbf{Name:} Google Docs Hook \item \textbf{Authorized JavaScript origins}: \texttt{{[}liferay-instance-URL{]}} (e.g., \texttt{http://localhost:8080} is the default for local development machines) \item \textbf{Authorized redirect URIs}: \texttt{{[}liferay-instance-URL{]}/oath2callback} \end{itemize} \item Create a new API key in your Google project. For instructions, see the Google API Console documentation on \href{https://support.google.com/googleapi/answer/6158862?hl=en}{creating API keys}. Be sure to restrict the key to HTTP referrers (web sites), and set it to accept requests from your Liferay DXP instance's URL. \end{enumerate} Your new OAuth client ID and public API access key now appear on your Google project's Credentials screen. Keep this screen open to reference these values as you specify them in Liferay DXP. \section{Configure Your Portal}\label{configure-your-portal} Now that you have a Google project set up for use with Liferay DXP, you must connect your installation to that project. You can do this at two scopes: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Globally, for all instances in your Liferay DXP installation. \item At the instance scope, for one or more instances in your Liferay DXP installation. \end{enumerate} You can override the global configuration for one or more instances by configuring those instances separately. Similarly, you can configure only the instances you want to connect to your Google project and leave the global configuration empty. Follow these steps to configure your Liferay DXP installation to connect to your Google project: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Note that the configuration options are the same in the global and instance-level configurations. To access the global configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Documents and Media}. To access the instance-level configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Documents and Media}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Google Drive}. \item Enter your Google project's OAuth 2 client ID and client secret into the \emph{Client ID} and \emph{Client Secret} fields. \item In the field \emph{Picker API Key}, enter the API key you created in the previous section. \item Click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/google-drive-system-settings.png} \caption{Enter your Google project's OAuth 2 client ID, OAuth 2 client secret, and Picker API key.} \end{figure} \section{Creating Linked Files}\label{creating-linked-files} With the preceding configuration steps complete, you can create files in your Document Library that link to files in Google Drive™ or images in Google Photos™. Follow these steps to do so: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In your Document Library, click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select \emph{Google Drive Shortcut}. The \emph{New Google Drive Shortcut} screen appears. \begin{figure} \centering \includegraphics{./images/dm-new-google-drive-shortcut.png} \caption{Select \emph{New Google Drive Shortcut} from the \emph{Add} menu in your Document Library.} \end{figure} \item Click the \emph{Select File} button to open Google's file picker. \item Use the file picker to select a file from Google Drive™ or Google Photos™. \item Click \emph{Publish}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/dm-google-select-a-file.png} \caption{You can select files from Google Drive™ or your photos.} \end{figure} A new file entry appears for the Google document you linked to. You can view the file entry as you would any file entry. The Google document's contents show in the file entry's preview pane. As with any file entry, the \emph{Options} button (\includegraphics{./images/icon-options.png}) gives you access to the Download, Edit, Move, Permissions, Move to Recycle Bin, and Checkin/Checkout/Cancel Checkout options. \chapter{Metadata Sets}\label{metadata-sets} You can define metadata fields that users fill out when they create or edit Document Library files. You do this by creating \emph{metadata sets} and then associating them with document types, which wrap Document Library files and thus apply your metadata fields to the files. Although you apply metadata sets via document types, metadata sets exist independently and you can apply them to any number of document types. \section{Managing Metadata Sets}\label{managing-metadata-sets} To see the available metadata sets, open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), expand your site's menu, and navigate to \emph{Content \& Data} → \emph{Documents and Media}. Then click the \emph{Metadata Sets} tab. Any existing sets appear in a table. To select a metadata set, select the checkbox to its left. To select all the sets, select the checkbox in the Management Bar. With one or more sets selected, an \texttt{X} icon appears in the Management Bar. Clicking it deletes the selected metadata set(s). Note that metadata sets don't support the Recycle Bin. If you delete a metadata set, it's gone forever. The Management Bar also contains other options for managing the metadata sets. The selector menu to the right of the checkbox filters the sets the table displays (it's set to \emph{All} by default). The \emph{Order by} selector orders the sets by Modified Date or ID. The up and down arrows sort the sets in ascending or descending order, respectively. You can also use the Search bar to search for a set. In the table, each metadata set has an Actions button (\includegraphics{./images/icon-actions.png}) for performing the following actions on that set: \textbf{Edit}: Edit the set. Alternatively, click the set's name in the table. \textbf{Permissions}: Configure the set's permissions. \textbf{Copy}: Copy the metadata set. \textbf{Delete}: Delete the set. \begin{figure} \centering \includegraphics{./images/dm-metadata-sets-list.png} \caption{The Metadata Sets management window lets you view existing sets and create new ones for applying to document types.} \end{figure} \section{Creating Metadata Sets}\label{creating-metadata-sets} Follow these steps to create a metadata set: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click your Site's name and navigate to \emph{Content \& Data} → \emph{Documents and Media}. Then click the \emph{Metadata Sets} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). The New Metadata Set form appears. \item Give your metadata set a name. \item Open the \emph{Details} section of the form to give your metadata set a description or select a metadata set to extend (both are optional). To select a metadata set to extend, click the \emph{Select} button for \emph{Parent Metadata Set} and then select the metadata set. When a user creates a document of a document type that uses an extended metadata set, the parent metadata set's fields appear above the extended metadata set's. \item Add the metadata fields that should be part of this metadata set. To do this, first select the editor's \emph{View} tab and select the \emph{Fields} tab within it. Icons representing the field types are listed on one side and the metadata set's canvas is on the other side. To add a field type to the metadata set, select its icon, drag, and drop it onto the canvas. The field appears on the canvas as it does for users. By dragging a field onto a field that's already on the canvas, you can nest the new field in the existing field. When you mouse over a field on the canvas, the field action icons (\includegraphics{./images/icon-dm-metadata-actions.png}) appear. Clicking the \emph{+} icon creates a duplicate of the current field and adds it below the current field. Clicking the trash can deletes the field. The following metadata fields are available: \begin{itemize} \tightlist \item \textbf{Boolean:} A check box. \item \textbf{Color:} Specifies a color. \item \textbf{Date:} Enter a date. A valid date format is required for the date field, but you don't have to enter a date manually. When you select the date field a mini-calendar pops up which you can use to select a date. \item \textbf{Decimal:} Enter a decimal number. The value is persisted as a \texttt{double}. \item \textbf{Documents and Media:} Select a file from a Documents and Media library. \item \textbf{Geolocation:} Specify a location to associate with the document. \item \textbf{HTML:} An area that uses a WYSIWYG editor to enhance the content. \item \textbf{Integer:} Enter an integer. The value is persisted as an \texttt{int}. \item \textbf{Link to Page:} Link to another page in the same site. \item \textbf{Number:} Enter a decimal number or an integer. The value is persisted either as a \texttt{double} or an \texttt{int}, depending on the input's type. \item \textbf{Radio:} Displays several clickable options. The default number of options is three but this is customizable. Only one option can be selected at a time. \item \textbf{Select:} This is just like the radio field except that the options are hidden and must be accessed from a drop-down menu. \item \textbf{Text:} Enter a single line of text. \item \textbf{Text Box:} This is just like the text field except you can enter multiple lines of text or separate paragraphs. \item \textbf{Web Content:} Select web content. \end{itemize} \begin{figure} \centering \includegraphics{./images/dm-metadata-set-fields.png} \caption{Add your metadata set's fields to the canvas.} \end{figure} \item Edit your fields to reflect their intended metadata. For example, a text field's default label is \emph{Text}. If you want to use the text field as a title, for instance, then you should change the field's label to \emph{Title}. To do this, first select the field on the canvas. This automatically selects the \emph{Settings} tab on the left. Alternatively, you can access the Settings tab by clicking the field's wrench icon. To edit a setting value, double-click it in the Settings table and enter the new value. Labels, default values, variable names, mouse-over tips, widths, and other settings can be configured for most fields. Some fields have a \emph{Required} setting for specifying whether users must populate the field. If a field's \emph{Repeatable} setting is \emph{Yes}, users can add multiple consecutive instances of the field to the document's metadata. Also note that you can translate each of a metadata set's field values to any supported locales. To specify a field value for a translation, select the flag that represents the locale and enter the field value for the locale. \item Click \emph{Save} when you're done specifying your new metadata set. \begin{figure} \centering \includegraphics{./images/dm-metadata-set-settings.png} \caption{Edit your metadata set's fields to match the metadata that you want each field to hold.} \end{figure} \end{enumerate} \chapter{Document Types}\label{document-types} Document types are made of metadata fields and help users define the purpose of Document Library files. For example, a \emph{Contract} document type may need metadata fields for the effective date, expiration date, contract type, legal reviewer, and more. When users create Document Library files of the Contract document type, they can then populate those metadata fields. Document types also help you integrate files with other features like search and workflow. Search works on file metadata so users can find files faster. You can also apply workflows to specific document types. And you can more cleanly organize document libraries by designating folders to hold particular document types exclusively. \section{Managing Document Types}\label{managing-document-types} To see the available document types, open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), expand your site's menu, and navigate to \emph{Content \& Data} → \emph{Documents and Media}. Then click the \emph{Document Types} tab. A searchable table lists any existing document types. The following actions are available for each document type via its Actions button (\includegraphics{./images/icon-actions.png}): \textbf{Edit}: Edit the document type. \textbf{Permissions}: Set the document type's permissions. \textbf{Delete}: Delete the document type. Note that document types don't support the Recycle Bin. Once you delete a document type, it's gone forever. \begin{figure} \centering \includegraphics{./images/dm-doc-types-list.png} \caption{The Document Types management window lets you view existing document types and create new ones.} \end{figure} \section{Creating Document Types}\label{creating-document-types} Follow these steps to create a document type: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Menu} (\includegraphics{./images/icon-menu.png}), expand your site's menu and navigate to \emph{Content \& Data} → \emph{Documents and Media}. Then click the \emph{Document Types} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). The \emph{New Document Type} form appears. \item Give your document type a name and a description. \begin{figure} \centering \includegraphics{./images/dm-doc-types-new.png} \caption{Create your new document type.} \end{figure} \item Define the metadata to use with your document type. You do this via these sections in the form: \textbf{Main Metadata Fields:} These are tied directly to the document type. They can be created only via the form and can't be used with other document types. You create and edit these metadata fields in the form the same way that you do when creating \href{/docs/7-2/user/-/knowledge_base/u/metadata-sets}{metadata sets}. \textbf{Additional Metadata Fields:} Select a metadata set to associate with the document type. Each document type must be associated with one or more metadata set. To differentiate document types that use the same metadata sets, define different main metadata fields. \item Define your document type's permissions via the form's \emph{Permissions} section. By default, anyone can view the document type, including site guests. You can restrict its view, update, delete, and permissions configuration to site members or the document type's owner. \item Click \emph{Save} when you're finished specifying your new document type. \end{enumerate} Your document type is now available when adding a document via the Documents and Media's \emph{Add} menu. When users create new files of the document type, they're presented with metadata fields to describe the document. \chapter{Online File Creation and Editing with Google Docs™}\label{online-file-creation-and-editing-with-google-docs} Although you can \href{/docs/7-2/user/-/knowledge_base/u/adding-files-to-a-document-library\#using-the-add-menu}{add} and \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{edit} Documents and Media files via upload and download, Liferay DXP doesn't contain a UI that lets you author or edit documents directly. You can, however, create and edit Documents and Media files online in Google Docs™, Google Sheets™, and Google Slides™. \noindent\hrulefill \textbf{Note:} For simplicity and readability, this documentation refers only to Google Docs™. The material, however, also applies to Google Sheets™ and Google Slides™. \noindent\hrulefill Note that when you use Google Docs™ to create or edit a Documents and Media file, that file isn't permanently stored in Google Docs™. Google Docs™ is only used for its editing UI. Your edits are then saved to the Documents and Media Library. \begin{figure} \centering \includegraphics{./images/google-docs-new.png} \caption{You can create new Google documents in Documents and Media.} \end{figure} \begin{figure} \centering \includegraphics{./images/google-docs-edit.png} \caption{You can also use Google's document editor to edit existing Documents and Media files.} \end{figure} \begin{figure} \centering \includegraphics{./images/google-docs-save-discard.png} \caption{When using Google's document editor, you can save or discard your changes via the editor's toolbar.} \end{figure} \chapter{Configuring Google Docs™ Integration}\label{configuring-google-docs-integration} Before you can use Google Docs™ to create and edit Documents and Media files, you must configure Liferay DXP to connect with an application in the \href{https://console.developers.google.com}{Google API Console}. \noindent\hrulefill \textbf{Note:} You must be an administrator to complete these steps. \noindent\hrulefill \section{Configure Your Google Project}\label{configure-your-google-project-1} First, you must configure your Google project to use the Google Drive™ API and set up OAuth 2 for use with that project. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \href{https://console.developers.google.com}{Google API Console}. If you don't have a suitable project, \href{https://support.google.com/googleapi/answer/6251787?hl=en&ref_topic=7014522}{create a new one}. \item Enable the Google Drive™ API for your project. For instructions, see the Google API Console documentation on \href{https://support.google.com/googleapi/answer/6158841}{enabling and disabling APIs}. \item Create an OAuth 2 client ID for your Google project. For instructions, see the Google API Console documentation on \href{https://support.google.com/googleapi/answer/6158849}{setting up OAuth 2.0}. Select \emph{Web application} when prompted to select your application type. Take note of the client ID and client secret that appear---you'll need them to configure the portal to use the Google Drive™ API. \end{enumerate} \section{Configuring the Portal}\label{configuring-the-portal} Now that you have a Google project set up for use with Liferay DXP, you must connect your installation to that project. You can do this at two scopes: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Globally, for all instances in your Liferay DXP installation. \item At the instance scope, for one or more instances in your Liferay DXP installation. \end{enumerate} You can override the global configuration for one or more instances by configuring those instances separately. Similarly, you can configure only the instances you want to connect to your Google project and leave the global configuration empty. Follow these steps to configure your Liferay DXP installation to connect to your Google project: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Note that the configuration options are the same in the global and instance-level configurations. To access the global configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Documents and Media}. To access the instance-level configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Documents and Media}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Google Drive}. \item Enter your Google project's OAuth 2 client ID and client secret into the \emph{Client ID} and \emph{Client Secret} fields. \item Click \emph{Save}. \end{enumerate} \noindent\hrulefill \textbf{Note:} To turn this feature off, delete the client ID and client secret values from the form. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} You can ignore the \emph{Picker API Key} field. This field is unrelated to the Google Docs™ online editing features in Liferay DXP. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/google-drive-system-settings.png} \caption{Enter your Google project's OAuth 2 client ID and client secret.} \end{figure} \chapter{Creating and Editing Files with Google Docs™}\label{creating-and-editing-files-with-google-docs} You can use Google Docs™ to create and edit text documents, spreadsheets, or presentations for storage in the Docs and Media library. When you finish your Google Docs™ editing session, your changes are automatically saved in the Documents and Media Library. You can \begin{itemize} \tightlist \item \hyperref[creating-files]{Create Files} \item \hyperref[editing-files]{Edit Files} \item \hyperref[multiple-editing-sessions]{Manage Multiple Editing Sessions} \end{itemize} \section{Authentication}\label{authentication} The first time you create or edit a Documents and Media file via Google Docs™, you must authenticate with your Google account. This links Google Drive™ to your portal account, so you only need to do this once. You can unlink your account at any time by navigating to User Menu → \emph{Account Settings} → \emph{General} → \emph{Apps}, and clicking \emph{Revoke} next to Google Drive™. \begin{figure} \centering \includegraphics{./images/google-docs-unlink.png} \caption{You can unlink your Google account from the portal.} \end{figure} \section{Creating Files}\label{creating-files} Follow these steps to create a new Documents and Media file via Google Docs™: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click your site's name, and navigate to \emph{Content \& Data} → \emph{Documents and Media}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select the type of Google document to add to the Document Library. \begin{itemize} \tightlist \item Google Docs™ \item Google Slides™ \item Google Sheets™ \end{itemize} When you select one of these options, Liferay DXP creates a temporary Documents and Media file and links it to a new Google file. Your browser then redirects you to that Google file so you can create its content. Note that some actions on the temporary Documents and Media file can affect its linked Google file. For more information, see \hyperref[multiple-editing-sessions]{multiple editing sessions}. \begin{figure} \centering \includegraphics{./images/google-docs-new.png} \caption{Select the type of Google document you want to create.} \end{figure} \item Use the Google Docs™ editor to create your document's content. All Google Docs™ features are available except for sharing. \item Save or discard your changes by clicking one of these toolbar buttons in the Google Docs™ editor: \textbf{Save and Return to Liferay:} Saves your document as a new file in the Documents and Media Library, deletes the Google file, and returns you to the portal. The saved file's format depends on the type of Google document you selected in step two above: \begin{itemize} \tightlist \item Google Docs™: Microsoft Word (\texttt{.docx}) \item Google Slides™: Microsoft PowerPoint (\texttt{.pptx}) \item Google Sheets™: Microsoft Excel (\texttt{.xlsx}) \end{itemize} \textbf{Discard Changes:} Returns you to the portal without saving your file in the Documents and Media Library or Google Docs™. Note that it's also possible to close the Google Docs™ window without clicking either button. In this case, the editing session remains open even though the window that displayed it is closed. For more information, see the section below on \hyperref[multiple-editing-sessions]{multiple editing sessions}. \begin{figure} \centering \includegraphics{./images/google-docs-save-discard.png} \caption{Save or discard your changes by using the toolbar in the editor.} \end{figure} \end{enumerate} \section{Editing Files}\label{editing-files} You can use Google Docs™ to edit the following types of Documents and Media files: \begin{itemize} \tightlist \item Text files (\texttt{.docx}, \texttt{.html}, \texttt{.txt}, \texttt{.rtf}, \texttt{.odt}) \item Presentation files (\texttt{.pptx}, \texttt{.odp}) \item Spreadsheet files (\texttt{.xlsx}, \texttt{.ods}, \texttt{.csv}, \texttt{.tsv}) \item PDF files \end{itemize} \noindent\hrulefill \textbf{Note:} Google Docs™ doesn't support older, non-XML-based Microsoft Office file types (\texttt{.doc}, \texttt{.ppt}, \texttt{.xls}). \noindent\hrulefill Follow these steps to edit a Documents and Media file in Google Docs™: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the file in the Documents and Media Library. \item Click the file's Actions icon (\includegraphics{./images/icon-actions.png}) and select \emph{Edit in Google Docs}. This automatically \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{checks out} the file, transfers its content to a new Google Docs™ file, and redirects you to that Google Docs™ file. \begin{figure} \centering \includegraphics{./images/google-docs-edit.png} \caption{Select \emph{Edit in Google Docs} from the file's Actions menu.} \end{figure} \item Edit the file in Google Docs™. The editing process is exactly the same as described above for creating files. \end{enumerate} \section{Multiple Editing Sessions}\label{multiple-editing-sessions} When you create or edit a Documents and Media file in Google Docs™, you can save or discard your changes by clicking \emph{Save and Return to Liferay} or \emph{Discard Changes}, respectively. If you instead close the window without clicking either, the editing session still exists. You can access it via the original file in Documents and Media. If the file didn't exist before (e.g., you were creating a new file), it appears in Documents and Media as a temporary file. When an editing session already exists for a Documents and Media file, the following actions are available via that file's Actions icon (\includegraphics{./images/icon-actions.png}): \textbf{Edit in Google Docs:} Resume editing the file in Google Docs™. \textbf{Check in:} Saves the Google file (including any changes) to the Documents and Media file, then deletes the Google file. This is equivalent to clicking \emph{Save and Return to Liferay} in a Google Docs™ editing window. \textbf{Cancel Checkout:} Deletes the Google file, discarding any changes. This is equivalent to clicking \emph{Discard Changes} in a Google Docs™ editing window. \chapter{Integration with Microsoft Office 365™}\label{integration-with-microsoft-office-365} Although you can \href{/docs/7-2/user/-/knowledge_base/u/adding-files-to-a-document-library\#using-the-add-menu}{add} and \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{edit} Documents and Media files via upload and download, Liferay DXP doesn't contain a UI that lets you author or edit documents directly. You can, however, create and edit Documents and Media files online in Word™, Excel™, and PowerPoint™. Note that when you use Office 365™ to create or edit a Documents and Media file, that file isn't permanently stored in Office 365™. Office 365™ is only used for its editing UI. Your edits are then saved to the Documents and Media Library. Here you can learn how to use it. \begin{figure} \centering \includegraphics{./images/office365-new.png} \caption{You can create new Office 365™ documents in Documents and Media.} \end{figure} \begin{figure} \centering \includegraphics{./images/office365-edit.png} \caption{You can also edit existing Documents and Media files in Office 365™.} \end{figure} \chapter{Configuring Office 365™ Integration}\label{configuring-office-365-integration} Before you can use Office 365™ to create and edit Documents and Media files, you must configure Liferay DXP to connect with an application in the \href{https://portal.azure.com/}{Azure portal}. \noindent\hrulefill \textbf{Note:} You must be an administrator to complete these steps. \noindent\hrulefill \section{Register an Application with the Microsoft Identity Platform}\label{register-an-application-with-the-microsoft-identity-platform} First, configure your application with the Microsoft identity platform™. To do so, follow the steps described in \href{https://docs.microsoft.com/en-gb/graph/auth-register-app-v2}{Microsoft's documentation}. To construct a URL for the \emph{Redirect URI} parameter, follow this pattern: \begin{verbatim} https://[hostname]/o/document_library/onedrive/oauth2 \end{verbatim} Here's the minimum permission set needed to use Office 365™ integration: \begin{itemize} \tightlist \item Files.Read.All \item Files.ReadWrite.All \end{itemize} For more information about permissions, see \href{https://docs.microsoft.com/graph/permissions-reference}{Microsoft's documentation}. \section{Configuring Liferay DXP}\label{configuring-liferay-dxp} Now you must connect your Liferay DXP installation with your Microsoft identity platform™ application. You can do this at two scopes: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Globally, for all instances in your Liferay DXP installation. \item At the instance scope, for one or more instances in your Liferay DXP installation. \end{enumerate} You can override the global configuration for one or more instances by configuring those instances separately. Similarly, you can configure only the instances you want to connect to your application and leave the global configuration empty. Follow these steps to configure your Liferay DXP installation to connect to your application: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Note that the configuration options are the same in the global and instance-level configurations. To access the global configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Documents and Media}. To access the instance-level configuration, go to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Documents and Media}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{OneDrive}. \item Enter your application's OAuth 2 client ID and client secret in the \emph{Client ID} and \emph{Client Secret} fields, respectively. \item Enter your tenant ID in the \emph{Tenant} field. To find your tenant ID, see \href{https://docs.microsoft.com/onedrive/find-your-office-365-tenant-id}{Microsoft's documentation}. \item Click \emph{Save}. \end{enumerate} \noindent\hrulefill \textbf{Note:} Once enabled, you can disable this feature by deleting the client ID, client secret, and tenant values from the form. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/onedrive-system-settings.png} \caption{Enter your application's client ID, client secret, and tenant.} \end{figure} \chapter{Creating and Editing Documents and Media Files with Office 365™}\label{creating-and-editing-documents-and-media-files-with-office-365} You can use Office 365™ to create and edit text documents, spreadsheets, or presentations for storage in the Documents and Media library. When you finish your Office 365™ editing session, you must check in the document to save the changes in the Documents and Media Library. Here, you'll learn how to do these things: \begin{itemize} \tightlist \item \hyperref[authentication]{Authenticate with OneDrive™} \item \hyperref[creating-files]{Create Files} \item \hyperref[editing-files]{Edit Files} \end{itemize} \section{Authentication}\label{authentication-1} The first time you create or edit a Documents and Media file via Office 365™, you must authenticate with your Microsoft account. This links OneDrive™ to your Liferay DXP account, so you only need to do this once. You can unlink your account at any time by navigating to User Menu → \emph{Account Settings} → \emph{General} → \emph{Apps}, and clicking \emph{Revoke} next to OneDrive™. \begin{figure} \centering \includegraphics{./images/office365-unlink.png} \caption{You can unlink your account from the portal.} \end{figure} \section{Creating Files}\label{creating-files-1} Follow these steps to create a new Documents and Media file via Office 365™: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click your Site's name, and navigate to \emph{Content \& Data} → \emph{Documents and Media}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select the type of Office 365™ document to add to the Document Library: \begin{itemize} \tightlist \item Word™ \item Excel™ \item PowerPoint™ \end{itemize} When you select one of these options, a new window opens for you to enter the document's name. \begin{figure} \centering \includegraphics{./images/office365-new.png} \caption{Select the type of document you want to create.} \end{figure} \item Enter the document's name in the \emph{Title} field, and click \emph{Save}. When you click \emph{Save}, Liferay DXP creates a temporary Documents and Media file and links it to the new Office 365™ file. Your browser then opens a new window with that Office 365™ file so you can create its content. \begin{figure} \centering \includegraphics{./images/office365-creation-modal.png} \caption{Give the document a name.} \end{figure} \item Use the Office 365™ editor to create your document's content. \item Save or discard your changes to Liferay DXP: \textbf{Check in:} Saves the Office 365™ file to Documents and Media, then deletes the file from Office 365™. The saved file's format depends on the document type you selected in step two above. \begin{itemize} \tightlist \item Word: Microsoft Word™ (\texttt{.docx}) \item PowerPoint: Microsoft PowerPoint™ (\texttt{.pptx}) \item Excel: Microsoft Excel™ (\texttt{.xlsx}) \end{itemize} \textbf{Cancel Checkout:} Deletes the Office 365™ file, discarding any changes. \end{enumerate} \section{Editing Files}\label{editing-files-1} You can use Office 365™ to edit the following types of Documents and Media files: \begin{itemize} \tightlist \item Text files (\texttt{.doc}, \texttt{.docx}, \texttt{.docm}, \texttt{.dot}, \texttt{.dotx}, \texttt{.dotm}, \texttt{.html}, \texttt{.txt}, \texttt{.rtf}, \texttt{.odt}) \item Presentation files (\texttt{.ppt}, \texttt{.pptx}, \texttt{.pptm}, \texttt{.pps}, \texttt{.ppsx}, \texttt{.ppsm}, \texttt{.pot}, \texttt{.potx}, \texttt{.potm}) \item Spreadsheet files (\texttt{.xls}, \texttt{.xlsx}, \texttt{.xlsm}, \texttt{.xlt}, \texttt{.xltx}, \texttt{.xltm}, \texttt{.ods}, \texttt{.csv}, \texttt{.tsv}, \texttt{.txt}, \texttt{.tab}) \end{itemize} Follow these steps to edit a Documents and Media file in Office 365™: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the file in the Documents and Media Library. \item Click the file's Actions icon (\includegraphics{./images/icon-actions.png}) and select \emph{Edit in Office 365}. This automatically \href{/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-files}{checks out} the file, transfers its content to a new Office 365™ file, and redirects you to that file Office 365™. \item Edit the file in Office 365™. The editing process is exactly the same as described above for creating files. \begin{figure} \centering \includegraphics{./images/office365-edit.png} \caption{Select \emph{Edit in Office 365} from the file's Actions menu.} \end{figure} \end{enumerate} \chapter{Store Types}\label{store-types} You can change the file system (the \emph{store}) that the Documents and Media library uses to store files. This is configured in the \texttt{portal-ext.properties} file by setting the \texttt{dl.store.impl=}property. Configuring stores is covered in \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{the Document Repository Configuration guide}. Here, you'll consider the ramifications of different stores: \textbf{Simple File System Store:} Uses the file system (local or a mounted share) to store files. This is the default store. \textbf{Advanced File System Store:} Nests files into directories by version, for faster performance and to store more files. \textbf{DBStore (Database Storage)}: Stores files in the Liferay DXP database. The file (stored as a blob) size limit is 1 GB. Use the Simple File System Store or Advanced File System Store to store larger files. \textbf{S3Store (Amazon Simple Storage)}: Uses Amazon's cloud-based storage solution. If you must move your files from one store to another, use the migration utility in \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration} → \emph{Data Migration}. \section{Simple File System Store}\label{simple-file-system-store} The Simple File System Store is the default store. It stores Documents and Media files on the server's file system (local or mounted). This store is heavily bound to Liferay DXP's database. The store's default root folder is \texttt{{[}Liferay\ Home{]}/data/document\_library}. You can change this via the \texttt{dl.store.file.system.root.dir=} property in a \texttt{portal-ext.properties} file, or in the Control Panel. For instructions on this, see the \href{/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration}{Document Repository Configuration guide}. The Simple File System Store uses a local folder to store files. You can use the file system for your clustered configuration, but you must make sure the folder you point the store at can handle things like concurrent requests and file locking. You must therefore use a Storage Area Network or a clustered file system. The Simple File System Store creates a folder structure based on primary keys in Liferay DXP's database. If, for example, you upload a presentation with the file name \texttt{workflow.odp} to a folder named \texttt{stuff}, the store creates a folder structure like this: \begin{verbatim} /companyId/folderId/numericFileEntryName/versionNumber \end{verbatim} \texttt{companyId}: The site's company ID. \texttt{folderId}: The ID of the Documents and Media folder containing the document. \texttt{numericFileEntryName}: The document's numeric file entry name. \texttt{versionNumber}: The document's version number. \begin{figure} \centering \includegraphics{./images/enterprise-file-system-store.png} \caption{The Simple File System Store creates a folder structure based on primary keys in Liferay DXP's database.} \end{figure} \noindent\hrulefill \textbf{Note:} Be careful not to confuse a document's numeric file entry name from its document ID. Each has an independent counter. The numeric file entry name is used in the folder path for storing the document, but the document ID is not. The numeric file entry name can be found in the \texttt{name} column of the \texttt{DLFileEntry} table in Liferay DXP's database; the document ID can be found in the \texttt{fileEntryId} column of the same table. \noindent\hrulefill \section{Using the Advanced File System Store}\label{using-the-advanced-file-system-store} The Advanced File System Store, like the Simple File System Store, saves files to the local file system. It uses a slightly different folder structure, however, and can overcome operating system limitations on the number of files stored in a particular folder by programmatically creating a structure that can expand to millions of files. It alphabetically nests the files in folders. This also improves performance, as there are fewer files stored per folder. \begin{figure} \centering \includegraphics{./images/enterprise-adv-file-system-store.png} \caption{The Advanced File System Store creates a more nested folder structure than the Simple File System Store.} \end{figure} The same rules apply to the Advanced File System Store as apply to the Simple File System Store. To cluster it, you must point the store to a network mounted file system that all the nodes can access. That networked file system must also support concurrent requests and file locking. Otherwise, you may experience data corruption issues if two users attempt to write to the same file at the same time from two different nodes. See the \href{/docs/7-2/deploy/-/knowledge_base/d/using-the-advanced-file-system-store}{Document Repository Configuration guide} for instructions on using the Advanced File System Store. \section{Using Amazon Simple Storage Service}\label{using-amazon-simple-storage-service} Amazon's Simple Storage Service (S3) is a cloud-based storage solution that you can use with Liferay DXP. It lets you store your documents to the cloud seamlessly from all nodes. When you sign up for the service, Amazon assigns you unique keys that link you to your account. In Amazon's interface, you can create \emph{buckets} of data optimized by region. Once you create these to your specifications, follow \href{/docs/7-2/deploy/-/knowledge_base/d/using-amazon-simple-storage-service}{these instructions} to connect your repository to Liferay DXP. Consult Amazon's S3 documentation for more information. \chapter{Liferay Sync}\label{liferay-sync} Liferay Sync synchronizes files between your Liferay DXP server and users' desktop and mobile environments. With Liferay Sync, users can publish and access shared documents and files from their native environments without using a browser. Clients for Windows and Mac OS desktops and Android and iOS mobile platforms are supported. As users add and collaborate on documents and files, Liferay Sync automatically synchronizes them across all configured Sync clients and your server. Liferay Sync integrates completely with Liferay DXP so that features such as authentication and versioning function in the supported environments. The Liferay Sync desktop client stores files locally so they're always available, even when users are offline. It automatically synchronizes files upon client reconnection. The Liferay Sync mobile client saves storage space on users' devices by downloading only the files they choose. \chapter{Administering Liferay Sync}\label{administering-liferay-sync} Before your users can use Liferay Sync with their Sites, you must install and configure it on your Liferay DXP server. The articles here walk you through this and also discuss important topics like preventing accidental file deletion and ensuring Sync security. As such, make sure you thoroughly read each article before letting your users connect to Sync. \noindent\hrulefill \textbf{Note:} To install and configure Liferay Sync on your Liferay DXP server, you must be an administrator. \chapter{Installing Liferay Sync's Prerequisites}\label{installing-liferay-syncs-prerequisites} Liferay Sync requires the \emph{Liferay CE Sync Connector} app from \href{https://web.liferay.com/marketplace}{Liferay Marketplace}. This app enables and configures Sync in your Liferay DXP instance. For example, you can disable Sync across the instance or on a site-by-site basis. Note that Sync is enabled by default for all Sites. For instructions on installing Marketplace apps, see \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{the Liferay Marketplace documentation}. \noindent\hrulefill \textbf{Note:} The Liferay Sync Security module that Sync requires is included and enabled by default in Liferay DXP. You can verify this by ensuring that the \texttt{SYNC\_DEFAULT} and \texttt{SYNC\_TOKEN} entries are enabled in \emph{Control Panel} → \emph{Configuration} → \emph{Service Access Policy}. \noindent\hrulefill If you want to use Sync Connector's default settings and are fine with Sync being enabled for all your Sites, you can skip the articles that follow on configuring Sync. However, before directing your users to install and configure the Sync desktop and mobile clients, \textbf{make sure to read} this guide's articles on preventing accidental file deletion and ensuring Sync security. You should also \textbf{warn your users} about the potential for accidental data loss. \chapter{Configuring Liferay Sync}\label{configuring-liferay-sync} Sync Connector lets you manage how, or if, clients connect to your Liferay DXP server. You can also configure default file permissions on a per-Site basis, and manage the devices that connect to your Liferay DXP instance. To access Sync Connector, select \emph{Control Panel} → \emph{Configuration} → \emph{Sync Connector Admin}. Sync Connector Admin has three tabs: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Settings:} Control Sync's general behavior. These settings apply globally to Sync. \begin{figure} \centering \includegraphics{./images/sync-admin-01.png} \caption{The Control Panel's Configuration section contains Sync Connector Admin.} \end{figure} \textbf{Allow the use of Sync?:} Whether Sync is enabled. \textbf{Allow users to sync their personal Sites?:} Whether users can sync data with their personal Sites. \textbf{Allow LAN Syncing?:} Whether desktop clients attempt to download updates from other desktop clients on the same local network before downloading from the server. This can help reduce server load and increase data transfer speeds. Note that LAN syncing only works with clients that also enable it. \textbf{Max Connections:} The maximum number of simultaneous connections each client is allowed per account. For example, if Max Connections is three, a client can simultaneously upload or download up to three files for each account. Note, this setting operates on a per client basis. If Max Connections is set to three and a user has two clients connected to an account (which is possible if Sync is installed on two different machines), then the user is effectively allowed six simultaneous connections. While increasing Max Connections can speed up file transfers it also places a heavier load on the server. \emph{Max Connections} is set to one by default. \textbf{Poll Interval:} The frequency in seconds that clients automatically check the Liferay DXP instance for updates. For example, if set to ten, connected clients check the instance for updates every ten seconds. The default Poll Interval is five. \textbf{Max Download Rate:} The maximum transfer rate, in bytes, at which clients can download. A value of 0 specifies no limit. This setting takes precedence over clients' download rate setting. \textbf{Max Upload Rate:} The maximum transfer rate, in bytes, at which clients can upload. A value of 0 specifies no limit. This setting takes precedence over clients' upload rate setting. \textbf{Force Security Mode:} Whether to force security mode on mobile clients. Security mode encrypts Sync files on the device and requires a passcode when accessing the Sync mobile app. \item \textbf{Sites:} Control Sync on a per-Site basis. \begin{figure} \centering \includegraphics{./images/sync-admin-02.png} \caption{Sync Connector Admin's Sites tab lets you manage Sync on a per-Site basis.} \end{figure} For each Site in the Liferay DXP instance, the Sites tab lists each Site's default file permissions (more on this in a moment) and whether Sync is enabled for that Site. Sync is enabled by default for all Sites. To disable Sync for a Site, click the Site's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Disable Sync Site}. \textbf{Please use caution} when disabling Sync for a Site, as doing so \textbf{deletes} files for that Site from the Sync clients. Disabling Sync for a Site, however, doesn't affect the Site's files on the server. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Warning:** Disabling Sync for specific Sites from Sync Connector Admin can result in data loss across clients. If Sync is disabled for a Site users are currently syncing, any files in the clients' sync folders for that Site are automatically deleted from their clients. If a user is offline when Sync is disabled for a Site, any offline changes or additions they make are deleted upon client reconnection. \end{verbatim} \noindent\hrulefill \begin{verbatim} You can enable Sync for a Site by selecting *Enable Sync Site* from its Actions button. Make sure that each Site for which Sync is enabled has a Documents and Media app on at least one of its pages. If a Site doesn't have the app on any of its pages and users click the *Open Website* link from their Sync menus, the error message *The requested resource was not found* appears. The Sites tab also sets default file permissions for files uploaded from Sync clients. The process for setting permissions is nearly the same as for enabling or disabling Sync for Sites. To set the default file permissions for a single Site, click its Actions button and select *Default File Permissions*. This lets you select the default file permissions for that Site. Click *Choose* for the permissions you want to use. ![ Click *Choose* to select the default file permissions for a Site in Sync.](./images/sync-admin-03.png) To set the default file permissions for several Sites, select the checkboxes for the Sites, click the *Default File Permissions* link that appears above the table, and select the permissions you want to use. Default file permissions might behave differently than you'd expect. They control *only* the permissions for new files uploaded through the Sync clients; they don't affect permissions for uploading or restrict document owners (the user who originally uploaded a document) in any way. For example, even if you set a Site's default file permissions to View Only, that Site's users can still upload new documents to the Site. The file's owner has edit permission; the rest of the Site's users have the View Only permission. \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item \textbf{Devices:} View and manage the devices registered with Sync. \begin{figure} \centering \includegraphics{./images/sync-admin-devices.png} \caption{Sync Connector Admin's Devices tab lists all the devices Sync has registered.} \end{figure} Each row in the Devices tab's table represents a device. The \emph{Name} column lists the user that registered the device. The remaining columns list each device's location, client type, client build number, last connection date, and status. Each device's Actions button (\includegraphics{./images/icon-actions.png}) manages that device. You can change a device's status from Active to Inactive by selecting \emph{Actions} → \emph{Disable Sync Device}. Inactive devices can't sync. Inactive mobile devices also can't access local Sync files. Once a device is Inactive, you can erase Sync files from it by selecting \emph{Actions} → \emph{Wipe Sync Device}. This also signs the device out and removes the account from the client. If the device is offline, this happens when it tries to reconnect. The Actions menu also enables or deletes an Inactive device. Deleting a device only removes it from the list of registered devices; it can still reconnect and reregister. \end{enumerate} \chapter{Preventing Accidental File Deletion in Liferay Sync}\label{preventing-accidental-file-deletion-in-liferay-sync} Liferay Sync's power rests in its ability to propagate between Liferay DXP and connected Sync clients. When a user deletes a file from a connected client, Sync also deletes the file in the instance and in any other connected clients. Likewise, if a user deletes a file in the instance, Sync also deletes the file in all connected clients. In other words, anywhere a user deletes a file, Sync deletes it \emph{everywhere}. But don't worry: Liferay DXP's Recycle Bin is enabled by default and lets you recover deleted files. You can access the Recycle Bin from each Site's \emph{Site Administration} menu. \noindent\hrulefill \textbf{Warning:} Liferay Sync automatically propagates file and folder deletion through the Liferay DXP server and in all connected clients. If an instance or Site administrator disables the Recycle Bin, deleted files can't be recovered. \noindent\hrulefill Liferay DXP instance and Site administrators can, of course, disable the Recycle Bin. Disabling the Recycle Bin in a Site, however, leaves the Site vulnerable to accidental file deletions that propagate through Sync. \chapter{Ensuring Liferay Sync Security}\label{ensuring-liferay-sync-security} As an administrator, you have a stake in the security of all connections to and from your servers. As long as Liferay DXP is configured to use HTTPS, Sync clients use user-supplied credentials to communicate securely. Users can only access the documents and Sites they're permitted to access. To support Security Mode in the Sync mobile client and securely transmit files, your Liferay DXP server must also use SSL. The next section demonstrates how Sync's permissions work with your Liferay DXP instance's permissions. \section{Liferay Sync Permissions Demonstration}\label{liferay-sync-permissions-demonstration} Sync uses Liferay DXP's default permissions to determine files and folders to sync with the user's devices. It can only sync files a user can access. After installing the desktop Sync client, follow the steps below to test this functionality. First, enter \texttt{classified\ information} into a new text file and save it on your desktop as \texttt{secret.txt}. Then use your browser to sign into Liferay DXP and create a new user with the user name \emph{secretagent} and the email address \emph{secretagent@liferay.com}. Give this user a password and then create a new private Site called \emph{Secret Site}. Create a page on the Site and add the Documents and Media app to it. Then add the secretagent user to the Secret Site and grant the \emph{Site Administrator} Role to the user. Log in as secretagent and navigate to the Secret Site. Then upload the \texttt{secret.txt} document to the Documents and Media app. Make sure you also have a user that isn't a member of the Secret Site and therefore doesn't have access to any of its documents through Sync. If you don't have such a user, create one now. Next, configure your Liferay Sync client to sign in with the secretagent user's credentials and sync with the Secret Site. Open the Liferay Sync menu from the system tray and select \emph{Preferences}. In the \emph{Accounts} tab, click the plus icon at the window's bottom left to add an account. Provide the secretagent user's credentials and uncheck all Sites except the Secret Site. Now confirm that Sync downloaded the \texttt{secret.txt} file to your new Sync folder. Open it and check that it contains the text \texttt{classified\ information}. Next, use Sync to connect to your Liferay DXP instance with the user that doesn't belong to the Secret Site. The file doesn't sync because this user isn't a Site member. Now go to Sync Connector Admin and set the Secret Site's default file permissions to View Only. Create a new user, add it to the Secret Site, and add its account in your Liferay Sync client. As with the secretagent user, Sync downloads the \texttt{secret.txt} file to this user's local Sync folder because the user is a member of the Secret Site. Now edit and save this file. Even though you can edit and save it locally, the edits aren't synced because the Site's default file permissions are View Only. After attempting the sync, a red \emph{x} appears next to the file in the local Sync folder. Right click the file to see the error. It confirms the user doesn't have the required permissions. \begin{figure} \centering \includegraphics{./images/sync-file-permissions-error.png} \caption{The upload error occurs because the user only has permission to view files.} \end{figure} To confirm that the error didn't propagate through Sync, open the file in the secretagent user's local Sync folder. It still contains the original text. Likewise, the original file remains in the Site's Documents and Media portlet. To get rid of the error in the other user's local Sync folder, return there and then right click the file and select \emph{Download From Server}. This replaces the file with the latest file in the Liferay DXP instance. Now edit \texttt{secret.txt} in the secretagent user's local Sync folder. When you check the file in the other user's local Sync folder and in the Liferay DXP instance, notice that Sync propagated the edits. The changes were propagated because the secretagent user owns the file in the instance. Owners can do anything with their files, even when the Site's default file permissions are set to View Only. \chapter{Using Liferay Sync on Your Desktop}\label{using-liferay-sync-on-your-desktop} Liferay Sync synchronizes files between your Liferay DXP Sites and desktop devices. It lets you work with your files without using a browser. The Sync clients also ensure that the files are updated with the latest changes made by other users. To use Liferay Sync in your desktop environment, you must install the Sync desktop client. It's currently available for Windows and Mac OS. The Sync client stores files locally so that they're always available, even when you're offline. Files are automatically synchronized upon your client's reconnection to your Liferay DXP server. On your desktop devices, Liferay Sync creates a new folder structure that it uses to synchronize files. You can treat the files the same as you do any others. Credentials, Sync folder location, and other options are configured in the client. Also, native desktop notification events inform you of what Sync is doing. The native menu and task bar integration keep Sync controls within easy reach. This guide walks you through setting up and using the Liferay Sync client on your desktop. Before proceeding, check your Liferay DXP instance or Site administrator to ensure that Sync is enabled for your Sites. \chapter{Installing and Configuring the Desktop Liferay Sync Client}\label{installing-and-configuring-the-desktop-liferay-sync-client} You can download the desktop client from the \href{https://customer.liferay.com/downloads}{Liferay customer downloads page}. Note that you'll need a Liferay account for this. Once you've downloaded the appropriate desktop client for your operating system, installing Liferay Sync on Windows or Mac OS is straightforward. \section{Installing the Liferay Sync Desktop Client}\label{installing-the-liferay-sync-desktop-client} To install the Liferay Sync client on Windows, you must have administrator privileges. Upon launching the Windows application installer, you're prompted to choose an install location. Select an appropriate location and click \emph{Install}. Sync automatically starts after the installation finishes. The first time Sync runs, you must configure it to connect and sync with Liferay DXP. The configuration steps are shown below. \noindent\hrulefill \textbf{Note:} You can upgrade previous versions of the desktop Liferay Sync client to version 3.0. When doing so, however, you must set up your account again in the new version of the client. Prior to upgrading, it's typically best to shut down Liferay Sync, backup files from your local Sync folder, and delete that folder. \noindent\hrulefill The Liferay Sync client for Mac is packaged in a DMG file. Double-clicking on the DMG file mounts it as a disk image and opens a window showing the image's contents. To install Sync, drag the Liferay Sync icon to your Applications folder. Once it's installed, run it from the Applications folder. If you're running Mac OS X 10.9 or lower, you're prompted for your machine's administrator credentials to install the Finder icon/context menu tool. This prompt only appears when installing or upgrading the tool. \begin{figure} \centering \includegraphics{./images/sync-mac-install.png} \caption{Drag the Liferay Sync icon to the Applications folder.} \end{figure} Next, you'll configure the Sync client. \section{Configuring the Liferay Sync Desktop Client}\label{configuring-the-liferay-sync-desktop-client} Now that you've installed Sync, you're ready to configure it! The configuration steps for Sync on Windows and Mac are identical. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open Sync and enter your server's address along with your account credentials. Click \emph{Sign In} when you're finished. \begin{figure} \centering \includegraphics{./images/sync-setup-01.png} \caption{The first time you run Liferay Sync, you must tell it how to communicate with your Liferay DXP server.} \end{figure} When connecting to a server via HTTPS, an error appears if the certificate can't be verified. Choosing \emph{Proceed Anyway} bypasses verification and leaves the connection open to compromise. Liferay Sync attempts to read the certificates specified in the Java Control Panel (\href{https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/jcp.html\#A1152831}{see section 20.4.5}). If Java isn't installed, you can also put your certificates in \texttt{{[}user.home{]}/.liferay-sync-3/certificates}. Liferay Sync trusts all certificates in this folder. \begin{figure} \centering \includegraphics{./images/sync-certificate-error.png} \caption{When connecting over HTTPS, Liferay Sync produces an error if it can't verify the security certificate. Choosing \emph{Proceed Anyway} bypasses verification and leaves the connection open to compromise.} \end{figure} \item Select the Sites you want to sync with. You can search for a Site in the \emph{Search} bar above the Site list. If you want to sync all the subfolders of your selected Sites, click \emph{Proceed} and move on to the next step. \begin{figure} \centering \includegraphics{./images/sync-setup-02.png} \caption{Select the Sites you want to sync with. Clicking a Site's gear icon opens another window where you can choose to sync with only specific subfolders in that Site.} \end{figure} To sync only specific folders in a Site, first click the Site's gear icon. In the window that appears, all folders are selected by default. Unselect the folders you don't want to sync with. Unselecting a subfolder causes the parent folder's checkbox to show a minus sign, indicating that you haven't selected all of the parent folder's subfolders. To sync only the documents at the top of a folder's hierarchy (no subfolders), unselect all of that folder's subfolders. You can also do this by clicking the folder's checkbox until the minus sign appears. Click \emph{Select} when you're finished with your selections, and then click \emph{Proceed} to move on to the next step. \begin{figure} \centering \includegraphics{./images/sync-select-folders.png} \caption{Choose the Site's subfolders that you want to sync with. The checkbox with the minus sign indicates that not all of the \emph{registration} folder's subfolders are selected.} \end{figure} \item Specify the local folder to sync. This folder is used exclusively for Sync: Sync creates it and it must not conflict with any existing local folder. The Sync folder's default name is the instance's host name, and its default location is the user's documents folder. For example, since the instance in the following screenshots runs locally at the address \texttt{http://localhost:8080/}, Sync creates a Sync folder named \emph{localhost} in the user's documents folder. You can, of course, specify any unique name and location for the Sync folder. Click \emph{Start Syncing} to begin syncing files. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Syncing to network drives is not supported because Liferay Sync can't reliably detect local file changes on such drives. \end{verbatim} \noindent\hrulefill \begin{verbatim} ![ Specify your local Sync folder's name and location.](./images/sync-setup-03.png) \end{verbatim} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{3} \item Celebrate! You've successfully set up Liferay Sync! Sync congratulates you on setting it up and begins to sync files from the Sites you selected to your local Sync folder. Note, completing the initial synchronization may take a significant amount of time, depending on the amount of data being transferred. You can safely close the window as syncing continues in the background. To view the local Sync folder, click \emph{Open Folder}. To open Sync's preferences, click the small gray text \emph{advanced setup} near the top-right. \begin{figure} \centering \includegraphics{./images/sync-setup-04.png} \caption{Congratulations, you've successfully set up Liferay Sync!} \end{figure} \end{enumerate} \chapter{Using the Liferay Sync Desktop Client}\label{using-the-liferay-sync-desktop-client} When Liferay Sync is running, its icon appears in your task bar (Windows) or menu bar (Mac). Clicking this icon opens a menu that lets you work with and manage Liferay Sync. \begin{figure} \centering \includegraphics{./images/sync-toolbar-01.png} \caption{The Liferay Sync menu in the Windows task bar and Mac menu bar gives you quick access to Sync.} \end{figure} The top of this menu shows your Sync status. If all your selected Sites are synced, then your status is \emph{Synced}. Below your Sync status, the menu lists three shortcuts for accessing your files: \textbf{Open Sync Folder:} Select a Site to open its local Sync folder. \textbf{View Website:} Select a Site to view the page in Liferay DXP that contains its Documents and Media app. \textbf{Recent Files:} Lists recently created and modified files in the repositories you can access. Note that if you sync with two or more Liferay DXP instances, Sync shows each at the top of the menu instead of your Sync status. Mouse over each instance to reveal a submenu with that instance's Sync status and file shortcuts. \begin{figure} \centering \includegraphics{./images/sync-toolbar-02.png} \caption{When you sync with more than one Liferay DXP instance, Sync shows submenus for each.} \end{figure} Finally, regardless of how many Liferay DXP instances you sync with, the menu lists the following three options: \textbf{Preferences:} Open Sync's preferences. \textbf{Help:} Open Sync's documentation. \textbf{Quit:} Shut down Sync on your machine. Next, you'll learn how to use Sync's preferences to control how Sync functions on your machine. \section{Using Sync Preferences}\label{using-sync-preferences} You can use Sync's preferences to add/remove Liferay DXP instances to sync with, edit connection settings, and control Sync's basic behavior. Open Sync's preferences by clicking the Sync icon in the task bar (Windows) or menu bar (Mac OS) and selecting \emph{Preferences}. A preference screen for your instance accounts displays. This is the \emph{Accounts} tab in \emph{Preferences}. \begin{figure} \centering \includegraphics{./images/sync-preferences-accounts-01.png} \caption{The Preferences menu's \emph{Accounts} tab lets you manage syncing with Sites per account.} \end{figure} The \emph{Accounts} tab contains the following options: \textbf{Accounts:} The accounts you sync with. When you select an account, the Sites you have permission to sync with are shown on the right under \emph{Syncing Sites}. You can use the plus, minus, and pencil icons at the bottom of the account list to add, delete, or edit an account, respectively. You should use caution when deleting an account from your Sync client, as doing so also deletes any local files and folders for that account. Adding an account takes you through the same set of steps you used to set up the Sync client. \href{/docs/7-2/user/-/knowledge_base/u/installing-and-configuring-the-desktop-liferay-sync-client\#configuring-the-liferay-sync-desktop-client}{Click here} for instructions on this. \textbf{Syncing Sites:} The Sites you have permission to sync with for the selected account. The Sites you currently sync with are shown under \emph{Selected Sites}. Other Sites available for syncing are shown under \emph{Unselected Sites}. To change the Sites you sync with, click the \emph{Manage Sites} button. The window that appears lets you select and/or unselect Sites to sync with. This window is identical to the one that appeared when you first configured the client. \href{/docs/7-2/user/-/knowledge_base/u/installing-and-configuring-the-desktop-liferay-sync-client\#configuring-the-liferay-sync-desktop-client}{Click here} and see step two for instructions on using it. Use caution when de-selecting Sites. De-selecting a Site deletes its folder on your machine. \textbf{Location:} The selected account's local Sync folder location. Click the \emph{Change} button to change this folder's location. The Preferences menu's \emph{General} tab contains settings for the Sync client's general behavior. It lists the following options: \textbf{Launch Liferay Sync on startup:} Starts Sync automatically each time your machine starts. \textbf{Show desktop notifications:} Shows a small notification in the corner of your screen when a synced file changes. \textbf{Automatically check for updates:} Automatically check for new client versions. You can click the \emph{Check Now} button to check for updates manually. \begin{figure} \centering \includegraphics{./images/sync-preferences-general-01.png} \caption{The Preferences menu's \emph{General} tab contains settings for Sync's general behavior.} \end{figure} Finally, the Preferences menu's \emph{Network} tab controls how Sync transfers data with your Liferay DXP servers. It contains the following options: \textbf{Download Rate:} To limit the rate at which Sync downloads data, select \emph{Limit to} and then specify the rate. \textbf{Upload Rate:} To limit the rate at which Sync uploads data, select \emph{Limit to} and then specify the rate. \textbf{Enable LAN Syncing:} Whether to download updates from other desktop clients on the same local network before downloading from the server. This can help reduce server load and increase data transfer speeds. Note that LAN syncing only works when enabled in the Liferay DXP instance by the administrator, and in other clients. \begin{figure} \centering \includegraphics{./images/sync-desktop-prefs-network.png} \caption{The Preferences menu's \emph{Network} tab contains settings for Sync's data transfer behavior.} \end{figure} Note that your Liferay DXP administrator can also limit the download/upload rate. In this case, Liferay DXP's settings take precedent. For example, if you set a 5.0 MB/s download rate in the client but Liferay DXP's download limit is 2.0 MB/s, the latter takes precedence. Also, the client's rate applies across all its accounts. For example, if the client connects to three accounts and its download rate is 5.0 MB/s, then the sum of the download rate for all three accounts never exceeds 5.0 MB/s. \chapter{Using Your Local Liferay Sync Folder}\label{using-your-local-liferay-sync-folder} Once you configure and run Sync, Sync automatically uploads to Liferay DXP any files you add or modify in your Sync folder. Sync also downloads to your Sync folder any file changes by other users. If you delete a file in your Sync folder, Sync also deletes it from the server and other clients. You should therefore use \textbf{extreme caution} when deleting files in your Sync folder. If, however, you accidentally delete a file, all is not lost! The file can still be recovered from the instance's Recycle Bin, which is enabled by default. Note, if the administrator has disabled the Recycle Bin, recovering deleted files is impossible. \noindent\hrulefill \textbf{Warning:} Deleting a file in your Sync folder also deletes it in the Liferay DXP instance and in other clients. If you accidentally delete a file, it can be recovered from the instance's Recycle Bin. The Recycle Bin is enabled by default. File recovery is, however, impossible if the instance or Site administrator has disabled the Recycle Bin. \noindent\hrulefill You can run through the following exercise to familiarize yourself with how to create, edit, download, and upload files with Sync. First, open the Sync folder in your file manager and create a new file called \texttt{README.txt}. Enter the word \texttt{test} in this file. Next, make sure you can access this file in your Site. Go to the Site you want to sync with and navigate to its Documents and Media app. It lists your \texttt{README.txt} file. Download the \texttt{README.txt} file to a convenient location on your machine. Open the file and check that it still says \texttt{test}. Now open the \texttt{README.txt} file in your Sync folder and edit it so that it says \texttt{second\ test}. Once the changes are synced, go back to your browser and refresh the page with your Documents and Media app. Click on the \texttt{README.txt} file's name, look at the file information displayed, and check that the file's version number has been incremented. \begin{figure} \centering \includegraphics{./images/sync-file-edit-01.png} \caption{Updating a file through Liferay Sync increments the file's version number. You can view a file's version number through the web interface.} \end{figure} If you download and open \texttt{README.txt} again, it now says \texttt{second\ test}. Your edit was uploaded to the Site! You can be confident that this edit was also downloaded by all other Sync clients connected to your Site. Now delete the \texttt{README.txt} file from your local Sync folder. When the changes finish syncing, go back to your browser and refresh the page containing your Documents and Media app. The file is gone! The file is also deleted from the local Sync folders of all other Sync clients connected to the Site. Remember this very important rule: deleting files in your local Sync folder deletes them \emph{everywhere}! Next, you'll learn how to use the Sync client for your mobile device. \chapter{Using Liferay Sync on Your Mobile Device}\label{using-liferay-sync-on-your-mobile-device} Liferay Sync for Android and iOS contains most of the \href{/docs/7-2/user/-/knowledge_base/u/using-liferay-sync-on-your-desktop}{desktop Sync client}'s functionality. The mobile client can, however, only be connected to one Liferay DXP account at a time. Also, mobile Sync doesn't automatically download files to your device. To save storage space on your device, the Sync mobile app lets you choose the files you want to work with. As with the Sync desktop clients, the latest versions of Sync on Android and iOS provide a consistent user experience across platforms. While this article details using Sync on Android, the instructions also apply to Sync on iOS. You need to download and install Sync on your Android or iOS device through its respective app store, the same as you do any other mobile app. To find the app, search Google Play or the App Store for \emph{Liferay}. You can also download Sync from the \href{https://customer.liferay.com/downloads}{Liferay customer downloads page}. Once you've installed the Sync app on your device, the rest of the articles in this guide show you how to use it. \chapter{Connecting Liferay Sync Mobile}\label{connecting-liferay-sync-mobile} When Liferay Sync first starts on your mobile device, press the \emph{Get Started} button to begin setup. The setup screen asks for your login credentials and your server's address. Once you enter them, press \emph{Sign In}. After signing in, you see a panel that shows your name, a gear icon for accessing the app's settings, and navigation options \emph{My Sites} and \emph{My Documents}. My Sites and My Documents encompass the Sites in Liferay DXP that you can sync with. My Documents is your personal user Site, while My Sites shows the other Sites with which you can sync. No matter how deep you are in the folder hierarchy of a Site, swiping to the right returns you to this panel. If you're in the first level of My Sites or My Documents, pressing the location bar at the top slides the screen slightly to the right to reveal a compact view of the panel. The following screenshots show both views of the panel. \begin{figure} \centering \includegraphics{./images/sync-mobile-panel.png} \caption{This panel lets you access the app's settings, as well as your Sites and documents.} \end{figure} \begin{figure} \centering \includegraphics{./images/sync-mobile-panel-compact.png} \caption{Tapping the title bar at the top of My Sites or My Documents opens the main Sync panel's compact view.} \end{figure} Press the gear icon to access Sync's settings. Settings shows your account information and an option to sign out of your Liferay DXP instance. Settings also lets you toggle \emph{Security Mode}. Security Mode protects files stored on your device by encrypting them. Using Security Mode requires you to set up a passcode to use when accessing the Sync app. Security Mode protects the files on your device and Liferay DXP instance in the event your device is lost or stolen. You should note, however, downloading and opening files in Security Mode takes slightly longer than usual because the Liferay DXP server must use SSL---if it didn't, your files would be transmitting in the open. Below the Security Mode toggle are the app's version and a link to send app feedback to Liferay. \begin{figure} \centering \includegraphics{./images/sync-mobile-settings.png} \caption{The Settings screen for the Sync app lets you sign out of your Liferay DXP instance, enable Security Mode, view the app's version, and send feedback.} \end{figure} \chapter{Managing Files and Folders in Liferay Sync Mobile}\label{managing-files-and-folders-in-liferay-sync-mobile} Whether you're working in My Documents or My Sites, you manage files and folders the same way. Pressing a Site or folder shows you a list of its files and folders. It displays each file's size and modification date. You can refresh the list by pulling down from the top of the screen. Your current location in the navigation hierarchy also appears at the top of the screen alongside a plus icon. Pressing the plus icon launches an upload screen for adding content in the current location. You can add a new folder, upload a file, or launch your device's camera app to take and upload a picture or video. Pressing the \emph{X} icon on the upload screen's top right corner cancels any action and returns you to the current file list. \begin{figure} \centering \includegraphics{./images/sync-mobile-site.png} \caption{Sync shows files and folders in a list.} \end{figure} To download a file to your device, press the file's name in the list. The label that previously showed the file's size and modification date is replaced by a download progress indicator. When the file finishes downloading, your device automatically opens it in the app you've configured to open files of that type. If you haven't configured your device to use a specific app for that file type, you're presented with a list of apps on your device that can open the file. If your device doesn't have an app that can open the file, Sync tells you to install one that can. Downloaded files appear in the list with the file size in blue instead of gray. For example, the screenshot below shows that \texttt{LiferayinAction.pdf} is on the device. \begin{figure} \centering \includegraphics{./images/sync-mobile-file-downloaded.png} \caption{Downloaded files appear in the list with their size in blue.} \end{figure} The Sync mobile app also lets you move, rename, and delete files and folders. To the right of each file and folder in the list is a circle icon with three dots. Pressing this icon slides open a context menu on the right that lets you move, rename, or delete that item. The screenshots below show these options. Note that you should use \textbf{extreme caution} when deleting files or folders. Deleting files or folders in the mobile Sync app also deletes them from Liferay DXP and across any synced clients. Accidentally deleted files can be restored from the Recycle Bin, which is enabled by default. If the instance or Site administrator disables the Recycle Bin, however, recovering deleted files is impossible. What if you want to delete a file on your device without also deleting it in the instance? Currently, you can only do this by signing out of your account in the app's Settings menu. Doing so removes all downloaded files from your device, but preserves them in the instance. If you're on Android, it may be possible to use a system file browser app to remove downloaded files manually. \noindent\hrulefill \textbf{Warning:} Deleting a file in the mobile Sync app deletes it in Liferay DXP and across any synced clients. If you accidentally delete a file, the instance or Site administrator can restore it from the instance's Recycle Bin. The Recycle Bin is enabled by default. If the instance or Site administrator disables the Recycle Bin, however, recovering deleted files is impossible. \noindent\hrulefill The context menu also provides additional options for files. A small badge on the file icon's top-right corner indicates the file's version in the Liferay DXP instance. You can also use the context menu to share files you've downloaded. Pressing the \emph{Share} icon opens a list of your device's apps capable of sharing the file. To close the context menu and return to the list of files and folders, swipe to the right. The following screenshot shows the options available in a file's context menu. \begin{figure} \centering \includegraphics{./images/sync-mobile-file-actions.png} \caption{The badge on the file's icon shows the file's version in the Liferay DXP instance. You can also share files that you've downloaded.} \end{figure} \chapter{Adapting Your Media Across Multiple Devices}\label{adapting-your-media-across-multiple-devices} Media providers must consider differences between devices (phones, laptops, tablets, etc.) when delivering content: not only their screen size but also their bandwidth and processing capabilities. Liferay DXP's Adaptive Media app allows administrators to control image quality and dynamically adjusts uploaded media to best fit the screen being used. \noindent\hrulefill \textbf{Note:} At this time, Adaptive Media only works for images in blog entries and web content articles. \noindent\hrulefill Adaptive Media integrates with Documents and Media, Blogs, and Web Content. It generates a set of images for use on various screens. When the content is accessed, Adaptive Media checks the screen type and resolution and selects appropriate the appropriate image. Adaptive Media comes preinstalled in Liferay DXP. In this section, you'll learn how to manage and use Adaptive Media. \chapter{Installing Adaptive Media}\label{installing-adaptive-media} The Adaptive Media app is installed in Liferay DXP by default. The following sections describe the Adaptive Media app's modules, and how to prepare Adaptive Media to handle animated GIFs. \noindent\hrulefill \textbf{Note:} Since the Adaptive Media app is installed by default, it's updated via Liferay DXP Fix Packs and Liferay Portal CE GA releases. Using \href{https://web.liferay.com/marketplace}{Liferay Marketplace} to update the app causes an error. \noindent\hrulefill \section{Adaptive Media's Modules}\label{adaptive-medias-modules} Some modules in the Adaptive Media app are mandatory and must be enabled for Adaptive Media to function, while others can be disabled. The Adaptive Media API modules, which export packages for the other modules to consume, are mandatory; disabling one also disables any other modules that depend on it. Here's a list of the Adaptive Media API modules: \begin{itemize} \tightlist \item Liferay Adaptive Media API \item Liferay Adaptive Media Content Transformer API \item Liferay Adaptive Media Image API \item Liferay Adaptive Media Image Item Selector API \end{itemize} The Adaptive Media core modules are also mandatory, and must be enabled to ensure that Adaptive Media works as expected: \begin{itemize} \tightlist \item Liferay Adaptive Media Document Library \item Liferay Adaptive Media Document Library Item Selector Web \item Liferay Adaptive Media Document Library Web \item Liferay Adaptive Media Image Content Transformer \item Liferay Adaptive Media Image Implementation \item Liferay Adaptive Media Image Item Selector Implementation \item Liferay Adaptive Media Image JS Web \item Liferay Adaptive Media Image Service \item Liferay Adaptive Media Image Taglib \item Liferay Adaptive Media Image Web \item Liferay Adaptive Media Item Selector Upload Web \item Liferay Adaptive Media Web \end{itemize} The Adaptive Media Blogs modules, which ensure that images uploaded to blog entries can be processed and adapted, are optional. Here's a list of these modules: \begin{itemize} \tightlist \item Liferay Adaptive Media Blogs Editor Configuration \item Liferay Adaptive Media Blogs Item Selector Web \item Liferay Adaptive Media Blogs Web \item Liferay Adaptive Media Blogs Web Fragment \end{itemize} The Adaptive Media Journal modules are optional. These modules apply Adaptive Media to web content articles: \begin{itemize} \tightlist \item Liferay Adaptive Media Journal Editor Configuration \item Liferay Adaptive Media Journal Web \end{itemize} There are two more optional modules included in Adaptive Media: \textbf{Liferay Adaptive Media Image Content Transformer Backwards Compatibility:} Ensures that content created before the Adaptive Media installation can use adapted images without the need to edit that content manually. It transforms the images both at startup and when a user views the content, which can negatively affect performance. We therefore recommend that you run some performance tests before using this module in production. You can disable this module if you don't have old content, are experiencing performance problems, or your old content doesn't need adapted images. \textbf{Liferay Adaptive Media Document Library Thumbnails:} Lets thumbnails in Documents and Media use adapted images. For this to work, you must first \href{/docs/7-2/user/-/knowledge_base/u/migrating-documents-and-media-thumbnails-to-adaptive-media}{migrate the original thumbnails to adapted images}. We highly recommend that you enable this module, but it's not mandatory. Great! Now you know the mandatory and optional modules that come with Adaptive Media. The next section discusses the installation requirements for using animated GIFs with Adaptive Media. If you don't need to use GIFs, you can skip ahead to the article on adding image resolutions to Adaptive Media. \section{Processing Animated GIFs}\label{processing-animated-gifs} To process animated GIFs, Adaptive Media uses an external tool called \href{https://www.lcdf.org/gifsicle}{Gifsicle}. This tool ensures that the animation works when the GIF is scaled to different resolutions. You must manually install Gifsicle on the server and ensure that it's on the \texttt{PATH}. Once it's installed, you must enable it in Adaptive Media's \href{/docs/7-2/user/-/knowledge_base/u/advanced-configuration-options}{advanced configuration options}. If Gifsicle isn't installed and \texttt{image/gif} is included as a supported MIME type in the advanced configuration options, Adaptive Media scales only a GIF's single frame. This results in a static image in place of the animated GIF. \chapter{Adding Image Resolutions}\label{adding-image-resolutions} To use Adaptive Media, you must first define the resolutions for the images delivered to users' devices. Adaptive Media then generates new images scaled to fit those resolutions, while maintaining the original aspect ratio. To access Adaptive Media settings, open the Control Panel and go to \emph{Configuration} → \emph{Adaptive Media}. Here you can create and manage resolutions. \noindent\hrulefill \textbf{Note:} Adaptive Media configurations apply only to the current Liferay DXP instance. \noindent\hrulefill Once you create a resolution, Adaptive Media automatically generates copies of newly uploaded images in that resolution. Images uploaded before you create the resolution aren't affected and must be adapted separately (see \href{/docs/7-2/user/-/knowledge_base/u/managing-image-resolutions\#generating-missing-adapted-images}{Generating Missing Adapted Images}). \begin{figure} \centering \includegraphics{./images/adaptive-media-image-resolutions.png} \caption{Adaptive Media's image resolutions are listed in a table.} \end{figure} \section{Adding a New Image Resolution}\label{adding-a-new-image-resolution} The number of image resolutions required and the values for each depend on the use case. More resolutions may optimize image delivery, but generating more images requires additional computational resources and storage space. To start, we recommend that you create resolutions to cover common device sizes like mobile phones, tablets, laptops, and desktops. If most users use one device (e.g., all Intranet users have the same company mobile phone), you can create a resolution to target that device. To add a new resolution, click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) on the Adaptive Media configuration page and provide the following information: \textbf{Name}: The resolution's name (this must be unique). This can be updated if a custom \texttt{Identifier} is defined. \textbf{Max Width}: The generated image's maximum width. If a \emph{Max Height} is given, this field is optional. This value must be at least \texttt{1}. \textbf{Maximum Height}: The generated image's maximum height. If a \emph{Max Width} is given, this field is optional. This value must be at least \texttt{1}. \noindent\hrulefill \textbf{Note:} Adaptive Media generates images that fit the Max Width and Max Height, while retaining the original aspect ratio. If you only provide one value (either Max Width or Max Height), the generated image scales proportionally to fit within the specified dimension, while maintaining its original aspect ratio. This ensures that adapted images are not distorted. \noindent\hrulefill \textbf{Add a resolution for high density displays (2x):} Defines a scaled up resolution for HIDPI displays. Selecting this option creates a new resolution double the size of the original with the same name and the suffix \texttt{-2x}. For example, if the original resolution is \texttt{400px} by \texttt{300px} (max width by max height), the high density resolution is \texttt{800px} by \texttt{600px}. \textbf{Identifier:} The resolution's ID. By default, this is automatically generated from the name. You can specify a custom identifier by selecting the \emph{Custom} option and entering a new \emph{ID}. Third party applications can use this ID to obtain images for the resolution via Adaptive Media's APIs. \noindent\hrulefill \textbf{Note:} Image resolutions and their identifiers can't be updated if the resolution has been used to adapt images. This prevents inconsistencies in generated images. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/adaptive-media-new-img-resolution.png} \caption{The form for adding a new Adaptive Media resolution.} \end{figure} \chapter{Managing Image Resolutions}\label{managing-image-resolutions} Adaptive Media lets you manage image resolutions and their resulting adapted images. For example, you can disable, enable, edit, and delete resolutions. You can also generate any adapted images that may be missing for a resolution. This article discusses these topics and more. \section{Disabling Image Resolutions}\label{disabling-image-resolutions} Disabling an image resolution prevents it from generating adapted images. Any images uploaded after the resolution is disabled use the most appropriate resolution that's still active. Adapted images previously generated by the disabled resolution are still available. To disable an image resolution, click its \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Disable}. \section{Enabling Image Resolutions}\label{enabling-image-resolutions} Image resolutions are enabled by default. If you need to enable a disabled resolution, click that resolution's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Enable}. While a resolution is disabled, it doesn't generate adapted images for new image uploads. After enabling a resolution, you should generate the adapted images that weren't generated while it was disabled (see \hyperref[generating-missing-adapted-images]{Generating Missing Adapted Images} for instructions on this). \section{Editing Image Resolutions}\label{editing-image-resolutions} You can't edit an image resolution that already has adapted images. This prevents odd behavior (of the adapted images---you're still free to be as odd as you want). This is because any changes would only be applied to images uploaded after the edit, creating an inconsistent set of adapted images. Odd indeed. Therefore, editing an image resolution is only possible if Adaptive Media hasn't yet generated adapted images for it. If you must change the values of a resolution that already has adapted images, you must delete that resolution and create a new one with the new values. The next section discusses deleting resolutions. \section{Deleting Image Resolutions}\label{deleting-image-resolutions} Be careful when deleting an image resolution, as any adapted images it created are irretrievably lost and are not automatically replaced by new image resolutions you create. Follow these steps to delete an image resolution: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Disable the resolution. You can't delete enabled resolutions. This prevents the accidental deletion of image resolutions. \item To delete the resolution and all its adapted images, select \emph{Delete} from the resolution's Actions menu (\includegraphics{./images/icon-actions.png}). \end{enumerate} \section{Generating Missing Adapted Images}\label{generating-missing-adapted-images} If Adaptive Media hasn't generated all the images you need---say, if new images were uploaded before a new image resolution was created or while the resolution was disabled--you must generate the missing images manually. \begin{figure} \centering \includegraphics{./images/adaptive-media-coverage.png} \caption{The \emph{Adapted Images} column shows the percentage of images that are adapted for each resolution.} \end{figure} To manually generate missing adapted images, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item For a single resolution, select \emph{Adapt Remaining} from the resolution's Actions menu (\includegraphics{./images/icon-actions.png}). \item For all resolutions at once, select \emph{Adapt All Images} from the Actions menu in the Control Menu at the top of the page. \end{enumerate} \section{The Recycle Bin and Adapted Images}\label{the-recycle-bin-and-adapted-images} You can't move adapted images directly to the Recycle Bin. But if the original image is in the Recycle Bin, the corresponding adapted images behave as if they are in the Recycle Bin and users can't view them. \noindent\hrulefill \textbf{Note:} URLs that point to adapted images whose original image is in the Recycle Bin return an error code of \texttt{404\ Not\ Found}. \noindent\hrulefill If the original image is restored from the Recycle Bin, the adapted images are accessible again. Awesome! Now you know how to manage image resolutions in Adaptive Media. Next, you'll learn about creating content with adapted images. \chapter{Creating Content with Adapted Images}\label{creating-content-with-adapted-images} Adaptive Media is mostly invisible for blog and web content creators. Once an image is added to the content, the app works behind the scenes to deliver an adapted image appropriate to the device in use. Content creators select an image when adding it to their content---they don't have to (and can't) select an adapted image. Adaptive Media identifies each adapted image in the content's HTML with a \texttt{data-fileentryid} attribute that is replaced with the latest adapted image when the user views the content. This lets Adaptive Media deliver the latest adapted images to your content, even if the content existed prior to those images. \noindent\hrulefill \textbf{Note:} If Adaptive Media is uninstalled, the original images are displayed in the blog entries and web content articles. \noindent\hrulefill \section{Including Adapted Images in Content}\label{including-adapted-images-in-content} Since Adaptive Media delivers the adapted images behind the scenes, content creators should add images to \href{/docs/7-2/user/-/knowledge_base/u/publishing-blogs}{blog entries} and \href{/docs/7-2/user/-/knowledge_base/u/creating-web-content}{web content} as usual: by clicking the image button in the editor and then selecting the image in the file selector. However, there are some important caveats. When using the file selector to include an image for a blog entry, Adaptive Media works only with images added from the \emph{Blog Images}, \emph{Documents and Media}, and \emph{Upload} tabs. Additionally, adapted images can only be applied to a blog entry's content--cover images excluded. Adaptive Media works for images added to a blog entry via drag and drop, as the image is automatically uploaded to the Blog Images repository, adapted, and then included in the blog entry's content. You can see this by inspecting the HTML and checking that the image contains the \texttt{\textless{}img\textgreater{}} tag and \texttt{data-fileentryid} attribute. For web content articles, Adaptive Media works only with images added from the file selector's \emph{Documents and Media} tab. Unlike blogs, Adaptive Media doesn't deliver adapted images for images added to web content articles via drag and drop. For both blog entries and media content articles, Adaptive Media doesn't work with images added from the file selector's \emph{URL} tab. This is because the image is linked directly from the URL and therefore provides no image file for Adaptive Media to copy. Note that you can see the \texttt{\textless{}img\textgreater{}} tag and \texttt{data-fileentryid} attribute in the HTML of a blog entry or a web content article while you're writing it. When the content is displayed, the HTML is automatically replaced and looks similar to this: \begin{verbatim} \end{verbatim} This example uses three different images, each with a different resolution. A \texttt{source} tag defines each of these images. Also note the original image (\texttt{img}) is used as a fallback in case the adapted images aren't available. \section{Using Adapted Images in Structured Web Content}\label{using-adapted-images-in-structured-web-content} To use adapted images in \href{/docs/7-2/user/-/knowledge_base/u/designing-uniform-content}{structured web content}, content creators must manually include an image field in the web content's structure. Then they can reference that image field in the matching template by selecting it on the left side of the editor. Here's an example snippet of an image field named \texttt{Imagecrrf} included in a template: \begin{verbatim} <#if Imagecrrf.getData()?? && Imagecrrf.getData() !=""> ${Imagecrrf.getAttribute( \end{verbatim} This snippet includes the \texttt{data-fileentryid} attribute to ensure that Adaptive Media replaces the image with an adapted image. If you inspect the resulting web content's HTML in the editor's code view, you should see a tag like this: \begin{verbatim} \end{verbatim} Note the \texttt{\textless{}img\textgreater{}} tag with a \texttt{data-fileentryid} attribute. Adaptive Media uses the file entry ID to replace the \texttt{\textless{}img\textgreater{}} element automatically with a \texttt{\textless{}picture\textgreater{}} element that contains the available adapted images for each resolution (see the \texttt{\textless{}picture\textgreater{}} example above). \section{Staging Adapted Images}\label{staging-adapted-images} Adaptive Media is fully integrated with Liferay DXP's \href{/docs/7-2/user/-/knowledge_base/u/staging}{content staging} and \href{/docs/7-2/user/-/knowledge_base/u/exporting-importing-widget-data}{export/import} functionality. Adaptive Media includes adapted images in staged content when published, and can update those images to match any new resolutions. Similarly, when content that contains adapted images is exported, Adaptive Media exports those images in the LAR file. That LAR file can then be imported to restore or transfer that content, along with its adapted images. Adaptive Media doesn't regenerate adapted images during export/import or the publication of staged content. To improve performance, Adaptive Media instead reuses the existing adapted images. Awesome! Now you know how create content that contains adapted images. You also know how Adaptive Media includes adapted images in the content's HTML. \chapter{Migrating Documents and Media Thumbnails to Adaptive Media}\label{migrating-documents-and-media-thumbnails-to-adaptive-media} Liferay DXP automatically generates thumbnails for images in Documents and Media. Once you deploy the Adaptive Media app, however, Liferay DXP doesn't display thumbnails until you migrate them to Adaptive Media. This article walks you through this migration process. \noindent\hrulefill \textbf{Note:} You must be a Portal Administrator to perform the actions described here. \noindent\hrulefill You'll get started by creating image resolutions for the thumbnails in Adaptive Media. \section{Adding the Replacement Image Resolutions}\label{adding-the-replacement-image-resolutions} To migrate the existing Documents and Media thumbnails, you must add new image resolutions in Adaptive Media that have maximum height and maximum width values that match the values specified in the following portal properties: \begin{verbatim} dl.file.entry.thumbnail.max.height dl.file.entry.thumbnail.max.width dl.file.entry.thumbnail.custom1.max.height dl.file.entry.thumbnail.custom1.max.width dl.file.entry.thumbnail.custom2.max.height dl.file.entry.thumbnail.custom2.max.width \end{verbatim} \noindent\hrulefill \textbf{Note:} Some of these properties may not be enabled. You need only create image resolutions in Adaptive Media for the enabled properties. \noindent\hrulefill To create the new Image Resolutions, follow the instructions found in the \href{/docs/7-2/user/-/knowledge_base/u/adding-image-resolutions}{Adding Image Resolutions} section of the Adaptive Media user guide. Now you're ready to to create the Adaptive Media images. \section{Creating the Adaptive Media Images}\label{creating-the-adaptive-media-images} Once the required image resolutions exist, you can convert the Documents and Media thumbnails to Adaptive Media images. As mentioned in \href{/docs/7-2/user/-/knowledge_base/u/installing-adaptive-media}{the Adaptive Media installation guide}, the module \emph{Liferay Adaptive Media Document Library Thumbnails} (which is included in the Adaptive Media app) enables this functionality. There are two different ways to migrate the Documents and Media thumbnails to Adaptive Media: \textbf{Adapt the images for the thumbnail image resolution:} This scales the existing thumbnails to the values in the Adaptive Media image resolutions, which can take time depending on the number of images. We only recommend this approach when there isn't a large number of thumbnails to process, or if you prefer to generate your images from scratch. This approach is covered in more detail in \href{/docs/7-2/user/-/knowledge_base/u/managing-image-resolutions\#generating-missing-adapted-images}{Generating Missing Adapted Images}. \textbf{Execute a migrate process that reuses the existing thumbnails:} This copies the existing thumbnails to Adaptive Media, which performs better because it avoids the computationally expensive scaling operation. The next section describes the steps to run this process. \section{Running the Migration Process}\label{running-the-migration-process} The migration process is a set of Gogo console commands. You can learn more about using the Gogo console in \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{the Felix Gogo Shell article}. Follow these steps to migrate your thumbnails from the Gogo console: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Run the \texttt{thumbnails:check} command. For each instance, this lists how many thumbnails are pending migration. \item Run the \texttt{thumbnails:migrate} command. This executes the migration process, which may take a while to finish depending on the number of images. \item Run the \texttt{thumbnails:cleanUp} command. This deletes all the original Documents and Media thumbnails and updates the count returned by \texttt{thumbnails:check}. Therefore, you should \textbf{only} run \texttt{thumbnails:cleanUp} after running the migrate command and ensuring that the migration ran successfully and no images are pending migration. \end{enumerate} \noindent\hrulefill \textbf{Note:} If you undeploy Adaptive Media at some point after running the migration process, you must regenerate the Documents and Media thumbnails. To do this, navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Server Administration} and click \emph{Execute} next to \emph{Reset preview and thumbnail files for Documents and Media}. \noindent\hrulefill Great! Now you know how to migrate your Documents and Media thumbnails to adapted images. \chapter{Advanced Configuration Options}\label{advanced-configuration-options} Adaptive Media's advanced configuration options are available in System Settings. Open the Control Panel and go to \emph{Configuration} → \emph{System Settings}, then select \emph{Adaptive Media}. There are two configurations listed under \emph{SYSTEM SCOPE}: \begin{itemize} \tightlist \item Images \item Processes \end{itemize} The \emph{Images} configuration contains the following options: \textbf{Supported MIME Types:} A list of the image MIME types that Adaptive Media supports. If an image is uploaded and its MIME type isn't in this list, Adaptive Media ignores the image. By default, this list contains many common MIME types. \textbf{Gifsicle:} To scale animated GIFs, Adaptive Media uses an external tool called \href{https://www.lcdf.org/gifsicle/}{Gifsicle}. First install Gifsicle on the server, ensure that it's on the \texttt{PATH} environment variable, and then click the box next to \emph{Gifsicle Enabled}. If Gifsicle isn't installed and \texttt{image/gif} is included as a supported MIME type, Adaptive Media scales only one frame of the GIF, making a static GIF. \textbf{Max Image Size:} Maximum size of the source images that Adaptive Media can use to generate adapted images. Adaptive Media will not generate adapted images for source images larger than this setting. The default value is 10 MB. To generate adapted images for all source images regardless of size, set this to -1. Since generating adapted images from large source images requires significant amounts of memory, you can specify a lower \emph{Max Image Size} to avoid out of memory errors. \begin{figure} \centering \includegraphics{./images/adaptive-media-config-01.png} \caption{You can configure Gifsicle and the maximum image size for Adaptive Media.} \end{figure} The \emph{Processes} configuration is related to Adaptive Media's asynchronous processing. These values can be modified to improve performance for specific scenarios or use cases. The following options are available: \textbf{Max Processes:} The maximum number of processes for generating adapted media. The default value is \texttt{5}. \textbf{Core Processes:} The number of processes always available for generating adapted media. The default value is \texttt{2}. This setting can't exceed the \emph{Max processes} setting. \noindent\hrulefill \textbf{Warning:} Larger values for Max Processes and Core Processes may cause out of memory errors, as processing more images at once can consume large amounts of memory. Out of memory errors can also occur if the source images Adaptive Media uses to generate adapted images are large. You can restrict the maximum size of such images via the \emph{Max Image Size} setting in the \emph{Adaptive Media Image} configuration, which is described next. You should run performance tests to optimize these settings for the amount of memory available on your system. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/adaptive-media-config-02.png} \caption{You can also configure Adaptive Media's image processing resources.} \end{figure} \chapter{Collaborating}\label{collaborating} Your users can leverage a robust suite of collaboration apps to get things done and form extensive communities. These apps provide all the features that you would expect of standalone apps, but these apps are integrated: they share a common look and feel, security model, and architecture because of Liferay's development framework. You can use them in combination with user registration and content management features to build a well-integrated, feature-rich site. This guide shows you how to administer and use the collaboration apps: \begin{itemize} \tightlist \item Blogs \item Message Boards \item Wiki \item Alerts and Announcements \item Knowledge Base \item Bookmarks \end{itemize} \begin{figure} \centering \includegraphics{./images/blog-entry-abstract.png} \caption{This blog entry looks fascinating.} \end{figure} \begin{figure} \centering \includegraphics{./images/message-boards-participate-in-threads.png} \caption{This is a great thread.} \end{figure} \begin{figure} \centering \includegraphics{./images/wiki-page-full.png} \caption{The Wiki widget displays your wiki on a Site page.} \end{figure} \chapter{Publishing Blogs}\label{publishing-blogs} The Blogs app's editor has a complete set of WYSIWYG controls that appear when and where you need them. You can also switch to source mode to edit your content's HTML code. In source mode, you can work with light text on a dark background or dark text on a light background. You can even open a dual-screen HTML editor to see your code rendered in real time. The Blogs app also contains a powerful set of tools for customizing how your blogs appear. For example, display templates like Abstract or Full Content let you choose how much of a blog post appears on a page. You can leverage the built-in display templates or create your own. You can also add a cover image to each of your blog entries. Let's face it---you might not be able to judge a book by its cover, but a blog post with a nice cover image is more likely to draw readers. Read on to learn about Liferay's blogging platform. \begin{figure} \centering \includegraphics{./images/blog-entry-abstract.png} \caption{This blog entry looks fascinating.} \end{figure} \chapter{Adding Blog Entries}\label{adding-blog-entries} Each site contains a built-in blog instance, so you can add blog entries to it right away. The easiest way to do this is in the Site Administration menu. Follow these steps to add a blog entry in Site Administration: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Menu button (\includegraphics{./images/icon-menu.png}) to open the product menu. Then expand the menu for your site and select \emph{Content \& Data} → \emph{Blogs}. This takes you to the Blogs app for your site. The \emph{Entries} tab is selected by default, which lists the site's blog entries. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to create a new blog entry. This presents the blog entry editor. Note that the same form appears when editing a blog entry. \begin{figure} \centering \includegraphics{./images/blogs-new-entry.png} \caption{This screenshot shows some of the blog entry editor's controls.} \end{figure} \item The first input field, \emph{Drag \& Drop to Upload}, is for optionally adding a cover image for your entry. By default, an \href{/docs/7-2/user/-/knowledge_base/u/publishing-assets}{Asset Publisher} shows this cover image as part of the blog entry's abstract. You can insert any image you like in this field, either via drag and drop or the \emph{Select File} button. The latter lets you choose an existing image in the blog, an image from Documents and Media, or an image that you upload from your machine. If you select an image from Documents and Media, you can make changes to it with the \href{/docs/7-2/user/-/knowledge_base/u/editing-images}{Image Editor}. Edits you make are applied automatically to a copy of the image, which you can then use as your cover photo. Upon upload, the image appears in the pane. To center the image, drag it into place. You can also add a caption. If you want to select a different image, click the \emph{Change} icon (\includegraphics{./images/icon-change.png}). Clicking the trash can icon removes the image from the blog entry. \item Enter a title for your blog entry. You can also add a subtitle if needed. \item Enter your blog entry's content in the \emph{Content} field. This field is small at first, but it expands as you add content. The editor displays the editing controls when you need them and hides them from view when you don't. When you select text in your blog post, for example, a bar with context-specific editing controls appears. This keeps your canvas uncluttered so you can focus on writing. You can also add images, videos, and tables in your blog entry's content. See \href{/docs/7-2/user/-/knowledge_base/u/using-the-blog-entry-editor}{Using the Blog Entry Editor} for instructions on creating your blog entry's content. \item Expand the \emph{Categorization} panel and associate your blog entry with \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{tags and/or categories}. Although this is optional, it improves search results for blog entries and gives your users more navigation options. For example, you can add the Tags Navigation app to another column on your blogs page, which lets users browse blog entries by tag. \item Expand the \emph{Related Assets} panel and choose any other content in your site that you want to associate with this blog entry. Although this is optional, related assets let you tie together content on your site. For example, you might want to write a blog entry about a discussion that happened on the forums. To link those two assets together, select the forum thread under Related Assets. For more information, see the \href{/docs/7-2/user/-/knowledge_base/u/defining-content-relationships}{related assets documentation}. \item Expand the \emph{Configuration} panel if you want to customize your blog entry's URL, abstract, or display date. You can also set whether to allow pingbacks for your blog entry. For the URL, the default selection of \emph{Automatic} generates the URL for you based on the blog entry's title. This URL appears in the \emph{Blog Entry URL} text box. Selecting \emph{Custom} lets you enter your own URL. Note that if you change the blog entry's URL after publishing the entry, the original URL redirects to the new URL. You can also specify the blog entry's abstract. Enter a 400 character text-only abstract, or a custom abstract that contains a thumbnail image and a manually written description. The \emph{Small Image} section lets you add a small image that appears when blog entries are displayed in list view. Below the abstract section, you can set the entry's display date and time. Note that if you're editing an existing blog entry, the \emph{Send Email Entry Updated} toggle appears. Setting this to \emph{YES} sends an email to any subscribers when the blog entry is updated. You can customize this email when \href{/docs/7-2/user/-/knowledge_base/u/configuring-the-blogs-app}{configuring the Blogs app}. Finally, you can allow \emph{pingbacks} for the blog entry. Pingbacks are XML-RPC requests that are sent automatically when you link to another site. If you link to another site in your blog entry, Liferay DXP sends a pingback to the other site to notify that site that you linked to it. Similarly, if someone links to your blog entry, Liferay DXP can receive a pingback from that site and record the link. \begin{figure} \centering \includegraphics{./images/blog-entry-configuration.png} \caption{When creating a blog entry, the Configuration panel lets you control when and where the blog entry appears, and what to use for the entry's abstract.} \end{figure} \item Expand the \emph{Display Page Template} panel if you want to select a \href{/docs/7-2/user/-/knowledge_base/u/display-pages-for-web-content}{display page template} for displaying your blog entry. The following options are available: \textbf{Default Display Page Template:} Use the default display page template. \textbf{Specific Display Page Template:} Click the \emph{Select} button to select the display page template you want to use. \textbf{No Display Page Template:} Do not use a display page template. \item Expand the \emph{Permissions} panel to customize your blog entry's permissions. Use the \emph{Viewable by} selector to set who can view the blog entry: \textbf{Anyone (Guest Role):} Anyone, including guests, can view the entry. \textbf{Site members:} Only site members can view the entry. \textbf{Owner:} Only the entry's owner can view the entry. Click the \emph{More Options} link to bring up a permissions table that lets you grant or revoke the following permissions for guests and site members: \textbf{Update Discussion}: Edit another user's comment on the blog entry. \textbf{Delete}: Move the blog entry to the \href{/docs/7-2/user/-/knowledge_base/u/using-the-recycle-bin}{Recycle Bin}. \textbf{Permissions}: View and modify the blog entry's permissions. \textbf{Delete Discussion}: Delete any comments on the blog entry. \textbf{Update}: Edit and modify the blog entry. \textbf{View}: View the blog entry. \textbf{Add Discussion}: Comment on the blog entry. \item Click \emph{Publish} to publish your blog entry. It now appears in the \emph{Entries} tab. \begin{figure} \centering \includegraphics{./images/blog-entries-site-admin.png} \caption{The Blogs app in Site Administration lists the site's blog entries.} \end{figure} \end{enumerate} \chapter{Using the Blog Entry Editor}\label{using-the-blog-entry-editor} When you \href{/docs/7-2/user/-/knowledge_base/u/adding-blog-entries}{create a new blog entry}, you create its content with the blog entry editor. This editor is simple yet powerful. Its editing tools are context-aware, only appearing when you need them. They aren't visible while you're writing, which lets you focus on the task at hand. But when you select text, for example, a toolbar appears that contains buttons for styling the text. There are similar contextual options for adding and editing images, tables, and other types of content. And this is all in the editor's text view. You can switch to code view to edit the content's HTML. Regardless of which view you use, your entry is automatically saved as a draft every 25 seconds, so a browser crash or network interruption won't cause you to lose your work. This guide shows you how to use this editor to create and edit blog entries. \begin{figure} \centering \includegraphics{./images/blogs-edit-entry.png} \caption{This screenshot shows some of the blog entry editor's controls.} \end{figure} \section{Using the Editor's Text View}\label{using-the-editors-text-view} When you create or edit a blog entry, the editor is in text view by default. Text view is a WYSIWYG editor that lets you enter and edit text and other types of content. To enter text, place your cursor in any text field (e.g., Title, Subtitle, Content, etc.) and type or paste your text. Note that the Content area expands to fit its contents. To style or format text in the Content area, first select the text. A toolbar appears above the text that contains the following options, as shown in the above screenshot: \textbf{Text Style:} This selector menu, set to Normal by default, lets you choose the text's style. Normal is typical body text, but you can also select from different heading styles, alert or error message styles, code style, and more. \textbf{Typeface:} Select bold, italic, or underline. \textbf{List Style:} Select a numbered or bulleted list. \textbf{Link:} Link the selected text to a specific URL, or to an item in the portal (e.g.~a file in Documents and Media). \textbf{Twitter:} Generates a link to tweet the selected text. When you park your cursor in the entry's content area, the \emph{Add} icon (\texttt{+}) appears. If you click this icon, it shows controls for inserting an image, video, table, or horizontal line (\includegraphics{./images/icon-content-insert-controls.png}). Follow these instructions to insert each: \textbf{Image:} Click the mountain icon, then select or upload an image in the image file selector screen that appears. Alternatively, you can drag-and-drop image files into the content area. You can also use the \href{/docs/7-2/user/-/knowledge_base/u/editing-images}{built-in Image Editor} to apply simple edits to an image. Any edits you make are automatically applied to a copy of the image. After you add an image to the blog entry, clicking the image brings up controls for justifying it to the right or left side of the article. \textbf{Video:} Click the play icon and insert the video's link. The video then appears in your content. \textbf{Table:} Click the table (grid) icon and then choose the number of rows and columns in your table. When you click inside the table, table editing controls appear. They let you designate the first row and/or column as table headers. The controls also enable you to add rows, columns, and cells. As you type in a cell, the column width automatically adjusts to fit the content. \textbf{Line}: Click the line icon. A simple, lightweight horizontal line then appears in your content. Such lines are good for separating sections of content in a large blog entry. \section{Using the Editor's Code View}\label{using-the-editors-code-view} To switch to code view, click the the \emph{Source} icon (\texttt{\textless{}/\textgreater{}}) that appears when you place your cursor in the Content area. The following buttons exist at the top-right of code view: \textbf{Text View} (\includegraphics{./images/icon-text.png}): Switch back to text view. \textbf{Dark/Light Theme} (\includegraphics{./images/icon-dark-theme.png} / \includegraphics{./images/icon-light-theme.png}): Switch the code editor between dark and light theme. \textbf{Fullscreen} (\includegraphics{./images/icon-enlarge.png}): Work in a dual-pane view that shows your HTML code on the left and a preview pane on the right. In this view, you can arrange the HTML and preview panes horizontally or vertically. You can also hide the preview pane so the HTML editor takes up the entire window space. \begin{figure} \centering \includegraphics{./images/blogs-code-view.png} \caption{Editing in code view lets you work with your blog entry's underlying HTML.} \end{figure} \chapter{Managing Blog Entries}\label{managing-blog-entries} The Blogs app in Site Administration helps bloggers and blog administrators manage blog entries. To access this app, click the Menu button (\includegraphics{./images/icon-menu.png}) to open the product menu, then expand the menu for your site and select \emph{Content \& Data} → \emph{Blogs}. The \emph{Entries} tab is selected by default, which lists the site's blog entries. \begin{figure} \centering \includegraphics{./images/blog-entries-site-admin.png} \caption{The Blogs app in Site Administration lists the site's blog entries.} \end{figure} You can use the Management Bar to manage your site's blog entries. If you've added blog entries via your site's Blogs app, then you're already familiar with the Management Bar's \emph{Add} button (\includegraphics{./images/icon-add.png}). The sections that follow describe how to manage your blog entries. \begin{itemize} \tightlist \item \hyperref[view-types]{View Types} \item \hyperref[finding-and-arranging-blog-entries]{Finding and Arranging Blog Entries} \item \hyperref[selecting-blog-entries]{Selecting Blog Entries} \item \hyperref[sharing-blog-entries]{Sharing Blog Entries} \end{itemize} \section{View Types}\label{view-types-1} The \emph{View Types} button is to the left of the Add button. It lets you choose how to display the blog entries in the Blogs app. The View Types button's icon depends on the selected view type: \textbf{Cards} (\includegraphics{./images/icon-view-type-cards.png}): Shows a card-like rendering of the blog entry, with the author's profile picture. If the entry doesn't contain a cover image, a generic rendering of the entry is displayed. Each card also contains the entry's timestamp, title, \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow} status (e.g., Approved, Draft, etc.), and an Actions menu (\includegraphics{./images/icon-actions.png}). \textbf{List} (\includegraphics{./images/icon-view-type-list.png}): Shows the same information as the Cards view type, in a list with the author's profile picture instead of the blog entry's cover image. \textbf{Table} (\includegraphics{./images/icon-view-type-table.png}): Shows the same information as the other view types, in a list with no file renderings. Also, the blog entry information is in columns. \section{Finding and Arranging Blog Entries}\label{finding-and-arranging-blog-entries} The Management Bar also contains tools that help you locate and arrange blog entries. The most prominent of these tools is the \emph{Search} bar, where you can find files by keywords. To the left of the Search bar, the Sort button (\includegraphics{./images/icon-sort.png}) arranges entries in ascending or descending order. You can also arrange entries via the \emph{Filter and Order} selector using these criteria: \textbf{All:} Shows all of the site's entries. \textbf{Mine:} Shows only the current user's entries. \textbf{Display Date:} Orders the entries by display date. \section{Selecting Blog Entries}\label{selecting-blog-entries} The checkbox on the left-most side of the Management Bar selects all currently displayed blog entries. Selecting multiple entries lets you act on all of them at once. You can also select multiple entries individually by using the checkboxes for each. When you select one or more entries, the Management Bar changes to reflect the actions you can take on the selected entries. Click the \emph{Trash} button (\includegraphics{./images/icon-trash.png}) to move the selected entries to the Recycle Bin. Unselect the checkbox to return the Management Bar to its normal view. \begin{figure} \centering \includegraphics{./images/blog-management-bar-selected.png} \caption{With multiple blog entries selected, the management bar changes to reflect the actions you can take on the selected entries.} \end{figure} \section{Sharing Blog Entries}\label{sharing-blog-entries} You can also share blog entries from the Blogs app in Site Administration. Sharing is enabled by default, as described in \href{/docs/7-2/user/-/knowledge_base/u/configuring-sharing}{Configuring Sharing}. Sharing blog entries works the same as \href{/docs/7-2/user/-/knowledge_base/u/sharing-files}{sharing files}. When sharing is enabled and you have sharing permission, you can share a blog entry by clicking its \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and selecting \emph{Share}. \begin{figure} \centering \includegraphics{./images/blog-share.png} \caption{You can share a blog entry via its Actions menu.} \end{figure} \chapter{Configuring the Blogs App}\label{configuring-the-blogs-app} By configuring the Blogs app in Site Administration, you can control how the app behaves for all blogs in your site. To access this app, click the Menu button (\includegraphics{./images/icon-menu.png}) to open the product menu, then expand the menu for your site and select \emph{Content} → \emph{Blogs}. The \emph{Options} menu (\includegraphics{./images/icon-options.png}) at the top-right of the Blogs app lets you configure permissions and notifications, or import/export the app's content. \begin{figure} \centering \includegraphics{./images/blog-instance-options.png} \caption{You can configure the options for your site's Blogs app.} \end{figure} Here are each of the options available in this menu: \textbf{Entries Permissions:} Configure the permissions that can be applied to the Blogs app. You can control which roles can add an entry, configure entry permissions, and subscribe to entries. \textbf{Export/Import:} Export or import a LAR file that contains the Blogs app's content. \textbf{Configuration:} Configure the following options for the Blogs app, in these tabs: \begin{itemize} \tightlist \item \textbf{Email From:} Define the \emph{From} field in the email messages that users receive from Blogs. \item \textbf{Entry Added Email:} Define a subject and body for the emails sent when a new blog entry has been added. \item \textbf{Entry Updated Email:} Define a subject and body for the emails sent when a new blog entry has been updated. \item \textbf{RSS:} Lets you enable RSS subscription and choose how blogs are displayed to RSS readers. The \emph{Maximum Items to Display} selector lets you choose the total number of RSS feed entries to display on the initial page. You can choose up to one hundred to be displayed. The \emph{Display Style} selector lets you choose between \emph{Full Content}, \emph{Abstract}, and \emph{Title} for the entry display in the RSS feed. Lastly, the \emph{Format} selector lets you choose which format the RSS feed uses to deliver the entries: Atom 1.0, RSS 1.0, or RSS 2.0. \end{itemize} \chapter{Displaying Blogs}\label{displaying-blogs} The Blogs app in Site Administration lets you \href{/7-2/user/-/knowledge_base/u/adding-blog-entries}{add}, \href{/7-2/user/-/knowledge_base/u/managing-blog-entries}{manage}, and \href{/docs/7-2/user/-/knowledge_base/u/configuring-the-blogs-app}{configure} your site's blogs. You can then display those blogs by adding a separate Blogs widget to a page. Adding the Blogs widget to a site page creates a shared blog for site members. Adding the widget to a user's personal site (dashboard) creates a blog just for that user. The widget works the same way in both cases. And of course, you can scope a blog to a page to produce a blog instance for just that page. To add a Blogs widget to a page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the page. \item From the \emph{Add} menu (\includegraphics{./images/icon-add-app.png}), open \emph{Widgets} → \emph{Collaboration}. \item Drag a \emph{Blogs} widget onto the page. \end{enumerate} By default, the Blogs widget lists abstracts of the site's recent blog entries. The listing shows each entry's cover image prominently. Each abstract in the listing also shows the number of comments, thumbs up/down ratings, and links to share the entry on Twitter, Facebook, LinkedIn, and other social networking sites. Clicking a blog entry lets you view its full content, where you can also comment on the entry. \begin{figure} \centering \includegraphics{./images/blog-entry-abstract.png} \caption{Fancy a lunar spelunking trip? This blog entry's abstract lets you know what you're getting into.} \end{figure} There are several display options that let you configure the listing to look the way you want. To configure the widget, click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in its title bar and select \emph{Configuration}. The display settings are in the \emph{Setup} tab: \textbf{Enable Ratings:} Whether readers can rate blog entries. \textbf{Enable Comments:} Whether readers can comment on blog entries. \textbf{Show View Count:} Whether to show the number views for each entry. \textbf{Social Bookmarks:} The social networking sites that users can share blog entries with. Only those in the \emph{Current} column are displayed via the share buttons on each blog entry. To move social networking sites between the \emph{Current} and \emph{Available} columns, select the sites and use the arrows between those columns. Similarly, use the up/down arrows beneath the \emph{Current} column to reorder the sites as they appear on each blog entry. For more information, see \href{/docs/7-2/user/-/knowledge_base/u/using-social-bookmarks}{the social bookmarks documentation}. \textbf{Display Style:} The display style for social bookmarks. \emph{Inline} is the default and displays the social bookmark icons in a row. \emph{Menu} hides them inside a single share menu. \textbf{Maximum Items to Display:} The total number of blog entries to display on the initial page. You can select up to 75 to display at once. \textbf{Display Template:} The overall appearance of blog entries in the listing. \emph{Abstract} is the default, and is shown in the above screenshot. You can also choose the following: \begin{itemize} \tightlist \item \textbf{Full Content:} Displays each blog entry's full content. \item \textbf{Title:} Displays only the blog entry's title. \item \textbf{Basic:} A stripped-down version of the Abstract, with less text and no cover image. \item \textbf{Card:} Displays each blog entry in a card-like rectangle that shows the cover image, title, author, post date, and a few lines of text. \end{itemize} To select a different widget template or create your own, click \emph{Manage Templates}. For more information, see the documentation on \href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{widget templates}. \begin{figure} \centering \includegraphics{./images/blogs-cards.png} \caption{The Card display template makes your blog posts look like fun little trading cards.} \end{figure} \textbf{Enable Report Inappropriate Content:} Whether to let users flag content as inappropriate, which sends an email to administrators. \textbf{Enable Ratings for Comments:} Whether to let readers rate blog entry comments. \textbf{Show Related Assets:} Whether to display related content from other apps/widgets in blog entries. There are also other tabs in \emph{Configuration}: \textbf{Communication:} Lists public render parameters the widget publishes to other widgets on the page. Other apps/widgets can read and take actions on these. For each shared parameter, you can specify whether to allow communication using the parameter and select which incoming parameter can populate it. \textbf{Sharing:} Embed the widget instance as a widget on any website, Facebook, Netvibes, or as an OpenSocial Gadget. \textbf{Scope:} Specify the blog instance the widget displays: the current site's blog (default), the global blog, or the page's blog. If the page doesn't already have a blog instance, you can select scope option \emph{{[}Page Name{]} (Create New)} to create a page-scoped blog instance. When you finish setting the options, click \emph{Save} and then close the dialog box. \chapter{Aggregating Blogs}\label{aggregating-blogs} You can set up a whole web site devoted just to blogging if you wish. The Blogs Aggregator lets you publish entries from multiple bloggers on one page, giving further visibility to blog entries. You can add it to a page from the \emph{Collaboration} category in the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} menu. If you click \emph{Configuration} from the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the widget's title bar, the Blogs Aggregator's configuration page appears. The \emph{Setup} tab contains these options: \textbf{Selection Method:} Set how the widget selects blogs for display. You can choose \emph{Users} or \emph{Scope}. If you select Users, the widget aggregates the entries of every blogger on your system. To refine the aggregation, you can select an organization by which to filter the users. If you select Scope, the widget aggregates the entries of users in the current scope. This limits the entries to members of the site where the widget resides. \textbf{Organization:} The organization whose blogs you want to aggregate. \textbf{Display Style:} Select the overall appearance for blog entries in the widget: \emph{Body and Image}, \emph{Body}, \emph{Abstract} (default), \emph{Abstract without Title}, \emph{Quote}, \emph{Quote without Title}, and \emph{Title}. \textbf{Maximum Items to Display:} The maximum number of entries the widget displays. \textbf{Enable RSS Subscription:} Whether to enable an RSS feed of the aggregated entries. This lets users subscribe to an aggregate feed of all your bloggers. Below this option, you can configure how you want to display the RSS feed: \begin{itemize} \tightlist \item \textbf{Maximum Items to Display:} The maximum number of RSS items to display. \item \textbf{Display Style:} The overall appearance of each entry in the RSS feed: \emph{Abstract}, \emph{Full Content}, or \emph{Title}. \item \textbf{Format:} The language to use for your RSS feed: \emph{Atom 1.0}, \emph{RSS 1.0}, or \emph{RSS 2.0}. \item \textbf{Show Tags:} Whether to display each entry's tags. \end{itemize} Here are descriptions for the other tabs in the Blogs Aggregator's configuration: \textbf{Sharing:} Embed the widget instance as a widget on any website, Facebook, Netvibes, or as an OpenSocial Gadget. \textbf{Scope:} Specify the blog instance the widget displays: the current site's blog (default), the global blog, or the page's blog. If the page doesn't already have a blog instance, you can select scope option \emph{{[}Page Name{]} (Create New)} to create a page-scoped blog instance. When you finish setting the options, click \emph{Save} and then close the dialog box. You'll notice that the Blogs Aggregator looks very much like the Blogs widget, except that it shows entries from multiple blogs. \begin{figure} \centering \includegraphics{./images/blogs-aggregator.png} \caption{The Blogs Aggregator lets you display blog entries authored by multiple authors from different sites.} \end{figure} \chapter{Highlighting Recent Bloggers}\label{highlighting-recent-bloggers} The Recent Bloggers widget lets you highlight the work of your site's most recent blog authors. This widget lists each recent author's name, profile picture, and number of posts. You can add the Recent Bloggers widget to a page from the \emph{Collaboration} category in the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} menu. To access the widget's configuration options, click \emph{Configuration} from the \emph{Options} menu (\includegraphics{./images/icon-app-options.png}) in the widget's title bar. The \emph{Setup} tab appears first: \textbf{Selection Method:} Set how the widget selects blogs authors to highlight. You can choose \emph{Users} or \emph{Scope}. If you select Users, the widget aggregates every recent blogger on your system. To refine the aggregation, you can select an organization by which to filter the users. If you select Scope, the widget aggregates the recent bloggers in the current scope. This limits the entries to members of the site where the widget resides. \textbf{Organization:} The organization whose recent bloggers you want to aggregate. \textbf{Display Style:} Select how the widget displays recent bloggers: \emph{User Name and Image}, or \emph{User Name}. \textbf{Maximum Bloggers to Display:} Select the maximum number of recent bloggers the widget displays. Here are descriptions for the other tabs in the widget's configuration: \textbf{Sharing:} Embed the widget instance as a widget on any website, Facebook, Netvibes, or as an OpenSocial Gadget. \textbf{Scope:} Specify the blog instance the widget displays: the current site's blog (default), the global blog, or the page's blog. If the page doesn't already have a blog instance, you can select scope option \emph{{[}Page Name{]} (Create New)} to create a page-scoped blog instance. When you're finished setting the options, click \emph{Save}. Then close the dialog box. \begin{figure} \centering \includegraphics{./images/blogs-recent-bloggers.png} \caption{You can show off your site or organization's most recent bloggers from the Recent Bloggers app.} \end{figure} \chapter{Creating Forums with Message Boards}\label{creating-forums-with-message-boards} Although you're likely already familiar with what a modern forum can do, here's a sampling of what users and administrators can do with Liferay DXP's Message Boards app. Users can: \begin{itemize} \tightlist \item Start and reply to threads \item Mark a thread as a question and select an answer from the replies. \item Subscribe to threads \item Author posts in BBCode or with the standard WYSIWYG editor \item Assign thread priority (e.g., sticky, announcement, etc.) \item Attach files to a thread \item Rate a thread (e.g., like/dislike) \item And more \end{itemize} Administrators can: \begin{itemize} \tightlist \item Organize threads into categories and subcategories \item Scope a message board to a page, a Site, or the entire portal. \item Publish threads via RSS \item Rank users by the number of messages they post and assign labels to these rankings (e.g., novice, legend, etc.) \item Create and modify thread priorities (e.g., sticky, announcement, etc.) \item Configure email notifications for thread activity \item And more \end{itemize} As you can see, there's something for everyone! The Message Boards app also integrates with the rest of Liferay DXP's features. In many web sites, it's obvious that there's no link between the main Site and the message boards. In some cases, users are even required to register twice: once for the main Site and once for the message boards. Sometimes it's even three times: once for the Site, once for the message boards, and once for the shopping cart. By providing a message boards app along with all of the other apps and widgets, Liferay DXP provides a unique, integrated approach to building Sites. Administrators can concentrate on building their Site while the integration work is done for them. \begin{figure} \centering \includegraphics{./images/message-boards-category-threads.png} \caption{The Message Boards widget lets you explore its categories, interact with message threads, and post new messages.} \end{figure} \begin{figure} \centering \includegraphics{./images/message-boards-participate-in-threads.png} \caption{A thread's view displays author information and thread content, for the thread and all replies to the thread.} \end{figure} \chapter{Creating Message Boards}\label{creating-message-boards} You can create and manage message boards in the Global, Site, and Page scopes. Regardless of scope, you manage a message board via the Site Administration menu. The following sections show you how to use this menu to manage message boards in each of these scopes. \section{Site-scoped Message Boards}\label{site-scoped-message-boards} By default, the Message Boards app in Site Administration is scoped to the current Site. To administer this message board, open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), expand the menu for your Site, then navigate to \emph{Content \& Data} → \emph{Message Boards}. The Message Boards administration screen then appears. Note that the options available on this screen are the same regardless of scope. The next sections show you how to change scope and then access this screen. \begin{figure} \centering \includegraphics{./images/message-boards-administration.png} \caption{A Message Board instance starts empty, ready for you to configure for your purposes.} \end{figure} \section{Page-scoped Message Boards}\label{page-scoped-message-boards} If you need a page-scoped message board, you must add a Message Boards widget to that page and then set its scope to the page. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the page. \item From the \emph{Add} menu (\includegraphics{./images/icon-add-app.png}), open \emph{Widgets} → \emph{Collaboration}. \item Drag a \emph{Message Boards} widget onto the page. \item Click the \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) in the widget's title bar and select \emph{Configuration}. \item From the \emph{Scope} menu in the \emph{Scope} tab, select the page's name or \emph{PageName (Create New)} if the page scope doesn't exist yet. \item Click \emph{Save}, and then close the dialog. \end{enumerate} Note that you must still use the Site Administration menu to administer a page-scoped Message Boards widget. You do so by setting the Site Administration menu's active scope. Follow these steps to do this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), expand the menu for your Site, then expand \emph{Content \& Data}. \item The current scope appears just below the \emph{Content \& Data} heading. \emph{Default Scope} is the current Site. To change this, click the gear icon (\includegraphics{./images/icon-control-menu-gear.png}) and then select your desired scope. This changes the Site Administration menu to reflect scope you selected. To work in a page's scope, for example, select that page from the gear icon. That page's name then becomes the Site Administration menu's title. \begin{figure} \centering \includegraphics{./images/mb-site-admin-scope.png} \caption{Select the page's scope under the \emph{Content \& Data} menu in Site Administration.} \end{figure} \item Select \emph{Message Boards} from the \emph{Content \& Data} menu. Any changes you make here apply to the scope that you selected in the previous step. \end{enumerate} \section{Globally-scoped Message Boards}\label{globally-scoped-message-boards} To manage a message board in the global scope, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), then click the compass icon (\includegraphics{./images/icon-compass.png}) on the Site Administration menu. This opens the Select Site dialog. \item Select the \emph{My Sites} tab, then select \emph{Global}. This closes the dialog and changes the Site Administration menu's title to \emph{Global}. \item Select \emph{Message Boards} from the \emph{Content \& Data} menu. Any changes you make here apply to the global scope. \begin{figure} \centering \includegraphics{./images/mb-global-scope.png} \caption{After changing to the global scope, select \emph{Message Boards} from the \emph{Content \& Data} menu in Site Administration.} \end{figure} \end{enumerate} \chapter{Configuring Message Boards}\label{configuring-message-boards} Before using a message board, configure it to your needs. First, open the Message Boards app in your scope's Site Administration menu, as described \href{/docs/7-2/user/-/knowledge_base/u/creating-message-boards}{earlier}. To open the message board's configuration screen, click the message board's \emph{Options} menu (\includegraphics{./images/icon-options.png}) and select \emph{Configuration}. The below sections cover these tabs. \section{General Setup}\label{general-setup} The \emph{General} tab contains general settings: \textbf{Allow Anonymous Posting:} Choose if users can post anonymously. Use this with caution---anonymous users tend to be mean. \textbf{Subscribe by Default:} Choose if users are subscribed automatically to threads in which they've posted. \textbf{Message Format:} Define the markup language of users' message board posts. You can choose BBCode or HTML. When creating posts, the type of WYSIWYG editor presented to users depends on which option is enabled. Both editors have a \emph{Source} button that lets users view a message's underlying BBCode or HTML. Users can compose messages using either the WYSIWYG or Source view and can switch between views during message composition by clicking the \emph{Source} button. For security reasons, BBCode is preferred. \textbf{Enable Report Inappropriate Content:} Choose if users can report content as inappropriate. This sends a message to administrators so they can take action. \textbf{Enable Ratings:} Choose if users can rate posts. \textbf{Thread as Question by Default:} This automatically checks the \emph{Mark as question} box in the new thread window. Threads marked as questions display \emph{waiting for an answer}. Replies to the original message can be marked as an answer. \textbf{Show Recent Posts from Last:} The \emph{Recent Posts} tab shows posts from the following timeframes you define here: \begin{itemize} \tightlist \item 24 hours \item 7 days (default) \item 30 days \item 365 days \end{itemize} After the allotted time has passed, the post expires from \emph{Recent Posts}, but is still accessible everywhere else in the message board. \section{Email Setup}\label{email-setup} Use these tabs to configure how the Message Boards app handles email notifications: \textbf{Email From}: The name and email address that sends email notifications. The default administrator account's name and email address. Default values, are from the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Admin\%20Portlet}{\texttt{admin.email.from.name} and \texttt{admin.email.from.address}} \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{portal properties}). These were set in the Basic Configuration Wizard when installing Liferay DXP. Make sure to update this email address to a valid one that can be dedicated to notifications. \textbf{HTML Format:} Support HTML in these emails. \textbf{Definition of Terms:} Shows variables you can use in the email templates you'll define next. \textbf{Message Added Email:} Create a template for email users receive when a message is added to a topic they subscribe to. \begin{itemize} \tightlist \item \textbf{Enabled:} Whether automatic emails are sent to subscribed users. \item \textbf{Subject:} Choose a prefix for the email's subject line. This lets users set up message filters in their email clients for these notifications. \item \textbf{Body:} The message body content. Use the variables defined in \emph{Definition of Terms} to customize this content for users. \item \textbf{Definition of Terms:} Shows variables you can use in the email templates. \end{itemize} \textbf{Message Updated Email:} This tab is identical to the Message Added Email tab, except it defines the email that users receive when a post is updated. \section{Thread Priorities}\label{thread-priorities} The \emph{Thread Priorities} tab defines custom priorities for message threads. This lets privileged Roles tag a thread with a certain priority, which highlights it for users. Three priorities are defined by default: \begin{itemize} \tightlist \item Urgent \item Sticky \item Announcement \end{itemize} To define a thread priority, enter its name, a URL to its image icon, and a priority number. Threads with a higher priority are posted above threads with a lower priority. \textbf{Thread Icons} \begin{longtable}[]{@{} >{\centering\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5526}} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4474}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\centering ~\textbf{Icon} \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright \textbf{Definition} \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot \includegraphics{./images/icon-message-boards-urgent.png} & Urgent \\ \includegraphics{./images/icon-message-boards-announcement.png} & Announcement \\ \includegraphics{./images/icon-message-boards-sticky.png} & Sticky \\ \includegraphics{./images/icon-message-boards-question.png} & Question \\ \end{longtable} The localized language field lets you name the priorities in each locale. You can select the locale, update the priority names for it, and save your updates. \section{User Ranks}\label{user-ranks} The User Ranks tab ranks users by the number of messages they have posted. Default ranks from 0 to 1000 are provided, but you can set custom ranks here as well. You can also use this to define message boards labels that appear on user profiles. For example, you can use the message boards label \emph{Moderator} for anyone who is a part of any of the Message Boards Administrator groups: the Site Role, the Organization, the Organization Role, the regular Role, or the User Group: \begin{verbatim} Moderator=organization:Message Boards Administrator Moderator=organization-role:Message Boards Administrator Moderator=regular-role:Message Boards Administrator Moderator=site-role:Message Boards Administrator Moderator=user-group:Message Boards Administrator \end{verbatim} As with thread priority names, the \emph{Localized Language} field localizes rank names. \section{RSS}\label{rss} Message board threads can be published as RSS feeds. The RSS tab enables/disables RSS subscriptions and defines how the feeds are generated: \textbf{Maximum Items to Display:} The number of items to display in the feed. \textbf{Display Style:} The feed's appearance. You can publish the full content, an abstract, or just the thread title. \textbf{Format:} The feed's format: RSS 1.0, RSS 2.0, or Atom 1.0. Once you've finished configuring your message board, make sure to \emph{Save} your changes. \chapter{Message Board Permissions}\label{message-board-permissions} Open the Message Boards app in your scope's Site Administration menu, as described in \href{/docs/7-2/user/-/knowledge_base/u/creating-message-boards}{the article on creating message boards}. Then click the \emph{Options} icon (\includegraphics{./images/icon-options.png}) and select the \emph{Home Category Permissions} option. This permissions screen is for granting and revoking access to message board functions. The permissions enable a Role to perform the following actions: \textbf{Permissions:} View and modify permissions. \textbf{Add File:} Attach a file to a message. \textbf{Ban User:} Forbid a user from participating in the message board. \textbf{Add Category:} Add a new category to the message board. \textbf{Reply to Message:} Respond to an existing message. \textbf{Lock Thread:} Stop any further additions or modifications to a thread's messages. \textbf{Subscribe:} Receive notifications on new and modified posts. \textbf{View:} View all the contents of message threads. \textbf{Add Message:} Post a new thread. \textbf{Move Thread:} Move a thread to a different category or subcategory. \textbf{Update Thread Priority:} Modify a thread's priority. Configure the Roles with the permissions you want and \emph{Save} your changes. After adding a Message Boards widget to a page, you can access that widget instance's general permissions. To do so, select the widget's \emph{Options} menu (\includegraphics{./images/icon-app-options.png}) and select \emph{Permissions}. This permissions screen lets you control access to the widget instance's Permissions, Preferences, and Configuration menus. \chapter{Message Board Categories}\label{message-board-categories} Message Board categories organize threads by topic. This makes it easier to find the right topic for discussion, and can also help discussions stay on topic. For example, a tropical fishkeeping message board may have separate categories for freshwater and saltwater topics. This article shows you how to create and manage message board categories. \section{Adding Categories}\label{adding-categories} Follow these steps to create a message board category: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Message Boards app in your scope's Site Administration menu, as described in \href{/docs/7-2/user/-/knowledge_base/u/creating-message-boards}{Creating Message Boards}. \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select \emph{Category}. This opens the Add Category form. \begin{figure} \centering \includegraphics{./images/message-boards-add-category.png} \caption{You have several options to create a message board category for your needs.} \end{figure} \item Enter a name and description for the category. \item Select the category's \emph{Display Style}. This controls how threads in the category appear. By default, you can choose these display styles: \textbf{Default:} Classic display style for general purpose discussions. \textbf{Question:} Threads appear in a question and answer style. You can create custom display styles and make them available for selection in this form. You must set the available display styles via the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Message\%20Boards\%20Portlet}{portal property} \texttt{message.boards.category.display.styles}. Similarly, you can set the default display style in \texttt{message.boards.category.display.styles.default}. \item Open the \emph{Mailing List} section of the form and set the mailing list options you want. To enable a mailing list for the category, set the \emph{Active} toggle to \emph{YES}. To enable anonymous emails in the list, set the \emph{Allow Anonymous Emails} toggle to \emph{YES}. The default for both toggles is \emph{NO}. For an explanation of these features, see \href{/docs/7-2/user/-/knowledge_base/u/user-subscriptions-and-mailing-lists\#mailing-lists}{the documentation on mailing lists for Message Boards}. \item Open the \emph{Permissions} section and set the category's permissions. The \emph{Viewable by} selector lets you pick who can view the category: \begin{itemize} \tightlist \item Anyone (Guest Role) \item Site Members \item Owner \end{itemize} To show more permissions options, click \emph{More Options}. A table appears with the rest of the category's permissions, which you can assign to the Guest and Site Member roles: \textbf{Delete:} Remove the category. \textbf{Permissions:} View and modify permissions. \textbf{Add File:} Attach a file to any of your messages. \textbf{Reply to Message:} Respond to existing messages. \textbf{Lock Thread:} Stop any further additions or modifications to a thread's messages. \textbf{Update:} Edit the category. \textbf{Subscribe:} Receive notifications on new and modified posts. \textbf{View:} View the category. \textbf{Add Message:} Post a new thread. \textbf{Move Thread:} Move a thread to a different category or subcategory. \textbf{Add Subcategory:} Add a new category within this category. \textbf{Update Thread Priority:} Modify a thread's priority. Note that after creating a category, you can revisit its permission options by clicking the category's \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and selecting \emph{Permissions}. \item Click \emph{Save} when you're finished. Your category now appears in the table. \end{enumerate} As you add categories to a message board, they appear on the message board's home screen. The list displays the category names and the numbers of subcategories, threads, and posts in each one. \begin{figure} \centering \includegraphics{./images/message-boards-home.png} \caption{Categories help you organize threads so users can find topical threads that interest them.} \end{figure} \section{Adding Subcategories}\label{adding-subcategories} Categories can contain as many subcategories as you like. If, however, you nest categories too deep, users can have trouble finding them. Follow these steps to add a subcategory to a category: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the category's name in the list, then click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select \emph{Category}. The Add Category form appears. \item Fill out the Add Category form with the values and settings you want to use for the subcategory. This form is populated with the parent category's properties by default. \item Click \emph{Save} when you're finished. Your subcategory now appears in the table. \end{enumerate} \section{Moving and Merging Categories}\label{moving-and-merging-categories} Each category can have any number of threads, and you can add as many categories and subcategories as you wish. You can also move and merge categories. Follow these steps to move a category or merge it with another: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the category's \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and select \emph{Move}. This brings up the Move Category form. \item Select a new parent category via the \emph{Select} button under the \emph{Parent Category} field. Note that this field is empty for top-level categories. \item If you want to merge the category with the selected parent category, select \emph{Merge with Parent Category}. \item Click \emph{Move}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/mb-move-merge.png} \caption{The Move Category form lets you move and merge categories.} \end{figure} \chapter{User Subscriptions and Mailing Lists}\label{user-subscriptions-and-mailing-lists} The Message Boards app notifies users of message boards activity via email in two ways: \begin{itemize} \tightlist \item User subscriptions \item Mailing lists \end{itemize} \noindent\hrulefill \textbf{Note:} Since multiple sites can use a globally scoped message board, such message boards don't support user subscriptions or mailing lists. Make sure to use a site-scoped or page-scoped message board if you need user subscriptions or a mailing list. \noindent\hrulefill \section{User Subscriptions}\label{user-subscriptions} In the user subscriptions mechanism, the Message Boards app uses its configured \emph{Email From} address to send email notifications to subscribed users. The app can also import email replies to message board notifications directly into the message board. Then, users can interact on the message board via email without logging in to view the message board directly. This is disabled by default. To enable it, add the following line to your \texttt{portal-ext.properties} file: \begin{verbatim} pop.server.notifications.enabled=true \end{verbatim} The user subscription mechanism uses the POP mail protocol. When the Message Boards app receives an email reply to a message board notification, it posts that reply to the message board and then deletes it from the mail server. Deleting the message from the mail server is the POP protocol's default behavior and the Message Boards app assumes that your POP mail server behaves this way. Most POP clients offer an option to leave mail on the mail server after it downloads, but you shouldn't exercise this option. If you configure mail to be left on the mail server, the Message Boards app sends copies of each retained message along with each new email notification it sends to subscribed users. When enabling Message Boards to import replies to email notifications, you must decide whether to handle notifications with a mail server subdomain. By default, the following property setting is specified in the portal properties: \begin{verbatim} pop.server.subdomain=events \end{verbatim} This property creates a special MX (mail exchange) subdomain to receive all virtual instance related email (e.g., events.liferay.com). If you don't want to use this approach, unset this value in a \texttt{portal-ext.properties} file: \begin{verbatim} pop.server.subdomain= \end{verbatim} Doing so tells Message Boards to use the \emph{Email From} address specified in the Message Board's configuration to receive message board notification email replies. For example, the \emph{Email From} address could be set to \emph{replies@liferay.com}. If you're not using a mail subdomain, Message Boards parses the message headers of emails from the \emph{Email From} address to determine the message board category and message ID. If you keep the \texttt{pop.server.subdomain=events} default, the email notification address takes the following form: \begin{verbatim} mb.[category_id][message_id]@events.liferay.com \end{verbatim} In this case, Message Boards parses the email address to find the category and message ID. Parsing the email address is safer than parsing message headers, since different email clients treat message headers differently. This is why the \texttt{events} subdomain is enabled by default. You can also configure the interval on which the \texttt{POPNotificationListener} runs. The value is set in one minute increments. The default setting is to check for new mail every minute, but you can set it to whatever you like: \begin{verbatim} pop.server.notifications.interval=1 \end{verbatim} \noindent\hrulefill \textbf{Note}: Depending on your mail provider, if you use multiple devices to access email through POP, you might need to configure in your POP settings something like Gmail's \emph{recent mode}, which keeps the last 30 days of email available on the server. Then, more than just the first client can receive email. To enable recent mode in Gmail, for example, prefix the value of your POP client's Username or Email field with \texttt{recent:}. If you don't use Gmail, IMAP may be a better solution for you. \noindent\hrulefill \section{Mailing Lists}\label{mailing-lists} Alternatively, the Message Boards app can use mailing lists to send email notifications. Any category in a message board can have its own mailing list. The mailing list mechanism, unlike the user subscription mechanism, supports both the POP and the IMAP protocols. POP is the default, but each message board's mailing list is configured independently. If you choose the IMAP protocol for a category's mailing list, make sure to configure the IMAP inbox to delete messages as they are pulled by the email client that sends messages to the users on the mailing list. Otherwise, each email message retained on the server is sent to the mailing list each time there's a new post or update in the category. When a mailing list is enabled for a message board category, Message Boards listens to the specific email inbox that's configured for the mailing list. Enabling the mailing list function lets users on the mailing list reply to the notification messages in their email clients. Message Boards pulls the messages from the email inbox it's configured to listen to and automatically copies those replies to the appropriate message board thread. To enable the mailing list functionality for a category, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set up a dedicated email address for the category. \item Click the category's \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and select \emph{Edit}. \item In the \emph{Mailing List} section of the form, set the \emph{Active} slider to \emph{YES}. Several options then appear. Fill these out as follows: \textbf{Email Address:} The email address of the account that receives the messages. \textbf{Protocol:} Select POP or IMAP. \textbf{Server Name:} Your mail server's host name. \textbf{Server Port:} The port on which your mail service is running. \textbf{Use a Secure Network Connection:} Whether to use an encrypted connection if your server supports it. \textbf{User Name:} The login name on the mail server. \textbf{Password:} The password for the account on the server. \textbf{Read Interval (Minutes):} How often to poll the server looking for new messages to post. \textbf{Email Address (Outgoing):} The email address originating messages from this category. If you want your users to be able to reply to the categories using email, this should be the same address as the incoming email address. \textbf{Use Custom Outgoing Server:} Use a different mail server than global default. Fields appear for configuring the server's name, port, user name, password, and secure connection. \item If you want to let emails from anonymous users post to the message board category, set the \emph{Allow Anonymous Emails} toggle to \emph{YES}. \item Click \emph{Save} when you're finished. \end{enumerate} \chapter{Using the Message Boards}\label{using-the-message-boards} You can add a Message Boards widget to a page from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) menu's \emph{Widgets} → \emph{Collaboration} section. The Message Boards interface is similar to other message boards that populate the Internet. In any case, it can't hurt to explore how to use Liferay DXP's Message Boards and discover its features. \begin{figure} \centering \includegraphics{./images/message-boards-category-threads.png} \caption{The Message Boards widget lets you explore its categories, interact with message threads, and post new messages.} \end{figure} Threads can be viewed many ways. At the top of the widget is a set of tabs: \textbf{Categories:} The message board's categories. \textbf{Recent Posts:} Posts from all categories, sorted by date. \textbf{My Posts:} The current user's posts. \textbf{My Subscriptions:} Lets users view and manage their thread subscriptions. \textbf{Statistics:} The number of categories, posts, participants, and a list of the top contributors. You can also access this from the same tab in Site Administration's Message Board app. You can also use the search bar at the top of the widget to search for threads and posts. Although search works on threads and posts within categories, it doesn't work on categories themselves. \section{Posting New Threads}\label{posting-new-threads} Follow these steps to post a new thread: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Message Boards widget's \emph{New Thread} button. Alternatively, click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select \emph{Thread} in the Message Boards app in Site Administration. Either way, the same \emph{Add Message} form appears. \begin{figure} \centering \includegraphics{./images/message-boards-add-thread.png} \caption{The Add Message form lets you create a new thread.} \end{figure} \item Give your thread a title in the \emph{Subject} field. \item Create your thread's content in the \emph{Body} field. This field uses the same editor as the Blogs app, except that it uses BBCode instead of HTML. For further instructions, see the documentation on \href{/docs/7-2/user/-/knowledge_base/u/using-the-blog-entry-editor}{using the editor}. Also note that you can \href{/docs/7-2/user/-/knowledge_base/u/mentioning-users}{mention} other users by entering the \texttt{@} character and their user name. \item If you want to add attachments, open the \emph{Attachments} section and add them via drag and drop or the \emph{Select Files} button. \item If you want to associate a tag with the message, open the \emph{Categorization} section and use the \emph{Select} button to select an existing tag. You can also create a new tag by entering it in the \emph{Tags} field and clicking \emph{Add}. See \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{the documentation on tags} for more information. \item If you want to select an existing asset in the portal (e.g., a media file, blog post, etc.) to relate to your thread, open the \emph{Related Assets} section and use the \emph{Select} button to select that asset. \item Open the \emph{More Settings} section and select the settings you want to use: \textbf{Mark as a Question:} Whether to mark this thread as a question. This lets you later select a post in the thread as the answer. \textbf{Anonymous:} Whether this thread is posted anonymously. \textbf{Subscribe Me:} Receive notifications for activity on the thread. \textbf{Priority:} The thread's priority in the Message Board. By default, you can choose \emph{Urgent}, \emph{Sticky}, or \emph{Announcement}. Additional priorities can also be \href{/docs/7-2/user/-/knowledge_base/u/configuring-message-boards}{configured} in the Message Boards app in Site Administration. \textbf{Allow Pingbacks:} Whether \href{https://en.wikipedia.org/wiki/Pingback}{pingbacks} are allowed for your thread. \item Open the \emph{Permissions} section and set the thread's permissions. Possible values in the \emph{Viewable by} selector are \begin{itemize} \tightlist \item Anyone (Guest Role) \item Site Members \item Owner \end{itemize} You can also click the \emph{More Options} link to select additional permissions: \textbf{Delete:} Remove the thread. \textbf{Permissions:} Grant/revoke thread permissions. \textbf{Update:} Edit the thread. \textbf{Subscribe:} Receive notifications for thread activity. \textbf{View:} View the thread. Note that you can revisit the thread's permissions after posting it. To do so, select the thread's Actions menu (\includegraphics{./images/icon-actions.png}) and select \emph{Permissions}. \item Click \emph{Publish} to publish the thread. Once it's published, it appears along with the other threads in the category. \end{enumerate} \section{Participating in Message Board Threads}\label{participating-in-message-board-threads} To find message board threads that interest you, browse a message board's Categories or Recent Posts tabs. In the Categories tab, you can view a category's thread listing by clicking the category's name. Within a category, you can subscribe to an RSS feed and/or emails that inform you about activity in that category. The Recent Posts tab also lists threads, except they're the latest threads across all categories. Click a thread to view it. Messages appear in a threaded view so that replies are aligned under their parent thread. This makes it easy to follow conversations. Thread replies are indented under their parent thread. \begin{figure} \centering \includegraphics{./images/message-boards-participate-in-threads.png} \caption{A thread's view displays author information and thread content, for the thread and all replies to the thread.} \end{figure} Subscribing to a thread causes Message Boards to send the user an email whenever a new message is posted to the thread. If you have enabled the mailing list feature for the thread's category, users can reply to these messages to post back to the thread without having to visit your site. Most threads get more interesting as users reply to them. Follow these steps to reply to a message in a thread: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Reply} button. This opens the quick reply form, which only contains a text field for entering your reply. \item Enter your reply in the text field. To access more options for your reply, click the \emph{Advanced Reply} link. This opens the full editor from the add/edit thread form. \item Click \emph{Publish} to publish your reply. \end{enumerate} In addition to replying to a message, you can rate it or flag it as objectionable. A message board moderator can evaluate flagged messages and decide how to handle the messages and their authors. \chapter{Managing Message Boards}\label{managing-message-boards} Message boards can become unwieldy if left unmanaged. The Message Boards in Site Administration facilitates day-to-day thread administration. You may wish to assign this function to a \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Role} that you give to one or more users. This frees you to concentrate on other areas of your Site. For example, you can create a Role called \emph{Message Board Administrator} scoped to the portal (globally), an Organization, or a Site. Members of a global Role can administer Message Boards throughout the portal. Members of an Organization or Site-scoped Role can only administer Message Boards in that Organization or Site, respectively. Follow these steps to create a global Role: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Control Panel, select \emph{Users} → \emph{Roles}. \item Select or create the Role. \item Select the Role's \emph{Define Permissions} tab and navigate to \emph{Site Administration} → \emph{Content \& Data} → \emph{Message Boards}. A screen appears that lets you configure Message Board permissions. \begin{figure} \centering \includegraphics{./images/message-boards-role-permissions.png} \caption{Define the permissions you want to use for the message boards administrators.} \end{figure} \item Select the permissions you want message board administrators to have, then click \emph{Save}. \item Add users to this Role. \end{enumerate} \section{Locking Threads}\label{locking-threads} You may encounter threads that you think should be preserved, but stopped. You can halt activity on a thread by selecting \emph{Lock} from the thread's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}). \section{Moving Threads}\label{moving-threads} If someone posts a thread to the wrong category, you can move it to the proper one. Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select \emph{Move} from the thread's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}). This opens the \emph{Move Thread} form. \item Click the \emph{Select} button and select the new category. \item If you want to add a post explaining the move, select \emph{Add explanation post}. \item Click \emph{Move} to move the thread. \end{enumerate} \section{Deleting Threads}\label{deleting-threads} Sometimes users begin discussing topics that are inappropriate or that reveal confidential information. In this case, administrators can delete the thread from the message boards. To do so, select \emph{Move to Recycle Bin} from the thread's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}). \section{Banning Users}\label{banning-users} Unfortunately, message board users can be abusive. In this case, you can ban the user from the message boards. While viewing any of the user's posts in any thread, select the post's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Ban this User}. To reinstate a banned user, you must use the Message Boards app in Site Administration. Navigate to this app and select the \emph{Banned Users} tab. Select the user's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Unban this User}. \section{Splitting Threads}\label{splitting-threads} Sometimes a thread goes on for a while and the discussion completely changes into something else. In this case, you can split the thread where the discussion diverged and create a whole new thread for the new topic. To split a thread at a certain post, administrators can select that post's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Split Thread}. This brings up a form that lets you add an explanation post to the split thread. Click \emph{OK} to split the thread. \section{Editing Posts}\label{editing-posts} Administrative users can edit anyone's posts, not just their own. Sometimes users post links to copyrighted material or unsuitable pictures. By editing these posts, you can redact information that shouldn't be posted, or remove content not conforming to your terms of use. You can also update the thread's priority or mark a reply as an answer to a thread's question. To edit a post, select its \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Edit}. \section{Post Permissions}\label{post-permissions} Permissions can be set not only on threads, but also on individual posts. You can choose to limit a particular conversation or post to only a select group of users. To do this, select the post's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) and select \emph{Permissions}. You can then choose which Roles have the following permissions: \begin{itemize} \tightlist \item Delete \item Permissions \item Subscribe \item Update \item View \end{itemize} Use this, for example, to let some privileged users post on a certain thread, while others are only allowed to view it. Other combinations of these permissions are also possible. \chapter{Mentioning Users}\label{mentioning-users} Have you ever wanted to include another user in a discussion on the Message Boards? Have you ever wanted to give kudos to a colleague in content you're writing? You can mention (notify and/or draw attention to) other users by entering the \texttt{@} character in front of each user's user name. When you mention a user, the user receives a site notification next to the user's profile icon and an email, alerting the user with a link to the content. You can mention users in a blog entry, a message boards post, or comments in any app that supports comments. A mention also links to the user's home page, so readers can find out more about that user. \begin{figure} \centering \includegraphics{./images/mentions-at-mention-menu.png} \caption{As you enter a user name after the \texttt{@} character, links to users that match the text you enter are displayed. Select the user you want to mention and publish your content.} \end{figure} A selector appears after entering the \texttt{@} character, listing users that match the name you're entering. In the selector, users are represented by their profile picture, name, and user name. Click the user you want to mention and finish editing your content. On publishing the content, mentioned users receive a notification and an email informing them that they've been mentioned. The notification and email indicate the author's name and content type, and contain links to the content. You can access your notifications by selecting \emph{Notifications} from your user menu. \begin{figure} \centering \includegraphics{./images/mentions-notification-list.png} \caption{Your notifications are accessible from your user menu and appear in a list.} \end{figure} Mentions are enabled by the Mentions app, which is a part of the Collaboration Suite. By default, the Mentions app is enabled globally. However, you can enable/disable it globally or per site. For a site to use Mentions, it must be enabled for the site's Virtual Instance. To access the global Mentions settings for your Virtual Instance, first open the \emph{Menu} (\includegraphics{./images/icon-menu.png}) and navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings}. Then click \emph{Community Tools} and select \emph{Mentions} under \emph{VIRTUAL INSTANCE SCOPE}. By default, all users are allowed to mention fellow site members and friends. To fine tune these options, select \emph{Define Mentions Capability for Users} and specify the settings you want. \begin{figure} \centering \includegraphics{./images/mentions-global-instance-setting.png} \caption{You can enable or disable the Mentions feature for all of the Virtual Instance's sites.} \end{figure} For Mentions to be available for a site, the app must be enabled for that site's Virtual Instance. Site administrators can enable or disable Mentions for a site. A site's Mentions app configuration is accessible from within the \emph{Menu} (\includegraphics{./images/icon-menu.png}). Once in the menu, navigate to \emph{{[}Site Name{]}} → \emph{Configuration} → \emph{Settings}. In the \emph{Social} tab, expand the \emph{Mentions} section. Enable or disable mentions via the toggle labeled \emph{Allow Users to Mention Other Users}. \begin{figure} \centering \includegraphics{./images/mentions-site-setting.png} \caption{Mentions can also be enabled or disabled per site.} \end{figure} \chapter{Working Together with the Wiki}\label{working-together-with-the-wiki} Wikis are for collaboratively building a collection of information. The most famous wiki on the planet is Wikipedia. It's a full encyclopedia developed collaboratively by users from all over the world, using a wiki. Liferay DXP's wiki does these things: \begin{itemize} \tightlist \item Creates multiple wikis in a single wiki app instance. \item Scopes wikis to a page, a site, or the entire portal. \item Creates and edit wikis in \href{http://www.wikicreole.org/}{WikiCreole syntax}. \item Attaches files to wiki articles. \item Associates wiki articles with other assets in the portal. \item And more. \end{itemize} As you can see, Liferay DXP's wiki is flexible and can be configured to fit nearly any use case. What's more, it's completely integrated with the portal's \href{/docs/7-2/user/-/knowledge_base/u/managing-users}{user management}, \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{tagging}, and \href{/docs/7-2/deploy/-/knowledge_base/d/securing-product}{security} features. \begin{figure} \centering \includegraphics{./images/wiki-page-full.png} \caption{The Wiki widget displays your wiki on a Site page.} \end{figure} \chapter{Getting Started with Wikis}\label{getting-started-with-wikis} The Menu (\includegraphics{./images/icon-menu.png}) is the best place to start working with your wikis. Click the \emph{Menu} (\includegraphics{./images/icon-menu.png}), navigate to your site, and select the \emph{Content \& Data} section. If you're updating an existing page-scoped wiki app instance, you can select that page scope from the scope menu the Gear icon (\includegraphics{./images/icon-control-menu-gear.png}) makes available. The site's wiki app instance is available in the Default scope. Once you're in the proper content scope, click \emph{Wiki}. The Wiki administration screen lets you add, modify, and delete wiki nodes. A Wiki app instance can contain many wiki nodes. By default, it contains one node: \emph{Main}. \begin{figure} \centering \includegraphics{./images/wiki-admin-empty.png} \caption{The Wiki app instance has a wiki node named \emph{Main} with a single front page. You can build on the Main node or click the Add icon to create a new node.} \end{figure} \section{Configuring Wikis}\label{configuring-wikis} Before adding to your wiki instance, you should configure it. The instance's interfaces for permissions, export and import, configuration, and application templates are accessible from the Options menu. Click the \emph{Options} icon (\includegraphics{./images/icon-options.png}) to open this menu. The following options are available in this menu: \textbf{Wikis Permissions}: Specify which \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles} can create wiki nodes and access the Wikis Permissions screen. For example, if you've created a specific Role for creating wiki nodes and want to enable that Role to create new wiki nodes in this wiki application instance, select the Role's check box in the \emph{Add Node} column and then click \emph{Save}. \textbf{Export / Import}: Import existing wiki content into your wiki app instance, or export wiki content to a file. For details, refer to \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Pages and Content}. \textbf{Configuration}: Configure email notifications and RSS feeds. The \emph{Email From}, \emph{Page Added Email}, and \emph{Page Updated Email} tabs are similar to other apps' notification email settings tabs; they customize who wiki emails come from and the format and text of the email sent when a page is added or updated. The \emph{RSS} tab lets you configure RSS feeds. \section{Adding Wikis}\label{adding-wikis} Follow these steps to create a new wiki node: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) to start creating a new wiki node. The \emph{New Wiki Node} form appears. \item Add a name and description for the wiki node. \item Open the form's \emph{Permissions} section and define the wiki node's permissions. You can select the following permissions in the \emph{Viewable by} menu: \begin{itemize} \tightlist \item Anyone (Guest Role) \item Site Members \item Owner \end{itemize} You can also click the \emph{More Options} link to assign permissions to specific Roles. \item Click \emph{Save} when you're done creating the wiki node. \end{enumerate} \begin{figure} \centering \includegraphics{./images/wiki-new-wiki-node.png} \caption{The New Wiki Node form lets you describe your new node, set view permissions, and set permissions for the Guest and Site Member roles.} \end{figure} \section{Wiki Node Options}\label{wiki-node-options} Next to each listed wiki node is an \emph{Actions} menu (\includegraphics{./images/icon-actions.png}). Here are the actions available in this menu: \textbf{Edit}: Edit the wiki's name and description. \textbf{Permissions}: Specify which roles can add attachments to wiki pages, add pages, delete pages, import pages, set permissions on the wiki node, subscribe to modifications, update existing pages, and view the wiki node. \textbf{Import Pages}: Import data from other wikis. This lets you migrate from another wiki application to the Liferay DXP wiki. You might want to do this if you're migrating your site from a set of disparate applications (i.e., a separate forum, a separate wiki, a separate content management system) to Liferay DXP, which provides all of these features. Currently, MediaWiki is the only supported wiki. \textbf{RSS}: Subscribe to an RSS feed using Live Bookmarks, Yahoo, Microsoft Outlook, or an application on your machine. \textbf{Subscribe}: Subscribe to a wiki node. Any time a wiki page is added or updated, the portal sends you an email notification. \textbf{View Removed Attachments}: Display attachments that have been removed from the wiki node. \textbf{Move to Recycle Bin}: Moves the wiki node to the \href{/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets}{Recycle Bin}. \begin{figure} \centering \includegraphics{./images/wiki-options.png} \caption{Each wiki node's Actions menu lists actions you can perform.} \end{figure} Before opening wiki nodes to contributors, you should consider whether to associate a workflow with them. For example, you could create a workflow that requires an administrator's approval to publish a wiki page modification (add, update, or delete). You can access your site's default \emph{Wiki Page} workflow from within the Site Administration Menu, by navigating to \emph{Configuration} → \emph{Workflow} for your site. To learn how to use workflow, see the \href{/docs/7-2/user/-/knowledge_base/u/workflow}{Workflow} section. \chapter{Adding and Editing Wiki Pages}\label{adding-and-editing-wiki-pages} Wiki nodes initially have no pages. When you navigate into a node for the first time, a default page called \emph{FrontPage} is created automatically. To view the page, click the wiki node's name and then click \emph{FrontPage}. The FrontPage appears and shows a message that explains the page is empty and needs you to add content. That message is a link; click it to start editing the page. The wiki page editing form then appears. \noindent\hrulefill \textbf{Note:} See the \href{/docs/7-2/user/-/knowledge_base/u/getting-started-with-wikis}{getting started article} for instructions on accessing your wiki nodes. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/wiki-empty-frontpage.png} \caption{Each empty wiki page presents a default message link you can click to edit the page.} \end{figure} \begin{figure} \centering \includegraphics{./images/wiki-page-editor.png} \caption{The wiki page editing form lets you create and edit your page's content.} \end{figure} Follow these steps to use the wiki page editing form: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enter your content in the field that contains the text \emph{Write your content here\ldots{}}. This is a rich-text, WYSIWYG editor that is almost identical to the one used in the Blogs app. The only difference is that the wiki editor uses Creole instead of HTML as its source. Click the link \emph{Show Syntax Help} if you need help with Creole syntax (e.g., syntax for text styling, header formatting, link creation, etc.). For a detailed explanation of the rest of the editor, see the \href{/docs/7-2/user/-/knowledge_base/u/using-the-blog-entry-editor}{Blogs documentation}. \item If you want to attach files to the page, open the \emph{Attachments} section of the form and add them via drag and drop or the \emph{Select Files} button. \item If you want to associate a tag with the page, open the \emph{Categorization} section and enter a new or existing tag in the \emph{Tags} field. See \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{the documentation on tags} for more information. \item If you want to select an existing asset in the portal (e.g., a media file, blog post, etc.) to relate to the page, open the \emph{Related Assets} section and use the \emph{Select} button to select that asset. \item In the form's \emph{Configuration} section, you can set the page to use Creole (default), plain text, or HTML. We recommend that you stick with the Creole format, as it allows for a much cleaner separation of content and code. You can also use the Configuration section to summarize your edit, and specify whether it's a minor edit. \item Click \emph{Publish} to publish the page when you're done editing it. \end{enumerate} As is common with wikis in general, if you link to a page that doesn't exist, clicking that link opens the new page form with a note stating that the page doesn't exist and that you are creating it. \noindent\hrulefill \textbf{Note}: When you create a page by clicking a link to a page that doesn't exist, the new page is \textbf{not} a child of the current page. The page is created at the wiki node's root. From Wiki in Site Administration, you can use the page's Move action to assign it a new parent page. Clicking the Move action brings up a window that lets you select a new parent for the wiki page. \noindent\hrulefill Return to the wiki node view to see a list of the node's top-level pages. If you navigate to a page that has child pages, its child pages are listed. In these page listings, each page's Actions menu (\includegraphics{./images/icon-actions.png}) lists the following actions you can take on the page: \textbf{Edit}: Opens the page in the page editor. \textbf{Permissions}: Lets you determine which roles can view, update, delete, subscribe to, or set permissions on the page, and add, update, or delete page discussions (comments). \textbf{Copy}: Opens a page editor window with all the content from the source wiki page. You're prompted to specify a new title for it. \textbf{Move}: Opens a dialog that lets you rename the page or assign the page to a new parent page within the wiki node. \textbf{Add Child Page}: Create a new child page of the wiki page. \textbf{Subscribe (or Unsubscribe)}: Subscribes you to (or unsubscribes you from) notifications for the wiki page's modifications. \textbf{Print}: Print the wiki page. \textbf{Move to Recycle Bin}: Moves the wiki page to the Recycle Bin. Each wiki page has a check box next to it. When you select a page's check box, the Management Bar changes to show an Info icon (\includegraphics{./images/icon-information.png}) and Recycle Bin icon (\includegraphics{./images/icon-trash.png}). To move the selected page to the Recycle Bin, click the Recycle Bin icon. To get additional information about the page via an info panel, click the Info icon. The info panel has a star icon that you can select to subscribe to the page's modifications. The info panel's Details section displays the page's summary, format, version, creation and modification dates, number of attachments, and RSS link. There are several more features in the wiki node view's Management Bar. The \emph{Filter and Order} menu orders the pages by title or modification date and filters them by page type. The arrows button sorts the pages in ascending or descending order. The search bar searches for pages. The \emph{View Types} button is next to the Info icon. It lets you choose how to display the pages. The View Types button's icon depends on the selected view type: \textbf{List} (\includegraphics{./images/icon-view-type-list.png}): Shows the pages in a list with an icon representing each page. Each page's entry contains the name of its author, when it was last modified, and its \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow} status (e.g., Approved, Draft, etc.). \textbf{Table} (\includegraphics{./images/icon-view-type-table.png}): Shows the same information as the List view type, in a smaller list with no page icon. Also, the page's information is in columns and includes the revision number. \begin{figure} \centering \includegraphics{./images/wiki-node-view-in-admin.png} \caption{The wiki node's view in site administration has features that help you access and learn information about a wiki node's pages.} \end{figure} \chapter{Using the Wiki on Site Pages}\label{using-the-wiki-on-site-pages} You can use the Wiki on Site pages via the Wiki widget. Follow these steps to add the Wiki widget to a page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the page where you want to place a wiki. \item From the \emph{Add} (\includegraphics{./images/icon-add-app.png}) menu, open \emph{Widgets} → \emph{Wiki} and add a \emph{Wiki} to the page. \end{enumerate} Your Site's wiki nodes appear in tabs across the top of the widget. \begin{figure} \centering \includegraphics{./images/wiki-page-full.png} \caption{Users can interact with your Wiki nodes when you add the Wiki widget to a page.} \end{figure} To view the Wiki widget's configuration options, click its \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. The Configuration screen appears with these tabs: \textbf{Setup}: Lets you choose wikis to display and gives you several options for displaying them. The \emph{Show Related Assets}, \emph{Enable Page Ratings}, \emph{Enable Comments}, \emph{Enable Ratings for Comments}, and \emph{Enable Highlighting} check boxes enable or disable those features for the Wiki. You can set how you want users to interact with wiki documents. The \emph{Display Template} selector menu lets you choose the Wiki's \href{/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates}{Widget Template}. Below this, you can set which wiki nodes are visible. For example, you might host two wikis on a given site, exposing one to the public and keeping the other private for site members. \textbf{Communication}: Configure communication across portlets, using predefined public render parameters. From here you can modify six public render parameters: \texttt{categoryId}, \texttt{nodeId}, \texttt{nodeName}, \texttt{resetCur}, \texttt{tag}, and \texttt{title}. You can perform these actions on each parameter: \begin{itemize} \item Ignore the values for this parameter that come from other portlets. For example, the wiki can be used along with the tags navigation app. When a user clicks on a tag in tags navigation, the wiki shows a list of pages with that tag. In some cases, an administrator may want the wiki to show the front page always, independently of any tag navigation done through other portlets. This can be achieved by selecting \emph{Ignore}, so that the values of the parameter coming from those other portlets are ignored. \item Read the value of a parameter from another app. This is an advanced but very powerful option that lets portlets communicate without prior configuration. For example, imagine that the wiki is used to publish information about certain countries, and a custom app that allows browsing countries for administrative reasons was written and placed on the same page. You could associate to this second app a public render parameter called \emph{country} to designate the name of the country. Using this procedure, you can cause the wiki to show the information from the country being browsed in the other app. You can do this here for the wiki by setting the value for the title parameter to be read from the country parameter of the other app. \end{itemize} \textbf{Sharing}: Displays options you're likely to be familiar with such as the sections for sharing the Wiki with websites, Facebook, and NetVibes. \textbf{Scope}: Set the wiki's scope. You can select the site-scoped or global-scoped instance, or select/create an instance for the page. If the page doesn't already have an instance scoped to it, you can click the \emph{{[}page name{]} (Create New)} menu option to create a page-scoped wiki instance. Once you set the wiki's configuration options the way you want them, click \emph{Save}. \begin{figure} \centering \includegraphics{./images/wiki-app-configuration-scope.png} \caption{Here the user has selected to create a new Wiki instance scoped to the current page named \emph{Welcome}} \end{figure} The Wiki's Options menu also contains the usual widget options: \textbf{Look and Feel Configuration:} Set the widget's \href{/docs/7-2/user/-/knowledge_base/u/look-and-feel-configuration}{look and feel}. \textbf{Export/Import:} \href{/docs/7-2/user/-/knowledge_base/u/exporting-importing-widget-data}{Export or import widget data}. \textbf{Permissions:} Set the widget's permissions. \textbf{Configuration Templates:} Use \href{/docs/7-2/user/-/knowledge_base/u/configuration-templates}{configuration templates} to store the widget's current setup or apply an existing archived setup. \textbf{Remove:} Remove the widget from the page. The Wiki displays links to all of the Wiki instance's nodes, and provides links for navigating around the wiki. Click on a wiki node's name to begin browsing that node's pages. The following navigation links are listed after the wiki nodes: \textbf{FrontPage:} The wiki node's front page article. This is shown by default when the node is initially selected. \textbf{Recent Changes}: Shows all of the recently updated pages. \textbf{All Pages}: A flat, alphabetical list of all pages currently stored in the wiki. \textbf{Orphan Pages}: A list of pages that have no links to them. This can happen if you remove a page link without realizing it's the only link to that page. This area lets you review such orphaned wiki pages so that you can re-link or delete them. \textbf{Draft Pages}: A list of unpublished pages. Users can edit pages and save their changes as drafts. They can come back later to finish their changes and publish them. The current wiki page's content shows in the wiki's main viewing area. Several features display above the wiki page content, depending on which wiki features are enabled and your permissions: \textbf{Add Child Page:} Add a wiki page as a child of the current wiki page. \textbf{Edit:} Edit the wiki page (if you have sufficient permissions). \textbf{Details:} View the wiki page's details (if you have sufficient permissions). This is explained further in \href{/docs/7-2/user/-/knowledge_base/u/wiki-page-details}{the documentation on page details}. \textbf{Print:} Print the wiki page. Additional features appear below the wiki page's content. A view counter displays the wiki page's view count. Ratings and comments also appear if they're enabled. \chapter{Wiki Page Details}\label{wiki-page-details} When viewing a wiki page, you can view its details by clicking \emph{Details} above the page content. Several tabs appear, to give you access to several categories of information about the page. \begin{figure} \centering \includegraphics{./images/wiki-page-details-link.png} \caption{Click \emph{Details} to view the wiki page's details.} \end{figure} \begin{figure} \centering \includegraphics{./images/wiki-page-details.png} \caption{The wiki page's details.} \end{figure} \section{Details}\label{details-1} The Details tab shows page statistics and lets you perform some actions on the page: \textbf{Title}: The page title. \textbf{Format}: The page's format (Creole, HTML, MediaWiki, or plain text). \textbf{Latest Version}: The page's latest version. The wiki automatically tracks page versions whenever a page is edited. \textbf{Created By}: The user who created the page. \textbf{Last Changed By}: The user who last modified the page. \textbf{Attachments}: The number of attachments to the page. \textbf{RSS Subscription}: An icon that opens a new page where you can subscribe to an RSS feed using Live Bookmarks, Yahoo, Microsoft Outlook, or an application you can choose from your machine. \textbf{Email Subscription}: Links that let you to subscribe to or unsubscribe from modifications notifications for the page and the entire wiki node. \textbf{Advanced Actions}: Links that let you modify the page's permissions, make a copy of the page, move (rename) the page, or move the page to the recycle bin. \section{History}\label{history} The History tab lets you access the page's activities and versions via tabs: \textbf{Activities:} Lists actions performed on the page. Each activity has an icon that represents the type of action, the name of the user, the action's description, date, and an \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) to revert the action or compare its resulting version to that of another action. \textbf{Versions:} Lists all the wiki page's versions. You can revert a page back to a previous version by selecting \emph{Revert} from that version's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}). You can also compare the differences between versions by selecting two versions and then clicking the \emph{Compare Versions} button. \begin{figure} \centering \includegraphics{./images/wiki-page-history.png} \caption{The Activities tab displays the actions taken on the wiki page.} \end{figure} \section{Incoming/Outgoing Links}\label{incomingoutgoing-links} The tabs \emph{Incoming Links} and \emph{Outgoing Links} list incoming and outgoing links, respectively. These are wiki links to and from the wiki page. You can use this tab to examine how this page links to other pages and how other pages link back to this page. \section{Attachments}\label{attachments} The \emph{Attachments} tab lists the name and size of each file attached to the page. You can attach any file to the wiki. Images are the most common type of file attached to a page. Referencing them using the proper WikiCreole syntax renders the image inline, which is a nice way to include illustrations in your wiki documents. \chapter{Other Wiki Widgets}\label{other-wiki-widgets} The widgets that accompany the main Wiki widget help you display and navigate particular wiki nodes. The following widgets are available: \textbf{Page Menu:} Displays a single wiki page's outgoing links. \textbf{Tree Menu:} Displays a wiki's page hierarchy as a tree. \textbf{Wiki Display:} Displays a single wiki node. You can find these widgets in the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{Wiki} menu. \section{Page Menu}\label{page-menu} The Page Menu widget displays a wiki page's outgoing links. It answers the question, ``What wiki pages can I access from this page?'' After adding the Page Menu widget to a site page, you must set the wiki page it displays links from. Follow these steps to do so: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. \item In the configuration's \emph{Setup} tab, choose the wiki node then click \emph{Save}. \item Still in the configuration's \emph{Setup} tab, select the wiki page then click \emph{Save} and close the configuration dialog. \end{enumerate} When you click a Page Menu link, the site page's Wiki or Wiki Display widget displays the wiki page associated with that link. \begin{figure} \centering \includegraphics{./images/wiki-page-menu.png} \caption{The Page Menu widget displays a wiki page's outgoing links.} \end{figure} \section{Tree Menu}\label{tree-menu} The Tree Menu widget displays a wiki's page hierarchy as a tree that lets you navigate all the wiki's pages. Much like the Page Menu setup, you configure the Tree Menu widget to focus on a wiki node. You can also configure how deep users can navigate into the page hierarchy. You can set the \emph{Depth} to a value from 1 to 5, or select \emph{All} to allow navigation to all of the wiki node's pages. Follow these steps to configure the Tree Menu widget after adding it to a site page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. \item In the configuration's \emph{Setup} tab, choose the wiki node then select the depth of wiki pages to display in the hierarchy. \item Click \emph{Save} and close the configuration dialog. \end{enumerate} In the Tree Menu, folder icons represent parent wiki pages and document icons represent child wiki pages at the end of the nodes. When you click a parent wiki page or child wiki page, the Wiki or Wiki Display widgets on the site page display the respective wiki page. \begin{figure} \centering \includegraphics{./images/wiki-tree-menu.png} \caption{The Tree Menu widget displays a wiki node's hierarchy to the configured depth.} \end{figure} \section{Wiki Display}\label{wiki-display} The Wiki Display widget lets you focus user attention on one wiki node. After adding the widget to a page, follow these steps to configure it: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. \item In the configuration's \emph{Setup} tab, choose the wiki node then click \emph{Save}. \item Still in the configuration's \emph{Setup} tab, select the wiki page then click \emph{Save} and close the configuration dialog. This page serves as the entry point for the wiki. \end{enumerate} The configuration options and user interface for the Wiki Display are almost identical to that of the Wiki widget. \chapter{Sending Alerts and Announcements}\label{sending-alerts-and-announcements} You can use the Alerts and Announcements widgets on Site pages to broadcast important information to users. The Alerts widget is designed for displaying high-priority information (e.g.~planned downtime alerts, security alerts, etc.). Each alert is therefore labeled with a red \emph{Important} tag. The Announcements widget displays all other information you want to broadcast on your site. Each announcement therefore lacks the red tag. To separate important alerts from more mundane announcements, you can place the Alerts and Announcements widgets on different pages. However, you can use either widget to display any information you wish. Besides the red tag, they function the same. You can also scope your alerts and announcements to specific groups of users. \begin{figure} \centering \includegraphics{./images/alerts-widget.png} \caption{The Alerts widget provides administrators with an easy way to communicate important information to appropriate groups of users.} \end{figure} These widgets have two tabs: \textbf{Unread:} Non-expired alerts/announcements that you haven't read. \textbf{Read:} Alerts/announcements that have expired, or that you've read. Click an alert/announcement's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) to edit or delete it. \section{Creating Alerts and Announcements}\label{creating-alerts-and-announcements} There are two places where you can create alerts and announcements: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The \emph{Announcements and Alerts} app. Access this app at \emph{Control Panel} → \emph{Configuration} → \emph{Announcements and Alerts}. Announcements and alerts are in separate tabs in this app. To begin creating an announcement or alert, select the appropriate tab and then click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This app gives administrators a central location to create announcements and alerts that are then displayed on Site pages by the Announcements and Alerts widgets. \item The Announcements and Alerts widgets, after adding them to a Site page from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{News} menu. To begin creating an announcement or alert, click the widget's \emph{Add Alert} or \emph{Add Announcement} button. \end{enumerate} Regardless of where you create the alert or announcement, the form for creating it is the same. Follow these steps to complete the form: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Use the \emph{Title} field to give the alert or announcement a title. Then create your content in the field \emph{Write your content here\ldots{}}. For a detailed explanation of the editor, see the \href{/docs/7-2/user/-/knowledge_base/u/using-the-blog-entry-editor}{Blogs documentation}. \begin{figure} \centering \includegraphics{./images/alerts-new-alert.png} \caption{Enter your alert or announcement's title and content.} \end{figure} \item Open the \emph{Configuration} section of the form and set the following options, if desired: \textbf{Distribution Scope:} The scope where the alert/announcement is displayed. The default \emph{General} scope sends the alert/announcements to everyone. Alternatively, you can select your site or specific roles as the scope. \textbf{URL:} A URL (optional) to include with the alert/announcement. For example, an announcement about a news story could include a link to the news article. The URL must be valid and begin with \texttt{http://} or \texttt{https://}. \textbf{Type:} The alert/announcement type. This can be \emph{General}, \emph{News}, or \emph{Test}. Note that each user can specify a different delivery mechanism for each type of alert/announcement. See \hyperref[user-configuration]{User Configuration} for details. \textbf{Priority:} The announcement's priority. This can be \emph{Normal} or \emph{Important}. Note that this is disabled for alerts because alerts are always high priority. \textbf{Display Date:} The display date of the alert/announcement. This determines when the alert/announcement is sent to users and appears in the widget. By default, the \emph{Display Immediately} box is checked. This sets the display date equal to the creation date. Uncheck this box to enter a custom display date. For example, administrators can create alerts/announcements for display on a later date. This date can be days, weeks, months, or years in the future. Once the \emph{Display Immediately} box is unchecked, clicking the Display Date field opens the date-picker. \textbf{Expiration Date:} The date and time the alert/announcement expires. Once an alert/announcement expires, the widget displays it in the Read tab. Clicking the Expiration Date field opens the date-picker. \begin{figure} \centering \includegraphics{./images/alerts-new-alert-config.png} \caption{Configure your new alert or announcement.} \end{figure} \item Click \emph{Save} when you're done. Your alert/announcement then appears in the widget. \end{enumerate} \section{User Configuration}\label{user-configuration} Users can configure how they'd like to receive announcements. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open your user menu and select \emph{Account Settings}. \item On the \emph{Preferences} tab, select \emph{Alerts and Announcements Delivery}. This shows options for customizing the delivery of alerts and announcements. \item Select a configuration for each type of alert/announcement (General, News, or Test). For each type, you can enable delivery by email and SMS (text message). Note that the \emph{Website} delivery option is selected and grayed out for each alert type. This means that each alert/announcement is always viewable in its respective widget on a site. \item Click \emph{Save} when you're finished. \begin{figure} \centering \includegraphics{./images/alerts-delivery.png} \caption{Each user can choose how they receive alerts and announcements.} \end{figure} \end{enumerate} \section{Alert and Announcement Roles}\label{alert-and-announcement-roles} You can also create roles for users to make general announcements. For instance, if you want someone specific to have strict control over announcements, give that person an Announcements Role. Follow these steps to create a simple Announcements Role: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Control Panel} → \emph{Users} → \emph{Roles}. \item With the \emph{Regular Roles} tab selected, click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This opens the \emph{New Role} form. \item Name your Role \emph{Announcements}, give it a description, and click \emph{Save}. \item Select the Role's \emph{Define Permissions} tab then grant these permissions: \begin{itemize} \tightlist \item In \emph{Control Panel} → \emph{General Permissions}, select \emph{Add General Announcements}. \item In \emph{Site Administration} → \emph{Applications} → \emph{Announcements}, select all the resource permissions. \end{itemize} Click \emph{Save} after selecting each permission. These permissions let the Role add alerts and announcements. \end{enumerate} Now you have a simple Announcements Role that can manage your site's general announcements. Of course, you can adjust this Role's permissions as needed. \chapter{Managing Notifications and Requests}\label{managing-notifications-and-requests} If you subscribed to a blog or message board, or if someone sent you a private message, invitation, event reminder, or mentioned you in a post, you received a notification or request. To access notifications and requests, click your user menu and select \emph{Notifications}. The \emph{Notifications List} tab is selected by default. This is where all your notifications appear. Click the \emph{Requests List} tab to view and manage your requests. \begin{figure} \centering \includegraphics{./images/mentions-notification-list.png} \caption{The \emph{Notifications List} section displays all your notifications in a paginated list.} \end{figure} \section{Managing Notifications}\label{managing-notifications} Notifications can pile up after some time, especially if you were away for a few days. The Management Bar gives you several ways to filter and sort your notifications. The \emph{Filter and Order} menu gives you the following options for viewing notifications: \textbf{All:} The default option. Displays both read and unread notifications. \textbf{Unread:} Displays notifications that haven't been marked as read. Unread notifications are indicated with a blue border on the left-hand side of the notification. \textbf{Read:} Displays notifications that have been marked as read. \textbf{Date:} Order notifications by date. By default, notifications are listed by date in descending order. To sort notifications by ascending order, click the up/down arrow icon in the management bar. Clicking the button again reverses the sort. Each notification's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) lets you mark the notification as read/unread, or delete the notification. \section{Managing Multiple Notifications}\label{managing-multiple-notifications} You can also manage multiple notifications at once. Select the checkbox next to notifications you want to manage and choose an option from the Management Bar. Select the checkbox above the notifications list to select all notifications on the current page. The Management Bar shows three actions for selected notifications: \begin{itemize} \tightlist \item Mark as Read (\includegraphics{./images/icon-envelope-open.png}) \item Mark as Unread (\includegraphics{./images/icon-envelope-closed.png}) \item Delete (\includegraphics{./images/icon-delete.png}) \end{itemize} \section{Managing Requests}\label{managing-requests} When you get a request, it appears in the \emph{Requests List} tab. In each request's \emph{Actions} menu (\includegraphics{./images/icon-actions.png}), you can click \emph{Confirm} to accept, \emph{Ignore} to decline, or \emph{Delete} to remove the request. \chapter{Using the Knowledge Base}\label{using-the-knowledge-base} The Knowledge Base app can be used to display professional product documentation or form complete books or guides. It even lets you import article source files written in Markdown. It's \href{/docs/7-2/user/-/knowledge_base/u/workflow}{workflow-enabled}, so you can require articles to be approved before publication. Additionally, you can create article templates that help users follow a common outline. Here are the Knowledge Base's key features: \begin{itemize} \tightlist \item Navigation is built into Knowledge Base Display. \item The suggestions interface enables user feedback on articles. \item Articles are stored in folders. \item Metadata fields exist for the friendly URL, source URL, categorization, and related assets. \item The \emph{Edit on GitHub} button (\includegraphics{./images/icon-edit-on-github.png}) can take readers to an article's source repository location (if you choose to use it that way). \item Markdown source files can be imported to create and update articles. \end{itemize} The Knowledge Base has several widgets you can add to Site pages: \begin{itemize} \tightlist \item Knowledge Base Article \item Knowledge Base Display \item Knowledge Base Search \item Knowledge Base Section \end{itemize} \begin{figure} \centering \includegraphics{./images/kb-display.png} \caption{Knowledge Base Display's navigation and viewing provide a great reading experience.} \end{figure} \chapter{Creating Knowledge Base Articles}\label{creating-knowledge-base-articles} The Knowledge Base app in Site Administration contains everything you need to create articles in the Knowledge Base. You can create articles by authoring them in the app's WYSIWYG editor or by importing them from Markdown files (\texttt{.markdown}, \texttt{.md}) in a ZIP archive. The sections below cover both ways of creating articles. \noindent\hrulefill \textbf{Note:} To access Knowledge Base in Site Administration, a Role must have the permission \emph{Knowledge Base} → \emph{Access in Site Administration}. To add or act on articles, folders, or suggestions, the Site administrator must grant the appropriate permissions using the Permissions window in Knowledge Base. \noindent\hrulefill To navigate to the Knowledge Base app, open the Menu (\includegraphics{./images/icon-menu.png}) then go to Site Administration (the menu for your Site) → \emph{Content \& Data} → \emph{Knowledge Base}. The Knowledge Base app has three tabs: \textbf{Articles:} Create and manage articles and folders. \textbf{Templates:} Create and manage templates. \textbf{Suggestions:} Manage user-submitted feedback for articles. Select the \emph{Articles} tab, then proceed to the sections below for instructions on creating articles. \begin{figure} \centering \includegraphics{./images/kb-admin-articles.png} \caption{The Knowledge Base app in Site Administration lets you create Knowledge Base articles.} \end{figure} \section{Authoring Articles in the Editor}\label{authoring-articles-in-the-editor} Follow these steps to create an article in the editor: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Articles tab, click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and choose \emph{Basic Article} or the name of an available template. This brings up the New Article form. \item Enter a title for the article. A URL-safe version of the title you provide is added to the end of the article's friendly URL. You can manage the friendly URL in the \emph{Configuration} section's \emph{Friendly URL} field. \item Use the WYSIWYG editor to create the article's content. To view or edit the article's HTML source, click the \emph{Source} button in the editor. The sections below the editor let you add attachments and tags, specify related assets, and set permissions for the article. By default, View permission is granted to the Guest role, meaning anyone can view your article. \begin{figure} \centering \includegraphics{./images/kb-admin-new-article.png} \caption{You can create and modify a Knowledge Base article's content using the WYSIWYG editor.} \end{figure} \item Click \emph{Publish} to submit the article for publication or click \emph{Save as Draft} to continue working on it later. Note that if you've enabled workflow for the Knowledge Base, your article must be approved before publication. \end{enumerate} Once the article is saved, it is converted automatically to HTML for the Knowledge Base. Articles are listed in a table in the Articles tab. \section{Importing Knowledge Base Articles}\label{importing-knowledge-base-articles} You can also create new Knowledge Base articles by importing them from a ZIP archive that contains articles in the Markdown format (\texttt{.markdown}, \texttt{.md}). For example, you could write articles in your favorite Markdown editor, package them in a ZIP file, and then import that ZIP file to create those articles in the Knowledge Base. The Knowledge Base can also prioritize articles by their filenames' numerical prefixes. For example, the Knowledge Base would list \texttt{01-article.markdown} and \texttt{02-article.markdown} in ascending order by their numerical prefix (\texttt{01}, \texttt{02}). For more information on article priority, see \href{/docs/7-2/user/-/knowledge_base/u/managing-the-knowledge-base\#managing-knowledge-base-articles}{Managing Knowledge Base Articles} For detailed information on the Knowledge Base importer, see the following topics: \begin{itemize} \tightlist \item \href{/docs/7-2/user/-/knowledge_base/u/importing-knowledge-base-articles}{Importing Knowledge Base Articles} \item \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-zip-file-requirements}{Knowledge Base ZIP File Requirements} \item \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-importer-faqs}{Knowledge Base Importer FAQs} \end{itemize} \noindent\hrulefill \textbf{Note:} To import articles, your Role must have the permission \emph{Knowledge Base} → \emph{Resource Permissions: Import Articles}. \noindent\hrulefill Follow these steps to import articles into the Knowledge Base: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Articles tab, click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and choose \emph{Import}. This brings up the Import form. \item Click \emph{Browse} and select the ZIP file that contains the articles you want to import. \item If you want to use the files' numerical prefixes to prioritize the imported articles in the Knowledge Base, select \emph{Apply numerical prefixes of article files as priorities}. \item Click \emph{Save} when you're finished. \end{enumerate} Like all articles, imported articles are automatically converted to HTML for the Knowledge Base and listed in a table with the rest of the articles in the Articles tab. \begin{figure} \centering \includegraphics{./images/kb-admin-import.png} \caption{You can import ZIP files that contain Knowledge Base articles in Markdown format.} \end{figure} \chapter{Managing the Knowledge Base}\label{managing-the-knowledge-base} The Knowledge Base app in Site Administration manages the Knowledge Base. To navigate to this app, open the Menu (\includegraphics{./images/icon-menu.png}) then go to Site Administration (the menu for your site) → \emph{Content \& Data} → \emph{Knowledge Base}. \noindent\hrulefill \textbf{Note:} To access Knowledge Base in Site Administration, a Role must have the permission \emph{Knowledge Base} → \emph{Access in Site Administration}. To add or act on articles, folders, or suggestions, the site administrator must grant the appropriate permissions using the Permissions window in Knowledge Base. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/kb-admin-articles.png} \caption{You can manage Knowledge Base articles, folders, and suggestions.} \end{figure} \section{Setting the Knowledge Base's Options}\label{setting-the-knowledge-bases-options} At the top-right of the Knowledge Base app, the Options menu (\includegraphics{./images/icon-options.png}) contains these options: \textbf{Subscribe:} Get notified when Knowledge Base articles are created, updated, or deleted. \textbf{Home Folder Permissions:} Define detailed permissions for the Knowledge Base app. You can choose the Roles that can perform the following tasks: \begin{itemize} \tightlist \item Add/delete articles, folders, and templates \item Change the Knowledge Base app's permissions \item Subscribe to articles \item View templates and suggestions \end{itemize} \textbf{Export/Import:} Export or import the Knowledge Base app's configuration. \textbf{Configuration:} Configure email notifications for article subscriptions and suggestions. You can also make the Knowledge Base app's articles available via RSS (enabled by default), and configure the RSS feed's options. \begin{figure} \centering \includegraphics{./images/kb-admin-options.png} \caption{The Knowledge Base App's options.} \end{figure} \section{Managing Knowledge Base Articles}\label{managing-knowledge-base-articles} Each article also has a \emph{priority} value that determines its position in the \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-display}{Knowledge Base Display widget's navigation}. Each article's priority value appears beneath the article's title. The Knowledge Base Display widget's navigation arranges articles in ascending priority. Priority 1 is the highest priority. The higher an article's priority, the higher it appears in the navigation. Articles are assigned the next lowest priority by default. This behavior can be changed via \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-system-settings}{Knowledge Base System Settings}. To assign articles a new priority value, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select \emph{Move} from the Actions menu (\includegraphics{./images/icon-actions.png}) next to the article. \item Enter a new priority value for the article. \item Click \emph{Move} to apply the new priority. \end{enumerate} You can also organize articles with folders. Follow these steps to create a folder: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Add button (\includegraphics{./images/icon-add.png}) and select \emph{Folder}. This opens a form for creating the new folder. \item Enter a name and an optional description. By default, anyone can view the folder. You can manage this setting along with the other permissions in the form's \emph{Permissions} section. \item Click \emph{Save}. The folder is then listed in a table in the Articles tab. \end{enumerate} The text immediately below the \emph{Filter and Order} selector at the top of the app shows your position in the folder hierarchy. Click a folder's name in the hierarchy to navigate to it. You can also move articles into folders and create child articles. Knowledge Base also supports nested folders. \begin{figure} \centering \includegraphics{./images/kb-admin-folder-hierarchy.png} \caption{This screenshot uses a red box to highlight the text that indicates the current position in the folder hierarchy.} \end{figure} Each folder's Actions menu (\includegraphics{./images/icon-actions.png}) lets you perform the following actions on the folder: \textbf{Edit:} Change the folder's name and description. \textbf{Move:} Relocate the folder under a new parent folder or update its priority. \textbf{Delete:} Remove the folder and its articles from the Knowledge Base. \textbf{Permissions:} Grant or revoke the following permissions: add an article to the folder, add a sub-folder to the folder, delete the folder, move the folder, set permissions on the folder, edit (update) the folder, and view the folder. You can also delete multiple articles or folders at once. To do this, select the checkbox for each item that you want to delete and click the \emph{Delete} button (\includegraphics{./images/icon-delete.png}) that appears in the Management Bar. You can also see the info for selected items by clicking the \emph{Info} button (\includegraphics{./images/icon-information.png}) in the Management Bar. \chapter{Knowledge Base Templates}\label{knowledge-base-templates} Templates give users a starting point. For example, you can create templates that contain default headers or other content for articles. Templates help foster consistent formatting and content organization for articles. You can create and manage templates from the Knowledge Base app in Site Administration. To navigate to this app, open the Menu (\includegraphics{./images/icon-menu.png}) and go to Site Administration (the menu for your Site) → \emph{Content \& Data} → \emph{Knowledge Base}. \noindent\hrulefill \textbf{Note:} To access Knowledge Base in Site Administration, a Role must have the permission \emph{Knowledge Base} → \emph{Access in Site Administration}. To add or act on articles, folders, or suggestions, the Site administrator must grant the appropriate permissions using the Permissions window in Knowledge Base. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/kb-admin-templates.png} \caption{The Knowledge Base app's Templates tab.} \end{figure} \section{Creating Templates}\label{creating-templates} To create a new template, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Templates} tab. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This brings up the \emph{New Template} form. \item Enter a title for the template. \item Use the WYSIWYG editor to create the template's content. To view or edit the article's HTML source, click the \emph{Source} button (\texttt{\textless{}/\textgreater{}}) in the editor. You can also set the template's permissions via the form's \emph{Permissions} section. \item Click \emph{Publish} to finish creating the template. \end{enumerate} \begin{figure} \centering \includegraphics{./images/kb-admin-new-template.png} \caption{The New Template form.} \end{figure} \section{Managing Templates}\label{managing-templates} Each template appears in a list in the Templates tab. You can take the following actions on each template via its Actions button (\includegraphics{./images/icon-actions.png}): \textbf{View:} Display the template. From here, you can print the template, use it to create an article, edit it, modify its permissions, or delete it. \textbf{Edit:} Change the template's title and content. \textbf{Permissions:} Configure the template's permissions. You can choose whether a Role can change permissions, update, view, or delete the template. \textbf{Delete:} Remove the template from the Knowledge Base. \chapter{Responding to Knowledge Base Feedback}\label{responding-to-knowledge-base-feedback} The Knowledge Base app's \emph{Suggestions} tab shows user feedback on articles and lets you mark progress on addressing the feedback. To navigate to this app, open the Menu (\includegraphics{./images/icon-menu.png}) then go to Site Administration (the menu for your Site) → \emph{Content \& Data} → \emph{Knowledge Base}. \noindent\hrulefill \textbf{Note:} To access Knowledge Base in Site Administration, a Role must have the permission \emph{Knowledge Base} → \emph{Access in Site Administration}. To add or act on articles, folders, or suggestions, the Site administrator must grant the appropriate permissions using the Permissions window in Knowledge Base. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/kb-admin-suggestions.png} \caption{The Suggestions tab in Knowledge Base displays each piece of feedback that users leave on Knowledge Base articles.} \end{figure} Each suggestion provides the link to the associated article, the user's feedback, the user's name, the feedback's time stamp, and the status on addressing the suggestion. You can use each entry's Actions menu (\includegraphics{./images/icon-actions.png}) to move the entry between the \emph{New}, \emph{In Progress}, and \emph{Resolved} states. \noindent\hrulefill \textbf{Note:} To view article suggestions, your Role must have the permission \emph{Knowledge Base} → \emph{Knowledge Base: View Suggestions}. To move suggestions between the \emph{New}, \emph{In Progress}, and \emph{Resolved} states, your Role must have the permission \emph{Knowledge Base} → \emph{Knowledge Base Article: Update}. Roles assigned this permission can also view and update the state of article suggestions from any of the other Knowledge Base widgets. \noindent\hrulefill When you move the suggestion to a different state, an email is sent notifying the user of the change. You can view and configure the automated emails from the Knowledge Base app's \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Configuration} menu. \chapter{Knowledge Base Display}\label{knowledge-base-display} You can use the Knowledge Base Display widget to display your published Knowledge Base articles. You can customize how this widget displays articles, and which ones it displays. To get started, add the widget to the Site page you want to display articles on: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the page and open the \emph{Add} menu (\includegraphics{./images/icon-add-app.png}). \item Open the \emph{Widgets} → \emph{Content Management} section, then add \emph{Knowledge Base Display} to the page. \end{enumerate} By default, the Knowledge Base Display widget displays articles from the Knowledge Base's Home folder. To change the location of its articles, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. This opens the Configuration dialog. \item In the \emph{Setup} tab, select the \emph{General} tab then click \emph{Select} in the \emph{Article or Folder} field. This brings up the \emph{Select Entry} form. \item Click \emph{Choose} next to the article or folder of articles you want to display. Alternatively, you can click the \emph{Choose This Folder} button at the top of the Select Entry form to select the current folder. \item Click \emph{Save} and close the Configuration dialog. \end{enumerate} \begin{figure} \centering \includegraphics{./images/kb-display-config-article.png} \caption{Select the article or folder of articles that the Knowledge Base Display widget displays.} \end{figure} The Knowledge Base Display widget's Options icon (\includegraphics{./images/icon-app-options.png}) also provides these common configuration options: \begin{itemize} \tightlist \item Look and Feel Configuration \item Export/Import \item Permissions \item Configuration Templates \end{itemize} For more information on these, see the section on configuring widgets in \href{/docs/7-2/user/-/knowledge_base/u/web-experience-management}{Web Experience Management}. The Knowledge Base Display's navigation menu and display options make it the perfect candidate for a full page widget. If you display a folder of articles, the navigation on the left side of the widget displays links to all the folder's articles. The viewing area on the right side of the widget displays the folder's leading article (the \emph{priority one} article). Click an article in the navigation to display it in the viewing area. The currently displayed article's link appears in bold in the navigation. You can also move between articles by clicking the links with arrows at the bottom of the widget. \begin{figure} \centering \includegraphics{./images/kb-display.png} \caption{Knowledge Base Display's navigation and viewing provide a great reading experience.} \end{figure} Knowledge Base Display can also show article hierarchies. Viewing an article that has child articles expands the navigation tree to show links to the child articles. Any expanded nodes collapse when you view a different top level article. The links at the top of the widget allow users to perform the following actions on an article: \begin{itemize} \tightlist \item Subscribe to an RSS feed of the Knowledge Base \item Subscribe to the current article \item View the current article's history \item Print the current article \end{itemize} Administrators have access to an additional set of links at the top of the widget that lets them perform the following actions: \begin{itemize} \tightlist \item Edit the article \item Add a child article \item Set the article's permissions \item Move the article \item Delete the article \end{itemize} Below the article's content is the rating interface, showing thumbs up/down icons. Users can also submit suggestions or comments below the article in the text box labeled \emph{Do you have any suggestions?}. Administrators can \href{/docs/7-2/user/-/knowledge_base/u/responding-to-knowledge-base-feedback}{view the suggestions and mark progress on them}. If the administrator enables the Knowledge Base app's source URL feature (more on this in a moment) and an article has an assigned source URL, an \emph{Edit on GitHub} button (\includegraphics{./images/icon-edit-on-github.png}) appears to the right of the article's title. This button lets users access the article's source in GitHub. You can use this feature to encourage users to contribute fixes or improvements to articles. If you're interested in this feature, you can direct your administrator to follow the instructions in \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-system-settings}{Knowledge Base System Settings}. \section{Displaying Different Article Sets}\label{displaying-different-article-sets} As an administrator, say that you've used folders to aggregate similar articles, and you want to provide an easy way for users to switch between these sets of articles. The Knowledge Base Display's content folder feature adds a selector to the top of the navigation that lets users switch between article sets. Follow these steps to set up content folders: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a folder in the Knowledge Base app in Site Administration. Then create sub-folders in this folder. These sub-folders are the \emph{content folders}. \item Add articles to each content folder. \item Select \emph{Configuration} from Knowledge Base Display's \emph{Options} menu (\includegraphics{./images/icon-app-options.png}). In the \emph{Setup} → \emph{General} tab, select the content folders' parent folder and click \emph{Save}. \end{enumerate} The content selector's values reflect the names of your content folders. Select one to view its articles. \begin{figure} \centering \includegraphics{./images/kb-display-content-selector.png} \caption{Knowledge Base Display's content folder feature lets users switch between different sets of articles.} \end{figure} You can also add a common prefix to the names shown in the selector: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select \emph{Configuration} from Knowledge Base Display's \emph{Options} menu (\includegraphics{./images/icon-app-options.png}). In the Configuration dialog, select the \emph{Setup} → \emph{Display Settings} tab. \item Enter the prefix into the \emph{Content Root Prefix} field and click \emph{Save}. \end{enumerate} \chapter{Other Knowledge Base Widgets}\label{other-knowledge-base-widgets} There are other Knowledge Base widgets you can add to Site pages besides \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-display}{Knowledge Base Display}: \textbf{Knowledge Base Article:} Display a single article's content. \textbf{Knowledge Base Section:} Publish articles associated with a specific topic (section). \textbf{Knowledge Base Search:} Search for articles. You can add these widgets from \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{Content Management}. \section{Knowledge Base Article}\label{knowledge-base-article} Knowledge Base Article displays a single article's content. It even shows abstracts of child articles. You can add multiple Knowledge Base Article instances to a page, and each one can show a different article. After adding Knowledge Base Article to a page, follow these steps to configure the widget: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click \emph{Please configure this portlet to make it visible to all users}. This opens the Configuration dialog. \item In the \emph{Setup} → \emph{General} tab, click \emph{Select}, choose an article, click \emph{Save}, and close the Configuration dialog. \end{enumerate} You can change your selection from the widget's \emph{Options} (\includegraphics{./images/icon-app-options.png}) → \emph{Configuration} menu. \begin{figure} \centering \includegraphics{./images/kb-article.png} \caption{The Knowledge Base Article app is great at displaying individual articles.} \end{figure} Knowledge Base Article shares the same UI as the Knowledge Base Display to display and manage its articles. Refer to the \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-display}{Knowledge Base Display documentation} for a detailed description of the widget's UI. \section{Knowledge Base Section}\label{knowledge-base-section} \noindent\hrulefill \textbf{Note:} as of Knowledge Base 3.0.0, the Knowledge Base Sections widget is deprecated and replaced by \href{/docs/7-2/user/-/knowledge_base/u/organizing-content-with-tags-and-categories}{categories}. \noindent\hrulefill The Knowledge Base Section widget lets you publish articles associated with a specific topic (section). For example, a news Site might have the sections \emph{World}, \emph{Politics}, \emph{Business}, and \emph{Entertainment}. \begin{figure} \centering \includegraphics{./images/kb-section.png} \caption{The Knowledge Base Section widget.} \end{figure} To use sections, an administrator must first configure the feature in System Settings, creating the section names for use in the Knowledge Base Section widget. This process is covered in detail in \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-system-settings}{Knowledge Base System Settings}. When creating or editing a Knowledge Base article, authors can then select the article's section in the \emph{Configuration} → \emph{Section} field. You can add multiple instances of the Knowledge Base Section widget to a page. Each widget can display articles from any number of sections. You can configure the widget to display article titles or abstracts. You can also define whether to show pagination or section titles. Follow these steps to configure an instance of the Knowledge Base Section widget: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select \emph{Configuration} from the Knowledge Base Section widget's \emph{Options} menu (\includegraphics{./images/icon-app-options.png}). This opens the widget's Configuration dialog. \item In the Configuration dialog's \emph{Setup} → \emph{General} tab, select the section or sections that you want to use and click \emph{Save}. \item Close the Configuration dialog to see the updates. \end{enumerate} The matching articles are displayed in the app beneath their section heading. \section{Knowledge Base Search}\label{knowledge-base-search} \noindent\hrulefill \textbf{Note:} as of Knowledge Base 3.0.0, the Knowledge Base Search widget is deprecated and replaced by Liferay Search. \noindent\hrulefill Even though the Knowledge Base can show the structure of its articles, it may be difficult to find exactly what you're looking for by browsing. That's where the Knowledge Base Search widget comes in. Enter your search term and press the \emph{Search} button. The results are displayed in a table with the following criteria for each matching article: \begin{itemize} \tightlist \item Title \item Author \item Create date \item Modified date \item Number of views \end{itemize} You can select the criteria to display in the widget's \emph{Options} (\includegraphics{./images/icon-app-options.png}) \emph{Configuration} dialog. \begin{figure} \centering \includegraphics{./images/kb-search.png} \caption{The Knowledge Base Search widget lets you search the Knowledge Base for keywords.} \end{figure} \chapter{Importing Knowledge Base Articles}\label{importing-knowledge-base-articles-1} As mentioned earlier, the Knowledge Base app can import articles in bulk. This lets you have an offline process where articles are prepared ahead of time before they're published. Articles are imported into the Knowledge Base as \href{http://commonmark.org}{Markdown} files. Markdown is a text-only file format that is easy to read, yet supports all the things you'd need to do to format your articles. \noindent\hrulefill \textbf{Note:} To import articles, your Role must be granted the \emph{Knowledge Base} → \emph{Resource Permissions: Import Articles} permission. \noindent\hrulefill The Knowledge Base supports a Markdown dialect known as \href{http://fletcher.github.io/MultiMarkdown-4/}{Multi-Markdown}. This dialect extends the original Markdown with features like table formatting, image captions, and footnotes. For the Knowledge Base to import your Markdown articles, they must adhere to these requirements: \begin{itemize} \tightlist \item All source files must use the \texttt{.markdown} or \texttt{.md} extensions. \item Articles must start with a top-level header (e.g., \texttt{\#\ Some\ Heading\ ...}). \item Each header must have an associated, unique ID for the article's friendly URL title and for anchor tags in the article's sub headers. Here's an example of a top-level header that correctly specifies an ID: \end{itemize} \texttt{\#\ Some\ Heading\ \ {[}{]}(id=some-heading)} Here's Markdown source text for a simple example article: \begin{verbatim} # The Moons of Mars [](id=the-moons-of-mars) As you look up from your chaise lounge, you're sure to see our neighboring planet Mars. Did you know that Mars has two moons? You might have to break out a pair of binoculars to see them. Its two moons are aptly named after the two sons of mythical Roman god Mars. Their names are Phobos and Deimos. \end{verbatim} In the first line above, notice the header's ID assignment \texttt{id=the-moons-of-mars}. On import, the ID value becomes the Knowledge Base article's URL title. Markdown is something of a standard: there's \href{https://help.github.com/articles/github-flavored-markdown}{Github Flavored Markdown}, a proposed \href{http://www.commonmark.org}{common Markdown syntax}, forums that support Markdown (reddit, StackExchange, and others), Markdown editors, and an \href{https://tools.ietf.org/html/rfc7763}{IETF draft} for making it an official Internet media type (text/markdown). Why is there so much interest in Markdown? \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item It's readable. Even if you don't know Markdown, you can read it without having to filter out the syntax. \item It gets out of a writer's way. You don't have to worry about mousing to various icons to change text into a heading or create bulleted lists. Just start typing. The syntax is very intuitive. \item There are tools to convert it to many other formats, though it was designed to convert to HTML. If your articles are in Markdown, it's straightforward to publish them to the web, mobile formats (Kindle, ePub), and print. \item Since it's only text, you can use existing tools to collaborate on that text. Using services like GitHub, people can contribute to your articles, and you can see all the changes that have been made to them. \end{enumerate} \chapter{Knowledge Base ZIP File Requirements}\label{knowledge-base-zip-file-requirements} The Knowledge Base importer supports article hierarchies, so Markdown files can be specified anywhere in the ZIP file's directory structure. They can be nested in any number of folders. Image files are the only files supported for attachments. \noindent\hrulefill \textbf{Note:} Imported articles are independent of the workflow settings. This means that \textbf{imported articles are automatically approved.} Only users with the \emph{Import Articles} permission assigned to their Role are able to import articles. This permission can be assigned manually through \emph{Control Panel} → \emph{Users} → \emph{Roles}. If you've upgraded from Liferay Portal 6.2, you can also assign this Role to every Role that was already able to add articles with a command from the Gogo shell. Open the \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Gogo shell}. Type \texttt{knowledgeBase:addImportArticlePermissions} and hit enter. \noindent\hrulefill The ZIP file's articles are imported in file order (alphanumerically). To designate an article's priority, add a numeric prefix to its file name. For example, the priorities for articles named \texttt{01-file.markdown} and \texttt{02-file.markdown} become \texttt{1.0} and \texttt{2.0}. To designate an article to be the parent of all other articles in the same source folder, end its file name with \texttt{-intro.markdown}. This creates a parent-child hierarchy. You can use the prefix \texttt{00} for parent articles to place them at the top of the folder's file order. The importer uses the numeric prefix of an intro file's folder as its article priority. Here's the underlying logic for the \texttt{00} prefix: \begin{itemize} \tightlist \item A file prefix of \texttt{00} for a non-intro file assigns the resulting article's priority to \texttt{1.0}. \item A file prefix of \texttt{00} for a top-level intro file sets the article's priority to the first folder numeric prefix found that is \texttt{1.0} or greater. \end{itemize} This convention lets you specify priorities for top-level (non-child) articles in your hierarchy. When importing, keep the checkbox labeled \emph{Apply numerical prefixes of article files as priorities} selected. If a file doesn't have a prefix, its article gets the next available priority (the highest current priority, plus one). Below is an example ZIP file structure that demonstrates the features mentioned so far: \textbf{ZIP File Structure Example:} \begin{itemize} \tightlist \item \texttt{01-winter-events/} \begin{itemize} \tightlist \item \texttt{00-winter-excursions-intro.markdown} \item \texttt{01-star-dust-snow-shoeing.markdown} \item \texttt{02-lunar-alpine.markdown} \end{itemize} \item \texttt{02-summer-events/} \begin{itemize} \tightlist \item \texttt{00-summer-excursions-intro.markdown} \item \texttt{01-lunar-rock-scrambling.markdown} \item \texttt{02-extra-terrestrial-mountain-biking.markdown} \item \texttt{03-lunar-olympics/} \begin{itemize} \tightlist \item \texttt{00-lunar-olympics-intro.markdown} \item \texttt{01-zero-gravity-diving.markdown} \end{itemize} \end{itemize} \item \texttt{images/} \begin{itemize} \tightlist \item \texttt{some-image.png} \item \texttt{another-image.jpeg} \end{itemize} \end{itemize} The above ZIP file specifies \texttt{00-winter-excursions-intro.markdown} as the parent of its neighboring Markdown files: \texttt{01-star-dust-snow-shoeing.markdown} and \texttt{02-lunar-alpine.markdown}. Likewise, \texttt{00-lunar-olympics-intro.markdown} is the parent of \texttt{01-zero-gravity-diving.markdown}. \texttt{00-lunar-olympics-intro.markdown} is also the peer of \texttt{01-lunar-rock-scrambling.markdown} and \texttt{02-extra-terrestrial-mountain-biking.markdown}, and the child of \texttt{00-summer-excursions-intro.markdown}. \textbf{ZIP Example's Resulting Relationships and Priorities} \begin{itemize} \tightlist \item \texttt{01-winter-events/00-winter-excursions-intro.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Winter Excursions \item \textbf{Relationship:} Peer of \emph{Summer Excursions} \item \textbf{Priority:} \textbf{1.0} \end{itemize} \item \texttt{01-winter-events/01-star-dust-snow-shoeing.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Star Dust Snow Shoeing \item \textbf{Relationship:} Child of \emph{Winter Excursions} \item \textbf{Priority:} 1.0 \end{itemize} \item \texttt{01-winter-events/02-lunar-alpine.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Lunar Alpine \item \textbf{Relationship:} Child of \emph{Winter Excursions} \item \textbf{Priority:} 2.0 \end{itemize} \item \texttt{02-summer-events/00-summer-excursions-intro.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Summer Excursions \item \textbf{Relationship:} Peer of \emph{Winter Excursions} \item \textbf{Priority:} \textbf{2.0} \end{itemize} \item \texttt{02-summer-events/01-lunar-rock-scrambling.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Lunar Rock Scrambling \item \textbf{Relationship:} Child of \emph{Summer Excursions} \item \textbf{Priority:} 1.0 \end{itemize} \item \texttt{02-summer-events/02-extra-terrestrial-mountain-biking.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Extra Terrestrial Mountain Biking \item \textbf{Relationship:} Child of \emph{Summer Excursions} \item \textbf{Priority:} 2.0 \end{itemize} \item \texttt{02-summer-events/03-summer-olympics/00-lunar-olympics-intro.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Lunar Olympics \item \textbf{Relationship:} Child of \emph{Summer Excursions} \item \textbf{Priority:} 3.0 \end{itemize} \item \texttt{02-summer-events/03-summer-olympics/01-zero-gravity-diving.markdown} \begin{itemize} \tightlist \item \textbf{Article:} Zero Gravity Diving \item \textbf{Relationship:} Grandchild of \emph{Summer Excursions} \item \textbf{Relationship:} Child of \emph{Opening Ceremonies} \item \textbf{Priority:} 1.0 \end{itemize} \end{itemize} ZIP files must meet the following requirements: \begin{itemize} \tightlist \item Each ZIP file must end in the suffix \texttt{.zip}. \item Each ZIP file must contain at least one Markdown source file, optionally organized in folders. \item All referenced image files must be in a folder named \texttt{images} in the ZIP file's root. \item Image files must be in a supported format and must use the appropriate file extensions. Supported extensions are \texttt{.bmp},\texttt{.gif},\texttt{.jpeg},\texttt{.jpg}, and \texttt{.png}. They're specified via an app system setting. For details, see \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-system-settings}{Knowledge Base System Settings}. \end{itemize} Once you have your article ZIP file, it's time to import it. Follow these steps to import your ZIP file: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Menu (\includegraphics{./images/icon-menu.png}), navigate to \emph{Site Administration} (the menu for your site) → \emph{Content} → \emph{Knowledge Base} → \emph{Articles}. \item Click \emph{Add} (\includegraphics{./images/icon-add.png}) → \emph{Import} to bring up the importer page. \item Browse to the location of your file, and in most cases leave the checkbox for the article priorities checked, and then click \emph{Save}. \end{enumerate} Your file is uploaded, and the importer converts each source file's Markdown text to HTML, applying the HTML to the resulting article. Any image files that are referenced in an article and included in the ZIP file are imported as attachments to the article. \begin{figure} \centering \includegraphics{./images/kb-admin-import.png} \caption{Selecting \emph{Add} → \emph{Import} in Knowledge Base brings up the interface for selecting a ZIP file of Markdown source files and images to produce and update articles in your Knowledge Base.} \end{figure} In addition to source files and images, you can configure a base source URL system setting for the importer that specifies your source file's repository location. Each article's \emph{Edit on GitHub} button (if enabled) takes the user to the source location. The importer prefixes each file's path with the base source URL. This constructs a URL to the article's repository source location; it looks like \texttt{{[}base\ URL{]}/{[}article\ file\ path{]}}. Here's an example base source URL: \begin{verbatim} https://github.com/liferay/liferay-docs/blob/master/develop/tutorials \end{verbatim} The source URL constructed from this base URL and article source file \texttt{folder-1/some-article.markdown} would be: \begin{verbatim} https://github.com/liferay/liferay-docs/blob/master/develop/tutorials/folder-1/some-article.markdown \end{verbatim} You specify the base source URL in a file called \texttt{.METADATA} in the ZIP file's root folder. The importer treats the \texttt{.METADATA} file as a standard Java properties file and uses the base source URL to construct the source URL for all of the ZIP file's resulting articles. To use the source URL feature, your administrator must enable it via the \href{/docs/7-2/user/-/knowledge_base/u/knowledge-base-system-settings}{Knowledge Base System Settings}. \chapter{Knowledge Base Importer FAQs}\label{knowledge-base-importer-faqs} \begin{itemize} \item \textbf{What happens when I import an existing article?} The importer checks if the source file's leading header ID (e.g., \texttt{\#\ Some\ Heading\ \ {[}{]}(id=some-heading)}) matches the URL title of any existing article in the Knowledge Base folder. If a match is found, the importer replaces the article's content with the incoming content converted from the source file. If no match is found, a new article is created. \item \textbf{Do I need to import all of a Knowledge Base folder's articles, even if I only want to create a new article or update a subset of the folder's current articles?} No.~You can import as many or as few new or modified articles as you like. \item \textbf{Does the importer remove articles?} No.~The importer only creates and updates articles. It doesn't delete any existing articles. To delete an article, you must manually do so via the Knowledge Base app. \item \textbf{Can I update an article's priority?} Yes. You can use the file/folder prefix convention and re-import the article to update its priority. \item \textbf{If I change an article's title, should I also change its header ID?} It depends on whether you've already published your article. If it hasn't been published, then there are no public links to it, so it's fine to change the header ID. If the article is already published, you must decide whether it's worth breaking existing links to the article, and worth having search engines rediscover and re-rank your article based on its new friendly URL. The new friendly URL is based on the new header ID. \end{itemize} \chapter{Knowledge Base System Settings}\label{knowledge-base-system-settings} Administrators can use the System Settings UI to set the Knowledge Base's global configuration (across sites). You can access this UI in \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Knowledge Base}. There are five sections of Knowledge Base configuration settings: \begin{itemize} \tightlist \item Service \item Knowledge Base Article \item Knowledge Base Display \item Knowledge Base Search \item Knowledge Base Section \end{itemize} The \emph{Service} section's settings apply defaults to all the Knowledge Base widgets, and to the Knowledge Base app in Site Administration. The other sections apply to specific Knowledge Base widgets and override the \emph{Service} defaults. \noindent\hrulefill \textbf{Important:} Advanced configuration of the Knowledge Base application's system settings should only be performed by an Liferay DXP administrator. \noindent\hrulefill The Knowledge Base has several optional features that are disabled by default, but can be enabled and configured from System Settings. These include source URL, import file conventions, new article priority increment, and sections. \section{Source URL Settings}\label{source-url-settings} The source URL settings define the source location of Markdown files for import. This should point to a source repository where the files are stored. GitHub is assumed as the default. Once defined, the Knowledge Base displays a button (default label is \emph{Edit on GitHub}) above each displayed article. Users can click the button to navigate to an article's source location. The source URL settings are accessible in the \emph{Service} section of the Knowledge Base's System Settings. To enable the source URL, check the \emph{Source URL Enabled} checkbox. To change the source URL button's label, specify a new value for the setting \emph{Source URL Edit Message Key}. Best practice is to specify the value as a \href{/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys}{language key}. For example, if you create a language key \texttt{edit-on-bitbucket=Edit\ on\ Bitbucket}, you can specify that language key as the button's new label: \begin{verbatim} edit-on-bitbucket \end{verbatim} Alternatively, you can specify the label explicitly: \begin{verbatim} Edit on Bitbucket \end{verbatim} \section{Importer File Convention Settings}\label{importer-file-convention-settings} These settings define the supported file extensions, the suffix for parent files, and the image folder's path within the imported ZIP files. These settings are accessible in the \emph{Service} section of the Knowledge Base's System Settings. The following settings specify the importer's supported file extensions: \textbf{Markdown Importer article extensions:} Sets the supported article extensions. The default values are \texttt{.markdown} and \texttt{.md}. \textbf{Markdown Importer Image File Extensions:} Sets the supported image file extensions. The default values are \texttt{.bmp}, \texttt{.gif}, \texttt{.jpeg}, \texttt{.jpg}, and \texttt{.png}. Follow these steps to modify the supported file extensions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{+} or \emph{-} button next to the setting to add or remove a supported file extension, respectively. \item If adding an extension, enter a new value. \item Click \emph{Save}. \end{enumerate} These settings define additional article configuration options for the importer: \textbf{Markdown Importer Article Intro:} Sets the parent article's file suffix. The default value is \texttt{intro.markdown}. \textbf{Markdown Importer Image Folder:} Sets the image folder path the importer looks for in the ZIP file. The default path is \texttt{/images}. \textbf{Article Increment Priority Enabled:} Whether to increment new article priorities by \texttt{1.0}. To disable this increment so that articles get a flat value of \texttt{1.0}, deselect the checkbox. Alternatively, you can enable or disable the article increment priority feature for each widget in the corresponding widget's configuration menu in System Settings. \section{Section Names Setting}\label{section-names-setting} The section names setting lets you specify names of arbitrary topics to attribute to articles. Using the Knowledge Base Section widget, you can display one or more \emph{sections} (groups) of articles. To use sections, you must first define them in the System Settings for the \emph{Knowledge Base Section} widget. Follow these steps to make new sections available: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Knowledge Base Section} configuration menu. \item Click the plus button next to the \emph{Admin Knowledge Base Article Sections} setting to add a new field for each section you want. \item Enter a name for each new section and click \emph{Update}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/kb-section-setting.png} \caption{Create the sections you want to use with the Knowledge Base Section widget.} \end{figure} Once your sections are added, you can follow the steps in the \href{/docs/7-2/user/-/knowledge_base/u/other-knowledge-base-widgets\#knowledge-base-section}{Knowledge Base Section documentation} to learn how to use them. \chapter{Inviting Members to Your Site}\label{inviting-members-to-your-site} The Invite Members widget lets site administrators send invitations to join the Site. You can add this widget to a page from the menu \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{Collaboration}. Click the \emph{Invite members} button to bring up the interface for sending invitations. \begin{figure} \centering \includegraphics{./images/invite-members-dialog.png} \caption{You can invite users by clicking the add sign next to the user's name.} \end{figure} Click the plus sign next to a User or click the \emph{Add Email Address} button to add a User to the invite list. Users that have already been invited but have not yet responded appear with a check mark next to their names. You can also invite Users to the \emph{Site Owner}, \emph{Site Content Reviewer}, and \emph{Site Administrator} Roles for your site by selecting that Role under the \emph{Invite to Role} heading. Once you've added all the Users you want to invite and have selected their Roles, click the \emph{Send Invitations} button to invite them. For more information on roles, see the \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{Roles and Permissions documentation}. The Site invitation shows up under the \emph{Requests List} tab on the User's \emph{Notifications} page. The User can then choose to \emph{Confirm} or \emph{Ignore} the invitation. \begin{figure} \centering \includegraphics{./images/invite-members-confirm.png} \caption{You can confirm or ignore the invitation.} \end{figure} When Users confirm such invitations, they become Site members assigned to the Roles you defined. \chapter{Assets}\label{assets} When you think of assets, you probably think of things like houses, cars, money, or gold bricks. Although Liferay DXP is capable of holding many assets, they aren't financial assets. Assets in Liferay DXP are any kind of content. For example, images, documents, and web content are all assets. And although such assets may not have the tangible value of a gold brick, they could equal or even exceed that value when used to support an organization's day-to-day business activities. You might even think of Liferay DXP as your very own Fort Knox (don't worry, we won't tell anyone). Here, you'll learn to configure Liferay DXP to apply \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{tags} to assets automatically. \begin{figure} \centering \includegraphics{./images/auto-tagging-images.png} \caption{The tags \emph{freight car} and \emph{electric locomotive} were automatically applied to this image.} \end{figure} \chapter{Configuring Asset Auto Tagging}\label{configuring-asset-auto-tagging} \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{Tagging assets} is a great way to organize content. Typically, the content creator applies tags while creating the content. It's also possible, however, to tag content automatically. For example, Liferay DXP can scan an image on upload and apply tags that describe the image's content. This lets you leverage tags without requiring content creators to apply them manually. \noindent\hrulefill \textbf{Note:} Auto-tagging currently works only for images, text-based documents, text-based web content, and blog entries. \noindent\hrulefill Here, you'll learn how to configure asset auto-tagging in general. This is required prior to configuring auto-tagging for specific asset types, which is documented separately: \begin{itemize} \tightlist \item \href{/docs/7-2/user/-/knowledge_base/u/auto-tagging-images}{Auto Tagging Images} \item \href{/docs/7-2/user/-/knowledge_base/u/auto-tagging-text}{Auto Tagging Text} \end{itemize} \section{Configuration Levels}\label{configuration-levels} Auto-tagging is enabled by default. You can configure it at three levels: \textbf{Global (System):} For auto-tagging to function on any level, it must be enabled globally. You can also set the default auto-tagging configuration for every portal instance. \textbf{Instance:} When enabled globally, auto-tagging is also enabled by default for each portal instance. However, you can override the global auto-tagging configuration on a per-instance basis. \textbf{Site:} When enabled for an instance, auto-tagging is also enabled by default for all that instance's sites. You can disable it for specific sites. \section{Global Configuration}\label{global-configuration-1} Follow these steps to configure auto tagging globally: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{SYSTEM SCOPE}, select \emph{Asset Auto Tagging}. \item The following settings are available: \textbf{Enable Auto Tagging of Assets:} Whether asset auto tagging is enabled. \textbf{Maximum Number of Tags:} The maximum number of tags that can be automatically applied to each asset. The default value of \texttt{0} means that there is no limit. \item Click \emph{Save} to save your changes. \end{enumerate} To set the default auto-tagging configuration for all instances, select \emph{Asset Auto Tagging} under \emph{VIRTUAL INSTANCE SCOPE}. The available settings are exactly the same as those in the SYSTEM SCOPE. \begin{figure} \centering \includegraphics{./images/auto-tagging-global.png} \caption{You can configure auto tagging globally in the Assets section of System Settings.} \end{figure} \section{Instance-level Configuration}\label{instance-level-configuration} When enabled globally, auto-tagging is also enabled by default for each instance. You can, however, disable or configure it for each instance. Follow these steps to configure auto tagging on the instance level: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Asset Auto Tagging}. \item The settings here are identical to those in the global configuration, but apply only to the current instance. \item Click \emph{Save} to save your changes. \end{enumerate} \begin{figure} \centering \includegraphics{./images/auto-tagging-instance.png} \caption{You can also configure auto tagging for each instance.} \end{figure} \section{Site-level Configuration}\label{site-level-configuration} When enabled for an instance, auto-tagging is also enabled by default for all that instance's sites. You can, however, enable or disable it for each site. Follow these steps to configure auto tagging for a site: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the \emph{Menu} (\includegraphics{./images/icon-menu.png}), click your site's name, and navigate to \emph{Configuration} → \emph{Settings}. \item In the \emph{General} tab, expand the \emph{Asset Auto Tagging} section. Use the toggle to enable or disable auto tagging for the site. \item Click \emph{Save} to save your changes. \end{enumerate} \begin{figure} \centering \includegraphics{./images/auto-tagging-site.png} \caption{You can enable or disable auto-tagging for a site.} \end{figure} \chapter{Auto Tagging Images}\label{auto-tagging-images} \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{Tags} help you find and organize files, including images. With \href{/docs/7-2/user/-/knowledge_base/u/configuring-asset-auto-tagging}{asset auto tagging enabled}, you can also enable image auto tagging. Image auto tagging automatically tags images uploaded to the Documents and Media Library. This lets you use tags without requiring anyone to apply them manually. \noindent\hrulefill \textbf{Note:} Currently, tags applied automatically are English only. \noindent\hrulefill Image auto tagging is disabled by default. To use it, you must do two things: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Ensure that \href{/docs/7-2/user/-/knowledge_base/u/configuring-asset-auto-tagging}{asset auto tagging} is enabled. Although it's enabled by default, administrators can disable it. \item Ensure that an image auto tagging provider is enabled. These providers are disabled by default. Here, you'll learn how to enable/disable them. \end{enumerate} \noindent\hrulefill \textbf{Note:} Prior to Liferay DXP Fix Pack 1 and Liferay Portal CE GA2, you must configure the providers shown here in \emph{Documents and Media} instead of \emph{Assets} (in System/Instance Settings). \noindent\hrulefill Three such providers are available: \hyperref[configuring-tensorflow-image-auto-tagging]{\textbf{TensorFlow:}} An open-source library that provides machine learning capabilities. TensorFlow image auto-tagging in Liferay DXP is based on \href{https://github.com/tensorflow/tensorflow/blob/master/tensorflow/java/src/main/java/org/tensorflow/examples/LabelImage.java}{TensorFlow's \texttt{LabelImage} sample for Java}, and uses the Inception5h model. Use this with caution, since its accuracy is limited. \hyperref[configuring-google-cloud-vision]{\textbf{Google Cloud Vision:}} Uses the \href{https://cloud.google.com/vision/}{Google Cloud Vision API} to automatically tag images. \hyperref[configuring-microsoft-cognitive-services]{\textbf{Microsoft Cognitive Services:}} Uses \href{https://azure.microsoft.com/en-us/services/cognitive-services/}{Microsoft Cognitive Services} to automatically tag images. \begin{figure} \centering \includegraphics{./images/auto-tagging-images.png} \caption{The tags \emph{freight car} and \emph{electric locomotive} were automatically applied to this image.} \end{figure} \section{Configuring TensorFlow Image Auto Tagging}\label{configuring-tensorflow-image-auto-tagging} Follow these steps to configure TensorFlow Image Auto Tagging: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{TensorFlow Image Auto Tagging}. The following settings are available: \textbf{Enable TensorFlow Image Auto Tagging:} Check this box to select whether image auto-tagging is enabled by default in any instance that has asset auto tagging enabled. Note that you can override this value for specific instances, as the next section shows. \textbf{Confidence Threshold:} TensorFlow assigns a confidence level between 0 and 1 for each tag, where 1 is the highest confidence and 0 is the lowest. This field sets the minimum confidence level that TensorFlow needs to apply a tag. Higher values yield fewer tags because TensorFlow needs more confidence before it applies a tag. Likewise, lower values yield more tags. \item Click \emph{Save} to save your changes. \end{enumerate} You can override these settings for each instance from \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \begin{figure} \centering \includegraphics{./images/auto-tagging-tensorflow.png} \caption{Configure TensorFlow image auto-tagging for your portal instances.} \end{figure} To optimize performance, you can also control the process that runs TensorFlow image auto tagging: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{SYSTEM SCOPE}, select \emph{TensorFlow Image Auto Tagging Process}. The following settings are available: \textbf{Maximum Number of Relaunches:} The maximum number of times the process is allowed to crash before it is permanently disabled. \textbf{Maximum Number of Relaunches Time Interval:} The time in seconds after which the counter is reset. \item Click \emph{Save} to save your changes. \end{enumerate} \begin{figure} \centering \includegraphics{./images/auto-tagging-tensorflow-process.png} \caption{You can fine tune the process that runs the TensorFlow image auto tagging in the portal.} \end{figure} \section{Configuring Google Cloud Vision}\label{configuring-google-cloud-vision} Follow these steps to configure Google Cloud Vision image auto tagging: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Google Cloud Vision Image Auto Tagging}. The following settings are available: \textbf{API Key:} The API key to use for the Google Cloud Vision API. For more information, see \href{https://cloud.google.com/docs/authentication/api-keys}{Google's documentation on API keys}. \textbf{Enabled:} Whether Google Cloud Vision image auto tagging is enabled. \item Click \emph{Save} to save your changes. \end{enumerate} You can override these settings for each instance from \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \begin{figure} \centering \includegraphics{./images/auto-tagging-image-google.png} \caption{The Google Cloud Vision provider requires an API key.} \end{figure} \section{Configuring Microsoft Cognitive Services}\label{configuring-microsoft-cognitive-services} Follow these steps to configure Microsoft Cognitive Services image auto tagging: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Microsoft Cognitive Services Image Auto Tagging}. The following settings are available: \textbf{API Key:} Your \href{https://azure.microsoft.com/en-us/try/cognitive-services/my-apis/?apiSlug=computer-services}{API key} for the Computer Vision API V2. \textbf{API Endpoint:} The endpoint for the Computer Vision API V2 (e.g., \texttt{https://westcentralus.api.cognitive.microsoft.com/vision/v2.0}). \textbf{Enabled:} Whether Microsoft Cognitive Services image auto tagging is enabled. For more information, see the \href{https://docs.microsoft.com/en-us/azure/cognitive-services/}{Microsoft Cognitive Services documentation}. \item Click \emph{Save} to save your changes. \end{enumerate} You can override these settings for each instance from \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \begin{figure} \centering \includegraphics{./images/auto-tagging-image-microsoft.png} \caption{The Microsoft Cognitive Services provider requires an API key and an endpoint.} \end{figure} \chapter{Auto Tagging Text}\label{auto-tagging-text} With \href{/docs/7-2/user/-/knowledge_base/u/configuring-asset-auto-tagging}{asset auto tagging enabled}, you can also configure text auto tagging. Text auto tagging automatically \href{/docs/7-2/user/-/knowledge_base/u/tagging-content}{tags} text-based assets. This lets you use tags without requiring anyone to manually apply them. \noindent\hrulefill \textbf{Note:} Currently, text auto tagging is only available for text-based documents, text-based web content, and blog entries. Tags applied automatically are English only. \noindent\hrulefill Text auto tagging is disabled by default. To use it, you must enable it: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Ensure that \href{/docs/7-2/user/-/knowledge_base/u/configuring-asset-auto-tagging}{asset auto tagging} is enabled. Although it's enabled by default, administrators can disable it. \item Ensure that a text auto tagging provider is configured and enabled for the asset types you want to auto tag. You'll learn how to do this here. Note that these providers aren't configured or enabled by default. \end{enumerate} \noindent\hrulefill \textbf{Note:} Prior to Liferay DXP Fix Pack 1 and Liferay Portal CE GA2, you must enable these providers separately for each content type in System/Instance Settings. For example, you must enable text auto tagging for documents and web content in \emph{Documents and Media} and \emph{Web Content}, respectively. \noindent\hrulefill There are two text auto-tagging providers in the portal: \hyperref[configuring-google-cloud-natural-language-text-auto-tagging]{\textbf{Google Cloud Natural Language Text Auto Tagging:}} Uses the \href{https://cloud.google.com/natural-language/}{Google Cloud Natural Language API} to analyze and automatically tag portal content. \hyperref[configuring-opennlp-text-auto-tagging]{\textbf{OpenNLP Text Auto Tagging:}} Uses the open source \href{https://opennlp.apache.org/}{Apache OpenNLP} library to analyze and automatically tag portal content. Three models are used: location name finder, organization finder, and person name finder. Use this provider with caution, as its accuracy may be limited. \section{Configuring Google Cloud Natural Language Text Auto Tagging}\label{configuring-google-cloud-natural-language-text-auto-tagging} Follow these steps to configure the auto-tagging provider for the Google Cloud Natural Language API: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{Google Cloud Natural Language Text Auto Tagging}. The following settings are available: \textbf{API Key:} The API key to use for the Google Cloud Natural Language API. For more information, see \href{https://cloud.google.com/docs/authentication/api-keys}{Google's documentation on API keys}. \textbf{Classification Endpoint Enabled:} Whether to enable auto tagging of text using the Google Cloud Natural Language API Classification endpoint. \textbf{Confidence:} Set the classifier's confidence of the category. This number represents how certain the classifier is that this category represents the given text. \textbf{Entity Endpoint Enabled:} Whether to enable auto tagging of text using the Google Cloud Natural Language API Entity endpoint. \textbf{Salience:} The salience score for an entity provides information about the importance or centrality of that entity to the entire text. \textbf{Enable Google Cloud Natural Language Text Auto Tagging For:} The asset types to enable text auto tagging for. Use the menu to select \emph{Document}, \emph{Blogs Entry}, or \emph{Web Content Article}. To add multiple asset types, click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select the asset type from the menu. You can delete any additional asset types by clicking the Trash icon (\includegraphics{./images/icon-trash-list.png}). \item Click \emph{Save} to save your changes. \end{enumerate} You can override these settings for each instance from \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \begin{figure} \centering \includegraphics{./images/auto-tagging-text-google.png} \caption{Configure Google Cloud Natural Language text auto tagging for your portal instances.} \end{figure} \section{Configuring OpenNLP Text Auto Tagging}\label{configuring-opennlp-text-auto-tagging} Follow these steps to configure the OpenNLP Text Auto Tagging provider: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Assets}. \item Under \emph{VIRTUAL INSTANCE SCOPE}, select \emph{OpenNLP Text Auto Tagging}. The following settings are available: \textbf{Confidence Threshold:} Set the minimum confidence threshold (from 0 to 1, where 1 is the highest confidence) above which tags will be applied. Higher values yield fewer tags because the provider needs more confidence before it applies a tag. Likewise, lower values yield more tags. \textbf{Enable OpenNLP Text Auto Tagging For:} The asset types to enable text auto tagging for. Use the menu to select \emph{Document}, \emph{Blogs Entry}, or \emph{Web Content Article}. To add multiple asset types, click the \emph{Add} icon (\includegraphics{./images/icon-add.png}) and select the asset type from the menu. You can delete any additional asset types by clicking the Trash icon (\includegraphics{./images/icon-trash-list.png}). \item Click \emph{Save} to save your changes. \end{enumerate} You can override these settings for each instance from \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Assets}. \begin{figure} \centering \includegraphics{./images/auto-tagging-text-open-nlp.png} \caption{Configure OpenNLP text auto tagging for your portal instances.} \end{figure} \chapter{Creating A Social Network}\label{creating-a-social-network} Liferay DXP contains several features and widgets for leveraging its social framework. The Activities widget lets you broadcast user activities on a Site. This is a good way for Site members to see what's going on in their communities. When placed on a user's private Dashboard, the Activities widget displays the activities of that user's social connections. Users can make those social connections via the Contacts Center app, which lets them establish connections and followers throughout the portal. What's more, widgets can be exported as OpenSocial gadgets and/or used with Facebook. This guide shows you how to do these things, and more. \begin{figure} \centering \includegraphics{./images/activities-widget.png} \caption{The Activities widget shows information about asset-related user activity in the current Site.} \end{figure} \begin{figure} \centering \includegraphics{./images/connection-request.png} \caption{Users get a notification that lets them respond to connection requests.} \end{figure} \begin{figure} \centering \includegraphics{./images/contacts-center.png} \caption{The Contacts Center widget lets users make connections.} \end{figure} \chapter{Using the Activities Widget}\label{using-the-activities-widget} The core social widget is Activities. It displays information about user activity on the Site where you added it. User activities tracked include updates to the Documents and Media library, blog posts, message boards posts, wiki pages, and bookmarks. Liferay DXP also tracks information about web content but only displays this information if the logged-in user is a Site administrator. This widget provides a summary of recent Site activity. You can use it on a Site's public or private pages to show what Site members have been up to, or you can use it on the public or private pages of a user's personal Site. When added to a personal Site, the Activities widget shows the activities of only that user. Add the Activities widget to a page from the \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{Social} menu. \begin{figure} \centering \includegraphics{./images/activities-widget.png} \caption{The Activities widget shows information about asset-related user activity in the current Site.} \end{figure} Note that the widget provides links to the assets listed in the feed. These links don't work, however, unless there's a way to display the assets on the page. For example, if you click a link to a blog post in the Activities widget, that page must have a Blogs widget to display that blog post. \chapter{Connecting Users}\label{connecting-users} By adding the Contacts Center and Activities widgets to users' private Dashboards, administrators can let users form social connections. The Contacts Center widget lets users form these connection types: \textbf{Connection:} A two-way relationship formed by a user accepting a connection request from another user. The Activities widget on each user's private Dashboard displays the activities of the other user. \textbf{Following:} A one-way relationship in which one user follows another user. The followed user's activities appear in the Activities widget on the follower's private Dashboard. To ensure that all users have a page on their private Dashboard that contains the Contacts Center and Activities widgets, you can create a user group and then create a page via a Site Template. For instructions on this, see the \href{/docs/7-2/user/-/knowledge_base/u/user-groups}{user groups documentation}. The widgets are in \emph{Add} (\includegraphics{./images/icon-add.png}) → \emph{Widgets} → \emph{Social}. \begin{figure} \centering \includegraphics{./images/contacts-center.png} \caption{The Contacts Center widget lets users make connections.} \end{figure} When you select a user in the Contacts Center, these buttons appear across the top of the widget: \textbf{Connect:} Send a connection request to the user. \textbf{Disconnect:} Disconnect from the user. \textbf{Follow:} Follow the user. \textbf{Unfollow:} Stop following the user. \textbf{Block:} Block the user. Blocking a user only prevents that user from following you or adding you as a connection. A blocked user can still send messages to and view the public profile information of the blocking user. \textbf{Unblock:} Stop blocking the user. \textbf{vCard:} Export the user's vCard and save it as a VCF file. vCard is a file format standard for electronic business cards. When you send a connection request, the user is notified and can confirm or ignore the request. \begin{figure} \centering \includegraphics{./images/connection-request.png} \caption{Users get a notification that lets them respond to connection requests.} \end{figure} \chapter{Exporting Widgets To Other Websites}\label{exporting-widgets-to-other-websites} You can publish its widgets to other websites via OpenSocial. This lets you provide your widget or content in the context of another website. Read on to find out how this is done. \section{Sharing OpenSocial Gadgets}\label{sharing-opensocial-gadgets} OpenSocial consists of a set of APIs for social networking. It may be beneficial for you to share widgets from your server with other sites, such as \href{http://ighome.com}{ighome.com} or \href{http://igoogleportal.com}{igoogleportal.com}. These sites let users customize their own pages with gadgets. Users can share Liferay DXP widgets on any OpenSocial-compatible site. Liferay DXP does this by sharing the widget's URL with the OpenSocial platform. This URL is unique to the user's specific widget instance. Users can therefore share multiple instances of the same widget as different OpenSocial gadgets. Follow these steps to share a widget with OpenSocial: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the widget you want to share to a site page. \item Click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. \item In the \emph{Sharing} tab, open the \emph{OpenSocial Gadget} section. \item Set the selector to \emph{YES} for \emph{Allow users to add {[}widget name{]} to an OpenSocial platform}. In the \emph{OpenSocial Gadget URL} field, replace \texttt{localhost:8080} with the name of your public domain and port. \item Click \emph{Save}. \item Close the dialog and click the widget's \emph{Options} icon (\includegraphics{./images/icon-app-options.png}). There's a new option available named \emph{Add to an OpenSocial Platform}. Select this option to add the widget to a page on an OpenSocial platform. \end{enumerate} \begin{figure} \centering \includegraphics{./images/open-social-sharing.png} \caption{You can share widgets via OpenSocial.} \end{figure} \chapter{Integrating with Facebook}\label{integrating-with-facebook} Liferay DXP provides tools for integrating your portal and its content with Facebook. For example, you can use Facebook for authentication and even export widgets as Facebook applications. This article shows you how. \section{Facebook Sign On}\label{facebook-sign-on} Like many websites you may visit, any site running on Liferay DXP can use Facebook for sign in. This makes it easier for users to sign in to your Site, since they don't need to remember another user name and password. \section{Using Your Widget as Facebook Applications}\label{using-your-widget-as-facebook-applications} You can add any Liferay DXP widget as an app on Facebook. To do this, you must first get a developer key. A link for doing this is provided to you in the Facebook tab in any widget's Configuration screen. You must create the app on Facebook and get the key and canvas page URL from Facebook. You can then copy and paste their values into the Facebook tab. Once you do that, your widget is available on Facebook. This integration lets you make things like Message Boards, Calendars, Wikis, and other content on your website available to a much larger audience (unless you already have a billion users on your site, in which case, kudos to you). If you're a developer, you can implement your widget on Liferay DXP and then publish it to Facebook. \chapter{Using Social Bookmarks}\label{using-social-bookmarks} Social bookmarks appear below content as buttons for sharing that content on social networks. For example, social bookmarks appear by default in the Blogs widget below each blog post. For more information on configuring social bookmarks in the Blogs widget, see the documentation on \href{/docs/7-2/user/-/knowledge_base/u/displaying-blogs}{displaying blogs}. These social bookmarks are available by default: \begin{itemize} \tightlist \item Twitter \item Facebook \item LinkedIn \end{itemize} \begin{figure} \centering \includegraphics{./images/social-bookmarks-menu.png} \caption{The default social bookmarks appear in a menu below content.} \end{figure} You can install the Social Bookmarks app from Liferay Marketplace. This app is available for \href{https://web.liferay.com/marketplace/-/mp/application/15194315}{Liferay CE Portal} and \href{https://web.liferay.com/marketplace/-/mp/application/15188453}{Liferay DXP}. It adds the following social bookmarks: \begin{itemize} \tightlist \item AddThis \item Delicious \item Digg \item Evernote \item Reddit \item Slashdot \end{itemize} If you need help installing apps from Liferay Marketplace, see \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{Using the Liferay Marketplace}. \chapter{Search}\label{search} Sites often feature lots of content split over lots of asset types. Web content articles, documents and media files, and blogs entries are just a few examples. Most content types are \emph{assets}. Under the hood, assets use the \href{/docs/7-2/frameworks/-/knowledge_base/f/asset-framework}{Asset API} and have an Indexer class. Any content that has these features can be searched. \begin{figure} \centering \includegraphics{./images/search-assets.png} \caption{The Type Facet configuration lists the searchable out-of-the-box asset types.} \end{figure} \section{Elasticsearch}\label{elasticsearch} The default search engine is Elasticsearch, which is backed by the Lucene search library. There's an Elasticsearch server embedded in all bundles, which is handy for testing and development purposes. Production environments must install a separate, remote Elasticsearch server (or even better, cluster of servers). For information on how to install Elasticsearch, read the \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch}{deployment guide}. \section{Search Features}\label{search-features} Searching is simple and straightforward. Find a search bar (there's one embedded in every page by default), enter a term, and click \emph{Enter}. \begin{figure} \centering \includegraphics{./images/search-bar.png} \caption{There's a search bar embedded on all pages by default.} \end{figure} After search is triggered, a results page appears. If there are hits to search engine documents, they appear as search results in the right hand column. In the left hand column are search facets. \begin{figure} \centering \includegraphics{./images/search-results.png} \caption{Results are displayed in the Search Results portlet.} \end{figure} The search bar, search results, and search facets make up three powerful features in the search UI. \section{Search Bar}\label{search-bar} The search bar is simple: it's where you enter \emph{search terms}. Search terms are the text you send to the search engine to match against the documents in the index. \section{Search Results and Relevance}\label{search-results-and-relevance} The search term is processed by an algorithm in the search engine, and search results are returned to users in order of relevance. Relevance is determined by a document's \emph{score}, generated against the search query. The higher the score, the more relevant a document is considered. The particular relevance algorithm used is dependent on \href{https://www.elastic.co/guide/en/elasticsearch/reference/current/relevance-intro.html\#relevance-intro}{algorithms provided by the search engine (Elasticsearch by default)}. \section{Search Facets}\label{search-facets} Facets allow users of the Search application to filter search results. Think of facets as buckets that hold similar search results. You might want to see the results in all the buckets, but after scanning the results, you might decide that the results of just one bucket better represent what you want. So what facets are included out of the box? \begin{itemize} \tightlist \item Category \item Folder \item Site \item Tag \item Type \item User \item Modified \item Custom \end{itemize} \begin{figure} \centering \includegraphics{./images/search-faceted-search.png} \caption{\emph{Site} and \emph{Type} are two of the facet sets you'll encounter. They let you drill down to results that contain the search terms you entered.} \end{figure} You've probably used something similar on any number of sites. You search for an item, are presented with a list of results, and a list of buckets you can click to further drill down into the search results, without entering additional search terms. Search facets work the same way. Facets are, of course, \href{/docs/7-2/user/-/knowledge_base/u/facets}{configurable}. \chapter{What's New in Search}\label{whats-new-in-search} Lots of new and improved search capabilities are present in 7.0, from new widgets to new APIs and infrastructure. \section{New and Improved Widgets}\label{new-and-improved-widgets} Add search widgets by clicking the Add (\includegraphics{./images/icon-add-widget.png}) icon on the page. Then expand the Widgets → Search section. \section{Custom Filter}\label{custom-filter} \textbf{New Widget} Add a widget to the page for each of the filters you'd like applied to the search results. Let search page users see and manipulate the filters or make them invisible and/or immutable (this is just a cool word for ``they can't be changed''). For example, add a custom filter to ensure that all returned results have the keyword \emph{street} in the content field. \section{Sort}\label{sort} \textbf{New Widget} The Sort widget reorders the results based on the value of certain \texttt{keyword} fields in the index. For example, show results in alphabetic order of the Title field. The default order is determined by the search engine's \emph{Relevance} calculation. Add more fields to the sort widget if the default options aren't enough. Click the widget Options (\includegraphics{./images/icon-app-options.png}) menu → Configuration. Enter a human readable label and the \texttt{fieldName} to sort by. Just make sure it's a \texttt{keyword} field. \section{Search Insights}\label{search-insights} \textbf{Improved} Past versions of Search Insights showed you the full query string sent to the search engine, but now it also displays the response from the search engine with an explanation of the score for each search hit. \section{New Search Admin Functionality}\label{new-search-admin-functionality} The Search Admin functionality is found in Control Panel → Configuration → Search. \section{Search Engine Info}\label{search-engine-info} The displayed Search Engine information is enhanced, showing the client and node information as well as the vendor and operation mode. \section{Field Mappings}\label{field-mappings} The Field Mappings tab shows the field mappings for all indexes in the search engine. \section{Indexing Progress}\label{indexing-progress} Indexing now displays a progress bar so you can see in the UI when the re-indexing action has completed. \section{New System Settings}\label{new-system-settings} Access the Search System Settings at Control Panel → Configuration → System Settings → Search. There's a new entry in the search category: \emph{Title Field Query Builder}. Use it to configure how search responds to matches on the Title field of a document. \textbf{Exact Match boost:} Give an additional boost when searched keywords exactly match the \texttt{title} field of a document. \textbf{Maximum Expansions:} Limit the number of documents to return when matching searched keywords to the \texttt{title} field as a phrase prefix. See Elasticsearch's \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-match-query-phrase.html}{Match Phrase Query documentation} for more information. \section{New Infrastructure}\label{new-infrastructure} There are some important search infrastructure changes to know about. \section{Elasticsearch Support}\label{elasticsearch-support} 7.0 supports Elasticsearch 6.5.x through 6.8.x, and 6.5.0 is included as the embedded version to use for testing out-of-the-box search behavior. See the \href{/docs/7-2/deploy/-/knowledge_base/d/elasticsearch}{deployment guide} for more information. \section{Application-Specific Indexes}\label{application-specific-indexes} You'll notice more search indexes in 7.0. That's because you can now configure application-specific indexes. At the time of this writing, the additional indexes are all related to the DXP Workflow Metrics feature. More will likely appear in future versions, and third party developers can use the \texttt{portal-search} APIs to create their own indexes. It's under development, so visit the \href{/docs/7-2/frameworks/-/knowledge_base/f/search}{Search Framework documentation} frequently to discover new search infrastructure changes that expose more functionality for developers. \section{API Enhancements}\label{api-enhancements} Enhancements to the \href{/docs/7-2/frameworks/-/knowledge_base/f/search}{search framework} APIs include \begin{itemize} \tightlist \item Low level indexing and queries \item Operations directly on indexed documents (no need for the Indexer framework) \item New Aggregation types \end{itemize} \section{Multi-Language Search}\label{multi-language-search} Liferay DXP's support for multi-language search took a step forward, with improvements to Documents and Media and Web Content. More improvements are necessary in this area and will be prioritized in future releases. See the \href{/docs/7-2/user/-/knowledge_base/u/searching-for-localized-content}{Multi-Language Search article for more information}. \chapter{Configuring Search Pages}\label{configuring-search-pages} There are multiple ways to skin the search cat (disclaimer: no actual cats were harmed during the writing of this article). If you're unsure which approach to take, use the \hyperref[default-search-pages]{default} configuration. It provides a sensible starting point that can be modified later, as needed. If you've been using Liferay DXP for a long time and like the search experience you've always used, use the \hyperref[legacy-search-experience]{legacy approach}. If you're in need of a fully customized experience, \hyperref[manual-search-page-configuration]{manually configure} the search experience. After choosing your approach and reading here to get it up and running, find the articles on the \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-bar}{Search Bar}, \href{/docs/7-2/user/-/knowledge_base/u/facets}{Search Facets}, and \href{/docs/7-2/user/-/knowledge_base/u/search-results}{Search Results} to understand the full suite of configuration options. Search display pages are where users go to enter search terms and browse search results. \section{Search Page Templates}\label{search-page-templates} The default search page is backed by a Search Page Template, and manually configured search pages can use the template, too. The template can be used in two ways: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enable inheriting changes to the template, if you want the search page to get any updates made to the template at a later date. \item Create the page based on the template, but independently configured after the initial creation. \end{enumerate} Out of the box, the Search Page Template includes these widgets: \begin{itemize} \item \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-bar}{Search Bar} \item \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-suggestions}{Suggestions} \item \href{/docs/7-2/user/-/knowledge_base/u/search-results}{Search Results} \item \href{/docs/7-2/user/-/knowledge_base/u/configuring-search\#widget-scoped-search-configuration}{Search Options} \item \href{/docs/7-2/user/-/knowledge_base/u/site-facet}{Site Facet}: This widget is hidden unless the Search Bar is configured to search the \emph{Everything} scope and results from multiple sites are returned. \item \href{/docs/7-2/user/-/knowledge_base/u/type-facet}{Type Facet} \item \href{/docs/7-2/user/-/knowledge_base/u/tag-and-category-facets}{Tag Facet} \item \href{/docs/7-2/user/-/knowledge_base/u/tag-and-category-facets}{Category Facet} \item \href{/docs/7-2/user/-/knowledge_base/u/folder-facet}{Folder Facet} \item \href{/docs/7-2/user/-/knowledge_base/u/user-facet}{User Facet} \item \href{/docs/7-2/user/-/knowledge_base/u/modified-facet}{Modified Facet} \end{itemize} Out of the box, widgets use the \emph{Barebone} Application Decorators: unless there's content to render in the widget, the widget body is hidden. The header is displayed if you hover over it. \begin{figure} \centering \includegraphics{./images/search-barebone-widgets.png} \caption{At first glance, not much is happening on the search page. But, there's more than meets the eye.} \end{figure} Because of this, when you visit a search page created from the default search page template, you won't see certain widgets fully rendered. By contrast, when you add a search widget to a page manually, they use the \emph{Borderless} decorator (by default), which shows more of the widget even when there is no content to display. \section{Default Search Pages}\label{default-search-pages} Using the default site and the default theme with the default search settings, the out-of-the-box search experience has two components for end users: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item A search bar embedded on each page. \item A default search page where search requests are routed. \end{enumerate} Behind the scenes, The search bar widget points to a hidden search page with the friendly URL \texttt{/search}. \begin{figure} \centering \includegraphics{./images/search-dest-page.png} \caption{By default, the embedded search bar points to the pre-configured \texttt{/search} destination page.} \end{figure} Enter a search term and you're redirected to the default search page, where results are displayed in the Search Results widget. \begin{figure} \centering \includegraphics{./images/search-default-page.png} \caption{The default page is pre-configured with the Search Results widget and the various Facet widgets to provide a full search experience.} \end{figure} The default search page is based on a Search page template, but it doesn't inherit changes from the page template by default. That means you can customize the search page directly without changing the template's inheritance configuration. \begin{figure} \centering \includegraphics{./images/search-page-config.png} \caption{Configure the Search page. By default, it doesn't inherit changes from the page template.} \end{figure} If you require just a few changes to the default page, don't abandon it and create one manually. Just make the configuration changes you need, including adding, configuring, and removing widgets on the page. On the other hand, if you want a clean break from the default search page, starting from scratch is also an option. \section{Manual Search Page Configuration}\label{manual-search-page-configuration} It's reasonable to create the search experience from the ground up. If you're working from a newly created site, it's a necessity. These steps show you how to switch to a manually configured search experience in the default site, but you can skip the step on deleting the default search page if you're starting with a new site: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Delete the existent search page by navigating to the default site's menu and clicking \emph{Site Builder} → \emph{Pages}. Click the Search page's Actions menu icon (\includegraphics{./images/icon-actions.png}) and select \emph{Delete}. Confirm you want to delete the page, and it's gone. Once deleted, the search bar disappears from your site pages, replaced by a warning message visible only to site administrators: \begin{figure} \centering \includegraphics{./images/search-bar-warning.png} \caption{The search bar is only visible if it points to an existent page.} \end{figure} \item Create a new page named whatever you want (\emph{Finders Keepers}, perhaps). Make it hidden or add it to the navigation as you please (the default search page is hidden from the navigation). If you want a pre-configured search page, create it from the Search page template. Find the template in the Add Site Page form. It's under \emph{Global Templates}. \begin{figure} \centering \includegraphics{./images/search-page-template.png} \caption{There's a handy page template for creating search pages.} \end{figure} \item If you're creating a page not backed by the template, add and configure all the widgets you need. You'll find all the available search widgets in the Add Widget menu's Search section. Lay them out however you want on the page. \item Configure the search bar at the top of the page, making sure it points to your new search page's friendly URL (for example, \texttt{/finders-keepers}). Click the Search Bar widget's Options menu (\includegraphics{./images/icon-app-options.png}). Click \emph{Configuration} and set the Destination Page to the search page's friendly URL. Click \emph{Save}. \end{enumerate} Now your search page is up and running. \section{Legacy Search Experience}\label{legacy-search-experience} In prior versions, the search experience was encapsulated in one application, \emph{Search}. It was embedded in the default theme, just like the search bar is now. It looked very similar, with only the search bar visible in the default view of the application. Once a search term is entered, the maximized view of the application is presented, with all the search facets and results now in view. It looks a lot like the new search behavior, only its monolithic structure means it's difficult to customize. If you liked the old application, it's still available. Enable it with these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Delete the default search page. From the site menu, click \emph{Site Builder} → \emph{Pages}. Click the Actions menu (\includegraphics{./images/icon-actions.png}) for the Search page and choose \emph{Delete}. \item Enable the legacy search application. Go to Control Panel → Configuration → System Settings → Search → Search Web and check the box for \emph{Classic Search Widget in Front Page}. \end{enumerate} Now your portal's search is backed by the legacy Search application, and it's embedded on each page in the default theme. To add the legacy Search application to a page, open the Add Widget menu, find the Search widget under the Tools category, and drag and drop it onto the page. Configure the portal's search behavior to suit your needs. Here you've seen three distinct search configurations. \chapter{Searching for Assets}\label{searching-for-assets} As explained in the \href{/docs/7-2/user/-/knowledge_base/u/search}{Search introduction}, all indexed assets can be returned as search results. Developers can create their own assets, so your installation might have additional asset types beyond the ones included by default. \noindent\hrulefill \textbf{Searching for Users:} When you click an asset in the search results, it's displayed in an Asset Publisher (unless the \emph{View in Context} option is selected in the Search Results portlet). Users are different, though. Think of them as invisible assets, not intended for display in the Asset Publisher application. While Users appear as search results with other indexed assets, when you click one you're taken to the User's profile page. If public personal pages are disabled, clicking on a User from the list of search results shows you a blank page. \noindent\hrulefill \section{Search Bar}\label{search-bar-1} Users enter the search context in the search bar. Users enter search terms, hit the \emph{Enter} button (or click the magnifying glass icon), and they're taken to a \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{search page} with various search widgets deployed. If using the Search Bar in the legacy search portlet, users see a maximized view of the search portlet displaying any results and facets that apply. See the article on \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages\#legacy-search-experience}{configuring search pages} to learn more about these options. \begin{figure} \centering \includegraphics{./images/search-bar.png} \caption{The default search configuration displays a search bar in its default view, beckoning users to enter the search context.} \end{figure} \section{Entering Search Terms}\label{entering-search-terms} Liferay's search infrastructure supports full text search as implemented by its supported search engines (\href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/full-text-queries.html}{Elasticsearch} and \href{http://lucene.apache.org/solr/features.html}{Solr}). Full text search compares all the words entered in a search query (for example, \emph{space vacation}) to all the words in each index document. A search engine like Elasticsearch calculates relevance scores to ensure the best results are returned first (like a Blogs Entry titled \emph{Is a vacation in space right for you?}) and lots of matching results are returned (anything with either the word \emph{vacation} or \emph{space} is returned). In addition to full text search, advanced search syntax is supported. Liferay DXP relies on the underlying search engine for this behavior, so consult the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-query-string-query.html\#query-string-syntax}{Elasticsearch} or \href{https://lucene.apache.org/solr/guide/6_6/query-syntax-and-parsing.html}{Solr} documentation for the details. \begin{figure} \centering \includegraphics{./images/search-advanced-syntax.png} \caption{Search for text in a specific field using Elasticsearch's Query String syntax.} \end{figure} \section{Matching Exact Phrases: Quoted Search}\label{matching-exact-phrases-quoted-search} What if users want their search terms (for example, \emph{agile frameworks}) to produce only results with the exact phrase, as typed? In a regular full text search, searching \emph{agile frameworks} returns search results containing just the terms \emph{agile} and \emph{frameworks}, and hits containing both terms but separated by other text, as well as results with the exact phrase match. To ensure that only hits with the exact phrase are returned, enclose it in quotes: \emph{``agile frameworks''}. \begin{figure} \centering \includegraphics{./images/search-quoted.png} \caption{Search for exact phrase matches by enclosing search terms in quotes. If a user searched for \emph{``agile frameworks''}, this result would not be returned.} \end{figure} \section{Prefix Searching}\label{prefix-searching} If you're searching in a site for classical musicians, you might search for the term \emph{instrument}. This search returns documents with the full word in them, but it also returns variants with \emph{instrument} as the prefix. For example, results with \emph{instruments}, \emph{instrumental}, and \emph{instrumentation} are also returned. \begin{figure} \centering \includegraphics{./images/search-prefix.png} \caption{Searching for \emph{lever} also returns \emph{leverage} and \emph{leveraging}.} \end{figure} \noindent\hrulefill \textbf{Note:} Prefix searching is available for many fields out of the box, but as with most things related to search behavior, it's more complicated under the hood. The details of the field mapping, including the analyzer used on the field and any transformations performed, determine the final behavior. \noindent\hrulefill Another way to ensure users see results is through \hyperref[search-suggestions]{search suggestions}. \section{Configuring the Search Bar}\label{configuring-the-search-bar} Configure the Search Bar's behavior via its portlet configuration screen. \begin{figure} \centering \includegraphics{./images/search-bar-configuration.png} \caption{Configure the search bar behavior in its configuration screen.} \end{figure} \noindent\hrulefill \textbf{Note:} When you configure the globally embedded Search Bar widget at the top of one page, it configures the page-top Search Bar widget on all pages in the site. It also overrides the destination \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{Search Page's} Search Bar portlet, if they're configured differently. However, it does not override Search Bar widgets manually placed on other pages. \noindent\hrulefill There are several options: \begin{description} \item[\textbf{Keywords Parameter Name}] Edit the parameter name for the keywords entered in the search. For example, the default URL when searching for the keyword term \emph{data} looks like this: http://localhost:8080/web/guest/search?q=data \end{description} If you change the Keywords Parameter Name to \emph{keyword} it looks like this: \begin{verbatim} http://localhost:8080/web/guest/search?keyword=data \end{verbatim} \begin{description} \tightlist \item[\textbf{Scope}] Choose between three options: This Site (default), Everything, and Let the User Choose. \emph{This Site} means only the assets associated with the site where the search is executed are searched. Expand the scope of the search to all sites by selecting \emph{Everything}. To let users choose which scope they want to search, select \emph{Let the User Choose}. \end{description} \begin{figure} \centering \includegraphics{./images/search-scope.png} \caption{Let the user choose which scope the search is executed for.} \end{figure} \textbf{Scope Parameter Name} : Set the URL parameter name for the scope where the search is taking place. This parameter only appears in the URL if the scope \emph{Let the User Choose} is selected. The default value is \emph{scope}, so searching for the word \emph{data} produces the default URL of \begin{verbatim} http://localhost:8080/web/guest/search?q=data&scope=this-site \end{verbatim} Changing \emph{scope} to \emph{target} would produce this URL: \begin{verbatim} http://localhost:8080/web/guest/search?q=data&target=this-site \end{verbatim} \begin{description} \tightlist \item[\textbf{Destination Page}] Provide a friendly URL to the \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{search page}. If not configured or if it points to a page that doesn't exist, a message appears for administrators that the search bar must be configured for it to appear to users. \item[\textbf{Use Advanced Search Syntax}] If using Elasticsearch, enabling this allows users to enter \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-query-string-query.html\#query-string-syntax}{Query String Syntax} into the Search Bar. If using Solr, consult its documentation for the \href{https://lucene.apache.org/solr/guide/6_6/query-syntax-and-parsing.html}{proper syntax}. \end{description} \section{Search Suggestions}\label{search-suggestions} Suggest search terms to users when their initial queries are suboptimal. Spell check settings allow administrators to configure the Search application so that if a user types a search term that doesn't return many results (for example, a slightly misspelled werd), the user can be prompted to improve the search. To configure the spell check settings, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item You must first reindex the spell check indexes. Go to \emph{Control Panel} → \emph{Configuration} → \emph{Search}, then click \emph{Execute} next to \emph{Reindex all spell check indexes}. \item Add the Suggestions widget to the search page. \item Open its configuration screen. Click the widget Options button (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/search-suggestions.png} \caption{Configure the suggestion settings to allow for user input mistakes and help lead users to results.} \end{figure} There are three main settings here: \textbf{Display ``Did you mean\ldots{}'' if the number of search results does not meet the threshold.} : Present users alternate, spell checked search queries if their search did not return a minimum number of results (50 by default). \begin{description} \tightlist \item[\textbf{Display Related Queries}] If the number of search results doesn't meet the specified threshold (50 by default), display up to a maximum number of alternative queries (10 by default). \item[\textbf{Add New Related Queries Based on Successful Queries}] Index a user's search query if it produces a minimum number of results (50 by default), so it can be displayed to users as a suggestion. If the Display Related Queries setting is enabled, it's used as a related query for similar search queries that don't produce enough results. \end{description} \chapter{Facets}\label{facets} Enter a keyword in the Search Bar and click the Search button. The default search experience redirects to a page with results on the right and a collection of \emph{facets} on the left. \begin{figure} \centering \includegraphics{./images/search-faceted-search.png} \caption{\emph{Site} and \emph{Type} are two of the facet sets you'll encounter.} \end{figure} A Facet aggregates search results by some common characteristic, with each facet holding search results that share something in common. After scanning the full list of results, a User might decide the results from just one facet are more appropriate (for example, all the results from a particular site, or all the results that are Blogs Entries). So what facets are included by default? \begin{itemize} \tightlist \item \textbf{Site Facet} for filtering results by their site. \item \textbf{Type Facet} for filtering results by the Asset Type. \item \textbf{Tag Facet} for filtering results by Tag. \item \textbf{Category Facet} for filtering results by Category. \item \textbf{Folder Facet} for filtering results by Folder. \item \textbf{User Facet} for filtering results by the content creator. \item \textbf{Modified Facet} for filtering results by the Last Modified Date. \item \textbf{Custom Facet} for filtering results by some other indexed field. See \href{/docs/7-2/user/-/knowledge_base/u/custom-facet}{here} for more information. \end{itemize} Each item in a facet (selected using the checkbox) is called a \emph{Facet Term} (\emph{term} for short). In this tutorial, you'll explore how facets and their terms are used and how to find a facet's configuration. The remaining articles show the configurations available for each facet. \section{Using Facets}\label{using-facets} If you're not actually an accomplished oboe player, pretend for a moment. You're visiting a site for classical musicians. You remember reading a great technical analysis of Johann Bach's compositions, but you forgot to bookmark it (or would it be a \emph{bachmark}?). You enter the keyword \emph{bach} into the search bar and, because Johann Bach was a very important and famous composer, you get lots of results: too many, in fact. At first you're discouraged, but you remember that there's a site member who produces most of the site's good technical content, who's named \emph{back2bach}. You see that his name is listed in the User facet, and there aren't many results in the facet count (the number in parentheses next to the facet). You click into the facet and quickly find the content you wanted. \begin{figure} \centering \includegraphics{./images/search-facets1.png} \caption{When presented lots of search results, facets narrow down the results list so users can find relevant content.} \end{figure} Clicking on a facet narrows down the search results. It's added to the filter list in the search query, and the results list is refined by the selected facets. \includegraphics{./images/search-facet-wc.png}. \section{Multiple Facet Selection}\label{multiple-facet-selection} Facet term selections within one facet are additive. Clicking more terms in the same facet expands the search results, because it's processed as if you want to see results matching \emph{Term-1} OR \emph{Term-2}, OR etc. To remove all the term selections from a facet, click the \emph{Clear} link. \begin{figure} \centering \includegraphics{./images/search-multiple-terms.png} \caption{Facet terms are additive when applied in the same facet. Any Blogs Entry OR Web Content article matching the keyword is shown here.} \end{figure} Facet term selections from different facets are exclusive. Clicking facet terms from multiple facets narrows the results because they're processed as if you want to see results matching \emph{Facet-1} AND \emph{Facet-2}, AND etc. This is intuitive. The facets \begin{figure} \centering \includegraphics{./images/search-multiple-facets.png} \caption{Facet terms selected from different facets are exclusive. These results must be of type Blogs Entry AND be from the User Marvin Smart.} \end{figure} Considering a case where you make two term selections in the Type Facet: (Blogs Entry and Web Content Article), and two term selections in the User Facet (James Jeffries and Marvin Smart). What results are displayed? \emph{Blogs Entries OR Web Content Articles AND authored by James Jeffries OR Marvin Smart}. If Marvin and James each created four pieces of content (two blogs and two Web Content Articles), all eight would appear in the Search Results. Any Blogs or Web Content created by other Users are not shown, and assets of other Type created by Marvin and James are not displayed. Content that isn't Blog Entries or Web Content Articles created by other Users are obviously not searched. \begin{figure} \centering \includegraphics{./images/search-facet-selections.png} \caption{Both intra-facet and inter-facet selection is possible.} \end{figure} \noindent\hrulefill \textbf{Note:} The new Search Facet widgets support the multiple selection of facet terms. Multiple facet selection is not supported in the classic Search portlet. \noindent\hrulefill \section{Facets and Friendly URLs}\label{facets-and-friendly-urls} In the classic, monolithic Search portlet, URLs like this were not uncommon: \begin{verbatim} http://localhost:8080/web/guest/home?_com_liferay_portal_search_web_portlet_SearchPortlet_formDate=1529671834606&p_p_id=com_liferay_portal_search_web_portlet_SearchPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_com_liferay_portal_search_web_portlet_SearchPortlet_mvcPath=%2Fsearch.jsp&_com_liferay_portal_search_web_portlet_SearchPortlet_redirect=http%3A%2F%2Flocalhost%3A7011%2Fweb%2Fguest%2Fhome%3Fp_p_id%3Dcom_liferay_portal_search_web_portlet_SearchPortlet%26p_p_lifecycle%3D0%26p_p_state%3Dnormal%26p_p_mode%3Dview&_com_liferay_portal_search_web_portlet_SearchPortlet_keywords=test&_com_liferay_portal_search_web_portlet_SearchPortlet_scope=this-site \end{verbatim} Search now uses friendly search URLs for facet filtering. With the default settings, here's the default main search URL when searching for keyword \emph{test}: \begin{verbatim} http://localhost:8080/web/guest/search?q=test \end{verbatim} Selecting a facet term causes a new parameter to the above URL. For example, selecting \emph{Blogs Entry} from the Type facet results in this URL: \begin{verbatim} http://localhost:8080/web/guest/search?q=test&type=com.liferay.blogs.model.BlogsEntry \end{verbatim} Selecting another facet term from the same facet category appends the same parameter again, but with the newly selected value: \begin{verbatim} http://localhost:8080/web/guest/search?q=test&type=com.liferay.blogs.model.BlogsEntry&type=com.liferay.portal.kernel.model.User \end{verbatim} The rest of the facets work the same way. Filtering by the last hour option in the Last Modified facet portlet produces this URL: \begin{verbatim} http://localhost:8080/web/guest/search?q=test&modified=past-hour \end{verbatim} The parameter names are configurable for each facet. Now that you know how facets work, read about configuring each of the included facets. \chapter{Site Facet}\label{site-facet} The Site Facet narrows search results down to those existing in a certain site. Each Site with content matching the searched keyword appears as a facet term. \begin{figure} \centering \includegraphics{./images/search-site-facet.png} \caption{Each Site with matching content is a facet term.} \end{figure} For the Site Facet to display multiple sites, the Search Bar must be configured to search \emph{Everything}. See more about search scope \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#configuring-the-search-bar}{here}. If not searching for Everything, only the current site is searched, and the Site Facet has nothing to display. When this occurs, the Site Facet is hidden on the page. \noindent\hrulefill \textbf{Note:} Configuring the globally embedded page-top Search Bar to search for Everything not only configures the embedded Search Bar on all pages. It also ensures that the Search Page's Search Bar searches Everything, because the page-top Search Bar's configuration overrides the Search Page's Search Bar configuration. The same does not apply to other Search Bar widgets in the site. Each of these must be configured as desired. If the global Search Bar is disabled, configure the Search Page's Search Bar widget to search for Everything. To configure the search scope, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Search Bar's Options menu (\includegraphics{./images/icon-options.png}) and click \emph{Configuration}. \item Set the Scope option to \emph{Everything}. \item Click \emph{Save} and close the pop-up. \end{enumerate} \noindent\hrulefill The Site Facet contains several configuration options: \begin{figure} \centering \includegraphics{./images/search-site-facet-config.png} \caption{The Site Facet is configurable.} \end{figure} \begin{description} \item[\textbf{Site Parameter Name}] Set the URL parameter name for the Facet. The default is \emph{site}. Searching for \emph{lunar resort} and clicking on a site facet produces the URL \begin{verbatim} http://localhost:8080/web/guest/search?q=lunar resort&site=20126 \end{verbatim} \item[\textbf{Max Terms}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Frequency Threshold}] Set the minimum frequency required for terms to appear in the list of facet terms. For example, if the frequency threshold of a facet is set to \texttt{3}, a term with two matching results doesn't appear in the term result list. \item[\textbf{Display Frequencies}] Choose whether or not to display the term frequencies. \end{description} \chapter{Type Facet}\label{type-facet} The Type Facet narrows search results down to those associated with a certain Asset Type. Each Type with content matching the searched keyword appears as a facet term. \begin{figure} \centering \includegraphics{./images/search-type-facet.png} \caption{Each Asset Type with matching content is a Type Facet term.} \end{figure} By default, all out of the box Asset Types are included as facet terms: \begin{itemize} \tightlist \item Wiki Page \item Document \item User \item Blogs Entry \item Form Record \item Documents Folder \item Dynamic Data Lists Record \item Web Content Article \item Message Boards Message \item Calendar Event \item Knowledge Base Article \end{itemize} The Type Facet contains several configuration options: \begin{figure} \centering \includegraphics{./images/search-type-facet-config.png} \caption{The Type Facet is configurable.} \end{figure} \begin{description} \item[\textbf{Type Parameter Name}] Set the URL parameter name for the Facet. The default is \emph{type}. Searching for \emph{lunar resort} and clicking on a site facet produces the URL \begin{verbatim} http://localhost:8080/web/guest/search?q=lunar resort&type=com.liferay.blogs.model.BlogsEntry \end{verbatim} \item[\textbf{Frequency Threshold}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Display Frequencies}] Choose whether or not to display the term frequencies. \item[\textbf{Current and Available}] Add or remove Asset Types from the facet. To remove types, select from the Current section by clicking and highlighting. Click the right arrow and move the Asset Type from \emph{Current} to \emph{Available}. Add Asset Types by moving them to the Current section. \end{description} \chapter{Tag and Category Facets}\label{tag-and-category-facets} If tags or categories were applied to an asset appearing in the result set, they're displayed in the Tag or Category facet, respectively. Like other facets with the Frequency Threshold configuration option, not all tags necessarily appear. By default the top 10 tags or categories are listed. \begin{figure} \centering \includegraphics{./images/search-tag-facet.png} \caption{Each Tag or Category with matching content is a facet term.} \end{figure} Tag and Category Facets contain identical configuration options: \begin{figure} \centering \includegraphics{./images/search-tag-facet-config.png} \caption{Tag and Category Facets are configurable.} \end{figure} \begin{description} \item[\textbf{Tag/Category Parameter Name}] Set the URL parameter name for the Facet. The default is \emph{tag}/\emph{category}. Searching for \emph{lunar resort} and clicking on a \emph{moon} Tag Facet term produces the URL \begin{verbatim} http://localhost:8080/web/guest/search?q=lunar resort&tag=moon \end{verbatim} \item[\textbf{Display Style}] Choose whether to display the facet terms in Cloud or List style. \item[\textbf{Max Terms}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Frequency Threshold}] Set the minimum frequency required for terms to appear in the result list. For example, if the frequency threshold of a facet is set to \texttt{3}, a term with two matching results doesn't appear in the term result list. \item[\textbf{Display Frequencies}] Choose whether or not to display the term frequencies. \end{description} \chapter{Folder Facet}\label{folder-facet} The Folder Facet narrows search results down to those contained in a certain Asset Folder. If you search for \emph{space}, a Folder titled \emph{Space Images} doesn't necessarily show up here. The content inside the folder must match the keyword. Only if its content matches the searched keyword does the Folder appear in the Folder Facet. Folders of these Types appear as Folder Facet terms: \begin{itemize} \tightlist \item Documents and Media Folder \item Web Content Folder \end{itemize} \begin{figure} \centering \includegraphics{./images/search-folder-facet.png} \caption{Each Folder with matching content is a facet term.} \end{figure} The Folder Facet contains several configuration options: \begin{figure} \centering \includegraphics{./images/search-folder-facet-config.png} \caption{The Folder Facet is configurable.} \end{figure} \begin{description} \item[\textbf{Folder Parameter Name}] Set the URL parameter name for the Facet. The default is \emph{folder}. Searching for \emph{lunar resort} and clicking on a Folder Facet produces the URL \begin{verbatim} http://localhost:8080/web/guest/search?q=lunar resort&folder=38716 \end{verbatim} \item[\textbf{Max Terms}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Frequency Threshold}] Set the minimum frequency required for terms to appear in the result list. For example, if the frequency threshold of a facet is set to \texttt{3}, a term with two matching results doesn't appear in the term result list. \item[\textbf{Display Frequencies}] Choose whether or not to display the term frequencies. \end{description} \chapter{User Facet}\label{user-facet} The User Facet narrows search results down to those created by a certain User. \begin{figure} \centering \includegraphics{./images/search-user-facet.png} \caption{Each User with matching content is a facet term.} \end{figure} The User Facet contains several configuration options: \begin{figure} \centering \includegraphics{./images/search-user-facet-config.png} \caption{The User Facet is configurable.} \end{figure} \begin{description} \item[\textbf{User Parameter Name}] Set the URL parameter name for the Facet. The default is \emph{user}. Searching for \emph{lunar resort} and clicking on a User Facet produces the URL \begin{verbatim} http://localhost:8080/web/guest/search?q=lunar resort&user=38716 \end{verbatim} \item[\textbf{Max Terms}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Frequency Threshold}] Set the minimum frequency required for terms to appear in the result list. For example, if the frequency threshold of a facet is set to \texttt{3}, a term with two matching results doesn't appear in the facet. \item[\textbf{Display Frequencies}] Choose whether or not to display the term frequencies. \end{description} \chapter{Modified Facet}\label{modified-facet} The Modified Facet narrows search results down to those that match the searched keyword and that were created or modified during a certain time period. \begin{figure} \centering \includegraphics{./images/search-modified-facet.png} \caption{Each time period with matching content is a facet term.} \end{figure} In addition to selecting a pre-configured time period, Users can select a Custom Range, specifying a From and To date using a date picker: \begin{figure} \centering \includegraphics{./images/search-modified-facet-custom.png} \caption{Users can include a Custom Range in the Modified Facet.} \end{figure} The Modified Facet supports configuration actions: \begin{itemize} \tightlist \item Modify existing time ranges \item Delete existing time ranges \item Create new time ranges \end{itemize} Edit or create time ranges using a time range alias. The available time range aliases include: \begin{verbatim} past-hour past-24-hours past-week past-month past-year \end{verbatim} \begin{figure} \centering \includegraphics{./images/search-modified-facet-config.png} \caption{The time ranges are set in the facet's configuration.} \end{figure} Each Range has an alias and a Label. By default, all the default ranges end in \texttt{*}, which evaluates to \emph{now}. For example, the past-week range is \begin{verbatim} [past-week TO *] \end{verbatim} You're not limited to ending Ranges. Instead of the \texttt{*}, specify another time range alias as the ending point. To set up a range from 12 months ago to one month ago, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the plus button in one of the existing ranges. \item Give it the label \textbf{1-12 Months Ago}. \end{enumerate} Give it a Range value of \begin{verbatim} [past-year to past-month] \end{verbatim} This gives you lots of flexibility in using alternative time ranges as Modified Facet terms. \chapter{Custom Facet}\label{custom-facet} All Facets are configurable, allowing you to narrow down search results based on a shared characteristic (all Blog Entries, for example). The Custom Facet lets you create entirely new Facets. The first thing to do is enter the Custom Facet's configuration screen. \begin{figure} \centering \includegraphics{./images/search-custom-facet-jobtitle.png} \caption{Custom Facets must be configured first.} \end{figure} The screenshot above shows a Custom Facet with the Job Title of each User that matched the search. The next screenshot shows how it was configured. \begin{figure} \centering \includegraphics{./images/search-custom-facet-config.png} \caption{Configure a Custom Facet in no time.} \end{figure} \begin{description} \item[\textbf{Aggregation Field}] Specify the non-analyzed keyword field whose value is used to create the facet terms. If the value of the search result's \texttt{jobTitle\_sortable} field is \emph{upsale manager}, that's what appears in the Custom Facet as a term. \item[\textbf{Custom Heading}] Enter a human readable heading for the Custom Facet. \item[\textbf{Custom Parameter Name}] Set the URL parameter to use for the facet. With the configuration pictured above, searching for \emph{jane} and clicking on \emph{chief of security} produces the URL http://localhost:8080/web/guest/search?q=jane\&jobtitle=chief\%20of\%20security \item[\textbf{Max Terms}] Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. \item[\textbf{Frequency Threshold}] Set the minimum frequency required for terms to appear in the result list. For example, if the frequency threshold of a facet is set to \texttt{3}, a term with two matching results doesn't appear in the term result list. \item[\textbf{Display Frequencies}] Choose whether to display the term frequencies. \end{description} \section{Finding Indexed Fields}\label{finding-indexed-fields} To use the Custom Facet, you must know which non-analyzed keyword field to specify. To browse the entire list of available fields, inspect the field mappings from Control Panel → Configuration → Search. Alternatively, use your search engine's API. For Elasticsearch, access the field mappings from your terminal using CURL to call the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/indices-get-mapping.html}{Get Mapping API}: \begin{verbatim} curl -X GET "localhost:9200/_mapping/LiferayDocumentType"?pretty \end{verbatim} Solr uses the \href{https://lucene.apache.org/solr/guide/6_6/schema-api.html\#SchemaAPI-ListFields}{ListFields API}: \begin{verbatim} curl http://localhost:8983/solr/liferay/schema/ \end{verbatim} Here's a snippet of output from the Elasticsearch example: \begin{verbatim} "ddmStructureKey": { "store": true, "type": "keyword" }, "ddmTemplateKey": { "store": true, "type": "keyword" }, "defaultLanguageId": { "store": true, "type": "keyword" }, "description": { "store": true, "term_vector": "with_positions_offsets", "type": "text" }, "discussion": { "store": true, "type": "keyword" }, \end{verbatim} Use Custom Fields to aggregate facet terms by shared non-analyzed keyword field values. \chapter{Search Results}\label{search-results} The ideal search experience involves a User entering a search term, waiting an infinitesimal amount of time, and having the perfectly matching asset delivered at the top of a list of other extremely relevant hits. Like this: \begin{figure} \centering \includegraphics{./images/search-results-perfect.png} \caption{The goal is to return the perfect results to Users searching your site.} \end{figure} The developers of an asset control much about how the asset's information is stored in the search engine (this process is called \emph{indexing}, and how its information is searched and returned in the search results. Developers who dislike how a particular asset behaves in search can use an \emph{Indexer Post Processor} to modify the asset's indexing behavior and how search queries are constructed to look up the assets in Liferay DXP. The Search Results behavior configurable through the UI is covered in this section: \begin{itemize} \item \href{/docs/7-2/user/-/knowledge_base/u/display-settings}{Search Results Display Settings} \item \href{/docs/7-2/user/-/knowledge_base/u/filtering-search-results-with-the-custom-filter-widget}{Filtering Results} \item \href{/docs/7-2/user/-/knowledge_base/u/sorting-search-results-with-the-sort-widget}{Sorting Results} \item \href{/docs/7-2/user/-/knowledge_base/u/search-results-behavior}{Search Results Behavior} \end{itemize} \chapter{Display Settings}\label{display-settings} The Search Results widget's default display is a paginated list. Each list item is a summarized hit to a search query. Click on a specific result to look at it in more detail. Configure display options by opening the Search Results options menu (\includegraphics{./images/icon-app-options.png}) and selecting \emph{Configuration}. \begin{description} \tightlist \item[\textbf{Enable Highlighting}] Highlight the search terms where they appear in the search result's title or summary. \item[\textbf{Display Selected Result in Context}] When an asset is clicked, show it in its native application. For example, if you click on a blog post in the search results, you see where the Blogs Entry is posted in the Blogs application. Note that you're not in the search context after clicking on a search result. When this option is unchecked, the asset displays in an Asset Publisher window while still in the search context. If you have the right permissions, you can even edit the content directly from the Search context. Click the back arrow to return to the search results. \item[\textbf{Display Results in Document Form}] Display results as search \href{/docs/7-2/frameworks/-/knowledge_base/f/search}{documents}. Never use this in production. Developers use this feature to view search responses in their indexed, document-based format. Part of a developer's job when writing search indexers is to convert documents (the objects that get indexed) to the actual object and back again. Thus, developers can see how their objects are being indexed. Once enabled, click the \emph{Details\ldots{}} link below the result summary to expand the result's document view. \end{description} \begin{figure} \centering \includegraphics{./images/search-results-document.png} \caption{Viewing a results document lets you inspect exactly what's being indexed for a particular asset. This is just a small portion of one document.} \end{figure} The next three configurations concern results pagination. \begin{figure} \centering \includegraphics{./images/search-results-pagination.png} \caption{The number of results per page and the URL parameter names used to control pagination behavior are configurable.} \end{figure} \begin{description} \item[\textbf{Pagination Start Parameter Name}] Set the name of the URL parameter for the results page. If the default value \emph{start} is preserved, this URL displays when the User navigates to the second results page after searching for \emph{test}: \begin{verbatim} http://localhost:8080/web/guest/search?q=test&start=2 \end{verbatim} \item[\textbf{Pagination Delta}] Set the number of results to display per results page. Defaults to \emph{20} unless you customized the \texttt{search.container.page.default.delta} property in your \texttt{portal-ext.properties} file. \item[\textbf{Pagination Delta Parameter Name}] Set the name of the URL parameter that stores the Pagination Delta value. This becomes visible in the browser if the User changes the number. If the User selects 10 results per page and searches for \emph{test}, the Search Page is reloaded with this URL: \begin{verbatim} http://localhost:8080/web/guest/search?q=test&delta=10 \end{verbatim} \item[\textbf{Federated Search Key}] If this widget is participating in a search on a non-default index, enter the key of the alternate search index. If not set, the widget participates in the default search, against the default index (\texttt{liferay-{[}comanyId{]}}. This value is usually the name of an application-defined index. \item[\textbf{Fields to Display}] If searching an alternate index using the Federated Search Key configuration, specify what fields to search from that index. \end{description} For further reading, check out how to \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-suggestions}{return suggestions for better search terms} (for example, ``Did you mean\ldots{}'') when not enough results are returned initially. \chapter{Filtering Search Results with the Custom Filter Widget}\label{filtering-search-results-with-the-custom-filter-widget} You often need to exert control over the displayed search results. One viable approach is to develop your own search portlets using the Liferay DXP APIs. That can be overkill if you just want to make a slight modification to how the search is executed, so many of the out-of-the-box search widgets give you this type of control without coding anything (Search Options, Custom Facet, and more). In 7.0, new widgets have been added: Sort and Custom Filter. With Custom Filters, you can contribute queries to the main search query, exerting control over the search results. Make the filter widgets visible or invisible to the search Users, and decide if they're changeable or immutable. To explore all the options you have with the Custom Filter widget, you need one on the page. \section{Adding and Configuring Custom Filters}\label{adding-and-configuring-custom-filters} To get started with Custom Filters, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Add menu (\includegraphics{./images/icon-add-widget.png}) for the page and expand the Widgets section. \item From the Search section, drag a Custom Filter onto the page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/search-custom-filter.png} \caption{A custom filter has no impact until it's configured.} \end{figure} Custom filters can do so many things, it's impossible to list them all. What follows is a widget configuration tour. Separate documentation will be written to provide a how-to demonstration of Custom Filters. \section{Custom Filter Configuration Options}\label{custom-filter-configuration-options} Open the widget Options menu (\includegraphics{./images/icon-app-options.png}) and click \emph{Configuration}. \begin{figure} \centering \includegraphics{./images/search-custom-filter-configuration.png} \caption{Once the Custom Filter is added to the page, mold it like soft clay into the beautiful sculpture you've envisioned.} \end{figure} \begin{description} \tightlist \item[\textbf{Filter Field (text)}] Most often, filters operate on a specific field. Set the name of the indexed field to be filtered (for example, \texttt{title}). You won't need this if the Filter Query Type is set to a type that doesn't require a field, such as \emph{Regexp}. \end{description} \begin{quote} The Query String and Script queries do not require a Filter Field to be set. All other queries require at least one field. \end{quote} \begin{description} \tightlist \item[\textbf{Filter Value (text)}] For most filters, you must enter a text value here that specifies the text to apply the filter on in the specified field (for example, set a \emph{Match} query to the text \emph{street} on the \texttt{title\_en\_US} field). Some Filter Query Types require special notation, as in the case of the \emph{Regexp} filter. \item[\textbf{Filter Query Type (select list)}] Select the query type to filter results by. Available types include Bool, Exists, Fuzzy, Match, Match Phrase, Match Phrase Prefix, Multi Match, Prefix, Query String, Regexp, Script, Simple Query String, Term, Wildcard. To learn more about these queries, visit the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl.html}{Elasticsearch documentation}. \item[\textbf{Occur (select list)}] Set the occurrence type for the query being contributed to the search. Options include Filter, must, must\_not, and should. To understand each type, see the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-bool-query.html}{Elasticsearch documentation}. \item[\textbf{Query Name (text)}] Set the name of the contributed query. This is unnecessary unless this filter acts as a parent query to another filter that contributes child clauses; in that case set this filter's Query Name as the child filter's Parent Query Name. This parent/child behavior is only available for filters of type Bool. \item[\textbf{Parent Query Name (text)}] When contributing a child clause to a Bool query, set this to match the Query Name configured in the parent Custom Filter widget. Otherwise, leave it blank. \item[\textbf{Boost (number)}] \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-term-query.html\#term-field-params}{Boost} the score of the results matching this query. Specify any whole or decimal number here that makes sense. If you always want results matching this at the top, set the Boost value really high (e.g., \emph{1000}). \item[\textbf{Custom Heading (text)}] Enter the heading to display for this filter. If not set, the Filter Field's value is displayed. \item[\textbf{Custom Parameter Name (text)}] Specify a URL parameter name for the filter. If not set, the Filter Field's value is used. \item[\textbf{Invisible (boolean)}] If checked, the widget is invisible to regular users. The Filter Value from the configuration is applied by default, but users can still filter for other values via URL Parameter. Don't worry, you can shut that down if you need to with the Immutable setting (see below). \item[\textbf{Immutable (boolean)}] Enable this to ensure that the Filter Value cannot be changed by regular users. The widget becomes invisible to them \emph{and} filter values set via URL parameters are not accepted. The Filter Value set in the widget configuration is applied at all times (unless it's disabled). \item[\textbf{Disabled (boolean)}] If checked, the query is ignored and doesn't participate in searches. This gives you a quick way to stop the filter, but keep the configuration so it can be re-enabled later. \item[\textbf{Federated Search Key (text)}] Enter the key of an alternate Search this widget is participating on. If it's set, be aware that the default Liferay DXP index isn't searched at all. If not set, this widget participates on the default search. Values in this field typically match the name of an application-defined index. \end{description} There are many use cases you can satisfy by incorporating Custom Filters into your search page. Two demonstrative articles are planned to show you some of the filtering capabilities at your disposal: \begin{itemize} \item \emph{Refine to One} (or \emph{Needle in a Haystack}) will show you how to add user-operated filters to the page so results can be refined down to just the result they were looking for. \item \emph{Complex filtering} shows you some more advanced filters and how they work. \end{itemize} Check out the Custom Filter and see what it adds to your search page. \chapter{Sorting Search Results with the Sort Widget}\label{sorting-search-results-with-the-sort-widget} The Sort widget gives Users configurable control over the order of returned results: no code necessary. Add it to a page and begin sorting results. By default, results are sorted by the \href{https://www.elastic.co/guide/en/elasticsearch/guide/master/scoring-theory.html}{relevance score} returned by the search engine. Users can choose from one of the out-of-the-box alternative sorting strategies, or one configured by a search administrator. Out of the box, order results in these ways as an alternative to relevance sorting: \begin{itemize} \tightlist \item alphabetically by Title \item by the Modified date (oldest first) \item by the Create date (newest first) \item by the Create date (oldest first) \item alphabetically by the User that created each matching asset \end{itemize} If the out-of-the-box alternatives aren't enough, an administrator can create additional sort options from the widget's configuration. It's also possible to delete unwanted sort options from the widget. \section{Adding and Configuring the Sort Widget}\label{adding-and-configuring-the-sort-widget} To get started with the Sort widget, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Add menu (\includegraphics{./images/icon-add-widget.png}) for the page and expand the Widgets section. \item From the Search section, drag a Sort widget onto the page. \end{enumerate} \begin{figure} \centering \includegraphics{./images/search-sort.png} \caption{Users can re-order search results with the Sort widget.} \end{figure} \section{Configuring the Sort Widget}\label{configuring-the-sort-widget} Three things can be done from the Configuration screen: \begin{itemize} \tightlist \item Editing existing Sort options \item Deleting options \item Adding options \end{itemize} \begin{figure} \centering \includegraphics{./images/search-sort-configuration.png} \caption{From the Sort widget's configuration, add, edit, or remove Sort options.} \end{figure} To access the widget configuration screen, open the widget Options menu (\includegraphics{./images/icon-app-options.png}) and click \emph{Configuration}. Each Sort option has two fields: \emph{Label} and \emph{Field}. \begin{description} \tightlist \item[\textbf{Label}] Set the displayed label for the type of sort being configured. \item[\textbf{Field}] The \texttt{fieldName} of the indexed field to sort. Most of the time this is a \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/keyword.html}{keyword} field. Other acceptable options are \texttt{date} and any \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/number.html}{numeric datatype}. There's even a way for persistent search administrators to coerce \texttt{text} fields into behaving with the Sort widget. Keep reading for details. \end{description} \subsection{Finding Sortable Fields}\label{finding-sortable-fields} To find the fields available for use in the Sort widget, Users with the proper permissions can navigate to \emph{Control Panel} → \emph{Configuration} → \emph{Search}. From there, open the Field Mappings tab and browse the mappings for each index. Scroll to the \texttt{properties} section of the mapping, and find any \texttt{keyword} field, \texttt{date} field, or a field with any numeric datatype. The \texttt{type} field is instructive: \begin{verbatim} "type" : "keyword" "type" : "date" "type" : "long" \end{verbatim} What if you really need to sort by a \texttt{text} field? You can do it by adding a new version of the field to the index, with the type \texttt{keyword}. Don't worry; you don't need to code anything to do this. From the field mappings screen mentioned above, look at the \texttt{firstName} field in the index called \texttt{liferay-{[}companyID{]}}. In fact, look at the next entry as well: \begin{verbatim} "firstName" : { "type" : "text", "store" : true }, "firstName_sortable" : { "type" : "keyword", "store" : true }, \end{verbatim} There's a corresponding field with the suffix \texttt{\_sortable}, and of the correct type for sorting (\texttt{keyword}). How did that get there? Via the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Lucene\%20Search}{portal property} \begin{verbatim} index.sortable.text.fields=firstName,jobTitle,lastName,name,screenName,title \end{verbatim} All the text fields listed here have a \texttt{fieldName\_sortable} counterpart created automatically in the index. To add more, copy this value into a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file into your Liferay Home folder, add any new field names you need to sort by, and restart the server. \subsection{Adding New Sort Options}\label{adding-new-sort-options} To sort by the new field, use the plus symbol below any option's \emph{Field} configuration make sure to use the \texttt{fieldName\_sortable} version of the field in the widget configuration. To add a new sort option that's already of the proper datatype, use the plus symbol below any option's \emph{Field} configuration and fill in the fields. The order of options here in the configuration screen matches the order Users see in the select list while configuring the widget for their search. \subsection{Editing and Deleting Sort Options}\label{editing-and-deleting-sort-options} To edit an existing option, edit the text in its configuration section. To delete an existing option, use the minus symbol below its \emph{Field} configuration. \section{Controlling the Sort Order}\label{controlling-the-sort-order} To control the order for the sort option, add a plus or minus symbol after the \texttt{fieldName}. Look how it's done for the existing sort options labeled \emph{Created} and \emph{Created (oldest first)} to understand how it works: \textbf{Label:} \emph{Created} \textbf{Field:} \texttt{createDate-} The \texttt{-} sign following the field name indicates that the order is \emph{descending}. Choosing to sort with this brings search results created most recently to the top of the list. \textbf{Label:} \emph{Created (oldest first)} \textbf{Field:} \texttt{createDate+} The \texttt{+} sign following the field name indicates that the order is \emph{ascending}. Choosing to sort with this brings the oldest (by creation date) results to the top of the list. \chapter{Search Results Behavior}\label{search-results-behavior} The previous article covered ways to display search results. This article covers these additional Search Results concepts and configurations: \begin{itemize} \tightlist \item \hyperref[filtering-results-with-facets]{Filtering search results with facets} \item \hyperref[search-results-relevance]{Understanding search results relevance} \item \hyperref[permissions-and-search-results]{The effect of permissions on search results} \item \hyperref[search-and-staging]{Search results in the staging environment} \item \hyperref[result-summaries]{Search results summaries} \item \hyperref[highlighting]{Search results term highlighting} \end{itemize} \section{Filtering Results with Facets}\label{filtering-results-with-facets} Results are filtered using \emph{facets}. Most users have encountered similar filtering capabilities in other applications, particularly during commerce activities. Users enter a search term, are presented with a list of results and search facets, which you can think of as buckets that group results together if they share a common characteristic. Administrators can configure facets. Read about \href{/docs/7-2/user/-/knowledge_base/u/facets}{configuring facets} to learn more. \section{Search Results Relevance}\label{search-results-relevance} The search engine decides which results appear at the top of the list using the concept of \emph{relevance}. Relevance is a score calculated by the search engine. There are numerous factors contributing to the total score of a returned document, and all of the implementation details of how relevance scoring works are algorithms provided by the \href{https://www.elastic.co/guide/en/elasticsearch/reference/current/relevance-intro.html\#relevance-intro}{search engine}. \section{Permissions and Search Results}\label{permissions-and-search-results} Users lacking \href{/docs/7-2/user/-/knowledge_base/u/roles-and-permissions}{VIEW permission} on an asset don't see it in the search results. A logged in User with the Site Administrator role likely sees more search results than a guest User to the site. In the background, there are two rounds of permissions checks. The first permissions check, \emph{pre-filtering}, happens in the search engine's index. It's faster than checking database permissions information, but occasionally the search index can have stale permissions information. To ensure the search engine's index has correct, up-to-date permissions information, a second, last-second permissions check, \emph{post-filtering}, is performed on the results prior to their display. \section{Initial Permissions Checking}\label{initial-permissions-checking} The first round of search results permissions filtering adds filter clauses to the search query. This ensures that results return from the search engine pre-filtered, containing results the current User can view. This initial permission checking is configurable at \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Search} → \emph{Permission Checker}. It includes two system level settings to configure how search processes User permissions. \begin{description} \tightlist \item[\textbf{Include Inherited Permissions}] Ignore this setting. It's deprecated, no longer used anywhere, and will be removed in a future release. \item[\textbf{Permissions Term Limit}] Limits the number of permission search clauses added to the search query before this level of permission checking is aborted. Permission checking then relies solely on the final permission filtering described below. \end{description} The only reason to limit permissions terms is performance. Users with administrative access to lots of sites and organizations generate many permissions terms added to the query. Too many terms in a query can make the search engine time out. \section{Final Permissions Checking}\label{final-permissions-checking} A final round of permission checking happens prior to presenting results in the UI. For example, the User searches for \emph{liferay}, and the search engine returns all relevant forum posts. As the Search Results iterates through the list of relevant forum posts, it performs one last permission check of the post to ensure the User can view the post and its categories. If a matching forum post exists in a category the User doesn't have permission to view, it isn't displayed in the list of search results. This final round of permission checking is configurable at \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Search} → \emph{Default Search Result Permission Filter}. It includes two settings: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The first setting, Permission Filtered Search Result Accurate Count Threshold, specifies the maximum number of search results to permissions-filter before results are counted. A higher threshold increases count accuracy, but decreases performance. Since results in the currently displayed page are always checked, any value below the search results pagination delta effectively disables this behavior. \item The second setting, Search Query Result Window Limit, sets the maximum batch size for each permission checking request. . This is again impacted by pagination. For example, if there are 100 results per page, and a User wants to jump all the way to page 200 of the search results, all results between page one and 200 must be checked to ensure the User has permission. That's 20,000 results to permissions check. Doing this in one trip to and form the search engine can result in performance issues. Set the maximum batch size for each permission checking request. \end{enumerate} \section{Search and Staging}\label{search-and-staging} With \href{/docs/7-2/user/-/knowledge_base/u/staging-content}{staging}, content is placed first in a preview and testing environment before being published for consumption by end Users (on the live site). Content added to the search index is marked so that the search API can decipher whether an item is live or not. In the live version of the site, only content that's marked for the live site is searchable. In the staged version of the site, all content---live or staged---is searchable. \section{Result Summaries}\label{result-summaries} A result summary includes the information from a document that the asset's developer felt is most useful to end Users searching for the asset. Each asset can have different fields included in the summary. For assets with text content, a common summary format includes the \emph{title} and some of the \emph{content}, with title displayed first. The asset type always appears on the second line, and a snippet of the content that matches the search term is on the last line. Assets without content fields, like Documents and Media documents, display the description instead. Users are different. Only the User's full name and the asset type (User) appear in User result summaries. \begin{figure} \centering \includegraphics{./images/search-results-user.png} \caption{User summaries contain only the User's full name.} \end{figure} For assets that contain other assets (Web Content and Documents \& Media folders) or whose content is not amenable to display (Dynamic Data List Records and Calendar Events), it makes more sense to display the title, asset type, and description in results summaries. There'd never be anything in a content field for these assets. \begin{figure} \centering \includegraphics{./images/search-results-folder.png} \caption{Documents and Media and Web Content folders include titles and descriptions in their summaries.} \end{figure} The asset developer determines which fields are summary-enabled, but there's logic invoked at search time that determines precisely the part of the summary fields to display. For example, a \texttt{content} field can have a lot of text, but the summary doesn't show it all. Instead, it shows a relevant snippet of the field's text. If the keyword searched for is present in the summary field, that portion of the field is used in the summary. In addition, the matching keyword is highlighted in the summary. \section{Highlighting}\label{highlighting} By now you've probably noticed that search terms appearing in the summary are highlighted by default. If this is undesirable, disable it in the widget configuration screen. \begin{figure} \centering \includegraphics{./images/search-results-highlight.png} \caption{Some document summaries have lots of highlights if the search term matches text that appears in the summary.} \end{figure} Highlighting is a helpful visual cue that hints at why the result is returned, but beware. A hit can score well and thus be returned near the top of the results, without having any highlights in the summary. That's because not all indexed fields appear in the summary. Consider a User named Arthur C. Clarke. He has an email address of \emph{acc@authors.org}, which is searchable. Because results summaries for Users only contain the full name of the User, searching for Mr. Clarke by his email address returns the User, but no term is highlighted. \begin{figure} \centering \includegraphics{./images/search-results-no-highlight.png} \caption{Results that match the search term won't always have highlights.} \end{figure} There are additional cases where search results don't have highlighting. \chapter{Search Insights}\label{search-insights-1} \textbf{{[}Feature intended for testing and development only{]}} \textbf{{[}Works with Elasticsearch only{]}} Add the Search Insights Widget to the Search Page to inspect two things: \begin{itemize} \item The query string that's constructed by the back-end search code when the User enters a keyword \item The response string returned from the search engine \end{itemize} \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 In 7.0, the Insights widget now adds the response string to the widget's output, and the \emph{Enable Score Explanation} option (enabled by default) prints a relevance score explanation for each returned result. When a search query is processed, results are returned. The concept of \emph{Relevance} determines how well results match the query. The Score explanation for returned search documents helps clarify seemingly odd results, letting you adjust the relevancy scoring process by making matches in certain fields count for more (\emph{boosting} the fields is the term for this). \section{Inspecting The Search Query String}\label{inspecting-the-search-query-string} To see the Search Insights widget in action, navigate to a Search Page in your test server and add it from the Add menu (\includegraphics{./images/icon-add-widget.png}). \begin{figure} \centering \includegraphics{./images/search-insights-default.png} \caption{The Search Insights widget is helpful during testing and development.} \end{figure} Once you search for keywords that return Search Results, the Search Insights portlet displays the returned query string in all its glory. \begin{figure} \centering \includegraphics{./images/search-insights-test-search.png} \caption{The full query string isn't for the faint of heart. This example is clipped to spare the reader.} \end{figure} \section{Explaining Search Results}\label{explaining-search-results} To enable or disable the Explain option, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Search Insight widget's Configuration screen. \item There's just one option: \emph{Enable Score Explanation}. It's a boolean field that's enabled by default. De-select it to disable the explanation of each result's relevance score. \end{enumerate} Under the hood, the Explain option in the Search Insights widget is exposing an Elasticsearch API: \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-explain.html}{Explain}. See the Elasticsearch documentation for more details. Here's an abbreviated portion of the scoring explanation for the Search Document of the Test Test User when the searched keyword was \emph{test}: \begin{verbatim} ```json _explanation":{ "value":9.461341, "description":"sum of:", "details":[ { "value":9.461341, "description":"sum of:", "details":[ { "value":1.0, "description":"emailAddress:*test*", "details":[ ] }, { "value":5.0, "description":"userName:*test*^5.0", "details":[ ] }, { "value":0.72928625, "description":"sum of:", "details":[ ... { "value":1.0027686, "description":"sum of:", "details":[ ... { { "value":0.72928625, "description":"sum of:", "details":[ ... { "value":1.0, "description":"screenName:*test*", "details":[ ] } ] }, ... ] }}]} ``` \end{verbatim} Now you're able to see the entire query string, the response string, and how each returned Search Document was scored. \chapter{Searching for Localized Content}\label{searching-for-localized-content} Liferay DXP supports setting a virtual instance-wide \href{/docs/7-2/user/-/knowledge_base/u/more-platform-section-instance-settings\#localization}{default language} and setting a In addition, many out of the box assets \href{/docs/7-2/user/-/knowledge_base/u/other-content-options\#localizing-content}{support translation}. How an asset's fields are indexed in the search engine plays an important role in the end user's experience. Not all assets are indexed in a way that supports searching in a language other than the default language. Even assets that are translatable might not support searching for the content in that language. In short, these assets contain text fields supporting localized search: \noindent\hrulefill Asset \textbar{} Fields \textbar{} Localized Search Approach \textbar{} Content Page \textbar{} \texttt{title} \textbar{} 2 \textbar{} Documents and Media Document \textbar{} \texttt{content} \textbar{} 3 \textbar{} Calendar \textbar{} \texttt{name}, \texttt{description} \textbar{} 1 \textbar{} Calendar Booking \textbar{} \texttt{title}, \texttt{description} \textbar{} 1 \textbar{} Dynamic Data List Record \textbar{} \texttt{content} \textbar{} 1 \textbar{} Form Record \textbar{} \texttt{content} \textbar{} 1 \textbar{} Web Content Article \textbar{} \texttt{title}, \texttt{content}, \texttt{description} \textbar{} 1 \textbar{} Asset Category\emph{ \textbar{} \texttt{title}, \texttt{description} \textbar{} 1 \textbar{} Asset Tag} \textbar{} \texttt{assetTagNames} \textbar{} 1 \textbar{} Wiki Page \textbar{} \texttt{title}, \texttt{content} \textbar{} 2 \textbar{} Blogs Entry \textbar{} \texttt{content}, \texttt{title} \textbar{} 2 \textbar{} Message Boards Message \textbar{} \texttt{title}, \texttt{content} \textbar{} 2 \textbar{} \noindent\hrulefill * Asset tags and categories don't have dedicated documents in the index. Instead, their indexed fields are added to the tagged or categorized asset's document. There are three localized search approaches represented in the table: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Fully Localized: The asset itself is localizable (in other words, translatable), and its translated fields are indexed into their respected locales. \item Fully Localized for Search: Even though the asset itself is not localizable/translatable, its fields are indexed into \emph{all} the supported locales in the site. This is a new approach, starting with 7.0. \item Site-Localized for search: The asset's fields are indexed with the site's locale appended. \end{enumerate} There are also assets with text fields and no localization support, meaning that they always index the plain field, without a locale appended (e.g., \texttt{title} is not localized, but \texttt{title\_en\_EN} is localized). That means they'll always be analyzed by the default language analyzer, and do not support localized search in any capacity. \section{What is Localized Search?}\label{what-is-localized-search} In localized search, fields are indexed with locale information appended (for example, \texttt{en\_US} for English, making a localized title field indexed as \texttt{title\_en\_US}). It's then passed to the proper \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/analysis-lang-analyzer.html}{language analyzer} in the search engine so that the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/analysis.html}{analysis} process is performed properly. Each localization approach is covered below. \section{Fully Localized}\label{fully-localized} Fully localized search works like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item One or more of an asset's fields are localizable in the user interface and database (the locale is appended based on the asset creator's language selection). \item The fields are indexed with the appended locale and analyzed with the corresponding language analyzer. \item At search time, properly indexed and analyzed content is returned according to search engine's relevance algorithms. \end{enumerate} This is the ideal approach for assets that support translation of some or all fields outside of the search context. \section{Fully Localized for Search}\label{fully-localized-for-search} Assets fully localized for search work like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The asset's fields are not localizable in the user interface or database. \item For at least one text field being indexed, the asset has indexed localized fields for every locale available in the site. \item At search time the result document is returned regardless of the search locale, because the content is available in all locales of the site. \end{enumerate} \section{Site-Localized Search}\label{site-localized-search} Site-localized search works like this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item The asset's indexed fields are appended with the locale of the site (set in Site Settings) and analyzed with the corresponding language analyzer. \item If the site language is changed, reindexing is required to append the proper site locale to the indexed fields and analyze with the corresponding language analyzer. \item At search time, if content exists matching the language of the site, it's properly returned according to the search engine's algorithms. \end{enumerate} Not all assets support localized search, however. Refer to the table at the beginning of this article for which assets and fields are localized for search. \section{Assets Supporting Localized Search}\label{assets-supporting-localized-search} Whether an asset supports localized search depends on how the asset was indexed in the search engine. At this time, no cohesive pan-asset approach to indexing assets for localized search exists. Localized search support is currently limited to the following assets: \section{Web Content Articles:}\label{web-content-articles} \begin{itemize} \tightlist \item The \texttt{title}, \texttt{content}, and \texttt{description} fields for each Web Content Article support fully localized search. \end{itemize} \noindent\hrulefill \begin{verbatim} **Note:** In 7.0 the default (non-localized) version of these fields are not indexed for Web Content Articles. Therefore, any custom `IndexerPostProcessor`, `ModelDocumentContributor` or `QueryPreFilterContributor` relying on the presence of fields `title`, `content` and `description` must be updated to use the localized version (e.g., `title_en_US`). \end{verbatim} \noindent\hrulefill \begin{itemize} \tightlist \item At search time, matching results (with any locale appended) can be returned. \end{itemize} \section{Categories:}\label{categories} \begin{itemize} \item The \texttt{name} and \texttt{description} fields support fully localized search. \item At search time, matching results (with any locale appended) can be returned. \end{itemize} \section{Documents and Media File Entries:}\label{documents-and-media-file-entries} \begin{itemize} \item The \texttt{content} field (which contains the content of an uploaded file) supports site-localized search. \item No other fields are indexed with a locale. This means they're always analyzed using the default language analyzer. \end{itemize} \section{Dynamic Data Mapping Fields:}\label{dynamic-data-mapping-fields} \begin{itemize} \item Dynamic Data Mapping (DDM) Fields include all form fields created in the Forms application and all fields created in Dynamic Data List Data Definitions and Web Content Structures. \item DDM Fields support fully localized search, with the exception that results can only be returned in the current display locale where the search is taking place. \end{itemize} \section{Examples}\label{examples} To see localized search in action, refer to the examples below. \section{Fully Localized Search for Web Content Articles}\label{fully-localized-search-for-web-content-articles} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a Basic Web Content article in English: \begin{itemize} \tightlist \item Title: \emph{What time is it?} \item Summary: \emph{It's soccer time!} \item Content: \emph{That's right, it's time for soccer. The 2018 World Cup is behind us, and teams all over the US are gearing up for soccer season. It's never too early to start practicing.} \end{itemize} \item Add a second article in English: \begin{itemize} \tightlist \item Title: \emph{What is the best soccer team ever?} \item Summary: \emph{There are many good teams? Which is the best?} \item Content: \emph{Here are the 10 best teams in the world: 1. The Lunar Resort's Club Team, Waxing Crescent FC\ldots{}} \end{itemize} \item Add a Portuguese (\emph{pt-BR}) translation for each field of the second article: \begin{itemize} \tightlist \item Title: \emph{Qual time de futebol é o melhor de todos os tempos?} \item Summary: \emph{Existem muitas boas equipes. Qual é o melhor?} \item Content: \emph{Aqui estao as 10 melhores equipes do mundo: 1. Selecao brasileira de Futebol 2. O time do Resort Lunar, Waxing Crescent FC\ldots{}} \end{itemize} \item Find a search bar widget and enter \emph{time} as the keyword. The first article is returned, and so is the appropriate translation of the article about soccer teams (because \emph{time} in Portuguese translates to the English word \emph{team}). Note that if your search context is English, searching for the word \emph{time} returns the English translation of the Web Content, which does not itself contain the matched keyword. The Portuguese translation contains the matching keyword, while the English translation is returned for English speaking search users. \end{enumerate} In fully localized search, fields are appended with the proper locale, and even fields with a locale other than the User's display context are returned if they contain matches to the searched keyword. \section{Site-Localized Search for Documents and Media}\label{site-localized-search-for-documents-and-media} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a text file named \texttt{search-test.txt} with the following contents: \begin{verbatim} Meu time de futebol favorito é o melhor \end{verbatim} \item Upload it as a Basic Document to the Documents and Media application. \item If your site's language is currently set to English, adding this file appends its content field with the \emph{en-US} locale. \item Search in the site for the keyword \emph{time}. \begin{figure} \centering \includegraphics{./images/search-site-localized1.png} \caption{Even though the content of this DM File is written in Portuguese, it was appended with the \emph{en} locale, so it's searchable in an English language site.} \end{figure} The file is returned because even though the text in the file is Portuguese, the locale appended to its \emph{content} field is for English. \item Now change the Site's default language to \emph{Portuguese (Brazil)}. Use Site Settings → Languages to accomplish this. \item Now search for \emph{time} in the site, and the document is not returned in the results, because the search is looking for the \emph{pt} locale. \begin{figure} \centering \includegraphics{./images/search-site-localized2.png} \caption{The uploaded DM File doesn't appear when the site language is changed, because only fields with the site's locale are searched.} \end{figure} \item Now go to Control Panel → Configuration → Search, and click \emph{Execute} next to \emph{Reindex all search indexes.} \item Search for \emph{time} in the site's Search Bar again, and now the document is returned in the results, because the content field's locale was changed from \emph{en\_US} to \emph{pt\_BR} when reindexed. \begin{figure} \centering \includegraphics{./images/search-site-localized3.png} \caption{Once the field is reindexed with the site's locale, it can be returned as a search result in the site.} \end{figure} \end{enumerate} If an asset supports site-localized search, its fields must be reindexed after the site language is changed in order to be returned as search results. \chapter{Configuring Search}\label{configuring-search} \emph{Configuring Search} could mean lots of different things: \begin{itemize} \tightlist \item System scoped search configuration \item Reindexing to make sure the search indexes are current with the database \item Tweaking the search widgets added to pages \item Creating new Search Pages \item Configuring the connectors that let Liferay DXP and the search engine communicate \end{itemize} In fact, \emph{Configuring Search} means all those things. This is a high level overview of what search behavior is configurable out of the box, and importantly, \emph{where} to find search configuration options. \section{System Scoped Search Configuration}\label{system-scoped-search-configuration} System scoped search configurations are primarily found in \href{/docs/7-2/user/-/knowledge_base/u/system-settings}{System Settings}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings}. \item Click the \emph{Search} category under the Platform section. Alternatively, search for \emph{Search}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/search-category-system-settings.png} \caption{There are numerous system scoped entries for search in System Settings.} \end{figure} These system scoped configurations are available in System Settings: \section{Default Keyword Query}\label{default-keyword-query} The Default Keyword Query entry contains one setting: \texttt{disabledEntryClassNames}: The \texttt{DefaultKeywordQueryContributor} code automatically adds \texttt{description}, \texttt{userName}, and \texttt{title} fields to the keyword search query. Specify the entry class names \texttt{DefaultKeywordQueryContributor} should ignore. \section{Default Search Result Permission Filter}\label{default-search-result-permission-filter} The Default Search Result Permission Filter entry allows configuration of \emph{post-filtering permission checking} (database permission checking that occurs after the results are returned from the search index). Read \href{/docs/7-2/user/-/knowledge_base/u/search-results-behavior\#final-permissions-checking}{here} for more information on these settings: \begin{itemize} \item \texttt{permissionFilteredSearchResultAccurateCountThreshold} \item \texttt{searchQueryResultWindowLimit} \end{itemize} \section{Index Status Manager}\label{index-status-manager} The Index Status Manager entry has one setting: \texttt{indexReadOnly}: Suspends all indexing operations and writes to the search engine. Searches return only the documents already indexed. This is useful for speeding up large data imports, but it should be disabled and a full re-index executed once the import is finished. \section{Indexer Writer Helper}\label{indexer-writer-helper} The Index Writer Helper entry contains only one valid entry. The second, \texttt{indexReadOnly}, is deprecated and unused, so setting it has no effect. Use \texttt{indexReadOnly} from the \hyperref[index-status-manager]{Index Status Manager} instead. \texttt{indexCommitImmediately}: When \emph{true} (the default), each write request forces the search engine to refresh the index reader, potentially flushing transactions to disk. This may negatively impact search engine performance. The default behavior is to commit immediately for index writing on individual assets (e.g.~add blog, update blog) but delay commits for bulk index writing operations (e.g.~index all users, index all form entries) until all entries have been sent to the search engine. Setting this to false changes the behavior for individual index operations, and may cause applications like Asset Publisher to exhibit a delayed response when showing newly added content. See the \href{https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html}{Elasticsearch documentation} for more information. \section{Index Registry}\label{index-registry} Configure the buffering of index requests: \texttt{buffered}: Disable or configure the buffering of indexing requests. To stop the buffering of index requests, choose \emph{Disabled}. \texttt{bufferedExecutionMode}: Allows administrators to select a different \texttt{IndexerRequestBufferExecutor}, used to execute a \texttt{IndexerRequest}. One implementation of the executor is provided out of the box (\emph{DEFAULT}). When a developer creates and deploys a new \texttt{IndexerRequestBufferExecutor} implementation, one of the properties they provided is a \texttt{buffered.execution.mode} which makes the implementation selectable from System Settings. \texttt{maximumBufferSize}: If buffering is enabled, set the Maximum Buffer Size so that any additional indexing requests are executed immediately. \texttt{minimumBufferAvailabilityPercentage}: When the capacity of the buffer has only the specified percent of space left, the existing requests in the buffer are executed in one batch and removed from the buffer. \section{Index Query Preprocessor}\label{index-query-preprocessor} This entry has one repeatable property (use array syntax if you're defining via \href{/docs/7-2/user/-/knowledge_base/u/creating-configuration-files}{OSGi configuration file}): \texttt{fieldNamePatterns}: Fields with names matching the patterns set here are treated as non-analyzed keyword fields. Instead of scored full text queries, matching is performed by non-scored wildcard queries. This is a resource intensive operation that degrades search engine performance as indexes grow larger. For substring matching, relying on the \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/analysis-ngram-tokenizer.html}{ngram tokenizer} usually performs better. \section{Reindex}\label{reindex} This entry contains only one property: \texttt{indexingBatchSizes}: Set the number of documents indexed per batch for model types that support batch indexing. Defaults to 10000. For models with large documents, decreasing this value may improve stability when executing a full re-index. \section{Engine Helper}\label{engine-helper} This entry has one repeatable property (use array syntax if you're defining via \href{/docs/7-2/user/-/knowledge_base/u/creating-configuration-files}{OSGi configuration file}): \texttt{excludedEntryClassNames}: Exclude an asset type from being searched in the catchall query constructed for the Search application. For example, fields of the Organization asset must be indexed to be searchable from the Users and Organizations application, but should not be searched in the Search application. Thus, Organizations are added to \texttt{excludedEntryClassNames}. \section{Permission Checker}\label{permission-checker} Configure \emph{pre-filtering permission checking} (permission checking on the search index) behavior. See \href{/docs/7-2/user/-/knowledge_base/u/search-results-behavior\#initial-permissions-checking}{here} for more information on these properties: \begin{itemize} \item \texttt{includeInheritedPermission} \item \texttt{permissionTermsLimit} \end{itemize} \section{Title Field Query Builder}\label{title-field-query-builder} Configure how search responds to matches on the Title field of a document. \textbf{Exact Match boost:} Give an additional boost when searched keywords exactly match the \texttt{title} field of a document. \textbf{Maximum Expansions:} Limit the number of documents to return when matching searched keywords to the \texttt{title} field as a phrase prefix. See Elasticsearch's \href{https://www.elastic.co/guide/en/elasticsearch/reference/7.x/query-dsl-match-query-phrase.html}{Match Phrase Query documentation} for more information. \section{Elasticsearch 6}\label{elasticsearch-6} Configure the connection between Liferay DXP and Elasticsearch 6. See \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector}{here} for more information on these properties: \begin{itemize} \tightlist \item \texttt{clusterName} \item \texttt{operationMode} \item \texttt{indexNamePrefix} \item \texttt{numberOfIndexReplicas} \item \texttt{numberOfIndexShards} \item \texttt{bootstrapMlocakAll} \item \texttt{logExceptionsOnly} \item \texttt{retryOnConflict} \item \texttt{zenDiscoveryUnicastHostsPort} \item \texttt{networkHost} \item \texttt{networkBindHost} \item \texttt{networkPublishHost} \item \texttt{transportTcpPort} \item \texttt{transportAddresses} \item \texttt{clientTransportSniff} \item \texttt{clientTransprtIgnoreClusterName} \item \texttt{clientTransportPingTimeout} \item \texttt{clientTransportNodesSamplerInterval} \item \texttt{HttpEnabled} \item \texttt{HttpCrsEnabled} \item \texttt{HttpCorsAllowOrigin} \item \texttt{HttpCorsConfigurations} \item \texttt{additionalConfigurations} \item \texttt{additionalIndexConfigurations} \item \texttt{overrideTypeMappings} \item \texttt{synchronizedSearch} \end{itemize} \section{Search Web}\label{search-web} This entry contains one property: \texttt{classicSearchPortletInFrontPage}: Revert the default search experience from using the new Search Widgets to the classic Search Portlet that was standard in past releases. See \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages\#legacy-search-experience}{here} for more information. \section{Search Administration}\label{search-administration} In \emph{Control Panel} → \emph{Configuration} → \emph{Search} there are two administrative UIs: Index Actions and Field Mappings \subsection{Index Actions}\label{index-actions} In Index Actions, re-index one of these: \begin{verbatim} - All indexable assets - An individual indexable asset - All spell check indexes \end{verbatim} \subsection{Field Mappings}\label{field-mappings-1} The Field Mappings tab shows you all field mappings that are effective in the system, by index. Currently, you can view the mappings, copy them, zoom in or out, and view them with a dark theme. Look for added functionality to this UI in future versions. \section{Portal Properties}\label{portal-properties} Portal properties are system scoped configurations as well. The \href{https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html\#Lucene\%20Search}{Lucene Search} portal properties configure low level search behavior. Review the properties and their descriptions and determine if they apply to your search requirements. \section{Site Scoped Search Configuration}\label{site-scoped-search-configuration} Search isn't configurable at the Site Scope by the strict definition of \href{/docs/7-2/user/-/knowledge_base/u/setting-up\#configuration-scope}{Site Scoped Configuration}. However, \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{Search Pages} influence site-specific search behavior. Commonly, Search Pages contain search widgets configured to search for all content within a particular Site. In addition, the Header Search (the Search Bar embedded in every Site page by default), whether populated by the new Search Bar widget or the legacy Search portlet, is Site scoped. Only one instance of the Header Search application exists per Site, and configuring it in one page context configures it for the entire Site. Because of the modularity of Search, there are some important configuration nuances to be aware of when using the new Search widgets: \begin{itemize} \item If the Header Search uses the Search Bar widget, its configuration always requires a \emph{destination page} to be set, where Users are redirected to complete their search activity, interacting with the other Search widgets (Results, Facets, Suggestions etc.). \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{Search destination pages} are ordinary pages holding the Search widgets. You can have as many pages with Search widgets across the Site as you want. \item Unlike the legacy Search portlet, the new Search Bar widget is instanceable, so one page can contain multiple Search Bar widgets configured differently. All Search Bar instances must point to a Search Page to be effective. \item \textbf{Important}: if the destination Search Page has a Search Bar widget instance besides the embedded Header Search, the configurations of the Header Search take precedence over the page's widget instance. Conversely, searching from a Search Bar widget instance on other pages honors their configurations, even if they differ from the Header Search configuration. \end{itemize} See the documentation on \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#configuring-the-search-bar}{configuring of a Search Bar} for more information. \section{Widget Scoped Search Configuration}\label{widget-scoped-search-configuration} Several search widgets are available, and each one has its own configuration options: \begin{description} \tightlist \item[\textbf{Search Results}] Configure how search results are displayed. Read \href{/docs/7-2/user/-/knowledge_base/u/search-results}{here} for more information. \item[\textbf{Search Bar}] Configure the behavior of how search keywords are processed. See \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#configuring-the-search-bar}{here} for more information. \item[\textbf{Search Facets}] Configure each facet's behavior and URL parameters. See \href{/docs/7-2/user/-/knowledge_base/u/facets}{here} for more information. \item[\textbf{Search Options}] This is a special case, where configuring this widget defines page scoped behavior. Add the Search Option widget to a page and define two booleans for the Search Page: \end{description} \begin{itemize} \item Allow Empty Searches: By default, failure to enter a keyword returns no results. Enabling this ensure that \emph{all} results are returned when no keyword is entered in the Search Bar. \item Basic Facet Selection: By default, facet counts are recalculated after each facet selection. Enable this to turn off facet recounting. \end{itemize} \begin{description} \tightlist \item[\textbf{Search Suggestions}] Suggest better queries and spell check queries. See \href{/docs/7-2/user/-/knowledge_base/u/searching-for-assets\#search-suggestions}{here} for more information. \item[\textbf{Search Insights}] Add this to the Search Page to inspect the full query string that's constructed by the back-end search code when the User enters a keyword. Only useful for testing and development. \item[\textbf{Custom Filter}] Add a widget to the page for each of the filters you'd like applied to the search results. Let search page users see and manipulate the filters or make them invisible and/or immutable. \item[\textbf{Sort}] Let Users reorder the search results based on the value of certain \texttt{keyword} fields in the index. For example, show results in alphabetic order of the Title field. The default order is determined by the search engine's \emph{Relevance} calculation. \end{description} \chapter{Low Level Search Options: Searching Additional or Alternate Indexes}\label{low-level-search-options-searching-additional-or-alternate-indexes} Low level search is a new concept in Liferay DXP version 7.2: it's a search that doesn't go through the \href{/docs/7-2/frameworks/-/knowledge_base/f/model-entity-indexing-framework}{Search and Indexing Framework}, which is infrastructure used for searching documents in the Liferay Index. A common use case for a low level search is to query an index other than the Liferay DXP index. By default, \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{Search Pages} search the Liferay DXP index, but you can also search another index, as long as it's in the same Elasticsearch cluster (this feature does not work with Solr). Add the Low Level Search Options widget to a search page and configure it to direct the search to the alternate index. To search multiple indexes from the same page, you can add multiple Low Level Search Options widgets and configure each one with its own Index Name and Federated Search Key. Searching alternate indexes is a low level operation that bypasses the Liferay DXP permission checking mechanisms, presenting whatever results the search engine returns. For this reason, only administrators can add and configure the Low Level Search Options widget. To use the Low Level Search Options widget, add it to a Search Page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Add menu (\includegraphics{./images/icon-add-widget.png}) on the page to open the Add Widgets menu. \item Drag the Low Level Search Options widget (from the Search section), and drop it on the page. \end{enumerate} It doesn't do anything unless you configure it. \section{Configuring Low Level Search}\label{configuring-low-level-search} There are several configuration options for the widget. Access them by clicking the widget Options menu (\includegraphics{./images/icon-app-options.png}) → Configuration, or by clicking the hypertext URL in the widget body: \emph{Configure additional low level search options in this page.} \begin{figure} \centering \includegraphics{./images/search-lowlvl-options.png} \caption{The Low Level Options widget has several configuration options.} \end{figure} \begin{description} \tightlist \item[\textbf{Indexes:}] Enter the comma-separated names of the alternative indexes to search. Do not enter the standard Liferay index name. \item[\textbf{Fields to Return:}] Enter the names of the stored fields to be returned from the search engine in a comma-separated list. Leave it blank to return all stored fields. \item[\textbf{Contributors to Include:}] Enter the ids of registered search contributors to be included in this search in a comma-separated list of each \texttt{SearchRequestContributor}'s Fully Qualified Class Name (e.g., \texttt{com.liferay.docs.request.contributor.MySearchRequestContributor}). If not set, all registered search contributors are applied. \item[\textbf{Contributors to Exclude:}] Enter the ids of registered search contributors to be excluded from this search, in a comma-separated list. If not set, all registered search contributors are applied. \end{description} \noindent\hrulefill \textbf{Note:} These \emph{Contributors} are components implementing the \texttt{com.liferay.portal.search.spi.searcher.SearchRequestContributor} interface (provided by the \texttt{com.liferay.portal.search.spi} artifact), which is an extension point (SPI) that intercepts search requests and adds query parts. \noindent\hrulefill \begin{description} \tightlist \item[\textbf{Federated Search Key:}] Enter the key of an alternate search this widget is participating in. If not set, this widget participates in the default search. This value is usually the name of an application-defined index. \end{description} \section{Example: Searching an Alternate Index}\label{example-searching-an-alternate-index} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Whether testing on the default search page or creating a new \href{/docs/7-2/user/-/knowledge_base/u/configuring-search-pages}{Search Page}, include the following widgets (removing extra widgets simplifies the exercise, but is not required for it to work): \begin{itemize} \tightlist \item Low Level Search Options \item Custom Filter \item Search Bar \item Search Results \end{itemize} \item Configure all the widgets to participate in an alternate search, by opening the widget's Options menu (\includegraphics{./images/icon-app-options.png}) and clicking \emph{Configuration}. For each, enter \emph{liferay-0} in the Federated Search Key setting. All the search widgets expected to react appropriately to the alternate search must be configured with the Federated Search Key. The following steps detail additional configuration. \item Make an additional configuration in the Low Level Search Options widget, adding the index name of the alternate index: Enter at least one index name in the \emph{Indexes} setting. To follow this example, use \emph{liferay-0}. \item Configure the Custom Filter to use the search bar's default query parameter (\emph{q}) and add a query to the search: Enter \emph{title} under field name to add the title field to the query. Choose a Filter Query Type (e.g., Match) for the field. Since you're overriding the default query to search an alternate index, there's nothing in the query by default. Add any query clauses using the Custom Filter widget(s). \end{enumerate} If you're using \emph{liferay-0} in your Federated Search Key and Indexes settings, search for \emph{dynamic} in the search bar. You'll see results like this: \begin{figure} \centering \includegraphics{./images/search-federated.png} \caption{Configure the search page to search a different index.} \end{figure} Now you're able to configure the out of the box search widgets to participate in searches against any Elasticsearch index in the cluster. \chapter{Search Tuning: Synonym Sets}\label{search-tuning-synonym-sets} Starting with 7.0 Service Pack 1, new search tuning features are available for administrative Users: Synonym Sets is one of them. Synonym Sets are mappings that you (the admin) create, so that if a User searches for a certain keyword or phrase, the synonymous terms in your mapping are also searched. Matches to synonyms keywords are scored equally to matches with the exact keyword by the search engine. \textbf{Lunar Resort Use Case:} Multiple content creators at the Lunar Resort write blogs about a variety of topics. Consistent terminology is a problem for some concepts. One writer might use the term ``rover'' for the vehicle that travels across the moonscape, while another uses ``lunar cart'' or ``moon ATV''. As the portal administrator, you must ensure that the search experience is such that searching for any of those keywords returns all relevant results. Synonym Sets are a key ally in this pursuit. \section{Requirements and Limitations}\label{requirements-and-limitations} Search tuning features like Synonym Sets are only supported when using Elasticsearch as the search engine. If you're using Solr, make sure you disable the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-solr\#blacklisting-elasticsearch-only-features}{search tuning features} (Synonym Sets and Result Rankings) when you upgrade your installation to Liferay DXP Service Pack 1 (Fix Pack 2). As of the initial release (7.0 SP-1), Synonym Sets work with fields indexed in two locales: English and Spanish. Thus, the assets supporting localization out-of-the-box work with Synonym Sets. Technically, this means that synonym searches operate on fields indexed with the \texttt{en\_*} and \texttt{es\_*} suffixes. Read the \href{/docs/7-2/user/-/knowledge_base/u/searching-for-localized-content}{multi-language search documentation} to learn which native Liferay DXP assets/fields support localization in the search index. All asset types that index their data into English and Spanish are analyzed with a synonyms-aware analyzer and can be found during a synonym search. The \texttt{=\textgreater{}} \href{https://www.elastic.co/guide/en/elasticsearch/guide/current/synonym-formats.html}{format} supported in Elasticsearch is not supported through the Synonyms Set UI. \section{Creating and Managing Synonym Sets}\label{creating-and-managing-synonym-sets} Create a synonym set by adding as many synonymous keywords to a set as you want. Once the synonym set is saved, any searches in the same company scope (that's any site from the Virtual Instance where the synonyms were configured) take effect. \begin{figure} \centering \includegraphics{./images/search-synonym-set.png} \caption{Add as many synonymous keywords to a set as you'd like.} \end{figure} To create a synonym set, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to Control Panel → Search Tuning → Synonyms. \item Click the Add button (\includegraphics{./images/icon-add.png}). \item Enter the list of synonyms in the set. The input of a synonym is accomplished by clicking \emph{Enter} or by entering a comma. \item When the list is finished, click \emph{Publish}. \end{enumerate} The available synonym sets appear and can be managed in bulk or individually. The management options are to update a synonym set or delete one or more synonym sets. \begin{figure} \centering \includegraphics{./images/search-synonym-sets.png} \caption{Synonym sets can be managed in bulk.} \end{figure} To edit or delete a single synonym set, click the Actions button (\includegraphics{./images/icon-actions.png}) for the synonym set and choose Edit or Delete. \section{Using Synonym Sets}\label{using-synonym-sets} When you have a synonym set defined, the synonyms are ready for use. To test them, find a Search Bar anywhere in the virtual instance and enter a keyword from one of your synonym sets. Results matching the keyword and any synonym are returned in the Search Results widget. \begin{figure} \centering \includegraphics{./images/search-synonomous-result.png} \caption{The Blogs Entry does not contain the word ``rover'' but it can be matched because of a synonym set mapping ``cart'' as its synonym. The synonym is even highlighted.} \end{figure} \section{Known Issues}\label{known-issues} There are several \href{https://issues.liferay.com/browse/LPS-99658}{known issues} for Synonym Sets. These are some of the most important ones: \href{https://issues.liferay.com/browse/LPS-100272}{LPS-100272}: Reindexing permanently deletes all Synonym Sets. Please refer to the ticket for a way to backup and preserve (restore) Synonym Sets across reindex operations. \href{https://issues.liferay.com/browse/LPS-98126}{LPS-98126} Users can create duplicate Synonym Set entries and update other Synonym Sets unintentionally. \section{Related Resources}\label{related-resources} \begin{itemize} \tightlist \item \url{https://www.elastic.co/guide/en/elasticsearch/guide/current/synonyms.html} \item \url{https://www.elastic.co/guide/en/elasticsearch/reference/6.8/analyzer-anatomy.html} \item \url{https://www.elastic.co/guide/en/elasticsearch/reference/6.8/analysis-synonym-graph-tokenfilter.html} \item \url{http://lucene.apache.org/core/7_7_0/analyzers-common/org/apache/lucene/analysis/en/EnglishPossessiveFilter.html} \end{itemize} \chapter{Search Tuning: Customizing Search Results}\label{search-tuning-customizing-search-results} Starting with 7.0 Service Pack 1, new search tuning features are available for administrative Users: Custom Result Rankings is one of them. Result Rankings provides a brute force method for intervening into the relevance scoring of the search engine, by doing these things: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Designate that certain results should appear at the top of the results if they are matched with a certain keyword. This is the idea of \emph{pinning} results to the top of the results list. \item By contrast, hide results that shouldn't appear in certain searches at all. \item Add results that aren't normally returned by searching a certain keyword. \item Re-order pinned results with a drag-and-drop interface. \end{enumerate} Result Rankings let you pin, hide, and add search results for a given set of keywords. These rankings apply only to searches using the newer search widgets (Search Bar, Search Results, etc.). The rankings you customize do not apply to the legacy Search portlet results or to the individual application search bars. \noindent\hrulefill \textbf{Use Case:} At the Lunar Resort website, visitors often search for activities, entering keywords like ``rover races'', ``atv rentals'', and ``lunar golf''. For all of these, the Lunar Resort always wants a certain \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{Content Page} to appear at the top of the search results. This is the Activities page in the Lunar Resort where guests can find all of the resort's adventurous offerings, including lunar rover races, ATV rentals, and information about golfing packages. By contrast, the Lunar Resort does not want the legal liability waiver form to appear during a search for fun activities: that's a bridge to be crossed when guests sign up for the activity. It shouldn't pollute a search for fun activities, even though it contains many of the keywords Users would search for. Result Rankings lets you \emph{pin} the Activities Content Page to the top of the results and \emph{hide} the liability waiver Web Content Article. In addition, a community member wrote a blog favorably reviewing the Lunar Resort, and you want that content added to searches for activities at the resort. This is a prime use case for Result Rankings. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/search-result-rankings-todo.png} \caption{The Lunar Resort wants to tweak these results: pin the Activities page to the top, and hide the legal content entirely.} \end{figure} \section{Availability}\label{availability} Search tuning features like Result Rankings are only supported when using Elasticsearch as the search engine. If you're using Solr, make sure you disable the \href{/docs/7-2/deploy/-/knowledge_base/d/installing-solr\#blacklisting-elasticsearch-only-features}{search tuning features} (Synonym Sets and Result Rankings) when you upgrade your installation to Liferay DXP Service Pack 1 (Fix Pack 2). Results Rankings was added in 7.0 Service Pack 1. \section{Requirements and Limitations}\label{requirements-and-limitations-1} Result Ranking entries are configured in a Virtual Instance, but are not applied only to that Virtual Instance. Instead, custom rankings made in one virtual Instance are shared across all Virtual Instances in the deployment, and even across separate deployments sharing an Elasticsearch cluster (in a multi-tenant scenario). Therefore, Result rankings shouldn't be used when connecting multiple Liferay DXP deployments to the same Elasticsearch cluster unless you intend for the same Result rankings to apply to every Liferay DXP deployment. See \href{https://issues.liferay.com/browse/LPS-101291}{LPS-101291} for more information. An existing Result Ranking cannot be renamed. Renaming requires recreating the ranking under a different name. See \href{https://issues.liferay.com/browse/LPS-96357}{LPS-96357} for more information. \section{Creating and Managing Result Rankings}\label{creating-and-managing-result-rankings} To manipulate result rankings, create a new \emph{Alias} containing the keywords/search terms you want to intercept. Perform a search to get results (you can also do a separate search if you want to grab results that haven't even been returned during a natural search for the alias keywords). Once you have the results, choose to pin, hide, re-order, or add results as you please. To create a new Result Rankings Alias: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to Control Panel → Search Tuning → Result Rankings. \item Click the Add button (\includegraphics{./images/icon-add.png}). \item On the New Ranking screen, enter one of the keywords or search phrases you want to intercept (it can be a phrase, instead of just one word; and don't worry, you can add more later) in the \emph{Search Query} field. Click \emph{Customize Results}. \end{enumerate} A search query is executed. The results are displayed and the tools for pinning, hiding, and adding results are made available. Re-ordering becomes possible after at least one result is pinned. First, consider whether to add one or more Aliases. \section{Adding Aliases}\label{adding-aliases} The Customize Rankings screen is ready to use, but any intervention only applies to the search query you initially entered in the New Ranking screen. To apply the customized rankings to additional search terms, add them as \emph{Aliases}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Aliases field, enter the search term to add as an Alias. \textbf{Warning:} Do not use quotes in your alias terms. \item To submit the search term as an alias, click Enter or a comma in the Aliases field. You can Add multiple aliases here. \begin{figure} \centering \includegraphics{./images/search-result-rankings-aliases.png} \caption{Apply your custom rankings to matched results of additional search terms.} \end{figure} \end{enumerate} Note that results not manipulated manually here are returned as usual when the alias term is queried for in the Search Bar. Now you can customize the rankings. \section{Activating and Deactivating Aliases}\label{activating-and-deactivating-aliases} \begin{quote} Available as of Liferay DXP 7.2, SP2 \end{quote} You can activate or deactivate existing aliases as you have need for them to take effect: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Find the Active alias and open its editor screen. \item Click the toggle switch currently labeled Active. \end{enumerate} Duplicate active aliases are not allowed, but you can deactivate an alias and then create a duplicate. After deactivating an alias, you can only reactivate it after deactivating any active duplicates. \section{Pinning and Hiding Results}\label{pinning-and-hiding-results} To pin or hide rankings, hover over the result of interest: two icons appear, one for pinning and one for hiding. Click the one that applies. Otherwise click the Actions button (\includegraphics{./images/icon-actions.png}), and select \emph{Pin Result} or \emph{Hide Result}. Once you select either option, it's applied immediately. A pinned result moves to the top of the list, and a hidden result disappears. Repeat the action as many times as necessary. If you're done customizing the results, click \emph{Save}, and it's applied immediately. \begin{figure} \centering \includegraphics{./images/search-result-rankings-pinned-result.png} \caption{Pin results to the top of the Search Results list.} \end{figure} \section{Adding Results}\label{adding-results} To add a result that was not returned by searching for the first keyword or phrase, click the \emph{Add Result} button and search for whichever asset you want to pin. \begin{figure} \centering \includegraphics{./images/search-result-rankings-add-result.png} \caption{Add results that aren't normally returned.} \end{figure} Click \emph{Save} if you're done customizing results. \section{Re-Ordering Pinned Results}\label{re-ordering-pinned-results} To re-order pinned results (results that are not pinned cannot be re-ordered), click the handle icon, drag the result, and drop it in the preferred location in the list. \begin{figure} \centering \includegraphics{./images/search-result-rankings-reorder.png} \caption{Re-order the pinned rankings if you want to emphasize or de-emphasize certain results.} \end{figure} Once finished customizing result rankings, click \emph{Save}. \section{Result Rankings Scope and Permissions}\label{result-rankings-scope-and-permissions} Because configuration of Result rankings happens at the virtual instance scope, there are scoping and permissions behaviors to be aware of. Scope is disregarded for pinned results: Pinned results existing in Site A always appear in searches from Site B, even if the Search Bar Scope is set to \emph{This Site}. Search from Result Rankings is global: When searching for results in Result Rankings admin, relevant results from all sites are returned. Permissions are applied as usual: If a User doesn't have permission to see an asset, pinning it does not make it appear in the search results for that User. \section{Result Rankings Aliases versus Synonyms}\label{result-rankings-aliases-versus-synonyms} Since both are new features without precedent in Liferay DXP, there can be confusion over Result Rankings Aliases and Synonyms. \href{/docs/7-2/user/-/knowledge_base/u/search-tuning-synonym-sets}{Synonyms} expand the search to include results matched by additional (synonymous) keywords, so more results are returned if there are matches to the synonyms. Result Rankings Aliases are just keywords that also have the particular ranking interventions applied to them. Only the searched keyword is matched to results, and then, the pins, hides, re-ordering, and additional results take effect after that. These features don't interact in a predictable way. If you need synonym-like behavior in results rankings, define aliases for the keyword. \section{Known Issues}\label{known-issues-1} There are several known issues and planned improvements for Result Rankings. See \href{https://issues.liferay.com/browse/LPS-99540}{LPS-99540} for the complete list. \chapter{Forms}\label{forms} Liferay Forms gives you robust form building capability. For a complete list of the form fields available, visit the \href{/docs/7-2/user/-/knowledge_base/u/form-field-types}{form fields reference article}. Because the complexity of use cases for Forms varies from a single input field to many pages of fields with different configurations, it makes sense to show you how to build and publish simple forms very quickly, and then show you all the additional features you can use for more complex use cases. Here's a sampling of the what the Forms application can do: \begin{itemize} \tightlist \item Populate a Select or Radio field with a REST Data Provider \item Make a field appear based on the value of another field \item Add extra pages to the form \item Enable CAPTCHA for a form \item Store results in JSON \item Enable workflow for the form \item Redirect to a different URL after a successful form submission \item Send an email notification to administrators whenever a form is submitted \item Provide a default value (entered if left alone by the user) or a placeholder value (not entered if left alone by user) for each field \item Validate fields using a number of different criteria \item Redirect users to a success page after form submission \item Define Form Rules to create dynamic form behavior (for example, show or hide a field based on input in another field). \item Translate form text into any supported language. \item Create partial forms (with fields and other elements and specific configurations) and save them for reuse. \item Drag and drop fields onto the form layout. \item Duplicate a form instead of starting a similar form from scratch. \end{itemize} Despite this long list of more complex options, developing a simple, elegant form to suit basic needs takes little effort. The next article covers basic form building. \section{Forms and Lists}\label{forms-and-lists} When you need a form, what you're really looking for is data. There are two applications for building forms to collect precisely the data you need: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \href{/docs/7-2/user/-/knowledge_base/u/forms}{Liferay Forms}: The primary form building application is for the simplest one or two question survey to the most complex, multi-page, homeowners insurance application containing rules and lists populated by a REST data provider. \item \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists (DDL)}: Provides a user interface tool for building reusable form- and list-based applications intended for display on pages, using \href{/docs/7-2/user/-/knowledge_base/u/using-templates-to-display-forms-and-lists}{templates}. \end{enumerate} \noindent\hrulefill \textbf{Kaleo Forms:} If you're a Liferay Digital Enterprise customer, there's a third form building tool called \href{https://help.liferay.com/hc/en-us/articles/360028821952-Kaleo-Forms}{Kaleo Forms}. It integrates form building with workflow to create form-based business processes, like a Conference Room Checkout Form, or a Support Ticket Process so support tickets go through the proper channels on their way to resolution. Read more about Kaleo Forms in the workflow \href{https://help.liferay.com/hc/en-us/articles/360028821952-Kaleo-Forms}{section}. \noindent\hrulefill \section{Which Form Builder Should I Use?}\label{which-form-builder-should-i-use} Liferay Forms (also referred to as Forms) is a relatively new application, first appearing in Liferay DXP version 7.0. If you can use Liferay Forms for your use case, you should. So the question ``Which form builder should I use?'' can be restated to ``When should I use Dynamic Data Lists?'' \begin{itemize} \item Use Dynamic Data Lists (DDL) if you need a way for users to enter data, \emph{and} you need to display the data in the user interface. \item Use DDL if you need to style your lists and forms with templates. \item Use DDL if there's a field type you need that's not included (yet) in Liferay Forms. These are the field types included in DDL that \emph{are not} in Liferay Forms at the time of this writing: - Color - Geolocation - Web Content - Link to Page \end{itemize} It's important to note that these (and more!) form field types will be included in future versions of the Liferay Forms application. When all form building features are fully merged into Liferay Forms, the best features of DDL, all the new features of Liferay Forms, and all future improvements will be in one application. Now is the time to familiarize yourself with Liferay Forms and begin using it for all your form building needs, except for the narrow use cases described above. \chapter{Creating and Managing Forms}\label{creating-and-managing-forms} The Forms widget can do a lot of things really well, but if you just need a simple form, how do you wade through all the features you don't need? Is your simple survey going to make you late for that lunch outing you've been planning with colleagues at that new shawarma place? No! Let's create a simple yet elegant form, give access to the intended users, and get you on your way to lunch. At The Lunar Resort, it's important to capture guests' feedback about their stay at the resort. After a (hopefully) safe journey home, guests should receive an email with a link to brief survey that prompts them to rate their stay from a list of selections, and add any additional comments they'd like in an optional field. \begin{figure} \centering \includegraphics{./images/forms-guest-survey.png} \caption{Get feedback from guests of The Lunar Resort.} \end{figure} \section{Viewing Forms}\label{viewing-forms} Whether creating a form or managing existing forms, it all starts in the same place: the Forms Application in your site's Content section. Access this in the Menu, first choosing the site to work in (for example, The Lunar Resort) and clicking \emph{Content \& Data} → \emph{Forms}. The first thing you'll see is a list of existing forms (if there are any). This list is styled by the Display Style selector next to the Add button (\includegraphics{./images/icon-add.png}). By default, forms are displayed in List format. \begin{figure} \centering \includegraphics{./images/forms-list-view.png} \caption{Forms are displayed in List format by default.} \end{figure} There's also a Table format. Change the style for a single site right here in the Forms site menu application, or change the default display style for the system scope in Control Panel → Configuration → System Settings → Forms (in the Content section). Click the \emph{Forms} entry and find the Default Display View property. Click \emph{Update} and your changes are propagated to all sites. \section{Building a Form}\label{building-a-form} To add a new form, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). The form builder view appears. \item Name the form. Replace \emph{Untitled Form} with \emph{Guest Survey}. \item For the description text enter \emph{Tell us how your stay was!} \item Add the fields. Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to open the sidebar if it's not already opened. \begin{figure} \centering \includegraphics{./images/forms-sidebar.png} \caption{You can choose from nine field types when creating forms.} \end{figure} \item Drag a \emph{Select from List} field onto the form builder and configure it like this: \textbf{Label:} \emph{Rate your visit to The Lunar Resort.} \textbf{Help Text:} Leave this blank for now. If you want a subheading for your field to provide additional guidance, this would be useful. Turn on the \emph{Required Field} selector. At a minimum, this form must capture whether guests like their stay or not. Leave the manual option checked for creating the list of selections. To learn about populating the field with a data provider, read \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{here}. Add these options: \begin{itemize} \tightlist \item \emph{It was out of this world!} \item \emph{I had a good time.} \item \emph{I'd rather go to the beach.} \item \emph{I'll never come back.} \end{itemize} Typing in one of the fields automatically adds another blank selection line. Just leave the last one blank when you're done. \item To see additional options, click \emph{Properties}. Close the sidebar when finished. \item Add a text field, using the same procedure you used for the select field. \textbf{Label:} \emph{Want to tell us more?} \textbf{Help Text:} Leave this blank again to give the form a consistent look. \textbf{My text field has:} Choose \emph{Multiple Lines}. Let guests prattle on about their stay if they want to. \textbf{Required Field:} Leave this unselected. Only require guests to fill out the select field and leave this one as optional. \item Close the sidebar. \item In the form builder, you can see the way the fields are laid out on the form page. \begin{figure} \centering \includegraphics{./images/forms-form-builder.png} \caption{The form builder page lets you preview your form layout, add a page to the form, or add some more fields.} \end{figure} \item When the form is finished, click \emph{Save Form}. It's also auto-saved every minute by default. \item Click \emph{Publish Form}. A dedicated URL to the form is generated, but nobody has the URL yet. \end{enumerate} Now your form can be added to a page, and Lunar Resort guests can be emailed and provided with a link to the page where the form is displayed. \section{Accessing Forms}\label{accessing-forms} Once the form is developed and published, there are two options for getting the published form to targeted users: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Place the Form widget on a site page. This approach lets users navigate to the page in the site. \item Copy the dedicated form URL and provide it to users (for example, via email). This limits access to the form to only those users who have the direct link. \end{enumerate} To display the form on a site page in The Lunar Resort site: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a page to the site (choose Full Page Application for the page type if you only want the form application on the page). Call it \emph{Guest Survey}. \begin{figure} \centering \includegraphics{./images/forms-guest-survey-page.png} \caption{Add a page for guests to view and fill out your new form.} \end{figure} \item Add the Form widget to the page if you've chosen a Widget Page. If you used a full page application, use the page configuration to choose \emph{Form} from the Full Page Application dropdown. \item Once the Form widget is on the page, click \emph{Select Form}, choose the \emph{Guest Survey} form, and click \emph{Save}. \item Close the \emph{Form---Configuration} dialog window and your form is ready for Lunar Resort site users. \end{enumerate} To display the form on a dedicated page accessed only by its link: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the form builder, click \emph{Publish Form} if you haven't already. \begin{figure} \centering \includegraphics{./images/forms-link-grayed.png} \caption{You must first publish a form before you can get a shareable link.} \end{figure} \item Once published, click the link icon at the top right of the builder. \begin{figure} \centering \includegraphics{./images/forms-link.png} \caption{Copy the link to your form.} \end{figure} \item Once you get the link out to users, it's showtime. \begin{figure} \centering \includegraphics{./images/forms-guest-survey.png} \caption{Lunar Resort guests can use a simple form to record their feelings about the resort.} \end{figure} \end{enumerate} Next you'll learn how to view the form entries. Since there aren't any yet, fill out and submit the form a few times. Now you know the basics of creating and managing forms, but this presentation didn't do the Forms application justice. It's much more powerful than hinted at here. The remaining articles in this section immerse you in more advanced form building features. \chapter{Managing Form Entries}\label{managing-form-entries} Once users begin submitting form entries, you'll want to do these things with them: \begin{itemize} \tightlist \item \hyperref[viewing-form-entries]{View form entries} \item \hyperref[exporting-form-entries]{Export form entries} \item \hyperref[deleting-form-entries]{Delete form entries} \end{itemize} Start by learning how to access and view the entries. \section{Viewing Form Entries}\label{viewing-form-entries} When users fill out forms, they're generating data. You'll want to see that data at some point. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the Menu, navigate back to the \emph{Content} → \emph{Forms} section of The Lunar Resort site. \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button for the form and select \emph{View Entries}. \begin{figure} \centering \includegraphics{./images/forms-view-entries.png} \caption{You can view the entries right in the Forms application.} \end{figure} \item What if you have a lot of form fields and you can't see all the data for each entry in the search container? Just click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button for the entry and select \emph{View}. You're shown all the specifics for that form entry. \begin{figure} \centering \includegraphics{./images/forms-view-entry.png} \caption{You can view a single entry right in the Forms application.} \end{figure} \end{enumerate} Viewing entries is great, but this is serious data we're talking about. You might need to get all the entries into a spreadsheet so you can work with them. \section{Exporting Form Entries}\label{exporting-form-entries} So you need to put your form entries in a spreadsheet to do spreadsheet things with them? No problem. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Forms application in The Lunar Resort site's Content \& Data section again. \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button and select \emph{Export}. \item Choose a File Extension. You can export entries in CSV, JSON, XLS, or XML formats by default. For this example, pick CSV. \item Click \emph{Okay}, and open the file or save it locally. Open it with your favorite spreadsheet program and verify your form entries. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-export-csv.png} \caption{You can export entries as CSV, JSON, XLS, or XML.} \end{figure} \noindent\hrulefill \textbf{Note:} The Forms application itself has an \emph{Import/Export} window accessible from the application's Configuration menu (\includegraphics{./images/icon-options.png}). This is how you import and export the application configuration and its data (forms and form entries). The file format for this type of import and export is a LAR file. For more information, see the article on \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{importing and exporting application content}. \noindent\hrulefill There's a system level setting to determine whether administrators can export entries in CSV format: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Configuration → System Settings and click the \emph{Forms} category in the CONTENT AND DATA section. \item Click the \emph{Forms} entry under SITE SCOPE. \item The CSV Export property has three options: \begin{itemize} \tightlist \item \emph{Enabled} to enable CSV Export without a warning \item \emph{Enabled (Show Warning)} to enable CSV Export with the following warning to administrators: This CSV file contains user supplied inputs. Opening a CSV file in a spreadsheet program may be dangerous. \item \emph{Disabled} to turn off CSV Export. \end{itemize} \end{enumerate} Once you export a batch of form entries, it can make sense to delete them from the database. \section{Deleting Form Entries}\label{deleting-form-entries} What if you export a form's entries and now you want to remove them from the Liferay database? It's easy to delete all of a form's entries at once. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate back to the Forms application In The Lunar Resort Content section. \item Click the \emph{Actions} (\includegraphics{./images/icon-actions.png}) button next to the Guest Survey form and select \emph{View Entries} again. \item Select all entries by checking the box next to \emph{Filter and Order}. An X appears in the top right corner of the Form Entries screen. Click it. \begin{figure} \centering \includegraphics{./images/forms-delete-entries.png} \caption{Delete all form entries in one fell swoop.} \end{figure} \end{enumerate} If you just wanted to delete a single entry, select that entry by checking its box; then delete it. If you're worried about deleting everything irrecoverably by accident, don't worry. You must confirm the deletion in a dialog box that pops up after clicking the trash can. Now you can create basic forms and manage the entries. Keep reading in this section to learn about the many additional form building features available to you. \chapter{Form Field Types}\label{form-field-types} A form without fields is no form at all. To meet your form-building needs, Liferay Forms provides useful and highly configurable field types. \begin{figure} \centering \includegraphics{./images/forms-field-types.png} \caption{There are many useful out-of-the-box form field types.} \end{figure} \textbf{Paragraph:} This is static text on the form. Users do not enter data into form text fields. The form creator enters text that form users see displayed on the form. This is useful for longer instructions. \begin{figure} \centering \includegraphics{./images/forms-paragraph.png} \caption{Use Paragraph fields to enter longer instructions on Form Pages.} \end{figure} \textbf{Text Field:} Users enter text into these fields. For example, a Full Name field is a text field. By default, a text field keeps all input on a single line of text. To accommodate longer responses, choose the multi-line setting when configuring the text field \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-forms\#building-a-form}{as in this example}. You can put limits on the text users can enter (e.g., numbers from 1-10, email addresses, etc.) by using the text field's \href{/docs/7-2/user/-/knowledge_base/u/validating-text-and-numeric-fields}{validation options}. \begin{figure} \centering \includegraphics{./images/forms-multiline.png} \caption{Text fields can be single line or multi-line.} \end{figure} \textbf{Select from List:} Users select one option (or more, if configured to allow it) from a list of choices. Choices are entered manually or are automatically populated by a data provider. For example, a Country of Residence field can be selected from list field populated by a Countries of the World data provider. \begin{figure} \centering \includegraphics{./images/forms-select-list.png} \caption{Use a select from list field to let Users choose predefined options.} \end{figure} \textbf{Single Selection:} Using a radio button, users select one option from a list of options displayed on the form. \begin{figure} \centering \includegraphics{./images/forms-single-selection.png} \caption{Single selection fields allow only one selection.} \end{figure} \textbf{Date:} Users select a date using a date picker. For example, a Birth Date field uses the Date field type. \begin{figure} \centering \includegraphics{./images/forms-date.png} \caption{Date fields show a date picker so Users enter a valid date.} \end{figure} \textbf{Multiple Selection:} Users select one or more options from check boxes (or toggles, if configured). \begin{figure} \centering \includegraphics{./images/forms-switcher.png} \caption{A multiple selection field can use a toggle.} \end{figure} \textbf{Grid:} Using radio buttons, users select from options laid out in rows and columns. One selection can be made per row. This is useful when the same response metric is needed for multiple questions. For example, a product survey form might ask users to rate a list of their product's characteristics as \emph{Wonderful}, \emph{Pretty Good}, \emph{Not So Good}, or \emph{Awful}. \begin{figure} \centering \includegraphics{./images/forms-grid.png} \caption{Grid fields use the same options (columns) for multiple categories (rows).} \end{figure} \textbf{Numeric:} Users enter numeric data (integers or decimals) into numeric fields. Non-number input is not accepted. For example, configure a numeric field that accepts integers to ask users how many pets they have. \begin{figure} \centering \includegraphics{./images/forms-numeric.png} \caption{Numeric fields accept only numeric input.} \end{figure} \textbf{Upload:} Users can select a file from the Documents and Media library or upload a file from their local filesystems. \begin{figure} \centering \includegraphics{./images/forms-upload.png} \caption{: Upload fields let Users attach files to the form.} \end{figure} \chapter{Form Rules}\label{form-rules} Chickens don't follow rules well, but dogs do. If you're skeptical, try teaching your chicken to sit on command or herd sheep. Better yet, get a team of chickens to pull a sled in the \href{http://iditarod.com}{Iditarod}. The Forms application is much more like the dog than the useful (southwestern omelet anyone?) but untrainable chicken, and it's only getting more trainable as time passes. Form rules are a good example of the trainable nature of the Forms application. With form rules, you can train your form fields to behave as you wish. There are several things you can make them do: \begin{description} \tightlist \item[Show/hide] Based on a predefined condition, set the visibility of a form field. \item[Enable/disable] Use a predefined condition to enable or disable a field. \item[Require] Use a predefined condition to make a field required. \item[Jump to Page] Based on user input, skip over some form pages directly to a relevant page. \emph{This rule doesn't appear in the rule builder until a second page is added to the form}. \item[Autofill with Data Provider] Use a \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{data provider} to populate fields when a condition is met in another field. \item[Calculate] Populate a field with a calculated value using data entered in other fields. \end{description} Form rules are for changing fields and form elements by acting on conditions. \emph{If {[}condition{]} do {[}action{]}.} If you're not already familiar with the Forms application, start \href{/docs/7-2/user/-/knowledge_base/u/forms}{here}. Once you know how to create forms, add and configure fields, and then publish forms, come back here and learn about form rules. \section{The Anatomy of a Form Rule}\label{the-anatomy-of-a-form-rule} Each rule consists of one or more conditions and actions. \emph{Conditions} determine whether any actions are executed. \emph{Actions} determine what happens if the condition is met. Rules are stored in the database in JSON format by default. \section{Creating Form Rules: Rule Builder}\label{creating-form-rules-rule-builder} Once you create a form and lay out its fields, you're ready to set up rules in your form: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Save the form. \item Open the Rule Builder by clicking the \emph{Rules} tab at the top of the \emph{Edit Form} screen. \item In the rule builder view, you can now begin developing your form rule. Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to get started. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-rule-builder.png} \caption{The Rule Builder gives you a handy interface for creating dynamic form rules.} \end{figure} Before looking at each type of rule condition and action you can use to develop rules, consider the \emph{OR} selector box at the right side of the \emph{Condition} (it's grayed out and unusable at first). You can choose \emph{OR} or \emph{AND} here, depending on what relationship the conditions should have with the action. \begin{description} \tightlist \item[OR] The action is triggered if \emph{any} of the conditions you specify evaluates to \emph{true} \item[AND] The action is triggered only if \emph{all} the conditions you specify evaluate to \emph{true} \end{description} This box becomes usable once you click the Add button (\includegraphics{./images/icon-add.png}) to add an extra condition. \section{Conditions}\label{conditions} Conditions are the gatekeepers of form rules. If the condition's \emph{if statement} evaluates to \emph{true}, the action is triggered. If it evaluates to \emph{false}, no action happens. A condition checks whether one field's value \begin{itemize} \tightlist \item \emph{Is equal to} a specific value or another field's value. \item \emph{Is not equal to} a specific value or another field's value. \item \emph{Contains} a specific value or another field's value. \item \emph{Does not contain} a specific value or another field's value. \item \emph{Is empty}. This assumes you want to do something if a field \emph{is} empty. \item \emph{Is not empty}. This assumes you want to do something as long as a field is \emph{not} empty. \end{itemize} One exception to this is the User condition, which is the last option in the Condition dropdown menu. The User condition doesn't act on a field at all. It checks whether a User belongs to a certain role. For example, if the condition If \texttt{User} \emph{belongs to} \texttt{Administrator} evaluates to \emph{true}, an action is triggered. A condition is the gateway into a form rule, but actions define what actually happens when the condition evaluates to \emph{true}. The remaining articles discuss the available actions and demonstrate their use. \chapter{Action: Show and Hide}\label{action-show-and-hide} With a show and hide rule, you use one or more conditions to determine whether to show or hide a field if the condition evaluates to \emph{true}. To set this example up, add these fields to a form: \begin{itemize} \item \emph{I am 18 Years Old or Older}, a required single selection field with two options: \emph{Yes} and \emph{No}. \item \emph{Legal Guardian Email Address}, a text field that accepts valid email addresses (use text field validation to dictate input type). \end{itemize} \noindent\hrulefill \textbf{Example:} If you're under 18 years old, you need the approval of a legal guardian to drive a sled in a sled dog race (even if you're racing chickens, not dogs). The form for registering your chicken team asks you the age of the driver. If you enter a number less than 18, the Legal Guardian Email Address field appears. To configure a Show/Hide rule, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Rules tab of the Edit Form page and click the Add (\includegraphics{./images/icon-add.png}) button. \item Define the rule: \begin{itemize} \tightlist \item If the \emph{I am 18 years old or older} field is equal to the Value \emph{No}, show the \emph{Legal Guardian Email Address} field. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-rule-development.png} \caption{Build form rules quickly by defining your conditions and actions.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-rule-list.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} Now the \emph{Legal Guardian Email Address} field is only displayed in the form if the user selects \emph{No} in the \emph{I am 18 years old or older} field. \noindent\hrulefill Show rules let you keep a field hidden until some condition is met. \chapter{Action: Require}\label{action-require} Use a require rule to make a field required based on one or more conditions. \noindent\hrulefill \textbf{Example:} If you are following the example, you already set up a \emph{show} rule, where a \emph{Legal Guardian Email Address} field appears if the user selects \emph{No} in the \emph{I am 18 years old or older} field. You also want to make the \emph{Legal Guardian Email Address} field required. To configure a require rule, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Edit the \href{/docs/7-2/user/-/knowledge_base/u/action-show-and-hide}{Show Rule} configured above. Open the Rules tab of the Edit Form page and click the kebab menu (\includegraphics{./images/icon-actions.png}) for the rule, and then click \emph{Edit}. \item Add an Action to the rule: \begin{itemize} \tightlist \item If the \emph{I am 18 years old or older} field is equal to \emph{No}, show the \emph{Legal Guardian Email Address} field and make it required. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-require-rule.png} \caption{Build form rules quickly by defining your conditions and actions.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-require-rule2.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} \noindent\hrulefill Require rules let you require fields based on input from other fields. \chapter{Action: Enable and Disable}\label{action-enable-and-disable} Use an enable/disable rule to make a field editable based on one or more conditions. \noindent\hrulefill \textbf{Example:} Part of the race registration fee pays for dog food. You don't have to feed your chicken team with the provided dog food though. There's a single selection field that asks \emph{Would you like to use the provided dog food?}. If you select \emph{Yes}, you can select how much food, in US pounds, you'll need for your team throughout the race. Since you're racing chickens, you'll select \emph{No}, and the \emph{Amount (US lb.)} field is disabled. To follow the example, add a single selection field \emph{Would you like to use the provided dog food?} with two options: \emph{Yes} and \emph{No}. Add a numeric field called \emph{Amount (US lb.)} and make it an Integer. Use field validation to make sure it's not greater than \emph{100}. To set up the enable/disable rule, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Rules tab of the Edit Form page and click the Add (\includegraphics{./images/icon-add.png}) button. \item Define the rule: \begin{itemize} \tightlist \item If the \emph{Would you like to use the provided dog food?} field is equal to \emph{Yes}, enable the \emph{Amount (US lb.)} field. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-enable-rule.png} \caption{Build form rules quickly by defining your conditions and actions.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-enable-rule2.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} \noindent\hrulefill Now users can fill out the amount of dog food they'll need only if they specify that they do indeed want to use the provided food. \chapter{Action: Jump to Page}\label{action-jump-to-page} Use a Jump to Page rule to navigate automatically to a specific page in the form based on one or more conditions. This is useful if some pages don't apply to all the form's users. Even fields marked as required on the skipped pages can be successfully skipped using this rule. This action doesn't appear in the rule builder unless the form has multiple pages. To follow the example here, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a second form page called \emph{Team Information}. \item On the \emph{first} form page, create a single selection field labeled \emph{Are you a returning racer, with the same team?} with two options: \emph{Yes} and \emph{No}. \item Create a text field on the \emph{second} form page called \emph{Animal Name}. \item Create a third form page called \emph{Final Confirmation}. \end{enumerate} \noindent\hrulefill \textbf{Example:} There's a question on the \emph{Team Information} page of the dog sled race registration form that asks \emph{Are you a returning racer with the same team?} If you select \emph{Yes}, when you click the form's \emph{Next} button, you skip to the final page of the form, since there's no need to fill out your animal's name again. Their monogrammed T-shirts will be ready at the start of the race. To configure the Jump to Page rule, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Rules tab of the Edit Form page and click the Add (\includegraphics{./images/icon-add.png}) button. \item Define the rule: \begin{itemize} \tightlist \item If field \emph{Are you a returning racer, with the same team?} is equal to the Value \emph{Yes}, Jump to Page \emph{Final Confirmation}. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-jump-to-page.png} \caption{Build form rules quickly by defining your conditions and actions.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-jump-to-page2.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} \noindent\hrulefill Once the form User fills out the first page and clicks \emph{Next}, the rule condition will evaluate hte answer to the field and either proceed to the next page or take the action of skipping to the page inidicatedd in the rule. If you use an \emph{is not equal to} condition for form fields on two different pages, the condition is checked after leaving the page of the first form field, and evaluates to \emph{true} since there's a value in the first field and no value in the second field. It's best to use this condition with fields existing on the same page. \chapter{Action: Autofill}\label{action-autofill} Autofill rules let you change the selection options of another field based on the value entered into a related field. A \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{data provider's} output is used to populate a field, as long as the condition is met. Before configuring an autofill rule, \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{set up a data provider}. That's how autofilled fields are populated. Pay careful attention to the input and output parameters you choose when setting up the rule. To follow this example: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Set up a data provider using the \texttt{get-countries} JSON web service. If you're running Liferay DXP at \texttt{localhost:8080}, you can access this web service here: \begin{verbatim} http://localhost:8080/api/jsonws?contextName=&signature=%2Fcountry%2Fget-countries-0 \end{verbatim} Make sure the output parameter is set to \texttt{\$..nameCurentValue}. If you're unsure how to do this, first read the article on \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{Data Providers}. \item On the last form page, add two fields: \begin{itemize} \item A Single Selection field called \emph{If I win I'd like my award to be:}, with two choices: \emph{Cash} and \emph{All Expenses Paid Vacation}. \item A Select from List field called \emph{Choose a Destination Country}. Under \emph{Create List}, choose \emph{From Autofill}. \end{itemize} \end{enumerate} \noindent\hrulefill \textbf{Example:} Before submitting the race registration, let users decide whether they want a cash prize or an all-expenses-paid vacation. If they choose the vacation, populate the Choose a Destination Country with output from the data provider. To configure an Autofill rule, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Rules tab of the Edit Form page and click the Add (\includegraphics{./images/icon-add.png}) button. \item Define the rule: \begin{itemize} \tightlist \item If field \emph{If I win I'd like my award to be} is equal to the Value \emph{All Expenses Paid Vacation}, Autofill the \emph{Choose a Destination Country} field from the \emph{countries} data provider (note that you might have named this differently when setting it up). \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-autofill.png} \caption{Build form rules quickly by defining your conditions and actions.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-autofill2.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} \noindent\hrulefill \section{Using Inputs with Autofill}\label{using-inputs-with-autofill} The above example is simple, using only an Output to autofill a Select from List field if another field has a certain value. Many times, the response from the REST provider must be filtered before display in the Select from List field. For this, a Data Provider Input field is required. For example, to configure an autofill rule to display the countries of the world filtered by a Region field (for example, Americas, Europe, or Oceania), \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create a Data Provider. \textbf{Name:} \texttt{restcountries} \textbf{URL:} \texttt{https://restcountries.eu/rest/v2/region/\{region\}?fields=name} \textbf{Inputs:} Fill in the Label (\emph{region}), Parameter (\emph{region}), and Type (\emph{Text}). \textbf{Outputs:} fill out a Label (\emph{name}), Path (\emph{\$..name}), and Type (\emph{List}). To understand more about these values, read the \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{Data Provider documentation}. \begin{figure} \centering \includegraphics{./images/forms-autofill-data-provider.png} \caption{Create a data provider for the autofill rule.} \end{figure} \item Create a form with these fields: \textbf{Text:} Use the Label \emph{Region}. \textbf{Select from List:} Label it \emph{Country}, and choose \emph{From Autofill} under Create List. \begin{figure} \centering \includegraphics{./images/forms-autofill-input-output-fields.png} \caption{Create a form with a text field and a select from list field. These are used to provide the input to the data provider and be autofilled by its output.} \end{figure} \item Configure the Autofill rule. \textbf{Condition:} If \emph{Region} \textbf{Is not Empty} \textbf{Action:} Do \textbf{Autofill} From Data Provider \texttt{restcountries}, Data Provider's Input: region---\emph{Region}, Data Provider's Output: name---\emph{Country}. \begin{figure} \centering \includegraphics{./images/forms-autofill-rule.png} \caption{Create the autofill rule. Brag of your prowess.} \end{figure} \end{enumerate} Once you're done, publish the form and try it out, by entering a valid region into the Region field, and observing that the options in the Select from List Field are filtered based on the Region. The \href{https://restcountries.eu}{restcountries.eu} service has these regions you can use: Africa, Americas, Asia, Europe, Oceania, and Polar. Autofill rules combine the power of data providers and form rules. \chapter{Action: Calculate}\label{action-calculate} Calculate rules let you automatically populate a numeric field by calculating its value based on other fields. Calculations are limited to numeric fields. To follow the example below: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create 5 Numeric fields called \emph{Animal Weight 1 (US lb.)}, \emph{Animal Weight 2 (US lb.)}, etc. \item Create a Numeric field called \emph{Total Food Required (US lb.)}. \end{enumerate} \noindent\hrulefill \textbf{Example:} A 16-dog sled team can consume 2,000 US lb. of food during the Iditarod. This equates to about 0.25 lb. of food per lb. of animal, if the race lasts ten days. We'll use five numeric fields for animal weight instead of sixteen here, because it's tedious to create sixteen fields, even with the field duplication function. When the form user enters the weight of each animal the Total Food Required field should be calculated based on this simple formula: \begin{verbatim} Animal Weight 1, Animal Weight 2, ... = AW1, AW2, ... Total Food Required = TFW (AW1 + AW2 + AW3 + AW4 + AW5) * 0.25 = TFW \end{verbatim} To configure a calculate rule: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Rules tab of the Edit Form page and click the Add (\includegraphics{./images/icon-add.png}) button. \item Define the rule: \begin{itemize} \tightlist \item If field \emph{Animal Weight 1} is greater than 0, Calculate the sum of the Animal Weight fields, multiplied by 0.25. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-calculate-rule.png} \caption{Build calculate actions with a handy calculator.} \end{figure} \begin{itemize} \tightlist \item Save the rule. \end{itemize} \begin{figure} \centering \includegraphics{./images/forms-calculate-rule2.png} \caption{Once a rule is saved, it is displayed so that you can easily understand what it does.} \end{figure} \end{enumerate} \noindent\hrulefill The calculation is defined using the embedded calculator. Use a mix of numeric field values, mathematical operators, and constants to define calculation rules. \chapter{Form Element Sets}\label{form-element-sets} If you're here looking for information on reusable field sets, you're in the right place. We call them Element Sets in the Liferay Forms application because these sets include more than just fields: they include the layout and configuration of the fields as well. In the future, additional styling elements will be available here, too. Element sets are more like composable Form fragments or reusable Form blocks. Sometimes you might be able to create an entire form by composing existing Element Sets. Your colleagues might call you lazy, but we'd call you industrious. \section{Creating Element Sets}\label{creating-element-sets} To create Element Sets, go to Site Menu → Content \&Data → Forms. The Forms view is displayed by default. Click the \emph{Element Sets} tab, and any existing Element Sets appear, just like existing Forms are displayed in the Forms view. Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). Here's the thing. If you know how to \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-forms}{create a Form}, you already know how to create an Element Set. The process is identical. Drag and drop elements onto the form builder palette, configuring fields as you go. When you're finished, click \emph{Save}. Element Sets aren't publishable, so there's no button for that. \begin{figure} \centering \includegraphics{./images/forms-element-sets.png} \caption{Creating Element Sets is just like creating Forms. You just can't publish them.} \end{figure} Once an Element Set is saved, it's instantly available for use, even in the same Element Set. That's right, you can use Element Sets to create Element Sets. \section{Using Element Sets}\label{using-element-sets} To use an Element Set in a Form: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Form Builder. \item If the Add Elements sidebar isn't already displayed, open it by clicking the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item The default view in the Add Elements sidebar is Elements. Instead click \emph{Element Sets}. \item Drag the Element Set onto the Form Builder, just like you would any single Form Element. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-add-element-set.png} \caption{Add an Element Set the same way you add other Form Elements, like fields.} \end{figure} You probably already guessed that the process is identical for using Element Sets to build other Element Sets. That's all there is to it. There are just a couple more things to note: \begin{itemize} \item Once an Element Set is added to a Form, there's no connection with the root Element Set. You're free to move or configure the Fields and Elements however you want. \item Editing an Element Set doesn't retroactively affect the Forms where the Element Set was used. \end{itemize} Think ahead. Are there some common fields you'll commonly need to configure in your Forms? If so, create them as Element Sets once and save yourself repetitive work. \chapter{Data Providers}\label{data-providers} Select from List fields can hold a lot of options. There are around 200 countries on Earth, for example. If you have unoccupied unpaid interns you could ask them to type each country into the Select from List field manually, or you could auto-populate your select fields using a REST web service. This saves you (or your interns) the trouble of typing all those options, and you can rely on someone else (hopefully a trustworthy expert) to keep the data updated. When setting up a data provider, you're accessing a \href{https://en.wikipedia.org/wiki/Representational_state_transfer}{REST web service}. Use the JSON web services registered in Liferay DXP, or any other REST web service you can access. To find a list of the registered JSON web services in Liferay DXP, navigate to \url{http://localhost:8080/api/jsonws} (assuming you're running a local server). Browse the available Liferay services. Many times, the services useful to you in the Forms application get a list of something. Find the \texttt{get-countries} JSON web service (there are two---use either one) and click on it, then click \emph{Invoke}. The \emph{Result} tab shows a list of countries using JSON syntax, like this: \begin{verbatim} [ { "a2": "AF", "a3": "AFG", "countryId": "20", "idd": "093", "mvccVersion": "0", "name": "afghanistan", "nameCurrentValue": "Afghanistan", "number": "4" }, ... \end{verbatim} That's the record for the country Afghanistan. As you can see in the \emph{URL Example} tab, the URL you entered into the data provider form is the same as the one generated for accessing the \texttt{get-countries} JSON web service. Find the URL for any registered JSON web service using this same procedure. Note the field you want Users to select. With this service, it's most likely \texttt{nameCurrentValue}, because it contains the full, properly capitalized name of the country. \noindent\hrulefill \emph{Enabling Access to Data on the Local Network:} By default, you cannot configure data providers to use URLs on the local network. This is a good default for security in a production environment, but makes testing more difficult. To enable local network access from data providers, got to Control Panel → Configuration → System Settings → Data Providers (under Content \& Data), and enable \emph{Access Local Network}. You'll need to configure this if you want to follow the basic example in the next section. \noindent\hrulefill \section{Adding a Basic Data Provider}\label{adding-a-basic-data-provider} To add a \emph{Countries of the World} Data Provider for use in your Forms, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Forms application. \item Click the \emph{Data Providers} tab. \item Click the Add button (\includegraphics{./images/icon-add.png}). The REST Data Provider form loads. \item Fill out the Name and Description fields. Name: \texttt{Countries\ of\ the\ World} \item Enter the URL and authentication tokens for the REST service. For the \texttt{get-countries} service: URL: \texttt{http://localhost:8080/api/jsonws/country/get-countries/} User Name: \texttt{adminuser@liferay.com} Password:\texttt{adminuserpass} \item In the Outputs fields, specify which field from the REST service populates your select list. Label: \texttt{Country\ Name} Path: \texttt{\$..nameCurrentValue} Type: \texttt{List} \item Save the Data Provider. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-simple-data-provider.png} \caption{Set up a simple data provider in no time.} \end{figure} What's that \texttt{\$..} before \texttt{nameCurrentValue}? It's JsonPath syntax to navigate the JSON data structure and specify the path to the output. Learn more about JsonPath \href{https://github.com/json-path/JsonPath/blob/master/README.md}{here} and \href{http://goessner.net/articles/JsonPath/}{here}. \section{Using a Data Provider in a Select Field}\label{using-a-data-provider-in-a-select-field} Once the Data Provider is configured, use it to populate a Select from List field: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the Form Builder (add a new form or edit an existing one) \item Drag a Select from List field onto the form. \item In the Create List section, choose \emph{From Data Provider}. \item Choose the Data Provider and its Output Parameter: Choose a Data Provider: \texttt{Countries\ of\ the\ World} Choose an Output Parameter: \texttt{Country\ Name} \item Publish the form and test it. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-select-data-provider.png} \caption{Form users select an option form the list populated by the Data Provider.} \end{figure} Your Data Provider is now being used to populate a select field. However, this form should be submitted by Guest users, who don't currently have permission to see the list of results from the data provider. Arrgh! Now what? \section{Granting Data Provider Permissions}\label{granting-data-provider-permissions} To configure the data provider's permissions, go to the Forms application (\emph{Site Administration} → \emph{Content \& Data} → \emph{Forms}). Open the Data Providers tab. For the data provider you want to configure, click the Actions button (\includegraphics{./images/icon-actions.png}), then \emph{Permissions}. Configure the permissions you need. If Guests are to fill out the form, they need the \emph{View} permission, or else they won't be able to see the options provided by the data provider. Once you grant permissions, click \emph{Save}. \section{Data Provider Configuration}\label{data-provider-configuration} The above instructions cover adding a basic Data Provider. Knowing more about each field in the Data Provider setup form opens up more possibilities. \begin{description} \item[\textbf{URL}] The URL of an internal or external REST service endpoint. Consider the REST service at https://restcountries.eu/, which contains a REST API endpoint to find countries by \texttt{region}: \texttt{https://restcountries.eu/rest/v2/region/\{region\}} \end{description} Data Provider URLs can take two parameter types: path parameters and query parameters. Path parameters are part of the URL calling the REST web service, and are added using the pattern \texttt{https://service-url.com/service/\{path\_parameter\_name\}}: The \texttt{restcountries.eu} service's \texttt{region} endpoint's path parameter is \texttt{\{region\}}. Path parameters are mandatory parts of the URL, so make sure you specify an Input (see below) with a \emph{Parameter} field value matching the path parameter from the URL. Query parameters are complementary parts of the URL that filter the output of the service call, following the pattern \texttt{?query\_parameter=query\_parameter\_value}: \begin{verbatim} https://restcountries.eu/rest/v2/all?fields=capital \end{verbatim} Unlike path parameters, query parameters are optional. \begin{description} \tightlist \item[\textbf{User Name and Password}] Credentials used to authenticate to the REST Web Service, if necessary. \item[\textbf{Cache data on the first request.}] If the data is cached, a second load of the select list field is much faster, since a second call to the REST service provider is unnecessary. \item[\textbf{Timeout}] The time (in ms) to allow the REST service call to process before aborting the request, if a response is not returned. \item[\textbf{Inputs}] Configure path or query parameters from the REST service to filter the REST service's response. Specify the Label, Parameter, and Type (Text or Number), and choose whether the input is required to use the Data Provider. You can add multiple Inputs. To provide a way for users to specify the input value, use an \href{/docs/7-2/user/-/knowledge_base/u/action-autofill}{\emph{Autofill} Form Rule}. A User enters input into one field, and their input is sent to the REST service. The REST service's response data is filtered by the input parameter. \item[\textbf{Outputs}] The Parameter to display in Select from List or Text fields with autocomplete enabled. You can add multiple Outputs. Outputs can be filtered by inputs (see above) but can also be displayed without configuring input filtering. Specify the Label, Path, and Type (Text, Number, or List). The Path field is specified in \href{https://github.com/json-path/JsonPath/blob/master/README.md}{JsonPath syntax}, so it must always start with a \texttt{\$}. The type of data returned by the Path must match the type you choose in the Type field. Using the \texttt{restcountries.eu} service, specify the \texttt{name} field as an Output by entering enter \texttt{\$..name} in the Path field. \end{description} If you have a more complex JsonPath expression to construct (for example, you need the names of all countries with a population over 100 million---\texttt{\$..{[}?(@.population\textgreater{}100000000){]}.name} with the \texttt{restcountries.eu} service), consider using an online JsonPath evaluator, like \href{http://jsonpath.herokuapp.com/}{this one} or \href{https://jsonpath.com/}{this one}. \noindent\hrulefill \textbf{Hint:} To display one value to the user, but persist another in the database, enter both into the Paths field, separated by a semicolon: \begin{verbatim} `$..name;$..numericCode` \end{verbatim} If this is used with the \texttt{restcountries.eu} data provider, the name of the country is displayed to the User, while the numeric country code is stored in the database. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/forms-data-provider-configuration.png} \caption{Set up Data Providers to display data retrieved from a REST service.} \end{figure} \section{Troubleshooting Data Provider Errors}\label{troubleshooting-data-provider-errors} To uncover errors arising from Data Provider failures, \href{/docs/7-2/user/-/knowledge_base/u/server-administration}{configure log levels} for these services: \textbf{Category:} \texttt{com.liferay.dynamic.data.mapping.data.provider.internal.DDMDataProviderInvokerImpl} \emph{Level:} WARN \textbf{Category:} \texttt{com.liferay.dynamic.data.mapping.form.field.type.internal.DDMFormFieldOptionsFactoryImpl} \emph{Level:} DEBUG With Data Providers, the world's (RESTful) data is at your disposal to use with the Forms application. \chapter{Auto-Save}\label{auto-save} Losing progress on a partially created form is bad. Make sure to save your work frequently as you're creating forms. But if you forget to save your work, Liferay Forms has your back. By default, a form is auto-saved every minute. You won't notice anything in the form builder while the back-end auto-saves the form. You can change the auto-save duration in \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Forms} (in the Content and Data section). To disable auto-save, set the interval to \emph{0}. For unpublished forms, an auto-save works just like a manual save. For a published form, however, auto-saved data isn't automatically propagated to the form. You must click the \emph{Save} button in the form builder to publish the changes. \begin{figure} \centering \includegraphics{./images/forms-autosave-interval.png} \caption{Configure the auto-save duration.} \end{figure} \chapter{Translating Forms}\label{translating-forms} Forms can be translated to any locale that Liferay DXP supports. The form builder specifies a translation of the form's default language. The form's default language and the available translations are set in the \href{/docs/7-2/user/-/knowledge_base/u/social-settings-and-languages\#languages}{site's language configuration}. Follow these steps to create a form translation: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} (your site's menu) → \emph{Content \& Data} → \emph{Forms} and open the form to translate. \item Click the + icon next to the current translation language and choose from the available languages. \begin{figure} \centering \includegraphics{./images/forms-add-translation.png} \caption{Add a translation for the form.} \end{figure} \item Translate the form's title, field labels, field options, field placeholder text, and any other text visible to the user. \item Save and publish the form. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-translate2.png} \caption{Translate as much of the form as possible into each language you expect users to need.} \end{figure} To fill out a translated form in a translated language, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Access the form. If a signed-in user accesses the form and a translation is available in the user's language, the user sees the translated form by default. \item To see the form in a different language, click the language icon and select a language. \item Fill out the form as usual and click \emph{Submit}. \end{enumerate} \noindent\hrulefill \textbf{Note:} Translations work differently depending on how a User accesses a Form: If \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-forms\#accessing-forms}{accessed in the Form widget on a Liferay DXP page}, the Form is displayed in the User's language automatically. If there's no translation available for the User's language, the default language of the Form is displayed. If accessed via direct \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-forms\#accessing-forms}{URL}, the Form translation must be selected manually. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/forms-translate3.png} \caption{Select the form's language.} \end{figure} \chapter{Autocompleting Text Fields}\label{autocompleting-text-fields} It's been scientifically proven that Internet users are lazy (not you, of course---other Internet users). For example, some users may not fill out your form if you make them type the entire title of their country in an employment application. This is especially true if they're filling out the form on their mobile devices. Make users' lives easier by configuring autocomplete on a form's text fields. Why not just use a select field with a data provider to guide user input? Sometimes a data provider can't encompass all possible field entries. For example, if your data provider doesn't include \href{https://en.wikipedia.org/wiki/Principality_of_Sealand}{mythical countries founded on old sea platforms}, users the Principality of Sealand can't enter anything into the select field. Instead use a text field with autocomplete so users can begin typing their country's name and then select it from a list when it appears. Autocomplete combines a text field (accepting any response that meets your validation criteria) and common choices to select from. It's a win-win situation. \section{Configuring Autocomplete}\label{configuring-autocomplete} Before configuring autocomplete for your text fields, \href{/docs/7-2/user/-/knowledge_base/u/creating-and-managing-forms}{create a form and add a text field}. If you want the autocomplete options to be populated by a REST data provider, \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{configure one} before creating your form. Now you're ready to configure autocomplete for the field: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the field configuration sidebar, click the \emph{Properties} tab. \item Click the \emph{Autocomplete} switcher so it's enabled. \item Select a data provider or create one manually. You can set up a data provider from a \href{/docs/7-2/user/-/knowledge_base/u/data-providers}{REST service}, or manually enter the options users should see when they start typing in the text field. \item Save and Publish the form. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-autocomplete-manually.png} \caption{You can configure a manual data provider to specify the options users can select from.} \end{figure} Once users begin entering text into the field, a selection list of options appears. As they enter additional text, the list is refined to include only options that contain the currently entered text. For example, the imaginary users from Sealand (all two of them) begin reluctantly typing their country of origin by entering an \emph{S}. They're delighted to see a selection list with a bunch of countries containing the letter \emph{S} appear for their selection convenience. If they continue typing and enter \emph{e}, the list is refined to options that have \emph{se} in their name (for example Serbia and Senegal). If they continue typing and enter \emph{a}, they'll now only see one option, Sealand, if it's in your data provider. Selecting it from the list after typing the first three letters is much easier than typing the remaining letters. \begin{figure} \centering \includegraphics{./images/forms-autocomplete-filtering.png} \caption{When typing in a field with autocomplete, users are presented a list of selections from the configured data provider. The displayed results are filtered to include only selections containing the text entered by the user.} \end{figure} What will the Forms team think of next? Configuring telepathic connections to the Forms application would be nice. Then users could just think their form field entries into existence. Stay tuned. \chapter{Form Success Pages}\label{form-success-pages} After users submit one of your whiz-bang forms, what's next? How will they know they're done and can close the browser window or tab? What if they think their submission didn't go through and wonder if they need to fill out the whole form again? By default, submitting a form displays the default success message and returns users to the form's now empty first page. Don't leave users feeling equally empty. Instead, configure a \emph{Success Page}. A Success Page is a terminal page showing users they've finished filling out the form and their submission was successfully received. A Success Page can even urge users to close the browser window or tab. \begin{figure} \centering \includegraphics{./images/your-request-completed-successfully.png} \caption{The default success message alerts users when their request completes successfully.} \end{figure} A Success Page is simple. It has a title in bold text and a description beneath the title. A common alternative to using a Success Page is to \href{/docs/7-2/user/-/knowledge_base/u/redirecting-users}{redirect users to a different page in your Site}. What should you put in a Success Page? Whatever you want. If you can't think of anything important or creative to say, use the default message: \begin{figure} \centering \includegraphics{./images/forms-success-page-default.png} \caption{There's a default Success Page message if you can't think of anything else to say.} \end{figure} To configure a Success Page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a form in \emph{Site Administration} (your site's menu) → \emph{Content \& Data} → \emph{Forms}. \item Click on the form page's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and choose \emph{Add Success Page}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-success-page-add.png} \caption{Add a Success Page using the edit menu for the form page.} \end{figure} Once the Success Page is added to your form, fill in the \emph{Title} and \emph{Content} fields however you please. When the form is saved and published, the Success Page is live for your form users. \noindent\hrulefill \textbf{Note:} You can't preview the Success Page. Success Pages can only be viewed once a form is submitted, and the \emph{Submit} button isn't available in the form preview. The \emph{Preview Form} link in the form builder only lets you preview the form's regular pages (use the \emph{Next} button to navigate through the form). To see what your Success Page looks like, submit a test entry of the form and then delete it if needed. For more information, see \href{/docs/7-2/user/-/knowledge_base/u/managing-form-entries\#viewing-form-entries}{the documentation on viewing and managing form entries}. \chapter{Workflow and Forms}\label{workflow-and-forms} \href{/docs/7-2/user/-/knowledge_base/u/workflow}{The workflow engine} is for sending a submitted asset through a workflow process before it's published. Most assets are configured to use workflow at the instance or Site level. \begin{figure} \centering \includegraphics{./images/forms-workflow-configuration.png} \caption{Workflow is enabled in the Control Panel or in Site Administration for most Liferay DXP assets.} \end{figure} Forms are different, so they don't appear in the above image. There are so many use cases for forms, and there could be so many per site, that a site- or instance-scoped workflow configuration won't serve your needs well. Instead, configure workflow for \emph{each form} separately. \section{Enabling Workflow in a Form}\label{enabling-workflow-in-a-form} To enable workflow in a form, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the form's editor by opening the Menu, selecting your Site, navigating to \emph{Content \&Data} → \emph{Forms}, and clicking on the form you want. \item Click the Options button (\includegraphics{./images/icon-options.png}) and choose \emph{Settings}. \item The Settings window has a \emph{Select a Workflow} drop-down. Find the workflow you want, select it, and then click \emph{Done}. \begin{figure} \centering \includegraphics{./images/form-settings.png} \caption{Enable workflow for each form in its Settings window.} \end{figure} \end{enumerate} \section{Testing the Workflow}\label{testing-the-workflow} Test the workflow process: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the form to a page. \item Click \emph{Submit for Publication} to submit the form entry. \end{enumerate} Next go find the form entry in the Forms application: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go back to the Forms application in the Menu in your Site's \emph{Content \& Data} section. \item Click the Form's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{View Entries}. The entry is currently marked \emph{Pending}. \end{enumerate} Now \href{/docs/7-2/user/-/knowledge_base/u/reviewing-assets}{approve the form record}: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{My Workflow Tasks} in the User Personal Menu. \item Click the \emph{Assigned to My Roles} tab. \item Click on the form entry. \item Click the Actions button (\includegraphics{./images/icon-actions.png}) and choose \emph{Assign to Me}. \item Click \emph{Done}. \item Click the Actions button (\includegraphics{./images/icon-actions.png}) again, then click \emph{Approve}. \item Click \emph{Done} again. \item Navigate back to the View Entries screen for the form, and now the entry is marked as \emph{Approved}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-view-entries-status.png} \caption{Each entry's status is visible in the Forms application's Form Entries screen.} \end{figure} \chapter{Duplicating Forms and Form Fields}\label{duplicating-forms-and-form-fields} Repetitive tasks are error prone. Instead of duplicating effort, duplicate forms and form fields. To duplicate a form, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} (your Site's menu) → \emph{Content \& Data} → \emph{Forms}. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for the form to duplicate. \item Click \emph{Duplicate}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-duplicate-form.png} \caption{The Duplicate option works the same for forms and form fields.} \end{figure} The form is duplicated and automatically named \emph{Copy of {[}Original Form Name{]}}. Once duplicated, you can edit the form however you want. When you duplicate a form, all configurations and form rules are duplicated as well. To duplicate a form field, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} (your Site's menu) → \emph{Content \& Data} → \emph{Forms} and open or add a form. \item In the Builder view, hover over the form field to duplicate and click the \emph{Copy} icon (\includegraphics{./images/icon-copy.png}). \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-duplicate-form-field.png} \caption{You can duplicate form fields.} \end{figure} The field is duplicated and labeled \emph{Copy of {[}Original Field Label{]}}. All the form's properties, including its data provider configurations, are copied as well. \chapter{Form Pages}\label{form-pages} Are users more likely to abandon long forms with lots of scrolling? Are they more likely to see a multi-page form and abandon it without a second look, assuming that it gets longer and more tedious with every passing page? Such usability questions are worth thinking about. If you decide multiple pages are appropriate for your form, Liferay Forms supports two pagination styles: default, and alternate. \begin{figure} \centering \includegraphics{./images/forms-pagination1.png} \caption{The default pagination style.} \end{figure} \begin{figure} \centering \includegraphics{./images/forms-pagination2.png} \caption{The alternate pagination style as seen in the Form Builder.} \end{figure} To add a form page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to a form's builder view. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) at the top-right corner of the form, then click \emph{Add New Page}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-page-actions.png} \caption{You can add new pages or reset the current page from the Page Actions menu.} \end{figure} You can also delete form pages, add \href{/docs/7-2/user/-/knowledge_base/u/form-success-pages}{success pages}, and switch pagination modes. \chapter{Help Text, Placeholder Text, and Predefined Values}\label{help-text-placeholder-text-and-predefined-values} Form fields can have help text, placeholder text, and predefined values. \begin{itemize} \item \textbf{Help Text:} Text that appears as a sub-heading to the field label, but doesn't appear in the field entry area. Enter help text in the Basic tab of the field's sidebar menu. \item \textbf{Placeholder Text:} Text in the field entry area that isn't submitted if the field is left untouched by the user. \item \textbf{Predefined Value:} Text in the field entry area that is submitted if the field is left untouched by the user. \end{itemize} All form field types can have help text, and all form field types that accept user input can have predefined values. Only text and numeric fields can have placeholder text. To enter placeholder text or predefined values, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open a field's sidebar menu. \item Open the \emph{Properties} tab. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-placeholder-predefined-values.png} \caption{Predefined values and placeholder text are entered in the Properties tab.} \end{figure} For example, many forms start with a \emph{Full Name} field. You can use help text and/or placeholder text to inform users that you need their full names, regardless of length. Alternatively, if you're asking users to specify how many sandwiches they eat for lunch, a predefined value of \emph{1} probably makes sense. \begin{figure} \centering \includegraphics{./images/forms-help-placeholder-predefined.png} \caption{The Full Name field here uses help text and placeholder text, while the sandwiches field uses a predefined value.} \end{figure} Remember, placeholder values aren't submitted if the field is left blank, so you don't have to worry about getting a bunch of submissions from \emph{Maximillian Aurelius Piroux the 11th}. \chapter{Validating Text and Numeric Fields}\label{validating-text-and-numeric-fields} Validation ensures that only certain values are entered in a field. Validation functionality is available for text and numeric fields. To enable validation, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a Text or Numeric field to a form in the Builder view. \item Open the field's \emph{Properties} tab. \item Turn on the \emph{Validation} toggle to enable validation and open its configuration options. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-text-validation.png} \caption{Validate data to ensure you're collecting only useful information.} \end{figure} \section{Validating Text Fields}\label{validating-text-fields} Validation for text fields contains several options. You must first choose a list of available conditions to check: \begin{itemize} \tightlist \item If Input \emph{Contains} \item If Input \emph{Does Not Contain} \item If Input \emph{Is not URL} \item If Input \emph{Is not Email} \item If Input \emph{Does not Match} \end{itemize} If the condition isn't met, an error message is displayed to the user. Where you go from there depends on which condition you used. \section{If Input Contains/Does Not Contain}\label{if-input-containsdoes-not-contain} When you validate text data to check if it contains a certain value, there are two additional steps to take after selecting the condition: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enter the text to check for. \item Enter an error message so users understand why their submission failed. \end{enumerate} \begin{figure} \centering \includegraphics{./images/forms-text-val-contains.png} \caption{If \emph{Liferay} isn't part of the field's value, an error message is displayed.} \end{figure} \section{If Input Is not URL/Email}\label{if-input-is-not-urlemail} Checking for properly formatted URLs and emails is easy. Just choose the condition from the \emph{If Input} drop-down and enter the error message. Valid URLs begin with \texttt{http://} or \texttt{https://}. Valid emails must contain \texttt{@}. \begin{figure} \centering \includegraphics{./images/forms-text-val-email.png} \caption{Use text field validation to make sure users enter a valid email address or URL.} \end{figure} \section{If Input Does Not Match}\label{if-input-does-not-match} The \emph{Does Not Match} condition is used for entering \href{https://en.wikipedia.org/wiki/Regular_expression}{regular expressions} to create custom validation criteria. For example, use this regular expression to ensure that ten consecutive numeric digits are entered in a phone number field: \begin{verbatim} ^[0-9]{10}$ \end{verbatim} If you use regular expression validation, provide some explanatory text (e.g., help text, placeholder text, and a clear error message) to guide form users in entering the proper data. \begin{figure} \centering \includegraphics{./images/forms-text-val-regex.png} \caption{Regular expression text validation opens up countless possibilities.} \end{figure} \section{Validating Numeric Fields}\label{validating-numeric-fields} Numeric field validation is similar to text field validation, but the conditions compare the value of the number entered to some other value. \begin{figure} \centering \includegraphics{./images/forms-numeric-val2.png} \caption{Numeric conditions constrain user-entered numeric data.} \end{figure} Available conditions to check include \begin{itemize} \tightlist \item Is greater than or equal to \item Is greater than \item Is not equal to \item Is less than or equal to \item Is less than \end{itemize} For example, to make sure users don't enter a number over 10, enable validation and use \emph{Is greater than} with a value of \emph{10}. Use the message \emph{Please enter 10 or less}. \begin{figure} \centering \includegraphics{./images/forms-numeric-val1.png} \caption{Make sure user-entered numeric data is within reasonable bounds. Nobody needs 11 sandwiches for lunch.} \end{figure} Note that numeric fields are text fields validated to allow only numeric data entry. That's why they're in the Customized Elements section of the form fields list. In addition, the property \emph{My numeric type is} (can be Integer or Decimal) on the Basic tab of a numeric form is another form of validation. \chapter{Enabling CAPTCHA on Form Submissions}\label{enabling-captcha-on-form-submissions} \href{https://en.wikipedia.org/wiki/CAPTCHA}{CAPTCHA} prevents a bot from submitting forms. It's often used in \href{/docs/7-2/deploy/-/knowledge_base/d/logging-into-liferay}{login apps}, but you can also use it in the Forms app. To enable CAPTCHA, click the form's \emph{Options} button (\includegraphics{./images/icon-options.png}) and select \emph{Settings}. Enable the \emph{Require CAPTCHA} setting, click \emph{Done}, and save the form. That's all there is to it! \begin{figure} \centering \includegraphics{./images/forms-settings-captcha.png} \caption{You can enable CAPTCHA for your form in the Form Settings window.} \end{figure} \begin{figure} \centering \includegraphics{./images/forms-captcha.png} \caption{Once you enable CAPTCHA, your form has protection against bot submissions.} \end{figure} \chapter{Form Notifications}\label{form-notifications} You can configure the Forms app to send a notification email each time a form entry is submitted. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the form's \emph{Form Settings} section by clicking the \emph{Options} button (\includegraphics{./images/icon-options.png}) and selecting \emph{Settings}. \item Click the \emph{Email Notifications} tab, enable the option \emph{Send an Email Notification for Each Entry}, and fill out these fields: \textbf{From Name:} The sender's name. This could be the Site name, the form name, or anything else informative to the recipient. \textbf{From Address:} The sender's email address. You can use something like \texttt{noreply@example.com}, so that recipients don't try to reply. \textbf{To Address:} The recipient's email address (e.g., \texttt{test@example.com}). \textbf{Subject:} The email's subject. An informative subject line tells the recipient what happened. For example, An application for employment was submitted in The Lunar Resort*. \end{enumerate} Note that if you \href{/docs/7-2/user/-/knowledge_base/u/sending-form-entries-through-a-workflow}{enabled workflow for the form} and it already sends a notification, you might not need to configure the Forms app to generate a notification. \begin{figure} \centering \includegraphics{./images/forms-notification-email.png} \caption{Configure email notifications each time a form entry is submitted.} \end{figure} \chapter{Redirecting Users}\label{redirecting-users} When users submit a form, you can present them with another page indicating success or some other information related to their submission. Sometimes all you need is a \href{/docs/7-2/user/-/knowledge_base/u/form-success-pages}{success page}, but other times you might want to send users to a specific URL. Whatever your use case is, follow these steps to set up a redirect URL: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the form's \emph{Form Settings} section by clicking the \emph{Options} button (\includegraphics{./images/icon-options.png}) and select \emph{Settings}. \item Enter the redirect URL in the \emph{Redirect URL} field. \end{enumerate} That's it! Now when users submit the form, they're not left wondering what to do next. \begin{figure} \centering \includegraphics{./images/forms-redirect.png} \caption{Redirect users after they submit a form.} \end{figure} \chapter{Form Permissions}\label{form-permissions} To access a form's permissions, first navigate to the Forms app in \emph{Site Administration} (your site's menu) → \emph{Content \& Data} → \emph{Forms}. Then click the form's \emph{Actions} button (\includegraphics{./images/icon-actions.png}), and select \emph{Permissions}. By default, you can grant these permissions for a form: \textbf{Delete:} Delete the form. \textbf{Permissions:} Access and configure the form's permissions. \textbf{Update:} Update form entries. \textbf{Add Form Instance Record:} Submit form entries. \textbf{View:} View the form. \begin{figure} \centering \includegraphics{./images/forms-form-permissions.png} \caption{You can configure a form's permissions.} \end{figure} Note that guest users can view and fill out forms by default. The \emph{Guest} Role has \emph{View} and \emph{Add Form Instance Record} permissions. \noindent\hrulefill \textbf{Note:} By default, all users inherit the Guest Role's permissions. The Guest Role represents unauthenticated visitors of your Site. If you want to let Guest users submit forms (the default setting), it makes sense that authenticated users can also. To disable automatic inheritance of the Guest Role's permissions, set \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#Permissions}{this property} in your \texttt{portal-ext.properties} file: \begin{verbatim} `permissions.check.guest.enabled=false` \end{verbatim} \chapter{Styling Form Pages}\label{styling-form-pages} Let's face it: nobody likes an ugly, confusing form. Styling your \href{/docs/7-2/user/-/knowledge_base/u/form-pages}{form pages} lets you make your forms user friendly. There are two features for styling your forms: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Create rows and columns for form fields. \item Move fields from one location to another. \end{enumerate} Sometimes it doesn't make sense to use the default single-column, vertically-oriented form. For example, a form with many fields can save space by putting them in different columns. You can also use a mixed approach, with each row broken into a different number of columns. The following screenshots show examples of these layouts. \begin{figure} \centering \includegraphics{./images/forms-form-row.png} \caption{This is the default single-column, vertically-oriented form.} \end{figure} \begin{figure} \centering \includegraphics{./images/forms-layout-multicolumn.png} \caption{Putting form fields in multiple columns can give you more space.} \end{figure} \begin{figure} \centering \includegraphics{./images/forms-layout-mixed.png} \caption{The first row is in two columns and the second row is in three columns.} \end{figure} By default, dragging a field onto a form page adds a field that occupies an entire row. Follow these steps to resize the field to make room for more fields in the row (columns): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Hover over the field to reveal its borders: \begin{figure} \centering \includegraphics{./images/forms-field-borders.png} \caption{Form field borders.} \end{figure} \item Drag the right or left edge of the field to resize it. \begin{figure} \centering \includegraphics{./images/forms-field-resized.png} \caption{After resizing, the field is smaller.} \end{figure} \item Add a new field to the remaining space in the row. \begin{figure} \centering \includegraphics{./images/forms-field-columns.png} \caption{There are now two fields in the row.} \end{figure} \end{enumerate} You can also move fields. To do so, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Hover over the field and the cursor becomes a hand. \item Drag the field to an open location in the builder. Available locations are highlighted in blue and outlined with a dotted line. A field moved to a new row fills that entire row. A field moved to an existing row fills that row's remaining space. \begin{figure} \centering \includegraphics{./images/forms-move-field.png} \caption{You can also move fields on form pages.} \end{figure} \end{enumerate} \chapter{Dynamic Data Lists}\label{dynamic-data-lists} Dynamic data lists display forms created from field sets called \emph{data definitions}. Data definitions consist of a form's field types (e.g., text, boolean, date, radio buttons, selector menus, etc.) and those fields' labels and settings. Data definitions effectively serve as data models for a dynamic data list. For example, you could create a data definition with two text fields: one for a user's name, and one for their comments. You could then display a form that gathers user feedback via a dynamic data list that uses that data definition. To summarize: \begin{itemize} \tightlist \item \textbf{Data Definitions:} Define a form's fields. \item \textbf{Dynamic Data Lists:} Display a form based on a data definition. \end{itemize} You can create one or multiple dynamic data lists from a single data definition. The user data entered for each dynamic data list is kept separate, even if the data definition is shared. For instance, you could use the example data definition above to create several dynamic data lists, and then place them anywhere you need to get feedback from users. Because each dynamic data list's form data is separate, you don't need to worry about trying to figure out which dynamic data list the user comment came from. Dynamic data lists are flexible. You don't have to restrict dynamic data lists to simple input. You could create something as complex as an entire data entry system for real estate listings, or any other simple list-based application you can dream up. You create data definitions and dynamic data lists in from the Site Menu's Content → Dynamic Data Lists application. Creating data definitions and lists doesn't require any coding. However, additional formatting can be added with \href{https://freemarker.apache.org/}{FreeMarker} templates. These tutorials show you how to create and use data definitions and dynamic data lists: \begin{itemize} \tightlist \item Creating data definitions. \item Creating dynamic data lists. \item Creating form and display templates. \end{itemize} \section{System Configuration}\label{system-configuration} There are two Dynamic Data Lists entries in System Settings. The Dynamic Data Lists Service entry contains one setting: \begin{description} \tightlist \item[\textbf{Add Default Structures}] This is enabled by default and pre-loads several embedded data definitions to base data lists on. Once loaded on portal startup, these definitions must be deleted manually from the Site Menu → Dynamic Data Lists application. This setting applies to the first start of a virtual instance. \end{description} The Dynamic Data Lists entry contains three settings: \begin{description} \item[\textbf{Changeable Default Language}] If enabled, the default language of a data definition becomes changeable. \item[\textbf{CSV Export}] Choose whether DDL records can be exported in CSV format with or without a warning, or disable this option. Here's what the warning says: Warning: This CSV file contains user supplied inputs. Opening a CSV file in a spreadsheet program may be dangerous. \item[\textbf{Default Display View}] Choose whether to use a table based default view or a list based default view. \end{description} \chapter{Creating Data Definitions}\label{creating-data-definitions} Follow these steps to create a data definition: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and expand your site's menu (the Site Administration menu). Then select \emph{Content \& Data} → \emph{Dynamic Data Lists}. This opens the Dynamic Data Lists screen. A table lists any existing dynamic data lists. \item Click the \emph{Options} button at the top-right (\includegraphics{./images/icon-options.png}) and select \emph{Manage Data Definitions}. The Data Definitions screen appears. A table lists any existing data definitions. Several definitions are embedded for common use cases like contacts, events, inventory, and more. \begin{figure} \centering \includegraphics{./images/ddl-definitions.png} \caption{The Data Definitions screen.} \end{figure} \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to begin creating a new data definition. This opens the \emph{New Data Definition} form. \item Give your data definition a name. Note that the definition's name appears for any users filling out a dynamic data list that uses the definition. Then expand the \emph{Details} section of the form and give it a description. \item The Details section of the form also contains the field \emph{Parent Data Definition}. This optional field lets you select an existing data definition (the parent) to form the basis of the new one (the child). The child definition inherits the parent's fields and settings, which you can then customize. When you create a dynamic data list from a child definition, it includes the fields of the parent and child definitions. This lets you use a common definition (the parent) as the basis of a specialized definition (the child). For example, if you were planning a rock climbing trip, you could use the default Events definition as the parent of a Rock Climbing Trip definition that contains fields unique to rock climbing (e.g., climbing equipment availability, altitude, etc.). To choose a parent definition, click the \emph{Select} button below the \emph{Parent Data Definition} field and then select an existing definition in the dialog that appears. \begin{figure} \centering \includegraphics{./images/ddl-definition-form-01.png} \caption{After naming your data definition, expand the Details section of the form and give your definition a description and parent definition, if desired.} \end{figure} \item Add the data definition's fields in the data definition designer, below the form's Details section. The designer's default \emph{View} tab lets you create the definition in a WYSIWYG editor. You can click the \emph{Source} tab to work with the definition's underlying JSON, but it's much easier to stick with the WYSIWYG editor. In the \emph{View} tab select the \emph{Fields} tab. Icons representing the field types are listed on one side and the data definition's canvas is on the other side. To add a field type to the definition, select its icon, drag, and drop it onto the canvas. By dragging a field onto a field that's already on the canvas, you can nest the new field in the existing field. When you mouse over a field on the canvas, the field action icons appear. Clicking the \emph{+} icon creates a duplicate of the current field and adds it below the current field. Clicking the trash can deletes the field. The following fields are available: \begin{itemize} \tightlist \item \textbf{Boolean:} A check box. \item \textbf{Color:} Specifies a color. \item \textbf{Date:} Enter a date. A valid date format is required for the date field, but you don't have to enter a date manually. When you select the date field a mini-calendar pops up which you can use to select a date. \item \textbf{Decimal:} Enter a decimal number. The value is persisted as a \texttt{double}. \item \textbf{Documents and Media:} Select a file from a Documents and Media library. \item \textbf{Geolocation:} Associate a location with the User's form entry. \item \textbf{HTML:} An area that uses a WYSIWYG editor to write and display HTML content. \item \textbf{Integer:} Enter an integer. The value is persisted as an \texttt{int}. \item \textbf{Link to Page:} Link to another page in the same site. \item \textbf{Number:} Enter a decimal number or an integer. The value is persisted either as a \texttt{double} or an \texttt{int}, depending on the input's type. \item \textbf{Radio:} Displays several clickable options. The default number of options is three but this is customizable. Only one option can be selected at a time. \item \textbf{Select:} This is just like the radio field except that the options are hidden and must be accessed from a drop-down menu. \item \textbf{Text:} Enter a single line of text. \item \textbf{Text Box:} This is just like the text field except you can enter multiple lines of text or separate paragraphs. \item \textbf{Web Content:} Select web content. \end{itemize} \begin{figure} \centering \includegraphics{./images/ddl-data-definition-designer.png} \caption{Use the data definition designer to add fields to the data definition.} \end{figure} \item Edit field labels to reflect their intended data. A text field's default label is \emph{Text}. To use the text field as a title, then you should change the field's label to \emph{Title}. First select the field on the canvas. This automatically selects the \emph{Settings} tab on the left. Alternatively, you can access the Settings tab by clicking the field's wrench icon. To edit a setting value, double-click it in the Settings table and enter the new value. The available settings are listed below. You can translate each of a data definition's field values to any supported locales. To specify a field value for a translation, select the flag that represents the locale and enter the field value for the locale. The following field settings are available. Note that some of these settings are only available for specific field types: \begin{itemize} \tightlist \item \textbf{Type:} The field's type (e.g., text, radio, etc.). This setting can't be edited, but a display template can reference it. \item \textbf{Field Label:} The field's display name. \item \textbf{Show Label:} Whether the field label is shown. \item \textbf{Required:} Whether users must fill out the field (not available for Boolean fields). \item \textbf{Name:} The field's internal identifier. You can use this value in a display template to read the field's data. This value is automatically generated, but you can change it if you wish. \item \textbf{Predefined Value:} The field's default value. \item \textbf{Tip:} Text to display in a tooltip. \item \textbf{Indexable:} Whether the field is indexed for search. \item \textbf{Localizable:} Whether the field can be translated. \item \textbf{Repeatable:} Whether users can make copies of the field. \item \textbf{Multiple:} Whether the user can select more than one option. This is only available for Select fields. \item \textbf{Options:} The options available for selection in Radio and Select fields. You can add and remove options, and edit each option's display name and value. \end{itemize} \begin{figure} \centering \includegraphics{./images/ddl-data-definition-settings.png} \caption{Configure the settings for each field in your data definition.} \end{figure} \item Click \emph{Save} when you're done. Your new data definition then appears in the table with the pre-defined ones and any you've already added. \end{enumerate} \chapter{Managing Data Definitions}\label{managing-data-definitions} There are several ways to manage your data definitions. Of course, you can edit a data definition, but you can also configure its permissions, manage its templates, copy it, or delete it. Follow these steps to access your data definitions: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and expand your site's menu (the Site Administration menu). Then select \emph{Content \& Data} → \emph{Dynamic Data Lists}. This opens the Dynamic Data Lists screen. A table lists any existing dynamic data lists. \item Click the \emph{Options} button at the top-right (\includegraphics{./images/icon-options.png}) and select \emph{Manage Data Definitions}. The Data Definitions screen appears. A table lists the data definitions. \end{enumerate} \begin{figure} \centering \includegraphics{./images/ddl-definitions-actions.png} \caption{You can copy an existing data definition, manage its templates, and more.} \end{figure} You can manage your data definitions via the \emph{Actions} menu (\includegraphics{./images/icon-actions.png}) for each definition: \textbf{Edit:} Edit the data definition. The edit screen uses the same form for \href{/docs/7-2/user/-/knowledge_base/u/creating-data-definitions}{creating data definitions}. Note that if you edit a data definition referenced elsewhere (e.g., by a dynamic data list or display template), then you must update that reference. \textbf{Manage Templates:} The \emph{Manage Templates} screen creates and manages templates for the data definition. For details, see \href{/docs/7-2/user/-/knowledge_base/u/using-templates-to-display-forms-and-lists}{Using Templates to Display Forms and Lists}. \textbf{Permissions:} Configure the data definition's permissions. Note that these permissions are for an individual definition accessed through the Dynamic Data Lists application in \emph{Site Administration} → \emph{Content} → \emph{Dynamic Data Lists}. For example, if Site members have View permission for a data definition, any Site member who also has a Role that can access the Dynamic Data Lists app and its data definitions can see this definition listed in the Manage Data Definitions screen. If you don't want this, remove the View permission for Site Member, and Site members won't see your data definition listed with the others. \textbf{Copy:} The \emph{Copy Data Definition} form copies the definition and its templates. In the form, give the copied definition a new name and description and select whether to also copy the original definition's templates. Click \emph{Copy} when you're done. The copied definition then appears in the Data Definitions table with existing definitions. You can create new definitions based on existing ones, and then modify the copied one to suit your needs. You can, of course, edit any definition in the portal, but if you copy a definition instead, you can still access the original. \textbf{Delete:} Delete the definition. \chapter{Creating Data Lists}\label{creating-data-lists} There are two places to create dynamic data lists: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Site Administration:} Open the Menu and expand your Site's menu (the Site Administration menu). Then select \emph{Content \& Data} → \emph{Dynamic Data Lists}. This opens the Dynamic Data Lists screen. A table contains any existing lists. Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to open \emph{New List} form. To add Dynamic Data Lists, you must have permission to access the Dynamic Data Lists app in Site Administration. \item \textbf{Dynamic Data Lists Display widget:} Navigate to the Site page where you want this widget and add it to the page from \emph{Add} (\includegraphics{./images/icon-add-app.png}) → \emph{Widgets} → \emph{Collaboration} → \emph{Dynamic Data Lists Display}. Then click the widget's \emph{Add List} button. This opens the \emph{New List} form. To do this, you must have permission to create a new list in the widget. \end{enumerate} Either option leads to the New List form: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Give the list a name and a description. \item Select the list's data definition: click \emph{Select} under the \emph{Data Definition} field, then click the definition you want to use. \item To use a workflow with this list, select it from the \emph{Workflow} field. \item To change the list's default permissions, expand the form's \emph{Permissions} section and make your selections. \item Click \emph{Save} to finish creating the list. Your new list appears in the table. \begin{figure} \centering \includegraphics{./images/ddl-add-list.png} \caption{The New List form.} \end{figure} \end{enumerate} \section{Creating List Records}\label{creating-list-records} By default, only administrators have permission to create list records. Follow these steps to give other users this permission: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to \emph{Content \& Data} → \emph{Dynamic Data Lists} in Site Administration. \item Click \emph{Actions} (\includegraphics{./images/icon-actions.png}) → \emph{Permissions} for the list getting the new permissions. \item Select \emph{Add Record} for the Roles that should have that permission, then click \emph{Save}. Allow unauthenticated Users to add records by giving Guest the Add Record permission. \end{enumerate} Create new records in a list from the same places you can create the lists themselves: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Site Administration:} In Site Administration, navigate to \emph{Content \& Data} → \emph{Dynamic Data Lists}. Click a list in the table to view any existing records, then click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This opens a form based on the list's data definition, which you can then fill out and submit to create a new record. To do this, you must have permission to access the Dynamic Data Lists app in Site Administration. \item \textbf{Dynamic Data Lists Display widget:} See above for instructions on adding this widget to a page. You must then configure the widget to display a list's records. To configure the widget to display a list's records: \begin{itemize} \tightlist \item Click the widget's \emph{Select List} button. \item In the dialog that appears, select a list, click \emph{Save}, then close the dialog. The widget then displays the list's existing records. \end{itemize} To add a record: \begin{itemize} \tightlist \item Click the widget's \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Fill out the form that appears and click \emph{Publish}. \end{itemize} \end{enumerate} See the section below for more information on configuring the widget. \begin{figure} \centering \includegraphics{./images/ddl-widget.png} \caption{Dynamic Data Lists Display widget.} \end{figure} \section{Configuring the Dynamic Data Lists Display Widget}\label{configuring-the-dynamic-data-lists-display-widget} The widget's default display template isn't exciting, but it shows the list's contents, and with permission, add and/or edit list items. To configure the widget, click its \emph{Options} menu (\includegraphics{./images/icon-app-options.png}) and select \emph{Configuration}. This opens the Configuration dialog, with the \emph{Setup} tab selected by default. The Setup tab contains two other tabs: \textbf{Lists:} Select the list that the widget displays. The currently displayed list appears at the top of the tab, while the available lists appear in a table. To change the widget's list, select the list from the table and click \emph{Save}. \textbf{Optional Configuration:} \textbf{Display Template:} Select the display template for the list. \textbf{Form Template:} Select the form template for the list. \textbf{Editable:} Whether users can add records to the widget's list. \textbf{Form View:} Whether to display the Add Record form by default, instead of the List View. Note that even without this selected, users can still add records via the widget's \emph{Add} button (\includegraphics{./images/icon-add.png}). \textbf{Spreadsheet View:} Whether the List View displays each record in a row, with columns for the record attributes. When finished, click \emph{Save} and close the Configuration dialog. \begin{figure} \centering \includegraphics{./images/ddl-widget-options.png} \caption{The Dynamic Data Lists Display widget's optional configuration.} \end{figure} \chapter{Using Templates to Display Forms and Lists}\label{using-templates-to-display-forms-and-lists} After creating data definitions and lists, you can control how the form appears to users, and how the resulting list of records is displayed. You do this by creating templates for each view (form view for displaying the form and display view for the list of records) and selecting them in the DDL Display portlet. For example, you might need to create a form with a subset of a data definition's fields. Rather than creating a new definition, you can create a template that displays only the fields you want from the existing definition. You could also use a template to arrange fields differently, and/or with different labels and configuration options. Data definitions can have as many form and display templates as you care to create (or none, if you're satisfied with the default templates). You then choose a list's template in the Dynamic Data Lists Display widget. \section{Managing Display and Form Templates}\label{managing-display-and-form-templates} Since Display and Form Templates correspond to a particular data definition, they're accessed from the Data Definitions screen of the Dynamic Data Lists application in Site Administration. See the \href{/docs/7-2/user/-/knowledge_base/u/creating-data-definitions}{Creating Data Definitions article} for instructions on accessing this screen. The Data Definitions screen lists each definition in a table. To start working with a definition's templates, click the definition's Actions button (\includegraphics{./images/icon-actions.png}) and select \emph{Manage Templates}. This opens a screen that lists the definition's templates. You can edit, copy, delete, or configure permissions for a definition via its Actions button (\includegraphics{./images/icon-actions.png}). \chapter{Creating Form Templates}\label{creating-form-templates} Form templates control how the data entry form appears for a data definition. Follow these steps to create a form template for a definition: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and expand your Site's menu (the Site Administration menu). Then select \emph{Content \& Data} → \emph{Dynamic Data Lists}. This opens the Dynamic Data Lists screen. A table lists any existing dynamic data lists. \item Click the \emph{Options} button at the top-right (\includegraphics{./images/icon-options.png}) and select \emph{Manage Data Definitions}. The Data Definitions screen appears. A table lists any existing data definitions. \item Click the definition's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Manage Templates}. This lists the definition's templates. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select \emph{Add Form Template}. This presents the same kind of graphical, drag-and-drop interface used to \href{/docs/7-2/user/-/knowledge_base/u/creating-data-definitions}{create definitions}. \item Give your template a name, then expand the \emph{Details} section and give it a description. You can also use the \emph{Mode} selector to select which mode the template applies to (\emph{Create} or \emph{Edit}). \item Scroll down to the graphical designer in the \emph{View} tab, and make your desired changes. For example, you can move or delete fields, change field labels, and more. \item Click \emph{Save} when you're finished. \end{enumerate} Alternatively, you can create form templates from the Dynamic Data Lists Display widget: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Follow the instructions in the \href{/docs/7-2/user/-/knowledge_base/u/creating-data-lists}{Creating Data Lists article} for adding and configuring the widget in a Site page. Make sure to configure the widget to show the list you're creating a template for. \item Click the widget's \emph{Add Form Template} button. This opens the same form as above for creating a form template for the list's definition. \end{enumerate} \chapter{Creating Display Templates}\label{creating-display-templates} For every data definition, you can create as many displays as you need. If you've created a form template that doesn't show all the fields of a particular data definition in the data list's form view, you probably don't want to display those fields in the list view, either. Modify the list view using Display Templates. \noindent\hrulefill \textbf{Note:} If you're familiar with \href{/docs/7-2/user/-/knowledge_base/u/designing-web-content-with-templates}{web content templates}, display templates customize the display of a list in the same way. Display templates are written in FreeMarker or Velocity, pulling data from the data definition in the same way that web content templates pull data from their structures. Also similar to web content templates, display templates can be embedded in other display templates. This allows for reusable code, JavaScript library imports, or macros imported by Velocity or FreeMarker templates in the system. Embedding display templates provides a more efficient process when you have a multitude of similar data definitions. Just import an embedded display template and work off of it for your new display template. \noindent\hrulefill As with \href{/docs/7-2/user/-/knowledge_base/u/creating-form-templates}{form templates}, you can create display templates from the Dynamic Data Lists app in Site Administration or the Dynamic Data Lists Display widget. Follow these steps to create a display template from Site Administration: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the Menu (\includegraphics{./images/icon-menu.png}) and expand your Site's menu (the Site Administration menu). Then select \emph{Content} → \emph{Dynamic Data Lists}. This opens the Dynamic Data Lists screen. A table lists any existing dynamic data lists. \item Click the \emph{Options} button at the top-right (\includegraphics{./images/icon-options.png}) and select \emph{Manage Data Definitions}. The Data Definitions screen appears. A table lists existing data definitions. \item Click the definition's \emph{Actions} button (\includegraphics{./images/icon-options.png}) and select \emph{Manage Templates}. This lists the definition's templates. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and select \emph{Add Display Template}. \item Give the template a name, expand the \emph{Details} section of the form, and give it a description. Also in the details section of the form, select the templating language to use from the \emph{Language} selector. You can choose \href{https://freemarker.apache.org/index.html}{FreeMarker}, or \href{https://velocity.apache.org/}{Velocity}. \item In the \emph{Script} section of the form, create your template in the editor using the templating language you chose in the previous step. The palette to the left of the editor contains common variables. Click a variable to insert it in the editor. The editor also autocompletes. In a FreeMarker template, type \texttt{\$\{}, which opens an autocomplete list of common variables. Select a variable to insert it in the editor. Alternatively, you can upload a complete script file via the \emph{Browse} button below the editor. \item Click \emph{Save} when you're done creating the template. \end{enumerate} \begin{figure} \centering \includegraphics{./images/ddl-template-editor.png} \caption{Create your display template in the editor.} \end{figure} Alternatively, you can use the Dynamic Data Lists Display widget to create display templates: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Follow the instructions in the \href{/docs/7-2/user/-/knowledge_base/u/creating-data-lists}{Creating Data Lists article} for adding and configuring the widget in a site page. Make sure to configure the widget to show the list's definition you're making into a template. \item Click the widget's \emph{Add Display Template} button. This opens the same form as above for creating a display template for the list's definition. \end{enumerate} \section{Display Template Editor}\label{display-template-editor} Helper variables are available in the template editor. These provide access to most of the data that you'll use in creating Display Templates. The variables under the heading Data List Variables let you inject specific information about the data definition the template is being created for: \textbf{Data Definition ID:} \texttt{reserved\_ddm\_structure\_id} \textbf{Data List Description:} \texttt{reserved\_record\_set\_description} \textbf{Data List ID:} \texttt{reserved\_record\_set\_id} \textbf{Data List Name:} \texttt{reserved\_record\_set\_name} \textbf{Template ID:} \texttt{reserved\_ddm\_template\_id} Inside a template, these variables give the ID for the record set as well as the name, description, and data definition. Display the list of records by retrieving them and assigning them to the handy \texttt{records} variable. Retrieve the list's records from \texttt{DDLDisplayTemplateHelper}, which contains these functions: \begin{verbatim} getDocumentLibraryPreviewURL getHTMLContent getLayoutFriendlyURL getRecords renderRecordFieldValue \end{verbatim} \texttt{DDLDisplayTemplateHelper} performs common tasks. Use the \texttt{getRecords} method to access a data definition's entries and assign them to a \texttt{records} variable: \begin{verbatim} <#assign records = ddlDisplayTemplateHelper.getRecords(reserved_record_set_id)> \end{verbatim} This \emph{fetches} the records of the associated data list. You haven't done anything with them yet, so your display is still empty. To list all the records, use the \emph{Data List Records} helper in the sidebar of the template editor. Remember to place your cursor in the proper place in the template editor window, then click \emph{Data List Records}. This code appears at the cursor: \begin{verbatim} <#if records?has_content> <#list records as cur_record> ${cur_record} \end{verbatim} This default code snippet spits out everything in the database for the given data definition, which is ugly and practically useless: \begin{verbatim} {uuid=52c4ac1c-afe7-963c-49c6-5279b7030a99, recordId=35926, groupId=20126, companyId=20099, userId=20139, userName=Test Test, versionUserId=20139, versionUserName=Test Test, createDate=2018-07-26 14:31:51.056, modifiedDate=2018-07-26 14:31:51.058, DDMStorageId=35927, recordSetId=35922, recordSetVersion=1.0, version=1.0, displayIndex=0, lastPublishDate=null} \end{verbatim} Here's a simple example template that uses a list based on the embedded Contacts data definition, and only displays the Company Name and Email fields in a bulleted list: \begin{verbatim} <#assign records = ddlDisplayTemplateHelper.getRecords(reserved_record_set_id)>

    Here are contacts by company name and email address.

    <#if records?has_content> <#list records as cur_record>
    • <#-- The below gets the Company field and wraps it in an tag --> Company Name: ${ddlDisplayTemplateHelper.renderRecordFieldValue(cur_record.getDDMFormFieldValues("company")?first, locale)}
      <#-- The below gets the Email field and wraps it in an tag --> Email: ${ddlDisplayTemplateHelper.renderRecordFieldValue(cur_record.getDDMFormFieldValues("email")?first, locale)}
    \end{verbatim} Here's what it looks like: \begin{figure} \centering \includegraphics{./images/ddl-contacts-template.png} \caption{Extract appropriate display information, rather than spitting out the whole object.} \end{figure} Now you're prepared to make data lists beautiful using Display Templates. \chapter{Workflow}\label{workflow} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay's workflow engine is named \emph{Kaleo}. In Greek, Kaleo means ``called ones,'' which is appropriate for a workflow engine that calls users to participate in a review process designed for them. Workflow makes it possible to define any number of simple to complex business processes/workflows, deploy them, and manage them through a portal interface. The processes have knowledge of Users, Groups and Roles. You don't have to write a single line of code to accomplish this: it's defined in an XML document. If you're a Liferay Digital Experience Platform (DXP) customer, you also have access to a visual process builder. There are several steps to effective workflowing: \begin{itemize} \item \href{/docs/7-2/reference/-/knowledge_base/r/crafting-xml-workflow-definitions}{Designing review processes in XML} \item (DXP only) \href{https://help.liferay.com/hc/en-us/articles/360028821892-Workflow-Designer}{Visually designing review processes} \item \href{/docs/7-2/user/-/knowledge_base/u/managing-workflows\#uploading-workflow-definitions}{Uploading workflow definitions} \item \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{Activating workflow for enabled assets} \item \href{/docs/7-2/user/-/knowledge_base/u/managing-workflows}{Managing Workflow definitions} \item \href{/docs/7-2/user/-/knowledge_base/u/reviewing-assets}{Sending assets through review} \item (DXP only) \href{https://help.liferay.com/hc/en-us/articles/360029042071-Workflow-Metrics-The-Service-Level-Agreement-SLA-}{Using Workflow Metrics} \end{itemize} After all that, you'll be familiar with using Liferay's workflow engine to set up approval processes for any \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{workflow-enabled content}. \section{What's New with Workflow?}\label{whats-new-with-workflow} There are some noteworthy enhancements to the workflow functionality: \section{DXP Feature: Workflow Metrics}\label{dxp-feature-workflow-metrics} For DXP subscribers, the \emph{Workflow Metrics} feature was introduced. Configure one or more Service Level Agreements (SLAs; think of these as deadlines) on a workflow definition's events, and workflow submissions are accordingly tracked and reported as on time or overdue. \subsection{Service Level Agreements (SLAs)}\label{service-level-agreements-slas} SLAs define the deadlines on a workflow process's events. They're like a contract between the workflow participants and Users submitting workflow items. SLAs can be formally agreed-upon deadlines between you and your customers, or informally created to meet internal goals, tracking events such as: \begin{itemize} \tightlist \item Total time to resolution \item Time to complete a specific workflow task \end{itemize} \begin{figure} \centering \includegraphics{./images/workflow-add-sla.png} \caption{Use Service Level Agreements (SLAs) to define how workflow metrics are reported.} \end{figure} For each workflow event to track, set the SLA duration and when the timer should be paused, if at all. \subsection{Workflow Reports}\label{workflow-reports-1} Once an SLA is set, workflow submissions that trigger the SLA timer are automatically reported on by the workflow metrics framework, and given the status \emph{on time} or \emph{overdue}. \begin{figure} \centering \includegraphics{./images/workflow-report.png} \caption{See Workflow Reports generated based on your SLAs.} \end{figure} See the article on \href{https://help.liferay.com/hc/en-us/articles/360029042071-Workflow-Metrics-The-Service-Level-Agreement-SLA-}{Workflow Metrics} to learn more about SLAs and available reports. \section{Control Panel Reorganization}\label{control-panel-reorganization} The Workflow section of the Control Panel is now a top-level section with its own subcategories: Process Builder and Submissions (Metrics, too, if you're a DXP subscriber). In Liferay DXP 7.1 Workflow was nested under Control Panel → Configuration. \begin{figure} \centering \includegraphics{./images/workflow-menu.png} \caption{Workflow has a top-level entry in the Control Panel.} \end{figure} \section{Workflow Definition Permissions: System Settings}\label{workflow-definition-permissions-system-settings} The Workflow System Settings category (Control Panel → Configuration → System Settings) has a new system scoped configuration entry: \emph{Workflow Definitions}. There's just one configuration option, but it's important: Enabling it gives administrators permission to publish workflows and scripts. \begin{figure} \centering \includegraphics{./images/workflow-publication-permission.png} \caption{Explicit permission must be granted before administrators are allowed to publish and edit workflow definitions.} \end{figure} Create your own workflows from scratch or leverage existing workflows. \section{Embedded Workflows}\label{embedded-workflows} In addition to the Single Approver definition, there are some workflow definitions that ship with Liferay DXP but are not pre-installed, since they're primarily included for test cases. They can be found in the Liferay source code in \begin{verbatim} /modules/apps/portal-workflow/portal-workflow-kaleo-runtime-impl/src/main/resources/META-INF/definitions \end{verbatim} They're also in your Liferay installation. Open your Liferay installation's \texttt{osgi/portal/com.liferay.portal.workflow.kaleo.runtime.impl.jar}, and then find and open the \texttt{com.liferay.workflow.kaleo.runtime.impl-{[}version{]}.jar}. The definitions are in the \texttt{META-INF/definitions} folder. \chapter{Activating Workflow}\label{activating-workflow} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Assets are integrated with the \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{workflow framework} if their content is meant to be sent through review processes. Since this is most often the case, there are many out of the box assets that integrate with workflow. In this article, learn how to select a workflow for use with each of these workflow-enabled assets. \section{Workflow Assets}\label{workflow-assets} Activate a workflow for these assets in Control Panel → Workflow → Process Builder, in the Configuration tab: \begin{itemize} \tightlist \item Blogs Entry \item Calendar Event \item Comment \item Knowledge Base Article \item Message Boards Message \item Page Revision \item User \item Web Content Article \item Wiki Page \end{itemize} Activate workflow for these assets at the Site level in Site → Configuration → Workflow: \begin{itemize} \tightlist \item Blogs Entry \item Calendar Event \item Comment \item Knowledge Base Article \item Message Boards Message \item Page Revision \item Web Content Article \item Wiki Page \end{itemize} What's the difference between the Site workflow configuration and the Control Panel Workflow configuration? As with most scoped configurations, the higher level setting (in the Control Panel) sets the default behavior. It's overridden at the more granular level (in the Site menu). User doesn't appear on the Site list because adding users is strictly a portal-wide administrator activity. Only assets that can be added and configured at the Site level (for example, those that are accessed from the Site menu) have workflow configuration controls at the Site level. \section{Activating Workflow in Applications}\label{activating-workflow-in-applications} Some assets that are workflow-enabled are activated in their respective application: Activate workflow for Web Content Folders from the folder settings menu: \begin{figure} \centering \includegraphics{./images/workflow-web-content-folder.png} \caption{Activate workflow on Web Content folders from the folder's edit screen.} \end{figure} Activate workflow for Documents and Media Folders from the folder settings: \begin{figure} \centering \includegraphics{./images/workflow-dm-folder.png} \caption{Activate workflow on Documents and Media folders from the folder's edit screen.} \end{figure} Enable workflow on Dynamic Data List entries in each list's Add form: \begin{figure} \centering \includegraphics{./images/workflow-ddl.png} \caption{Activate workflow for each individual Dynamic Data List.} \end{figure} Activate workflow for each individual form's entries from the Form Settings screen: \begin{figure} \centering \includegraphics{./images/workflow-form.png} \caption{Activate workflow on each form's entries from the Form Settings window.} \end{figure} \section{Workflow Behavior}\label{workflow-behavior} Most of the resources listed above behave just as you might expect with workflow activated: The Publish button for the resource's \emph{Add} form is replaced by a \emph{Submit for Publication} button, and instead of instant publication, the asset has its status set as \emph{Pending} and must proceed through the workflow before publication. \begin{figure} \centering \includegraphics{./images/submit-for-publication.png} \caption{Instead of a Publish button, a Submit for Publication button appears for workflow-enabled resources.} \end{figure} Page revisions are slightly different. Page revisions only occur in \href{/docs/7-2/user/-/knowledge_base/u/staging-content}{staging environments} that have Page Versioning enabled. When a Page Variation or Site Page Variation is created, its creator must click \emph{Submit for Publication} at the top of the page, and the variation must be approved in the workflow before it can be published to the live Site. \begin{figure} \centering \includegraphics{./images/page-revision-submission.png} \caption{With workflow enabled on Page Revisions, the Site administrator must submit their page variation for publication before it can go live.} \end{figure} \chapter{Managing Workflows}\label{managing-workflows} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Like other resources, workflow definitions can be added, edited, and deleted. But that's just the beginning of workflow management. \begin{itemize} \item Because workflow definitions can be complex works in progress, they can be versioned. \item Unpublished drafts can be saved. \item Because workflow definitions are XML files, they're portable. Thus, they can also be uploaded. \end{itemize} Start by learning basic workflow management. \section{Workflow Definition Publication Permissions}\label{workflow-definition-publication-permissions} Users with permission to edit or publish workflow definitions can add \href{/docs/7-1/user/-/knowledge_base/u/leveraging-the-script-engine-in-workflow}{Groovy scripts} to the workflow. Access to the scripting engine means access to the Java Virtual Machine (JVM) of the server. Users who publish (or edit) workflow definitions containing scripts, therefore, can get access to any data within the reach of the JVM, such as data contained in a separate \href{/docs/7-1/user/-/knowledge_base/u/virtual-instances}{Virtual Instance} of Liferay DXP itself. Because of this far-reaching access, permission to create or edit workflow definitions is limited to Regular Administrators of the Default Virtual Instance. To grant Users with these Roles the workflow publication access in additional Virtual Instances, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Make sure you understand the access you're granting these admins. \item Navigate to Control Panel → System Settings → Workflow → Workflow Definition. \item Check the box for the setting \emph{Allow Administrators to Publish and Edit Workflows}. \end{enumerate} This only applies to Virtual Instances that have been added to the system. The Default Virtual Instance provides workflow publication access to Regular Administrators (via Control Panel → Configuration → Workflow), and, if running Liferay DXP, to Site Administrators and other Users with access to the Kaleo Forms Admin applications. \section{Adding, Editing, and Deleting}\label{adding-editing-and-deleting} To add a workflow definition, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Workflow → Process Builder. \item In the default view (Workflows), click the Add button (\includegraphics{./images/icon-add.png}). \item From here you're either \href{/docs/7-2/reference/-/knowledge_base/r/crafting-xml-workflow-definitions}{writing an XML definition}, \href{https://customer.liferay.com/documentation/7.2/admin/-/official_documentation/portal/workflow-designer}{designing a definition in in the visual designer (DXP only)}, or \hyperref[uploading-workflow-definitions]{uploading an existing definition}. \end{enumerate} To edit a definition, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Workflow → Process Builder. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for the workflow, and click \emph{Edit}. \end{enumerate} To delete a definition, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Workflow → Process Builder. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) for the workflow, and click \emph{Unpublish}. A published workflow cannot be deleted, so you must unpublish its workflow definition first. You can't unpublish a definition if it's activated for an asset. First dissociate the workflow definition from any assets that use it. See \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{here} for more information. \end{enumerate} \section{Uploading Workflow Definitions}\label{uploading-workflow-definitions} If you have a local XML definition file (perhaps you want to create a new workflow based on one of the \href{/docs/7-2/user/-/knowledge_base/u/workflow\#embedded-workflows}{embedded workflows}), upload it to Liferay DXP: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to Control Panel → Workflow → Process Builder. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Name the workflow. \item In the \emph{Source} tab, click the hyperlink \emph{import a file} in the sentence \texttt{Write\ your\ definition\ or\ import\ a\ file} \item Find the XML file and upload it. Once uploaded, the definition's XML appears in the workflow editor. \item If it's ready to publish, click \emph{Publish}. Otherwise, \emph{Save} it and it stays Unpublished. \end{enumerate} What's the difference between saving and publishing? \section{Published Versus Unpublished}\label{published-versus-unpublished} The difference between a published and unpublished workflow is important: \textbf{Published:} Validation is complete, and the workflow can be assigned to assets. \textbf{Unpublished:} Validation is not performed on the unpublished workflow, and it cannot be assigned to assets until it's published. \section{Workflow Versions}\label{workflow-versions} You're making a simple edit to a workflow, when suddenly you remember you have a meeting with your boss. Quickly you save the workflow and hurry off to your meeting. Congratulations! You were promoted to Director of Business Productivity! You have no time to edit workflows now, so your colleague must finish editing and publishing the workflow. Unfortunately, in all the excitement of your promotion, you forgot what you changed in the workflow. It's best to revert to the prior version and start editing it from scratch. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the workflow editor. Go to \emph{Control Panel} → \emph{Workflow} → \emph{Process Builder}, and select the workflow from the list (click its title to open the editor). \item Click the \emph{Information} button (\includegraphics{./images/icon-information.png}) \item There are two information panel sections: Details and Revision History. The Details screen shows information about the creation of the workflow and its last modification, and a summary of the total modifications. The Revision History screen shows the workflow's current and prior, restorable versions. To view an old workflow or to restore it if you're sure it's the right version, click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select either \emph{Preview} or \emph{Restore}. \item When you click \emph{Restore} and see the success message, the prior version is the current version of the workflow. You can now edit the restored version of the workflow. \item If edits are necessary, edit and click \emph{Update}. This creates another version of the workflow. \end{enumerate} \begin{figure} \centering \includegraphics{./images/workflow-revisions.png} \caption{View and restore prior versions of a workflow.} \end{figure} Alternatively, you can refer to the \href{/docs/7-2/user/-/knowledge_base/u/workflow\#embedded-workflows}{embedded definitions} to get workflow definition ideas. \chapter{Reviewing Assets}\label{reviewing-assets} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} User interaction is required at each workflow process Task Node. Where do users complete tasks? In the \emph{My Workflow Tasks} application of the User menu. \begin{figure} \centering \includegraphics{./images/workflow-myworkflow-tasks-menu.png} \caption{Users manage workflow tasks from their My Workflow Tasks widget.} \end{figure} \section{Asset Submission to Workflow}\label{asset-submission-to-workflow} If an Asset has a workflow activated, when a user clicks Submit for Publication, the workflow definition determines the next step. A user assigned a Role associated to the workflow task receives a Notification indicating that there is a workflow task to complete. \textbf{Workflow Assignees Have Implicit Asset Permissions:} Users with permission to execute a workflow task (e.g., Users with the Portal Content Reviewer Role) have full resource action permissions over the assets they can review. These permissions apply in the My Workflow Tasks widget in the User's personal page and anywhere else actions on the Asset can be performed. For example, consider a User with two permissions: \begin{itemize} \tightlist \item The Portal Content Reviewer Role enables Users to review workflow submissions and grants edit and delete permissions on the content they're reviewing. \item Users also have permission to view Web Content Articles in the Site's \emph{Content} section. \end{itemize} Neither permission explicitly grants the User management permissions on Web Content Articles. Users cannot normally edit or delete a Web Content Article, for example. However, if a Web Content Article is sent to the workflow, Users can access the Web Content Article for review (in their \emph{Assigned to Me} or \emph{Assigned to my Roles} section of My Workflow Tasks), and they can edit or delete the content while reviewing it in the workflow. While it's in the status \emph{Pending}, they can also edit or delete the article from Site Administration → Content → Web Content because of their implicit permissions granted by the workflow system. This additional permission is temporary, and the normal resource permissions are activated once the Web Content Article exits the workflow process (for example, it's rejected or approved). \begin{figure} \centering \includegraphics{./images/workflow-approved-permissions.png} \caption{A User with VIEW permission on Web Content cannot manage Approved Articles.} \end{figure} \begin{figure} \centering \includegraphics{./images/workflow-pending-permissions.png} \caption{A User with access to Web Content in the Workflow can manage Pending Articles.} \end{figure} \section{Assigning the Task}\label{assigning-the-task} Workflow Tasks can be completed only by certain users, based on the \href{/docs/7-2/reference/-/knowledge_base/r/workflow-task-nodes\#assignments}{Assignment}. All workflow tasks assigned directly to a user are listed in the My Workflow Task widget's \emph{Assigned to Me} tab. \begin{figure} \centering \includegraphics{./images/workflow-assigned-to-me.png} \caption{The assets assigned to a user are listed in \emph{Assigned to Me}.} \end{figure} If a workflow was assigned to a Role that the user occupies, the workflow's tasks appear in the \emph{Assigned to My Roles} tab. \begin{figure} \centering \includegraphics{./images/workflow-assigned-to-my-roles.png} \caption{The Assets assigned to Roles are listed in each associated user's \emph{Assigned to My Roles} tab.} \end{figure} To claim a task, the user must move the task into the \emph{Assigned to Me} tab. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Asset's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Assign to Me}. \item Add a comment in the pop-up box if necessary, and click \emph{Done}. \end{enumerate} Alternatively, assign the task to another user. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Asset's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Assign to \ldots{}}. \item Select the user to assign the task. \item Add a comment in the pop-up box if necessary, and click \emph{Done}. \end{enumerate} \section{Completing the Task}\label{completing-the-task} Once a task is assigned, it's ready to be completed. There's a fast way to send an asset along in the review process: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Assigned to Me} tab, click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Approve} or \emph{Reject}. Note that options names in this menu are identical to the workflow definition's \href{/docs/7-2/reference/-/knowledge_base/r/workflow-definition-nodes}{Transition} names. Your menu might have different options than the \emph{Approve} and \emph{Reject} options in the figure below. \item Enter a comment if desired and click \emph{Done}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/workflow-complete-task.png} \caption{Complete Tasks right from the \emph{Assigned to Me} list.} \end{figure} Here's how to get a closer look at the Asset before sending it along in the workflow: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the \emph{Assigned to Me} tab, click the title of the Asset to review. The Task screen appears showing details about the Asset: \begin{figure} \centering \includegraphics{./images/workflow-task-review.png} \caption{Inspect Assets before completing the Task.} \end{figure} \item Inspect the Asset to your liking (or even edit it if you have permission) and click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}). \item Send it along in the workflow by clicking one of the Transition names (for example, \emph{Approve} or \emph{Reject} in the Single Approver Definition). \end{enumerate} And, you're done. Once you've completed your workflow tasks, kick back and wait for more to come in. \chapter{Workflow Metrics: The Service Level Agreement (SLA)}\label{workflow-metrics-the-service-level-agreement-sla} A brand new feature in Liferay DXP 7.2, \emph{Workflow Metrics} gives insights into the time certain workflow events take to complete. To use it, set up deadlines on a workflow process's events. These deadline configurations are referred to as SLAs (Service Level Agreements). Once defined, Workflow Reports measure compliance with the SLAs. \noindent\hrulefill \textbf{Requires Elasticsearch:} To use Workflow Metrics, you must be using Elasticsearch to index your @product data. Read \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{here} to learn about installing Elasticsearch. \noindent\hrulefill SLAs define the deadlines on a workflow process's events. They're like a contract between the workflow participants and Users submitting workflow items. Workflow Reports shows data for all processes with SLAs, including each workflow item's SLA status: on time or overdue. \noindent\hrulefill \textbf{Editing a Workflow with SLAs:} Editing a workflow (e.g., removing nodes, editing a task name) with SLAs defined on it may invalidate the SLA for items already in the workflow/SLA pipeline. \textbf{Creating or Editing SLAs for Active Processes:} Editing an SLA's duration or defining a new SLA while items are already in the workflow process causes a recalculation for all instances currently in the workflow. Completed workflow instances are not recalculated. \noindent\hrulefill \section{Adding SLAs}\label{adding-slas} To add an SLA, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Control Panel → Workflow → Metrics. \item Click on the title of the Process. The Reports UI for the process is displayed. \item If there's no SLA for the process, a warning message stating as much appears. Click the \emph{Add a new SLA} link from the warning to access the New SLA form directly. Alternatively, click the Options (\includegraphics{./images/icon-options.png}) menu and select \emph{SLA Settings}. \begin{figure} \centering \includegraphics{./images/workflow-add-sla.png} \caption{Add SLAs to a workflow definition from the Metrics application.} \end{figure} \item On the SLAs screen, click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item In the New SLA form, Give the SLA a Name and Description. \item Define the time frame for the SLA, specifying three things: \begin{itemize} \tightlist \item Start: When the item makes it to the event defined here, the SLA timer begins counting. \item Pause: If there's an event in the workflow when time should stop counting, enter it here. For the Single Approver workflow, you might choose to pause the SLA timer when the item is in the Update task. \item Stop: Choose when the SLA is completed. If the item makes it to the Stop event before the defined SLA duration (the deadline), it's \emph{On Time} according to the SLA. If it fails to make it to the Stop event in the specified duration, it's \emph{Overdue}. \end{itemize} \item Define the duration (i.e., the deadline) for the SLA. Fill out at least one of the two time boxes. \textbf{Days:} Enter a whole number of days. \textbf{Hours:} Enter hours and minutes in the format HH:MM \item Once you click \emph{Save}, you'll see the SLA listed on the SLAs screen. \end{enumerate} \noindent\hrulefill \textbf{System Calendar:} By default, there's an internal calendar that assumes the SLA duration should continue counting all the time: in other words, 24 hours per day, seven days per week. If you need a different calendar format, find a friendly developer to create a custom calendar. Official docs will be written for this extension point, but the basic idea is to implement the \texttt{WorkflowMetricsSLACalendar} interface. New implementations of this service are picked up automatically by the Workflow Metrics applications, so it becomes available as soon as the module holding the service implementation is deployed. The interface has three methods to implement: \texttt{public\ Duration\ getDuration(\ \ \ \ LocalDateTime\ startLocalDateTime,\ LocalDateTime\ endLocalDateTime);} \texttt{public\ LocalDateTime\ getOverdueLocalDateTime(\ \ \ \ LocalDateTime\ nowLocalDateTime,\ Duration\ remainingDuration);} \texttt{public\ String\ getTitle(Locale\ locale);} See the \texttt{DefaultWorkflowMetricsSLACalendar} from the \texttt{portal-workflow-metrics-service} module for example code. 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, make the timer run for 9 hours per day, 5 days per week). \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/workflow-metrics-sla-list.png} \caption{Manage SLAs from the SLAs screen.} \end{figure} \subsection{Valid Start and Stop Events}\label{valid-start-and-stop-events} Any workflow task can be used as a start or end parameter for the SLA. When defining the tasks to act as the SLA's Start Events, choose between three events: \begin{itemize} \tightlist \item The start node \item Entry into a task \item Exit from a task \end{itemize} When defining the tasks to act as the SLA's Stop Events, choose between three events: \begin{itemize} \tightlist \item Entry into a task \item Exit from a task \item The end node \end{itemize} The SLA can be paused at any task that falls between the start node and the end node, and it's defined by setting the node(s) when the SLA should be paused. \emph{The SLA timer is paused the entire time a workflow item is in the specified node}. \subsection{Durations}\label{durations} Define the SLA durations in at least one of the available boxes (Days and Hours). Here are some examples: \begin{description} \tightlist \item[Example Duration: 1 day, 24 hours] Valid configuration --- Days: \emph{1} Invalid --- Hours: \emph{24:00}. The Hours box must not exceed \emph{23:59}. \item[Example Duration: 36 hours] Valid --- Days: \emph{1}, Hours: \emph{12:00} Invalid --- Days: \emph{1.5}. Only whole numbers are accepted. \item[Example Duration: 6.5 hours] Valid --- Hours: \emph{06:30} \end{description} Once your SLAs are configured, activate the workflow on an asset, stretch your fingers, and get ready for the submissions to roll in if you're one of the workflow assignees. You're on the hook to get those workflow items through the process within the SLA duration! \chapter{Workflow Metrics: Reports}\label{workflow-metrics-reports} As soon as you enter the Metrics screen (Control Panel → Workflow → Metrics) you see metrics on each workflow installed in the system. \begin{figure} \centering \includegraphics{./images/workflow-metrics-reports1.png} \caption{In this view, the only process with pending items is the Single Approver.} \end{figure} A table view of all installed workflow processes shows you how many items are Overdue, how many are On Time, and how many are Pending in the workflow process. There's more to Metrics than the overview report though. Get more detailed reports by clicking on one of the workflow processes. \noindent\hrulefill \textbf{Requires Elasticsearch:} To use Workflow Metrics, you must be using Elasticsearch to index your @product data. Read \href{/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch}{here} to learn about installing Elasticsearch. \noindent\hrulefill \section{Understanding Reports}\label{understanding-reports} The Reports UI has two main views, represented as tabs: \emph{Pending} (changed to \emph{Dashboard} as of 7.0 Service Pack 1---SP1) and \emph{Completed} (changed to \emph{Performance} in SP1). \emph{Pending/Dashboard} items are those currently in the workflow process, and include items untracked by the SLA. This might include items in the paused step of the workflow, or items that are outside the scope of the SLA duration. \emph{Completed/Performance} items show any item that has completed processing in the workflow. SP1 includes a new chart on this screen: \emph{Completion Velocity}. When you first click into the metrics for a specific process, you're presented with two valuable reports on pending items: the Pending Items overview and Workload by Step. \begin{figure} \centering \includegraphics{./images/workflow-metrics-reports2.png} \caption{See data on the Pending Items and the Workload by Step for a process.} \end{figure} \section{Pending Items}\label{pending-items} Pending Items shows you the overview of items by their SLA status. Drill down by clicking on any of the statuses to see the specific items that are enumerated in Pending Items. \section{Workload by Step}\label{workload-by-step} Workload by Step shows a breakdown of the items that are in each step of the workflow process, by their SLA status (Overdue or On Time). \section{Completed Items}\label{completed-items} Click the \emph{Completed} tab (\emph{Performance} on SP1) to see the items that have completed the workflow process. Workload by Step data doesn't make sense in this case, because by definition, these items are no longer in any workflow process step. Instead, there's a \emph{Completion Velocity} chart that shows the performance over time. \section{Completion Velocity}\label{completion-velocity} A line chart shows you the completion rate for the workflow process. The default display shows the number of completed workflow instances per day, for the last 30 days. \begin{figure} \centering \includegraphics{./images/workflow-reports-completion-velocity.png} \caption{View the completion rate of items in a workflow process over time.} \end{figure} The overall completion rate for the time period is displayed in the top right corner of the chart (as \emph{Inst/timeUnit}), while the trend-line is presented in the chart body. The overall performance metric and the chart body are updated when you select a new time period; the time unit changes depending on the total time period you're measuring. For some of the longer durations, the time unit is configurable: \begin{description} \tightlist \item[\textbf{Today}] Calculate \emph{Inst/Hour} from \emph{00:00}, or \emph{12:00 AM}, of the current day until the current time (rounded to the nearest whole hour). \item[\textbf{Yesterday}] Calculate \emph{Inst/Hour} From \emph{00:00-23:59}, or \emph{12:00 AM to 11:59 PM}, of the previous day. \item[\textbf{Last 7 Days}] Calculate \emph{Inst/Day}. The current day counts as 1 day, so this is from 6 days ago to the current day. \item[\textbf{Last 30 Days}] Calculate the \emph{Inst/Week} or the \emph{Inst/Day}. The current day counts as 1 day, so this is from 29 days ago to the current day. \item[\textbf{Last 90 Days}] Calculate the \emph{Inst/Month}, \emph{Inst/Week}, or \emph{Inst/Day}. The current day counts as 1 day, so this is from 89 days ago to the current day. \item[\textbf{Last 180 Days}] Calculate the \emph{Inst/Month} or \emph{Inst/Week}. The current day counts as 1 day, so this is from 179 days ago to the current day. \item[\textbf{Last Year}] Calculate the \emph{Inst/Month} or \emph{Inst/Week}. The current day counts as 1 day, so this is from 364 days (365 for a leap year) ago to the current day. \end{description} \section{Items View}\label{items-view} Hover over the status you're interested in, from either the \emph{Pending} or the \emph{Completed} tabs (on SP1, these tabs were renamed to \emph{Dashboard} and \emph{Completion}). Click into the All Items screen from the overview report and a more detailed table appears, including the following columns: \begin{description} \tightlist \item[\textbf{ID}] This is the workflow item's numeric identifier to the system. Importantly, you can click this to enter the Item Detail view. \item[\textbf{Item Subject}] This shows a human readable summary of the item, to help administrators identify the item. \item[\textbf{Process Step}] This identifies where the item is in the workflow. \item[\textbf{Created By}] This shows the user name of the submitting User. \item[\textbf{Creation Date}] This shows the date and time the item was submitted to the workflow. \end{description} The All Items view can be filtered so you can find the subset of items you want to analyze. \section{Filtering by SLA Status}\label{filtering-by-sla-status} Filter items based on whether they're Overdue, On Time, or Untracked. \begin{figure} \centering \includegraphics{./images/workflow-metrics-reports4.png} \caption{Filter by SLA status: Overdue, On Time, or Untracked.} \end{figure} \begin{description} \tightlist \item[\textbf{Overdue}] Overdue items have breached at least one SLAs defined deadline. \item[\textbf{On Time}] On Time items have not breached \emph{any} SLA deadline. \item[\textbf{Untracked}] Untracked items are items in the workflow process that aren't currently under the purview of an SLA. The can be in a task identified as a \emph{Pause} in the SLA, or perhaps outside the scope of the SLA entirely, if the SLA isn't defined for the entire process (Process Begins to Process Ends in the SLA Definition screen). \end{description} \section{Filtering by Process Status and Completion Period}\label{filtering-by-process-status-and-completion-period} Filter items based on whether they're Pending or Completed in the workflow process. If you filter by the Completed status, you'll get an additional filtering option: filter items by the Completion Period. Select from these time periods: \begin{itemize} \tightlist \item Today \item Yesterday \item Last 7 Days \item Last 30 Days (default) \item Last 90 Days \item Last 180 Days \item Last Year \item All Time \end{itemize} \begin{figure} \centering \includegraphics{./images/workflow-reports-process-status-period.png} \caption{Filter by Process Status and Completion Period.} \end{figure} \section{Filtering by Process Step}\label{filtering-by-process-step} Filter items based on where they are in the workflow definition. For example, in the Single Approver workflow process, you can choose to see a report including all items in the Review task. This is different for each workflow definition. \section{Combining Filters}\label{combining-filters} Use a combination of filters to find just the items you need to see. For example, below are all items in the Single Approver process's Review task that have the status Completed or Pending, whether On time or Overdue. Untracked items aren't shown. \begin{figure} \centering \includegraphics{./images/workflow-metrics-reports13.png} \caption{Combine filters to see just the items you want.} \end{figure} \section{Item Details}\label{item-details} To see the metrics for a single workflow process item, click the ID field while in the All Items view. A pop-up shows you more detailed information on the item. \begin{figure} \centering \includegraphics{./images/workflow-reports-item-detail.png} \caption{Item Details include SLA status information and whether the item is Resolved or Open.} \end{figure} From here you can view detailed information about the asset and even click \emph{Go to Submission Page}, which redirects you to the item's view in the Submissions section of the Control Panel. The top of the Item Detail view is important. It shows you the information about the due date for the item in the SLA, and its SLA completion status: \emph{Open} or \emph{Resolved}. \begin{description} \tightlist \item[\textbf{Open}] The defined SLA goals are not yet met. Open items can be of status Overdue or On Time. \item[\textbf{Resolved}] The defined SLA goals are completed. Resolved items can be of status Overdue or On Time. \end{description} From the overall metrics of a workflow process down to the details on a single item in the workflow, the new Workflow Metrics functionality gives you insights into the time it takes to \emph{get things done} in Liferay DXP. \chapter{Workflow Designer}\label{workflow-designer} With the proper permissions, users can publish assets. Even if your enterprise has the greatest employees in the world, many of the items they want to publish must still be reviewed, for a variety of reasons. The Workflow Designer lets you design workflow definitions so your assets go through a review process before publication. With the Workflow Designer, you develop workflow definitions using a convenient drag and drop user interface. You don't need to be familiar with writing XML definitions by hand. Some of the features can be enhanced, however, if you're familiar with Groovy, a supported Java-based scripting language. All that is to say, don't be scared off when you come to a block of code in these articles. Just decide if you need the feature and find someone familiar with Java or Groovy to help you out. \noindent\hrulefill \textbf{Note:} By default, only one workflow definition is installed: the Single Approver Workflow definition. What you might not know is that you have access to several others too. Look in \texttt{{[}Liferay\_Home{]}/osgi/marketplace/} and find the \texttt{Liferay\ Forms\ and\ Workflow\ -\ Liferay\ Portal\ Workflow\ -\ Impl.lpkg}. Open it, find the \texttt{com.liferay.portal.workflow.kaleo.runtime.impl-{[}version{]}.jar}, and look in its \texttt{META-INF/definitions} folder. You'll see the following workflow definitions: \begin{verbatim} category-specific-definition.xml legal-marketing-definition.xml single-approver-definition.xml single-approver-definition-scripted-assignment.xml \end{verbatim} To work with any of these definitions in the Workflow Designer, extract them from the JAR file first. Once you have the XML files locally, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add a new workflow. Go to Control Panel → Workflow → Process Builder, and click the Add button (\includegraphics{./images/icon-add.png}). \item Go to the Source tab. \item Click \emph{import a file} and upload the XML file. \item Name the definition appropriately, and click either \emph{Save} (to save it as a draft) or \emph{Publish} (see below for more information on saving and publishing). \end{enumerate} Now you can begin exploring or modifying the definition. \noindent\hrulefill It's time to start exploring the Workflow Designer and its features. \chapter{Managing Workflows with the Workflow Designer}\label{managing-workflows-with-the-workflow-designer} The Workflow Designer gives you an intuitive interface for creating workflow definitions, from the simplest approval processes to the most complex business processes you can imagine. It features a drag and drop interface, workflow definition versioning, and a graphical representation of definitions and their nodes. Without the Workflow Designer, you'd have to hand-craft your entire workflow definition in XML. With the Workflow Designer, you might never need to look at a single line of XML. Of course, the Workflow Designer also lets you directly manipulate the XML (using the \emph{Source} tab) if you find it convenient. \section{Adding New Workflow Definitions with the Workflow Designer}\label{adding-new-workflow-definitions-with-the-workflow-designer} Access the Workflow Designer by going to the Control Panel → Workflow → Process Builder. Click the Add icon (\includegraphics{./images/icon-add.png}). Give the workflow definition a title and you're ready to start designing your workflow. \begin{figure} \centering \includegraphics{./images/workflow-designer-canvas.png} \caption{The Workflow Designer's graphical interface makes designing workflows intuitive.} \end{figure} \section{Saving and Publishing Workflow Definitions}\label{saving-and-publishing-workflow-definitions} First, look below the canvas to see the buttons that let you \emph{Save} or \emph{Publish}. Saving the definition as a draft lets you save your work so it's not lost (due to a timeout, for example). It won't be published (and assignable to assets), and it won't be considered a version until the Publish button is clicked. Each time you save the workflow as a draft, a new revision is added to the Revision history. To see the Revision history and manage workflow versions, open the Info sidebar (\includegraphics{./images/icon-information.png}) and click \emph{Revision History}. \begin{figure} \centering \includegraphics{./images/workflow-designer-definitions.png} \caption{View a list of the current workflows that can be edited in the Workflow Designer.} \end{figure} \section{Adding Nodes}\label{adding-nodes} A new workflow is already populated with a start node, an end node, and a transition between them. To make the workflow the way you want it, add nodes to the workflow. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \emph{Drag} a node from the \emph{Nodes} palette on the right of the designer and \emph{drop} it on the canvas. \item You'll see it's not connected to other nodes by a transition, so right now it can't be used in the workflow. Delete the existing transition and then you can make new transitions to direct the \emph{flow} of your workflow (see more about transitions below if you're not sure what they're for or how to use them in the Workflow Designer). \end{enumerate} Alternatively, start by deleting the default transition, then click the edge of the start node, drag a new transition from the start node to a blank spot on the canvas, and release it. You're prompted to create a node at that spot, because you can't have a transition without a starting point and an ending point on a node. \begin{figure} \centering \includegraphics{./images/workflow-designer-add-node.png} \caption{You can add a node by creating a transition that ends at a blank spot on your Designer canvas.} \end{figure} That's it. Of course, if you drag, say, a \emph{Task} node onto the canvas, it must be configured. \section{Node Settings}\label{node-settings} Now you know how to add nodes to the workflow definition. By default you have three things added to your canvas: a start node, a transition, and an end node. Think of the \emph{EndNode} as the point in the workflow where an asset reaches the \emph{Approved} status. The \emph{StartNode} is where the asset goes from the \emph{Draft} status to \emph{Pending}. You might decide to name your nodes to reflect what's happening in each one. To name a node, double click it, and its \emph{Settings} appear. Then double click the value of the \emph{Name} property and you can edit the name. Click \emph{Save} when you're done. \begin{figure} \centering \includegraphics{./images/workflow-designer-node-settings.png} \caption{You can edit a node's settings.} \end{figure} Of course, there's more you can do besides changing node names. Actions, Notifications, and Assignments can be used to make your workflow definition useful and interactive. Keep reading to learn about these features. \section{Related Topics}\label{related-topics-3} \href{/docs/7-2/user/-/knowledge_base/u/kaleo-forms}{Kaleo Forms} \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{Activating Workflow} \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Liferay's Workflow Framework} \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists} \chapter{Workflow Definition Nodes}\label{workflow-definition-nodes} Once you know the basics of \href{/docs/7-2/user/-/knowledge_base/u/workflow-designer}{creating workflow definitions} with the workflow Designer, you can get into the details. In this tutorial you'll learn about Actions and Notifications, two important features your workflow nodes can use. You'll also learn how to affect the processing of the workflow using Transitions, Forks, Joins, and Conditions. There are several node types you can use in workflow definitions: \begin{itemize} \tightlist \item Task nodes \item Fork and Join nodes \item Condition nodes \item Start nodes \item End nodes \item State nodes \end{itemize} Because they're the most complex node, and often the meat of your workflow definitions, Task nodes are covered \href{/docs/7-2/user/-/knowledge_base/u/creating-tasks-in-workflow-designer}{separately}. Fork, Join, and Condition nodes are discussed, along with Transitions, in a tutorial on \href{/docs/7-2/user/-/knowledge_base/u/affecting-the-processing-of-workflow-definitions}{workflow processing}, since they're used for affecting the processing of the workflow. This tutorial discusses State nodes, Start nodes, and End nodes, along with Actions and Notifications. \section{Node Actions and Notifications}\label{node-actions-and-notifications} Any node can have Actions and Notifications. \section{Actions}\label{actions} Actions do additional processing before entering the node, after exiting a node, or once a task node is assigned. They're configured by accessing a node's Properties tab, then double clicking \emph{Actions}. \begin{figure} \centering \includegraphics{./images/workflow-designer-action.png} \caption{You can add an Action to a Task node.} \end{figure} The Single Approver workflow contains an Update task with an action written in Groovy that sets the status of the asset as \emph{denied}, then sets it to \emph{pending}. \begin{verbatim} import com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("denied"), workflowContext); WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("pending"), workflowContext); \end{verbatim} Why would the action script first set the status to one thing and then to another like that? Because for some assets, the \emph{denied} status sends the asset creator an email notification that the item has been denied. The end node in your workflow definition has an action configured on it by default, on entry to the end node: \begin{verbatim} import com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("approved"), workflowContext); \end{verbatim} This is a Groovy script that updates the status to \emph{approved}, since that's usually the goal of a workflow process. You can do something simple like the actions above, or you can be as creative as you'd like. It's good to assign a task to a user, and it's even more useful if the user is notified about workflow tasks. \section{Notifications}\label{notifications} Notifications are sent to tell task assignees that the workflow needs attention or to update asset creators on the status of the process. They can be sent for tasks or any other type of node in the workflow. To set up notifications, double click on \emph{Notifications} in a node's Properties tab and create a notification. \begin{figure} \centering \includegraphics{./images/workflow-designer-notification.png} \caption{You can send a Notification from a Task node.} \end{figure} You must specify the Notification Type, and you can choose User Notification or Email. You can use Freemarker if you need a template, or you can choose to write a plain text message. \noindent\hrulefill \textbf{Note:} Instant Messenger and Private Message also appear as Notification Type options, but these are non-functional and will be removed in a future version. \noindent\hrulefill Here's a basic Freemarker template that reports the name of the asset creator and the type of asset in the notification: \begin{verbatim} ${userName} sent you a ${entryType} for review in the workflow. \end{verbatim} You can also choose to link the sending of the notification to entry into the node (On Entry), when a task is assigned (On Assignment), or when the workflow processing is leaving a node (On Exit). You can configure multiple notifications on a node. Commonly, the assignment and notification settings are teamed up so a user receives a notification when assigned a task in the workflow. To do this, choose \emph{Task Assignees} under Recipient Type when configuring the notification. \noindent\hrulefill \textbf{Note:} The \emph{from name} and \emph{from address} of an email notification are configurable via portal properties. Place these settings into a \texttt{portal-ext.properties} file, in your Liferay Home folder. Then restart the server: \begin{verbatim} workflow.email.from.name= workflow.email.from.address= \end{verbatim} These can also be set programmatically into the \texttt{WorkflowContext}, and the programmatic setting always takes precedence over the system scoped portal property. \noindent\hrulefill \section{Start and End Nodes}\label{start-and-end-nodes} Start and end nodes kick off the workflow processing and bring the asset to its final, approved state. Often you can use the default start and end nodes without modification. If you want to do some more processing (in the case of a start node), add an action to the node using the Properties tab as described in the section on Actions above. End nodes have a default action that sets the workflow status to Approved using the Groovy scripting language: \begin{verbatim} import com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("approved"), workflowContext); \end{verbatim} Add more to the action script if you need to do additional processing. By default, there's a transition connecting the start node and end node, but you'll probably want to delete it, since most workflows don't proceed straight from the initial state to approved. \section{State Nodes}\label{state-nodes} State nodes can have Notifications and Actions. The default end node added by workflow Designer is a pre-configured state node that sets the workflow status to Approved. Perhaps you want to create a node that sets the status to \emph{Expired}. You could create a state node for it by dragging one onto your workflow Designer canvas, then configuring an action in it that sets the status to Expired. Here's what it would look like in Groovy: \begin{verbatim} import com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("expired"), workflowContext); \end{verbatim} Next, you'll learn to do parallel processing using fork and join nodes. \section{Related Topics}\label{related-topics-4} \href{/docs/7-2/user/-/knowledge_base/u/kaleo-forms}{Kaleo Forms} \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{Activating Workflow} \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Liferay's Workflow Framework} \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists} \chapter{Affecting the Processing of Workflow Definitions}\label{affecting-the-processing-of-workflow-definitions} Workflow definitions all contain nodes: a Start Node, and End node, and at least one Task node. You might know that for the workflow to progress from one node to the other, you need Transitions. In this tutorial you'll learn about using transitions to move the asset through the workflow from node to node, but you'll also learn about some other features that affect the processing of the workflow. \begin{itemize} \tightlist \item Transitions \item Forks \item Joins \item Conditions \end{itemize} Start by learning about the ever important Transition. \section{Transitions}\label{transitions} What are transitions? Workflow transitions connect one node to another. On exiting the first node, processing continues to the node pointed to by the transition. Every time you create an arrow from one node to another, Workflow Designer creates a transition. \begin{figure} \centering \includegraphics{./images/workflow-designer-single-approver.png} \caption{You connect nodes and direct workflow processing with transitions. The Single Approver workflow has transitions named Submit, Resubmit, Reject, and Approve.} \end{figure} Each node you add has a pop-up menu letting you delete the node. As you hover your mouse over the edges of a node, notice your mouse pointer changes to a cross. The cross indicates you can connect the current node to another node. Hold down your mouse button and drag the mouse to start drawing your transition to another node. If you stop before reaching the edge of the next node, a pop-up displays node types you can create and connect to on-the-fly. To connect with an existing node, continue dragging the connector to that node. When developing workflows in the Workflow Designer, make sure you go through all the transitions and name them appropriately. By default, these transitions get system generated names, so rename them to something more human-readable, as they're displayed to workflow users as links that send the item to the next step in the workflow. \begin{figure} \centering \includegraphics{./images/workflow-designer-transition-link.png} \caption{In the Single Approver workflow, a user in the Review task can choose to Approve or Reject the asset, which sends the asset either to the EndNode or to the Update task.} \end{figure} To rename transitions, click on the arrow representing the transition and use the Properties tab to set the name just like you do for a node. \section{Forks and Joins}\label{forks-and-joins} Sometimes you don't need to wait for one task to be completed before moving on to another one. Instead, you want to do two or more things at the same time. To do this, transition to a fork node, make two transitions from the fork to your parallel tasks, and then come back together using a join node. \begin{figure} \centering \includegraphics{./images/workflow-designer-fork-join.png} \caption{Forks and Joins are used to enable parallel processing in the workflow.} \end{figure} With a regular Join node, for the workflow to proceed beyond the join, the transition from both parallel executions must be invoked. However, if you use a Join XOR node instead, the workflow proceeds as long as the transition from one of the parallel executions is invoked. Keep in mind that you must balance your fork and join nodes. In other words, for every fork, there must be a join that brings the parallel workflow threads back together. \section{Conditions}\label{conditions-1} Sometimes you must inspect an asset or its execution context, and depending on the result, send it to the appropriate transition. You need a node for a script that concludes by setting a value to one of your transitions. \begin{figure} \centering \includegraphics{./images/workflow-designer-cat-specific-condition.png} \caption{The Category Specific Approval definition starts with a Condition node.} \end{figure} From the \emph{Category Specific Approval} (\texttt{category-specific-definition.xml}), this is the script in the condition node that starts the workflow (coming directly from the start node): \begin{verbatim} import com.liferay.asset.kernel.model.AssetCategory; import com.liferay.asset.kernel.model.AssetEntry; import com.liferay.asset.kernel.model.AssetRenderer; import com.liferay.asset.kernel.model.AssetRendererFactory; import com.liferay.asset.kernel.service.AssetEntryLocalServiceUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; import com.liferay.portal.kernel.workflow.WorkflowHandler; import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil; import java.util.List; String className = (String)workflowContext.get(WorkflowConstants.CONTEXT_ENTRY_CLASS_NAME); WorkflowHandler workflowHandler = WorkflowHandlerRegistryUtil.getWorkflowHandler(className); AssetRendererFactory assetRendererFactory = workflowHandler.getAssetRendererFactory(); long classPK = GetterUtil.getLong((String)workflowContext.get(WorkflowConstants.CONTEXT_ENTRY_CLASS_PK)); AssetRenderer assetRenderer = workflowHandler.getAssetRenderer(classPK); AssetEntry assetEntry = assetRendererFactory.getAssetEntry(assetRendererFactory.getClassName(), assetRenderer.getClassPK()); List assetCategories = assetEntry.getCategories(); returnValue = "Content Review"; for (AssetCategory assetCategory : assetCategories) { String categoryName = assetCategory.getName(); if (categoryName.equals("legal")) { returnValue = "Legal Review"; return; } } \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. You may be wondering what that \texttt{returnValue} variable is. It's the variable that points from the condition to a transition, and its value must match a valid transition in the workflow definition. This script looks up the asset in question, retrieves its \href{/docs/7-2/user/-/knowledge_base/u/defining-categories-for-content}{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} (the content-review task in the workflow), and if it does it goes through \emph{Legal Review} (the legal-review task in the workflow). Now you're equipped with the basic knowledge to design beautiful, effective workflows so that your assets can be properly reviewed before they're published in your sites. \section{Related Topics}\label{related-topics-5} \href{/docs/7-2/user/-/knowledge_base/u/kaleo-forms}{Kaleo Forms} \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{Activating Workflow} \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Liferay's Workflow Framework} \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists} \chapter{Creating Tasks in the Workflow Designer}\label{creating-tasks-in-the-workflow-designer} Task nodes have several parts and are the most complex parts of a workflow definition. 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: the assignment specifies who that User is. Commonly, task nodes contain Notifications, Assignments, and Actions (defined in scripts). See more about Notifications and Actions in the article on \href{/docs/7-2/user/-/knowledge_base/u/workflow-definition-nodes}{workflow nodes}. Task nodes and their assignments are more complex and deserve their own article (this one). To get started, drag and drop a task node on your workflow canvas if you haven't already. Open its Properties and give it a name. Then double click \emph{Actions} in the task's Properties pane. You can define a notification (often Task Assignee is appropriate), or write a Groovy script defining an action that's triggered for your task. Next learn about creating Assignments for your task nodes. \section{Assignments}\label{assignments} Workflow tasks must be completed by a User. You can choose how you want to configure your assignments. \begin{figure} \centering \includegraphics{./images/workflow-designer-assignment.png} \caption{You can add an Assignment to a Task node.} \end{figure} You can choose to add assignments to specific Roles, 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. 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, 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 still 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 configured, 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 you want to use for defining your workflow assignment. \end{itemize} How do you go from finding the resource action to using it in the workflow? Use the Workflow Designer's interface for setting up a resource action assignment. When configuring your task node's Assignment, select Resource Actions as the Assignment Type, then specify the Resource Actions to use for the assignment (for example, UPDATE). \begin{figure} \centering \includegraphics{./images/workflow-designer-resource-action-assignment.png} \caption{Configure resource action assignments in the Workflow Designer.} \end{figure} Here's what the assignment looks like in the Source (Workflow XML) tab: \begin{verbatim} UPDATE \end{verbatim} As usual, assign the workflow to the appropriate workflow enabled asset. 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} You can determine the probable resource action name from the permissions screen for that 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{Scripted Assignments}\label{scripted-assignments} You can also use a script to manage the assignment. Here's the script for the Review task assignment in the Scripted Single Approver workflow definition (\texttt{single-approver-definition-scripted-assignment.xml}): \begin{verbatim} import com.liferay.portal.kernel.model.Group; import com.liferay.portal.kernel.model.Role; import com.liferay.portal.kernel.service.GroupLocalServiceUtil; import com.liferay.portal.kernel.service.RoleLocalServiceUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.workflow.WorkflowConstants; long companyId = GetterUtil.getLong((String)workflowContext.get(WorkflowConstants.CONTEXT_COMPANY_ID)); long groupId = GetterUtil.getLong((String)workflowContext.get(WorkflowConstants.CONTEXT_GROUP_ID)); Group group = GroupLocalServiceUtil.getGroup(groupId); roles = new ArrayList(); Role adminRole = RoleLocalServiceUtil.getRole(companyId, "Administrator"); roles.add(adminRole); if (group.isOrganization()) { Role role = RoleLocalServiceUtil.getRole(companyId, "Organization Content Reviewer"); roles.add(role); } else { Role role = RoleLocalServiceUtil.getRole(companyId, "Site Content Reviewer"); roles.add(role); } user = null; \end{verbatim} Don't let all that code intimidate you. It's just assigning the task to the \emph{Administrator} Role, then checking whether the \emph{group} of the asset is an Organization and assigning it to the \emph{Organization Content Reviewer} Role if it is. If it's not, it's assigning the task 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. \section{Related Topics}\label{related-topics-6} \href{/docs/7-2/user/-/knowledge_base/u/kaleo-forms}{Kaleo Forms} \href{/docs/7-2/user/-/knowledge_base/u/activating-workflow}{Activating Workflow} \href{/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework}{Liferay's Workflow Framework} \href{/docs/7-2/user/-/knowledge_base/u/dynamic-data-lists}{Dynamic Data Lists} \chapter{Kaleo Forms}\label{kaleo-forms} Business processes are often form-based and workflow-driven. They start with entered data and progress by sending that data to other people or groups. Then it's processed in some way (for example, further data is entered or approval is granted), and the process moves on until completion, when each interested party has seen and manipulated the data. To write an app for each of these processes is laborious. It's much better to have a tool for quickly defining a process to suit each use case. The process architect must define both the data that gets collected and the process the data moves through to reach its final state. To accomplish this, Liferay DXP already includes the \href{/docs/7-2/user/-/knowledge_base/u/creating-data-definitions}{Dynamic Data Lists app} for defining forms, and the \href{/docs/7-2/user/-/knowledge_base/u/workflow-designer}{Workflow Designer app} for designing workflows. The Kaleo Forms solution combines the features of these apps, letting you use a single UI to design an integrated process for sending forms through a workflow. \section{Creating Kaleo Forms Process}\label{creating-kaleo-forms-process} To start creating a Kaleo Forms Process you need to get to Kaleo Forms Admin: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} (your Site's menu) → \emph{Content \& Data} → \emph{Kaleo Forms Admin}. The Kaleo Forms app appears with a list of any defined processes. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) to open the New Process Wizard. \item Give the process a name and a description and click \emph{Next}. \begin{figure} \centering \includegraphics{./images/kaleo-forms-add.png} \caption{Add a Kaleo Forms Process to link a form with a workflow definition.} \end{figure} \item Define the fields that can appear in your process's forms. There are two ways to do this: \begin{itemize} \item Use an existing field set. Click the field set's Actions button (\includegraphics{./images/icon-actions.png}) and select \emph{Choose}. \item Create a new field set/data definition. Click the \emph{Add Field Set} button. If you need help with this, see the documentation on \href{/docs/7-2/user/-/knowledge_base/u/creating-data-definitions}{creating data definitions}. After creating the field set, select it as you would an existing field set. \end{itemize} Click \emph{Next} to move to the wizard's next step. \begin{figure} \centering \includegraphics{./images/kaleo-forms-fields.png} \caption{Define and choose your form's fields.} \end{figure} \item Select a workflow to use for your forms. To do this, click the workflow's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Choose}. You can also edit an existing workflow or create a new one: \begin{itemize} \item To edit a workflow, click its \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Edit}. \item To begin creating a new workflow, click the \emph{Add Workflow} button. \end{itemize} In either case, you use the same UI to edit/create the workflow. This UI is called \href{/docs/7-2/user/-/knowledge_base/u/workflow-designer}{Workflow Designer}. It lets you create your workflow graphically instead of via code. Once you select a workflow to use with your forms, click \emph{Next}. \begin{figure} \centering \includegraphics{./images/kaleo-forms-spa-order-definition.png} \caption{This example workflow has three tasks that happen sequentially.} \end{figure} \item Select or create a form to use for each workflow task. To do this, click each task's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Assign Form}. On the screen that appears, select an existing form or click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and to create one. Click \emph{Save} when you're finished. Your process is done and appears in Kaleo Forms Admin's table. \begin{figure} \centering \includegraphics{./images/kaleo-forms-task-forms.png} \caption{Assign a form to each task in the workflow, and for the initial state.} \end{figure} \end{enumerate} \section{Adding Records to a Process}\label{adding-records-to-a-process} To add records to a process, click it in Kaleo Forms Admin and then click the \emph{Add} button (\includegraphics{./images/icon-add.png}). This brings up the form you assigned to the workflow's initial state. Fill it out and click \emph{Save}. Once submitting the initial form, the workflow engine then takes over and moves through each task in the workflow. Whatever Users or Roles you assigned to the tasks receive notifications, and the task appears in the \emph{Assigned to My Roles} section of the My Workflow Tasks app. A notification also appears in the Notifications app. Once in the task, the user views and approves the form or clicks the \emph{Edit} button. At this point, the workflow task forms you created come into play. Each assigned user fills out the form, saves it, and sends it along in the workflow. \chapter{Segmentation and Personalization}\label{segmentation-and-personalization} Liferay's Segmentation and Personalization shows the right content to the right people at the right time. It provides the tools you need to manage different audiences and dynamically provide personalized experiences for people using your site. For example, if you're creating a campaign to promote new financial service products, you need a way to display offers to customers who are likely to be interested in those offers. You don't want to display information on a basic free checking account for an ``advanced'' customer who carries a high balance across several types of accounts, but you do want to show that information to a visitor who entered the site through a landing page from a promotion at a local college. At the same time, you probably don't want to recommend options for optimizing retirement account contributions to the college student, but the other customer might be a great target for that campaign. By using data like user attributes or visitor interactions, you can dynamically target relevant content to your site's guests. \section{Defining Segments}\label{defining-segments} The first part of the equation is defining the types of segments that you need. You can create Segments to capture every case. Segments are composed of different criteria. In the previous example you might have a segment for \emph{Free Checking Account Prospects} that contains criteria based on user data, like customers that don't currently have an open checking account; or based on user behavior, like visitors who came to the site through specific channels. To learn more about Segmentation options, see the \href{/docs/7-2/user/-/knowledge_base/u/the-segment-editor}{overview of the Segment editor}, practice \href{/docs/7-2/user/-/knowledge_base/u/creating-user-segments}{creating basic Segments}, or create more complicated \href{/docs/7-2/user/-/knowledge_base/u/creating-segments-with-custom-fields-and-session-data}{Segments with custom fields and session data}. \section{Integrating Segments with Analytics Cloud}\label{integrating-segments-with-analytics-cloud} There are two different stories that User Segments can tell. One is targeting content to specific audiences that encourages engagement and positive user experiences. The other is defining groups of users and visitors to analyze their behavior and interactions with your site. To tell the second story, you must integrate with Analytics Cloud. Analytics Cloud is a Liferay service that provides in-depth information on who uses your site and how they use it. Analytics Cloud is a key component to fully utilizing Segments and Personalization, since it enables you to see the full picture of how users and visitors on your site behave and interact with both standard and targeted content. You can learn more about this in \href{https://help.liferay.com/hc/en-us/articles/360029041751-Using-Analytics-Cloud-With-User-Segments}{Using Analytics Cloud with User Segments}. \section{Personalizing Experiences}\label{personalizing-experiences} The most important piece of the puzzle isn't defining groups or analyzing user behavior. It's the final step of using the data to provide users and site visitors with the best possible experience, and driving campaigns and content engagement. If you strategically create segments, you can then use that to enhance user experiences, and make sure that users see content targeted to them. Content Page Personalization and Content List Personalization are two key aspects of this. \section{Content Page Personalization}\label{content-page-personalization} Content Page Personalization dynamically changes the page layout and content based on who is viewing the page. You can create \emph{Experiences} for any \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{Content Page} which provide different text, images, widgets, and even different layouts based on the segment criteria of the user viewing the page. You can see a step by step demonstration of this in \href{/docs/7-2/user/-/knowledge_base/u/content-page-personalization}{Content Page Personalization}. \section{Content Set Personalization}\label{content-set-personalization-1} \href{/docs/7-2/user/-/knowledge_base/u/content-sets}{Content Sets} organize and display content. Content Set Personalization provides dynamic selection of Content Sets based on User Segments. This means the Content Set which displays in a given context is determined by their segment criteria. For example, you could use a content list to display ``featured'' articles at the top of a page. Then you could create Segments containing users who should receive more specialized content, rather than the default. Those Segments would then see content personalized to their interest rather than the default. You can see a step by step demonstration of this in \href{/docs/7-2/user/-/knowledge_base/u/content-set-personalization}{Content Set Personalization}. \chapter{The Segment Editor}\label{the-segment-editor} Segmentation and Personalization in 7.0 also provides an editor for defining User Segments. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} and select the site that you wish to create segments for. \item Click \emph{People} → \emph{Segments}. \item Click the \emph{Add User Segment} button (\includegraphics{./images/icon-add.png}). \end{enumerate} At the top of the editor you can set the name, view the current members of the segment as it is defined, and choose to \emph{Save} the Segment or \emph{Cancel} to discard changes. \begin{figure} \centering \includegraphics{./images/sp-editor-top.png} \caption{The top portion of the Segment Editor has the segment name and its members.} \end{figure} On the right side of the page, there's a Properties menu with the following options: \begin{itemize} \tightlist \item User \item Organization \item Session \end{itemize} \begin{figure} \centering \includegraphics{./images/sp-segment-editor-full.png} \caption{You use the Segment Editor to create new Segments.} \end{figure} In addition to the various properties, there are operations and conjunctions that you use to define criteria. \section{User Properties}\label{user-properties} User Properties are user attributes you want to capture. This is made up of user metadata as defined in their accounts, but also contains certain group memberships (like Roles and User Groups) as well as information like the date the user profile was last modified. \section{Organization Properties}\label{organization-properties} Organization Properties is a selectable list of Organizations to include in your Segment. They contain similar criteria as the User selection, like \emph{Name} and \emph{Date Modified}. \section{Session Properties}\label{session-properties} Session Properties contains criteria based on the user's activity, browser, and system information. You can use this to target the user's device or OS, or for activity-based criteria like if a user entered the website through a specific campaign driven landing page. \section{Operations and Conjunctions}\label{operations-and-conjunctions} There are a number of different ways to evaluate properties, and different ways that you can relate different fields. For a comprehensive list, see the \href{/docs/7-2/reference/-/knowledge_base/r/defining-segmentation-criteria}{Defining Segment Criteria Reference}. \chapter{Creating User Segments}\label{creating-user-segments} To learn how to use segmentation, you'll step through an example for defining two segments for a site. The segments use user data like the \emph{Job Title} field and organization membership for evaluating segments. The first Segment you'll create, \emph{American Engineers}, uses standard fields as criteria. To get started, navigate to the Segments page. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} for the Bank site. \item Open the \emph{People} section and select \emph{Segments}. \end{enumerate} \section{Creating a Custom Segment}\label{creating-a-custom-segment} On the Segments page, you'll see a list of all the currently available segments, if they're available. Create a segment named \emph{American Engineers} for employees of your company. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) button. \item Click in the top text area and enter the name \emph{American Engineers}. \item Open the \emph{User} tab in the right side \emph{Properties} menu and drag the \emph{Job Title} property to the \emph{Conditions} area. \item Click on the operator field and set it to \emph{contains}. \item Click on the text field and enter \emph{Engineer}. \begin{figure} \centering \includegraphics{./images/sp-set-date.png} \caption{Setting the comparator to \emph{contains} includes variations of ``Engineer'' like ``Software Engineer'' in the segment.} \end{figure} \item Open the \emph{Organization} properties and drag over an \emph{Organization} field. \item Set the comparator between \emph{User Properties} and \emph{Organization Properties} to \emph{And}. \item Set the \emph{Organization} field to be \emph{equals} and select the organization. \begin{figure} \centering \includegraphics{./images/sp-select-orgs.png} \caption{You can prevent typos by directly selecting Organizations through the interface.} \end{figure} \item Click \emph{Save} to save your Segment. \end{enumerate} As you edit, a count of members meeting the criteria appears at the top of the page. You can click on \emph{View Members} to see the list. This helps you determine if you are correctly defining the Segment. \begin{figure} \centering \includegraphics{./images/sp-segment-members.png} \caption{You can view the list of Segment members at any time.} \end{figure} \section{Managing Segments}\label{managing-segments} After you create your Segment, you can see it in the list of Segments on the \emph{People} → \emph{Segments} page. From there you can edit the segment, delete it, or change the permissions for it. You cannot delete a segment if it's being used in an experience. Also, changing permissions only affects who has access to manage the Segment; it doesn't change Segment membership or criteria. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{People} → \emph{Segments}. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to the \emph{American Engineers}. \end{enumerate} You can manage the options here. You can also click on the Segment's name to edit it. \begin{figure} \centering \includegraphics{./images/sp-options.png} \caption{You can edit, delete or manage permissions from the options menu.} \end{figure} Next, you'll define a Custom Field and use it as part of your Segmentation criteria. \chapter{Creating Segments with Custom Fields and Session Data}\label{creating-segments-with-custom-fields-and-session-data} Now that you created a segment, you can take things to the next level and use a Custom Field to define segment criteria. \section{Creating a Custom Field}\label{creating-a-custom-field} First, create a custom field to use for the Segment: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the \emph{Control Panel} go to \emph{Configuration} → \emph{Custom Fields}. \item Click on \emph{User}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) button in the top right. \item On the next page, click on \emph{Dropdown}. \item For the \emph{Field Name} enter \emph{Cardholder Type}. \item For values, enter \emph{None,} \emph{Basic}, \emph{Gold}, and \emph{Platinum} on four separate lines. \item Click \emph{Save}. \end{enumerate} Now any time user is created, they are prompted to enter the \emph{Cardholder Type}, and existing users can select it from their user profiles. \begin{figure} \centering \includegraphics{./images/sp-create-custom-field.png} \caption{You can easily create custom fields to capture whatever kind of data you need.} \end{figure} For more information on adding Custom Fields, see \href{/docs/7-2/user/-/knowledge_base/u/custom-fields\#adding-custom-fields}{Adding Custom Fields}. \section{Defining a Segment with a Custom Field}\label{defining-a-segment-with-a-custom-field} Next, use a custom field to define another segment. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From Segments Administration, click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Click in the text area at the top of the page, enter the name \emph{Premium Card Prospects}. \item For User Properties select \emph{Cardholder Type}. \item Click on the operator field and set it to \emph{equals}. \item Select \emph{Basic} from the select box. \end{enumerate} \begin{figure} \centering \includegraphics{./images/sp-select-custom-field.png} \caption{The custom field you created is seamlessly integrated into segment creation.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{8} \tightlist \item Click \emph{Save} to save your Segment. \end{enumerate} As you can see, segment criteria can be easily defined using Liferay DXP's built-in criteria or your own custom fields. Now, you'll use session data to make this Segment definition even more robust. \section{Extending a Segment With Session Data}\label{extending-a-segment-with-session-data} So far, you've used criteria derived from user profiles to determine if they should be members of a segment; now it's time to use session data to make your criteria more effective. \noindent\hrulefill \textbf{Note:} For this exercise to work, you must have set a cookie on the specified page. \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to the \emph{Premium Card Prospects} segment and select \emph{Edit}. \item Click the \emph{Session} dropdown from the Properties menu. \item Set the comparator for Session Properties to \emph{Or}. \item Drag \emph{Cookies} into the Session Properties section. \item Change the selection box to \emph{contains}. \item Enter \emph{Cards} in the Key text box. \end{enumerate} Now any user who has a cookie from visiting the \emph{Cards} page is evaluated as part of the \emph{Premium Card Prospects} segment. \chapter{Using Analytics Cloud With User Segments}\label{using-analytics-cloud-with-user-segments} To use Analytics Cloud with User Segments, you must first connect your DXP data source to Analytics Cloud and enable synchronization of users and analytics. For more information about Analytics Cloud, including instructions for connecting it with DXP, see the official \href{https://learn.liferay.com/analytics-cloud/latest/en/connecting-data-sources/connecting-liferay-dxp-to-analytics-cloud.html}{Analytics Cloud Documentation}. Once you're connected to Analytics Cloud, you can create Segments to analyze user behavior. \section{Creating a New Segment}\label{creating-a-new-segment} Synchronization with Analytics Cloud is not instant, so once you have connected Analytics Cloud and Liferay DXP, you must first wait for the users and data to synchronize. After that completes, you can create Segments in Analytics Cloud to capture data in DXP. Only Segments that contain at least one member are synchronized with Liferay DXP. This means that empty Segments created with Analytics Cloud are unavailable to use on Liferay DXP. See Analytics Cloud's documentation on \href{https://help.liferay.com/hc/en-us/articles/360028721412-Creating-User-Segments}{creating Segments} for more information. Next, you can use your new Segment to define behaviors on your server. \section{Getting Segment Analytics}\label{getting-segment-analytics} After you create and sync a Segment in Analytics Cloud, you can view it and customize it in Liferay DXP. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the \emph{Segments} page. \item Click on the new Segment. \end{enumerate} \begin{figure} \centering \includegraphics{./images/segments-ac-list-item.png} \caption{When you see Analytics Cloud Segments in the list of Segments, they are marked with the Analytics Cloud icon.} \end{figure} Analytics are based on the criteria that you set on Analytics Cloud, but you can set additional criteria here to use this Segment for personalization in DXP. Changing the Segment criteria here doesn't affect the gathered analytics data, unless it is configured in some way that restricts its members from viewing content that you are using as an Analytics Cloud criteria. When you put it all together to provide personalized experiences and analyze user behavior, you can see the true power of Segmentation. \chapter{Personalization Experience Management}\label{personalization-experience-management} There is no direct location for managing all aspects of Experience Personalization. All the different aspects are managed from whatever you're personalizing. The key integration points of personalization are Content Pages and Content Sets. You can see an overview of their personalization options below. \section{Managing Content Page Personalization}\label{managing-content-page-personalization} Before you can Personalize Content Pages, you first need to \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{create Content Pages}. When you edit a Content Page, you can click on the \emph{Experience} to manage the options for that page. \begin{figure} \centering \includegraphics{./images/manage-content-page-experience.png} \caption{You can add, edit, delete, or change priority for Experiences.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages}. \item Click the \emph{Actions} button \includegraphics{./images/icon-actions.png} → \emph{Edit} for any Content Page. \item Click on the \emph{Default} Experience to manage experiences. \end{enumerate} From here you have three options: \textbf{\includegraphics{./images/icon-edit.png}} changes the name or selected Segment for the Experience. \textbf{\includegraphics{./images/icon-delete.png}} deletes the Experience. \textbf{\includegraphics{./images/icon-priority.png}} changes the priority of the Experience. If a user meets the criteria for more than one Experience, the highest ordered one is displayed. When creating a new Experience, you must define the audience by choosing a Segment. If your target audience for the Experience is not yet represented by a Segment, you can create one from the New Experience interface. \begin{figure} \centering \includegraphics{./images/add-seg-from-exp.png} \caption{You can add a new Segment while creating a new Experience.} \end{figure} \noindent\hrulefill \textbf{Note:} Creating new Segments from the New Experience interface is available in Liferay DXP 7.2 Fix Pack 1+ and Liferay Portal GA2+. \noindent\hrulefill Next you'll learn about managing Content Set personalization. \section{Managing Content Set Personalization}\label{managing-content-set-personalization} Managing Personalization options for Content Sets is similar. First you must \href{/docs/7-2/user/-/knowledge_base/u/content-sets}{create Content Sets} then you can personalize them. To create a new Personalized Variation of a Content Set, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Actions} button \includegraphics{./images/icon-actions.png} → \emph{Edit} for the Content Set you wish to personalize. \item Next to the content set is the message \emph{No Personalized Variations yet}. Click the \emph{New Personalized Variation} button. \item Select a Segment from the next page to create a variation for that Segment. \end{enumerate} \begin{figure} \centering \includegraphics{./images/select-content-set-variation.png} \caption{Select a Segment to create a variation for.} \end{figure} You can create a personalized variation for any existing Segment. Each new variation copies the default Content Set, but then essentially functions as its own Content Set after that. To edit or manage a Content Set Variation, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the Segment name under \emph{Personalized Variations}. \item Click on the \emph{Actions}8 button (\includegraphics{./images/icon-actions.png}) and you can select \emph{View Content} to preview the content in that set or \emph{Delete} to remove it. \end{enumerate} \begin{figure} \centering \includegraphics{./images/manage-content-set-segments.png} \caption{You can preview or delete a Personalized Variation from the \emph{Actions} menu.} \end{figure} \section{Previewing User Experiences}\label{previewing-user-experiences} As an administrator, when you view a page, you can preview the different experiences that users can have on that page. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Simulation} button (\includegraphics{./images/icon-simulation.png}) icon from the top of any page. \item Select a segment from the \emph{Segments} selection to preview the page as a member of that segment. \end{enumerate} Viewing the perspective of a segment previews for the administrator any personalizations for Content Pages or Content Sets for that Segment. \begin{figure} \centering \includegraphics{./images/personalization-segment-preview.png} \caption{You can preview different experiences from the Preview Panel.} \end{figure} \chapter{Content Page Personalization}\label{content-page-personalization-1} In \href{/docs/7-2/user/-/knowledge_base/u/creating-segments-with-custom-fields-and-session-data}{Creating Segments with Customer Fields and Session Data} you created a Segment called \emph{Premium Card Prospects}. Now, you'll use it to demonstrate Content Page Personalization. If you're not familiar with Content Pages, see the \href{/docs/7-2/user/-/knowledge_base/u/creating-content-pages}{Creating Content Pages} article before you get started here. \section{Creating the Default Page}\label{creating-the-default-page} First, you need to create the \emph{Credit Cards} page. To do this, create a new content page, add some fragments to it, and edit the content to match. First you need to build the bones of the page: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Site Builder} → \emph{Pages}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}). \item Select \emph{Content Page} and name the page \emph{Credit Cards}. \item Open the \emph{Sections} → \emph{Basic Sections} from the build menu on the right side of the screen and add a \emph{Banner} to the page. \item Next, click on \emph{Section Builder}, open \emph{Layouts}, and add a three column layout to the page, above the banner. \end{enumerate} \begin{figure} \centering \includegraphics{./images/section-builder-layouts.png} \caption{Open Layouts from the Section Builder.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{5} \tightlist \item Open the \emph{Basic Components} tab and add a \emph{Card} inside each of the columns. \end{enumerate} Next, edit the content (this step is optional): \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the text for the cards and the banner to edit it and provide content relevant to someone looking for information about credit cards. \item Click on each image and provide an appropriate image. \item Click \emph{Publish}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/personalization-default-content.png} \caption{Your final result might look something like this.} \end{figure} This is the default page that anyone visiting the site sees. \section{Defining Custom Experiences}\label{defining-custom-experiences} Next, define an experience specifically tailored to customers whom you have identified as \emph{Premium Card Prospects} using User Segments. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item At the top of the page, for the \emph{Experience} click on \emph{Default} to open the experience selection dialog. \begin{figure} \centering \includegraphics{./images/select-experience.png} \caption{Click on the current experience to create a new one or select a different existing experience.} \end{figure} \item Click on \emph{New Experience}. \item Name it \emph{Card Prospects} and select \emph{Premium Card Prospects} for the \emph{Audience}. \item Add a \emph{Banner} fragment above the three columns you added earlier. \item Edit the Banner to provide information specifically related to upgrading a card for an existing customer. \item Click \emph{Publish}. \end{enumerate} The \emph{Default} version of the page appears for everyone except for those defined as \emph{Premium Card Prospects}, but customers in that segment have an experience curated just for them. \begin{figure} \centering \includegraphics{./images/personalization-prospects.png} \caption{Your final result for the card prospects might look something like this.} \end{figure} \noindent\hrulefill \textbf{Note:} When you create a new experience, it copies the \emph{Default} experience at the time that it is created. Any further changes to the \emph{Default} experience do not effect any of experiences for that page. \chapter{Content Set Personalization}\label{content-set-personalization-2} In \href{/docs/user/7-2/-/knowledge-base/u/creating-user-segments}{Creating User Segments} you created a Segment called \emph{American Engineers}. Now, you'll use it to demonstrate Content Set Personalization. For this example, create a Content Set to be the default displayed on the \emph{Home} page. Then you'll modify it to create a personalized variation containing technical articles for members of the \emph{American Engineers} segment. If you're not familiar with Content Set, see the \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets} article before you get started here. \section{Creating and Setting the Default Content Set}\label{creating-and-setting-the-default-content-set} First create the default Content Set and configure it on the Home page using the Asset Publisher. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \emph{Site Administration} → \emph{Content \& Data} → \emph{Content Sets}. \item Click the Add button (\includegraphics{./images/icon-add.png}) and choose \emph{Manual Selection}. \item Name it \emph{Home Page Content}. \item For the new Content Set, click \emph{Select} next to \emph{Asset Entries} and select \emph{Basic Web Content}. \begin{figure} \centering \includegraphics{./images/create-default-content-set.png} \caption{Click \emph{Select} to add a new Asset Entries.} \end{figure} \item On the \emph{Select Basic Web Content} page, check the boxes next to the content you want to add and click \emph{Add}. \item Navigate to the \emph{Home} page and add an Asset Publisher to the page. \item Open \emph{Configuration} for the Asset Publisher. \item Under \emph{Asset Selection} select \emph{Content Set}. \item Under \emph{Select Content Set} click \emph{Select}, choose \emph{Home Page Content}, and click \emph{Save}. \end{enumerate} Now the Content Set that you configured appears in the Asset Publisher on the \emph{Home Page}. Next configure the Content Set for Personalization. \section{Personalizing the Content Set}\label{personalizing-the-content-set} Now create the content set for engineers and configure its display. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go back to the Content Set from \emph{Site Administration}. \item Click \emph{New Personalized Variation} and select the \emph{American Engineers} segment \begin{figure} \centering \includegraphics{./images/create-personalized-variation.png} \caption{Create a new Personalized Variation.} \end{figure} \item Click \emph{Select} next to \emph{Asset Entries} and select \emph{Basic Web Content}. \item Select articles appropriate to an engineering audience and click \emph{Add}. \end{enumerate} Now anytime a member of the \emph{American Engineers} segment views this Content Set being displayed, they see the personalized version and not the default. Test this now, using the \emph{Simulator}. \chapter{Recommending Content Based on User Behavior}\label{recommending-content-based-on-user-behavior} \noindent\hrulefill \textbf{Note:} A/B Testing is available for Liferay DXP 7.2 SP1+. \noindent\hrulefill A site's content generates clicks from users. For example, if someone visits a sporting goods store's site and clicks on several hunting promotional ads, you can deduce an interest in hunting products and can promote this type of content when this user visits the site again. Accomplishing this with \href{/docs/7-2/user/-/knowledge_base/u/creating-user-segments}{Segment-based personalization} is possible, but that method is really targeted for vertical specific messaging or content with a preconceived audience. You may have to create hundreds of Segments to target all combinations of customer use cases. Instead, you need an infrastructure that tracks user views and displays the appropriate content based on behavior. You can accomplish this with \emph{Content Recommendation}. This is done by adding tags to content/Pages and monitoring the users who visit them. When a user views a specific content type or Page, its tags are attached to that user as \emph{interests}. When the user visits other pages, content that matches their interests is displayed to them. The monitoring process is facilitated by \href{https://help.liferay.com/hc/en-us/articles/360006608732-Generating-New-Business-Using-Analytics}{Analytics Cloud}, so you must have your DXP instance synced with it. If you haven't done this yet, start by \href{https://help.liferay.com/hc/en-us/articles/360006653472-Adding-a-Liferay-DXP-Data-Source}{adding your DXP instance as a data source}. Once your DXP instance is synced with Analytics Cloud and you're leveraging Content Recommendation, a user's interests are viewable by navigating to the left menu → \emph{Individuals} → \emph{Interests}. \begin{figure} \centering \includegraphics{./images/content-interests.png} \caption{A user's interests are stored and accessible from Analytics Cloud.} \end{figure} You can learn more about Analytics Cloud's individual analytics \href{https://help.liferay.com/hc/en-us/articles/360006946171-Profiling-Individuals}{here}. To begin recommending content to users, you must \begin{itemize} \tightlist \item Add tags to content and/or Pages. \item Display content based on user behavior. \end{itemize} You'll step through these processes next. \section{Adding Tags to Track User Behavior}\label{adding-tags-to-track-user-behavior} To track user behavior and accumulate their interests, you must add tags to the content and Pages they visit. First, you'll add tags to web content and configure it to be viewable using a Display Page Template. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to Site Administration → \emph{Site Builder} → \emph{Pages} → \emph{Display Page Templates}. Select the \emph{Add} button (\includegraphics{./images/icon-add.png}), give it a name, and click \emph{Save}. \item In the right menu, select \emph{Mapping} (\includegraphics{./images/icon-mapping.png}), choose the \emph{Web Content Article} content type and choose \emph{Basic Web Content} for the subtype. Then click \emph{Save}. \item Add a Fragment to the Display Page and map its field to \emph{Basic Web Content}. For example, click \emph{Section Builder} → \emph{Basic Components} and drag the \emph{Paragraph} Fragment to the page. Then click the \emph{Map} button and select \emph{Basic Web Content} for its Source and Field. \item Publish the Display Page Template. \item \href{/docs/7-2/user/-/knowledge_base/u/creating-web-content}{Begin creating Basic Web Content}. Before publishing the content, navigate to the \emph{Display Page Template} section and select \emph{Specific Display Page Template} from the selector. Then select the Display Page Template your created previously. \item Go to the \emph{Metadata} section in the right menu. Assign tags that characterize the content. These are the tags that are referenced as interests when a user views the content. Then click \emph{Publish}. \end{enumerate} Now your web content is mapped to a Display Page, which allows the assigned tags to be tracked as interests when the web content is clicked. You can alter this process based on the asset types you want to recommend. You can also assign tags to a Page's SEO configuration, which would then be assigned to users as interests when they visit the Page. Here's how to do this: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to Site Administration → \emph{Site Builder} → \emph{Pages}. \item Click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) → \emph{Configure} for a Page you want to add tags to. \item Select \emph{SEO} → \emph{Categorization} and add relevant tags to the page. Then click \emph{Save}. \end{enumerate} Awesome! Now your content and Pages have tags that are assigned to users as interests when they visit them. These interests are assessed when recommending content, which you'll learn how to leverage next. \section{Displaying Content Based on User Behavior}\label{displaying-content-based-on-user-behavior} Now that your Site's users' have their interests tracked using tags, you'll want to set up an Asset Publisher to display the content based on their behavior. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Site Administration → \emph{Content \& Data} → \emph{Content Sets}. \item Select the \emph{Add} button (\includegraphics{./images/icon-add.png}) and click \emph{Dynamic Selection}. Assign a name and click \emph{Save}. \item Under the Content Recommendation tab, enable Content Recommendation. Then save the Content Set. \begin{figure} \centering \includegraphics{./images/enable-content-recommendation.png} \caption{Enable Content Recommendation for your Content Set.} \end{figure} For more information on Content Sets, see \href{/docs/7-2/user/-/knowledge_base/u/creating-content-sets}{Creating Content Sets}. \item Add an Asset Publisher widget to a Page. Navigate to its \emph{Options} (\includegraphics{./images/icon-app-options.png}) → \emph{Configuration} menu and select the \emph{Content Set} asset selection. \item Select the Content Set you want to display. Then click \emph{Save}. \end{enumerate} In a realistic scenario, Content Sets have many assets with differing tags. That way, content similar to a user's interests is displayed over other content. Great! Now when users have accumulated interests based on views, the Asset Publisher only shows content based on their interests. \chapter{A/B Testing}\label{ab-testing} \noindent\hrulefill \textbf{Note:} A/B Testing is available for Liferay DXP 7.2 SP1+. \noindent\hrulefill Liferay DXP can help you hone your messages to customers by periodically testing different UX and messaging schemes. You can show different versions of your site to different users to see which is more effective. \emph{A/B Testing} lets you maintain a page's current UX and messaging, but provide alternative page variants for a select group of visitors. Then the current page and page variant(s) are tested based on algorithms to determine which pages perform better for a given goal (e.g., bounce rate, clicks, etc.). For example, a Marketing team for a bank provides a Content Page advertising a new credit card. The page has been published for a few weeks, but a redesign might help promote the new credit card better. With A/B Testing, the team can create a new page variant and display both pages at random to visitors. Then they can analyze the clickthrough rate for the two pages and find which page is more effective. If the new variant is more effective than the original page, they can publish it and remove the old page. If improving your site's UX and messaging is something you're interested in, continue on to learn more! \chapter{Enabling A/B Testing}\label{enabling-ab-testing} Before creating an A/B test, you must ensure some conditions are met: \begin{itemize} \item You must have Liferay DXP connected to \href{https://learn.liferay.com/analytics-cloud/latest/en/index.html}{Analytics Cloud}. To begin, \href{https://learn.liferay.com/analytics-cloud/latest/en/connecting-data-sources.html}{add a Data Source}. \item Your page must be a Content Page, since only Content Pages (not Widget Pages) support Experiences for different Segments. \item The Content Page you intend to test must be published. \end{itemize} If these conditions are met, A/B Testing is automatically enabled for any Content Page you navigate to, assuming you have the proper permissions. You'll learn more about how to assign permissions for A/B Testing next. \section{Setting A/B Testing Permissions}\label{setting-ab-testing-permissions} To use all the features of A/B Testing, you must have \emph{Update} permissions for the Content Page. To assign \emph{Update} permissions for a Content Page, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to Site Administration → \emph{Site Builder} → \emph{Pages}. \item Select the Actions button (\includegraphics{./images/icon-actions.png}) next to the Content Page and click \emph{Permissions}. \item Enable the \emph{Update} permission for the Role(s) you want to grant A/B Testing access for and click \emph{Save}. \end{enumerate} For more information, see \href{/docs/7-2/user/-/knowledge_base/u/changing-page-permissions}{Page Permissions} and \href{/docs/7-2/user/-/knowledge_base/u/managing-roles}{Managing Roles}. \chapter{Creating A/B Tests}\label{creating-ab-tests} To begin leveraging A/B Testing, you must first create an A/B test. You cannot create a test on an Experience that already has an active test running. To create an A/B test, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Content Page you want to test. \item Click the A/B Testing (\includegraphics{./images/icon-ab-testing.png}) button from the Control Menu. \item Choose the Experience you want to test. This option is only available when you have a custom Experience (an Experience other than the default). A test can be performed on the Default Experience as well as a personalized Experience mapped to a Segment. When an Experience is being used in an A/B test, it cannot be edited. Deleting a Page/Experience being used in an A/B test also deletes the test for that Page. \item Click \emph{Create Test}. \item Assign the test a name and description (optional). \item Assign the goal you want the test to track. There are two: \emph{Bounce Rate}: the percentage of users who don't exhibit any activities on the page (click, scroll, etc.) and then navigate away from the site without visiting another page. \emph{Click}: the percentage of users who clicked on the page (per session). \end{enumerate} \begin{figure} \centering \includegraphics{./images/create-ab-test.png} \caption{Fill out the form to create your A/B test.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{6} \tightlist \item Click \emph{Save}. \end{enumerate} You now have an A/B test! Notice that the test's status is \emph{Draft}. This means it's not yet visible to visitors. \noindent\hrulefill \textbf{Note:} You can only create an A/B test for one page/experience at a time. \noindent\hrulefill \noindent\hrulefill \textbf{Note:} The \emph{Control} entity represents the currently published Content Page. \noindent\hrulefill \begin{figure} \centering \includegraphics{./images/new-ab-test.png} \caption{You now have an A/B test, but there are additional configurations you can apply.} \end{figure} You can always edit or delete the new A/B test by clicking the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) in the top right of the A/B Test menu. Deleted tests are not recoverable (i.e., not sent to the Recycle Bin). These options are not available for an active test. You can also, at any time, view your A/B Testing history by selecting the \emph{History} tab. This displays all completed and terminated A/B tests. Now it's time to create your test Variant(s). A test Variant is a customization of the Experience you want to optimize. An A/B test must contain at least one Variant before it can run. To create a Variant, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the A/B Test menu, click \emph{Create Variant}. \item Give the Variant a name and click \emph{Save}. \item Select the new Variant's Edit button \includegraphics{./images/icon-edit.png}. The current Control page's content/formatting is copied and displayed as the baseline for the Variant. \item Edit the Variant as desired. Then click \emph{Save Variant}. \end{enumerate} You now have a Variant of the Control Page. You can create as many Variants as you want. If you selected the Click goal, you must select the clickable element you want to target on the Control and Variant pages. If you selected a different goal, you can skip the steps below. To configure the Click goal target, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \tightlist \item Click \emph{Set Target} under the Click Goal heading of your A/B Test. Any clickable element on the page is highlighted. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Only links and buttons with an ID attribute can be selected as a target for the Click goal. \end{verbatim} \noindent\hrulefill \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{1} \item Select the element you want to set as the click target for your Control and Variant pages. \begin{figure} \centering \includegraphics{./images/set-click-target.png} \caption{Set the click target to be tracked.} \end{figure} \end{enumerate} Your Click goal is now set! You can edit the target at any time before starting the test. \begin{figure} \centering \includegraphics{./images/click-goal-set.png} \caption{Once the click target is set, you can run the A/B test.} \end{figure} Once you're finished creating Variants and configuring goals for your A/B test, you're ready to run the A/B test. You'll learn how to do this next. \chapter{Running A/B Tests}\label{running-ab-tests} Once you've created and configured your A/B test, you'll want to run it to begin gathering data on your Control Page and Variants. To run an A/B test, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \emph{Review and Run Test} button from the A/B Test menu. \item Configure how your test should run. There are two configurations: \emph{Traffic Split}: the percentage of visitors that are randomly split between the Variants when visiting the Page. A visitor randomly assigned a Variant always sees the same Variant until the test is finished. \emph{Confidence Level Required}: the accuracy of the test results (i.e., when the winning Variant truly outperforms the other Variants). Typically you want to have the highest confidence level possible, but this impacts test duration. The higher the required confidence level, the longer it takes to declare a winning Variant. Choose the percentage based on your expectations. The \emph{Estimated Time to Declare Winner} field is also displayed. This is the estimated duration the test runs. This is calculated based on the selected traffic split, confidence level, and projected page traffic. Your page's past traffic history is provided by Analytics Cloud. \begin{figure} \centering \includegraphics{./images/run-ab-test.png} \caption{Configure the final parameters of your A/B test before running it.} \end{figure} \item Select \emph{Run}. \end{enumerate} Your A/B test is now running! While an A/B test is running, you have two buttons available to help manage the test: \emph{Terminate Test}: terminates the test. To delete a test, you must terminate it first. \emph{View Data in Analytics Cloud}: redirects you to your A/B Testing dashboard hosted on Analytics Cloud. Here you can view your test's traffic, reports, statistics, etc. related to your test. See the \href{/docs/7-2/user/-/knowledge_base/u/monitoring-a-b-test-results}{Monitoring A/B Test Results} article for more information. Awesome! You now have a running A/B test accumulating data based on user interactions with your Page. Next, you'll learn how to monitor your A/B test's results. \chapter{Monitoring A/B Test Results}\label{monitoring-ab-test-results} The information from your A/B test created in Liferay DXP is automatically synchronized with \href{https://help.liferay.com/hc/en-us/articles/360006608732}{Analytics Cloud}. The test results used to calculate the winning Variant based on user interaction data is viewable there. You can also view your A/B testing history, meaningful statistics, helpful graphs, etc. from Analytics Cloud. To navigate to your test's Analytics Cloud dashboard from Liferay DXP, click the \emph{View Data in Analytics Cloud} button in the A/B Test sidebar panel while it's running. Liferay DXP only displays your test's status (draft, running, etc.) and the winning Variant once the test finishes. For more information on what you can view/manage from Analytics Cloud in relation to your A/B test, see the \href{https://help.liferay.com/hc/en-us/sections/360001492292-Analyzing-Touchpoints}{A/B Testing Analytics} article. \chapter{Publishing A/B Test Variants}\label{publishing-ab-test-variants} Once the A/B test has concluded and Analytics Cloud has computed the test results, the status for the test displays as \emph{Winner Declared} (if a winning Variant was found) or \emph{No Winner} in the A/B Test sidebar panel. You're also alerted to the result via a notification, which you can view from the User Menu. \begin{figure} \centering \includegraphics{./images/ab-testing-winner.png} \caption{If you're satisfied with the A/B test's results, publish the winning Variant.} \end{figure} \noindent\hrulefill \textbf{Note:} When the required confidence level is not met during the time duration, there is no winning Variant. \noindent\hrulefill Click the \emph{Publish} button to publish your winning Variant Experience. Once you publish a Variant, the A/B test's status is \emph{Completed} and the test is finished. \begin{figure} \centering \includegraphics{./images/ab-test-complete.png} \caption{Once you've published a Variant, the A/B test is complete.} \end{figure} If you want to publish a Variant that was not declared a winner, select the Variant from the A/B Test panel and click \emph{Publish}. You can select \emph{Discard Test} to ignore the A/B test recommendations and keep the currently published Control Page. The most productive Variant is now available to all users who visit the Page. Awesome! You successfully ran an A/B test and published the Variant that is most effective for your Site's users. \chapter{Content Publication Management}\label{content-publication-management} Today's enterprises generate an enormous amount of content. You can use advanced publishing tools to manage content for a seamless publication experience. Staging lets you change your Site behind the scenes without affecting the live Site. This is done by creating your Site in a temporary staging area (local or remote) and then publishing all the changes at once. Continue on to begin managing content publication! \chapter{Staging}\label{staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Staging is a tool used to manage content publication. The concept of staging is simple: you can modify your site behind the scenes and then publish all your updates in one shot. You don't want users seeing your web site change before their eyes as you're modifying it, do you? The staging environment lets you make changes to your site in a specialized \emph{staging area} that's linked to a production environment. Typically the staging site is used only by content editors and site administrators, while the production environment is public. Content is published from staging to production all at once. Site administrators can set up their staging environments locally or remotely. With Local Live staging, your staging environment and live environment are hosted on the same server. Remote Live staging has the staging and live environments on separate servers. You'll learn more about the differences between these two staging environments and how to enable them for your portal instance. You can also leverage the Page Versioning feature. This feature works with both Local Live and Remote Live staging and lets site administrators create multiple variations of staged pages. This allows several different versions of sites and pages to be developed at the same time. Variations can be created, merged, and published using a Git-like versioning system. In the next section, you'll jump in to see how to enable staging. \chapter{Enabling Staging}\label{enabling-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You have two different ways to set up staging: \begin{itemize} \tightlist \item \href{/docs/7-2/user/-/knowledge_base/u/enabling-local-live-staging}{Local Live} \item \href{/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging}{Remote Live} \end{itemize} Whether you enable Local Live or Remote Live staging, the interface for managing and publishing staged pages is the same. Local Live staging lets you publish site changes quickly, since the staged and live environments are on the same server. It's also easier to switch between the staged and live environments using Local Live staging. Since the staged content, however, is stored in the same database as the production content, your server must have more resources, and the content isn't as well protected or backed up as with Remote Live staging. Also, you can't install new versions of widgets for testing purposes in a Local Live staging environment, since only one version of an widget can be installed at any given time on a single Liferay server. With Remote Live staging, your staging and live environments are hosted on separate servers, so your data is separated. This lets you deploy new versions of widgets and content to your staging environment without interfering with your live environment. Publishing is slower, however, with Remote Live staging since data must be transferred over a network. Of course, you also need more hardware to run a separate staging server. Visit the staging environment article (Local or Remote) that most closely aligns with your goal for staging content. \chapter{Enabling Local Live Staging}\label{enabling-local-live-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Local Live staging places both your staging environment and your live environment on the same server. When it's enabled, a clone of the site is created containing copies of all of the site's existing pages. This means the staging and live environments share the same JVM, database, portlet data (depending on which portlets are selected when staging is enabled), and configurations, such as the properties set in the \texttt{portal-ext.properties} file. The cloned site becomes the staging environment and the original site becomes the live environment. You can enable local staging for a site by navigating to the \emph{Publishing} → \emph{Staging} menu. To get some hands-on experience with enabling Local Live staging, you can complete a brief example which creates a Local Live staging environment for your site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Product Menu (left side) and select \emph{Publishing} → \emph{Staging}. \item Select \emph{Local Live}. You can also enable page versioning and select staged content. For more information on these options, see the \href{/docs/7-2/user/-/knowledge_base/u/enabling-page-versioning-and-staged-content}{Enabling Page Versioning and Staged Content} article. \item Click \emph{Save}. \end{enumerate} You've officially begun the staging process! Because Local Live staging creates a clone of your site, you should only activate staging on new, clean sites. Having a few pages and some widgets (like those of the example site you created) is no big deal. If you've already created a large amount of content, however, enabling staging can take a lot of time since it's a resource intensive operation. Also, if you intend to use page versioning to track the history of updates to your site, you should enable it as early as possible, \emph{before} your site has many pages and lots of content. Your site's update history isn't saved until you enable page versioning. Page versioning requires staging (either Local Live or Remote Live) to be enabled. If you ever need to turn off the staging environment, return back to \emph{Staging} from the Publishing dropdown. For more information on this, see the \href{/docs/7-2/user/-/knowledge_base/u/disabling-staging}{Disabling Staging} article. Great! Now you're ready to use Local Live Staging. \chapter{Enabling Remote Live Staging}\label{enabling-remote-live-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} In Remote Live staging, a connection is established between the current site and another site on a remote Liferay server. The remote site becomes the live environment and the current site becomes the staging environment---an instance of Liferay used solely for staging. The remote (live) Liferay server and the local (staging) Liferay server should be completely separate systems. They should not, for example, share the same database. When Remote Live staging is enabled, all the necessary information is transferred over the network connecting the two servers. Content creators use the staging server to make their changes while the live server handles the incoming user traffic. When changes to the site are ready to be published, they are pushed over the network to the remote live server. Before enabling Remove Live staging, ensure you've configured your Liferay server and remote server appropriately. Follow the \href{/docs/7-2/user/-/knowledge_base/u/configuring-servers-for-remote-live-staging}{Configuring Servers for Remote Live Staging} article to do this. You can enable remote staging for a site by navigating to the \emph{Publishing} → \emph{Staging} menu. Step through the instructions below to create a Remote Live staging environment for your site. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Product Menu (left side) and select \emph{Publishing} → \emph{Staging}. \item Select \emph{Remote Live}. Additional fields appear for Remote Live Connection Settings. \begin{figure} \centering \includegraphics{./images/remote-live-staging-settings.png} \caption{After your remote Liferay server and local Liferay server have been configured to communicate with each other, you have to specify a few Remote Live connection settings.} \end{figure} \item Enter your remote Liferay server's IP address into the Remote Host/IP field. This field should match the host you specified as your \texttt{tunnel.servlet.hosts.allowed} property in the \texttt{portal-ext.properties} file. If you're configuring an IPv6 address, it must contain brackets when entered into the \emph{Remote Host/IP} field (e.g., \emph{{[}0:0:0:0:0:0:0:1{]}}). If the remote Liferay server is a cluster, you can set the Remote Host/IP to the load balanced IP address of the cluster to increase the availability of the publishing process. See the \href{/docs/7-2/deploy/-/knowledge_base/d/configuring-remote-staging-in-a-clustered-environment}{Configuring Remote Staging in a Clustered Environment} for details. \item Enter the port on which the remote Liferay instance is running into the Remote Port field. You only need to enter a Remote Path Context if a non-root portal servlet context is being used on the remote Liferay server. \item Enter the ID of the site on the remote Liferay server that's for the Live environment. If a site hasn't already been prepared on the remote Liferay server, you can log in to the remote Liferay server and create a new blank site. After the site has been created, note the site ID so you can enter it into the Remote Site ID field on your local Liferay server. You can find any site's ID by selecting the site's name on the Sites page of the Control Panel. \item Check the \emph{Use a Secure Network Connection} field to use HTTPS for the publication of pages from your local (staging) Liferay server to your remote (live) Liferay server. \item Decide whether to enable page versioning and select staged content. For more information on these options, see the \href{/docs/7-2/user/-/knowledge_base/u/enabling-page-versioning-and-staged-content}{Enabling Page Versioning and Staged Content} article. \item Click \emph{Save}. \end{enumerate} You've officially begun the staging process! If you fail to configure your current and remote server properly, you won't be able to enable staging and an error message appears. If you have issues, \href{/docs/7-2/user/-/knowledge_base/u/configuring-servers-for-remote-live-staging}{verify you've configured your servers properly}. When a user publishes changes from the local (staging) server to the remote (live) server, Liferay DXP passes the user's email address, screen name, or user ID to the remote server to perform a permission check. For a publishing operation to succeed, the operation must be performed by a user that has identical credentials and permissions on both the local (staging) and the remote (live) server. This is true regardless of whether the user attempts to publish the changes immediately or attempts to schedule the publication for later. If only a few users should have permission to publish changes from staging to production, it's easy enough to create a few user accounts on the remote server that match a selected few on the local server. The more user accounts that you have to create, however, the more tedious this job becomes and the more likely you are to make a mistake. And you not only have to create identical user accounts, you also have to ensure that these users have identical permissions. For this reason, it's recommended that you use LDAP to copy selected user accounts from your local (staging) Liferay server to your remote (live) Liferay server. Liferay's Virtual LDAP Server application, available on Liferay Marketplace, makes this easy. See the \href{/docs/7-2/user/-/knowledge_base/u/disabling-staging}{Disabling Staging} article to learn how to turn off the staging environment. Great! Now you're ready to use Remote Live Staging. \chapter{Configuring Servers for Remote Live Staging}\label{configuring-servers-for-remote-live-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Before you can enable Remote Live staging for a site, you must satisfy some necessary requirements: \begin{itemize} \tightlist \item Add the remote Liferay server to the current Liferay server's list of allowed servers, and vice versa. \item Specify an authentication key to be shared by your current and remote server. \item Enable each Liferay server's tunneling servlet authentication verifier. \item Update the Tunnel Auth Verifier Configuration of your remote Liferay instance. \end{itemize} Follow the steps below to configure your servers for Remote Live staging. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Add the following lines to your current Liferay server and remote Liferay server's \texttt{portal-ext.properties} file: \begin{verbatim} tunneling.servlet.shared.secret=[secret] tunneling.servlet.shared.secret.hex=true \end{verbatim} Liferay DXP's use of a pre-shared key between your staging and production environments helps secure the remote publication process. It also removes the need to send the publishing user's password to the remote server for web service authentication. Using a pre-shared key creates an authorization context (permission checker) from the provided email address, screen name, or user ID \emph{without} the user's password. \item Specify the values for the servers' \texttt{tunneling.servlet.shared.secret} property. The values for these properties depend on the chosen configured encryption algorithm, since different encryption algorithms support keys of different lengths. See the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html\#HTTP\%20Tunneling}{HTTP Tunneling} properties documentation for more information. Note that the following key lengths are supported by the available encryption algorithms: \begin{itemize} \tightlist \item AES: 128, 192, and 256 bit keys \item Blowfish: 32 - 448 bit keys \item DESede (Triple DES): 56, 112, or 168 bit keys (However, Liferay places an artificial limit on the minimum key length and does not support the 56 bit key length) \end{itemize} To prevent potential character encoding issues, you can use one of the following two strategies: 2a. Use hexadecimal encoding (recommended). For example, if your password was \emph{abcdefghijklmnop}, you'd use the following settings in your \texttt{portal-ext.properties} file: \begin{verbatim} tunneling.servlet.shared.secret=6162636465666768696a6b6c6d6e6f70 tunneling.servlet.shared.secret.hex=true \end{verbatim} 2b. Use printable ASCII characters (less secure). This degrades the password entropy. If you don't use hexadecimal encoding (i.e., if you use the default setting \texttt{tunneling.servlet.shared.secret.hex=false}), the \texttt{tunneling.servlet.shared.secret} property's value \emph{must} be ASCII compliant. Once you've chosen a key, make sure the value of your current server matches the value of your remote server. \textbf{Important:} Do not share the key with any user. It is used exclusively for communication between staging and production environments. Any user with possession of the key can manage the production server, execute server-side Java code, etc. \item Add the following line to your remote Liferay server's \texttt{portal-ext.properties} file: \begin{verbatim} tunnel.servlet.hosts.allowed=127.0.0.1,SERVER_IP,[STAGING_IP] \end{verbatim} The \texttt{{[}STAGING\_IP{]}} value must be replaced by the staging server's IP addresses. If the server has multiple interfaces, each IP address must also be added, which would show as a source address for the http(s) requests coming from the staging server. The \texttt{SERVER\_IP} constant can remain set for this property; it's automatically replaced by the Liferay server's IP addresses. If you're validating IPv6 addresses, you must configure the app server's JVM to not force the usage of IPv4 addresses. For example, if you're using Tomcat, add the \texttt{-Djava.net.preferIPv4Stack=false} attribute in the \texttt{\$TOMCAT\_HOME\textbackslash{}bin\textbackslash{}setenv.{[}bat\textbar{}sh{]}} file. \item Update the \emph{TunnelAuthVerfierConfiguration} of your remote Liferay instance. To do this, navigate to the Control Panel → \emph{Configuration} → \emph{System Settings} → \emph{API Authentication} → \emph{Tunnel }Authentication\emph{. Click }/api/liferay/do* and insert the additional IP \emph{addresses you're using in the }Hosts allowed* field. Then select \emph{Update}. Alternatively, you can also write this configuration into an OSGi file (e.g., \texttt{osgi/configs/com.liferay.portal.security.auth.verifier.tunnel.module.configuration.TunnelAuthVerifierConfiguration-default.config}) in your Liferay DXP instance: \begin{verbatim} enabled=true hostsAllowed=127.0.0.1,SERVER_IP,[Local server IP address] serviceAccessPolicyName=SYSTEM_USER_PASSWORD urlsIncludes=/api/liferay/do \end{verbatim} \item Restart both Liferay servers after making these configuration updates. After restarting, log back in to your local Liferay instance as a site administrator. \end{enumerate} That's all you need to do to configure Remote Live Staging! You can now \href{/docs/7-1/user/-/knowledge_base/u/enabling-remote-live-staging}{enable it}! For additional information on configuring Remote Live staging, see the topics below. \section{Applying Patches When Using Remote Staging}\label{applying-patches-when-using-remote-staging} When applying patches to a remote staging environment, you must apply them to all your servers. Having servers on different patch levels is not a good practice and can lead to import failures and data corruption. It is essential that all servers are updated to the same patch level to ensure remote staging works correctly. \section{Configuring Remote Staging's Buffer Size}\label{configuring-remote-stagings-buffer-size} Similar to Local Live staging, it is a good idea to turn remote staging on at the beginning of your site's development for good performance. When you're using Remote Live staging, and you are publishing a large amount of content, your publication could be slow and cause a large amount of network traffic. Liferay DXP's system is very fast for the amount of data being transferred over the network. This is because the data transfer is completed piecemeal, instead of one large data dump. You can control the size of data transactions by setting the following portal property in your \texttt{portal-ext.properties} file: \begin{verbatim} staging.remote.transfer.buffer.size \end{verbatim} This property sets the file block sizes for remote staging. If a LAR file used for remote staging exceeds this size, the file will be split into multiple files prior to transmission and then reassembled on the remote server. The default buffer size is 10 megabytes. \chapter{Enabling Page Versioning and Staged Content}\label{enabling-page-versioning-and-staged-content} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Enabling page versioning for a site lets site administrators work in parallel on multiple versions of the site's pages. Page versioning also maintains a history of all updates to the site from the time page versioning was enabled. Site administrators can revert to a previous version of the site at any time. This flexibility is very important in cases where a mistake is found and it's important to publish a fix quickly. You can enable page versioning for public pages or private pages on the Staging Configuration page below the menu for selecting your staging environment (Local or Remote). If you've already enabled staging, you can navigate to the Product Menu → \emph{Publishing} → \emph{Staging} and click the Options (\includegraphics{./images/icon-options.png}) button and select \emph{Staging Configuration}. \begin{figure} \centering \includegraphics{./images/staging-page-versioning-staged-content.png} \caption{You can decide to use versioning and choose what content should be staged.} \end{figure} You can also choose content for the staging environment to manage on the Staging Configuration page. Choosing content to be staged may sound self-explanatory, but content must have specific attributes in Liferay DXP to use it in a staged environment. Content or an entity should be site-scoped, so they are always part of a site; otherwise, they are not eligible for staging. For example, page-scoped entities are only eligible for staging on published pages. When scoped data is on a page (e.g., Web Content Display widget) and the page is published, the scoped data is published with it. Liferay DXP by default supports the following content groups for staging: \begin{itemize} \tightlist \item Blogs \item Bookmarks \item Calendar \item Documents and Media \item Dynamic Data Lists \item Forms \item Knowledge Base \item Message Boards \item Mobile Device Families \item Polls \item Web Content \item Widget Templates \item Wiki \end{itemize} Before you activate staging, choose which of these widgets' data you'd like to copy to staging. You'll learn about many of the collaboration widgets listed under the Staged Content heading when you read the \href{/docs/7-2/user/-/knowledge_base/u/collaboration}{Collaboration Suite's} section of articles. For now, be aware that you can enable or disable staging for any of these widgets. Why might you want to enable staging for some widget types but not others? In the case of collaborative widgets, you probably \emph{don't} want to enable staging since such widgets are designed for user interaction. If their content were staged, you'd have to publish your site manually whenever somebody posted a message on the message boards to make that message appear on the live site. Generally, you want web content to be staged because end users aren't creating that kind of content---web content is the stuff you publish to your site. But widgets like the Message Boards or Wiki should \emph{not} be staged. Notice which widgets are marked for staging by default: if you enable staging and accept the defaults, staging is \emph{not} enabled for the collaborative widgets. The listed widgets, or content groups, contain one or more specific entity. For example, selecting the Web Content widget does not mean you're only selecting web content itself, but also web content folders. Certain content types can be linked together and can reference each other on different levels. One of the responsibilities of staging is to discover and maintain these references when publishing. Site administrators and content creators have control over the process on different levels: staging can be enabled for a content group and a content group can be selected for publication. Disabled staged content types can cause unintended problems if you're referring to them on a staged site. For example, the Asset Publisher portlet and its preferences are always staged. If the content types it's set to display are not enabled for staging, the Asset Publisher can't access them on a staged site. Make sure to plan for the content types you'll need in your staged site. Turning Staging on and off for individual portlet data could cause data inconsistencies between the staging and live sites. Because of this, it's not possible to modify the individual portlet configuration once you enable staging. In case you need adjustments later on, you must turn staging off and re-enable it with your new configuration. Besides managing the widget-specific content, Liferay DXP also has several special content types such as pages or users. For instance, pages are a part of the site and can reference other content types, but in a special way. The page references widgets, which means publishing a page also implies publishing its widgets. The content gives the backbone of the site; however, content alone is useless. To display content to the end user, you'll need widgets as the building blocks for your site. Before you begin exploring the Staging UI, it's important to understand the publishing process for staging to make informed decisions so you use the staging environment efficiently and effectively. \chapter{Publishing Staged Content Efficiently}\label{publishing-staged-content-efficiently} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Now that you understand how staging works, you'll dive deeper into the publication process and some prerequisites you should follow before publishing. By understanding how the process works, you can make smart and informed decisions about how you want to publish your site's content. \section{Understanding the Publication Process}\label{understanding-the-publication-process} In simple terms, a publication is the process where all content, referenced content, apps and their preferences, pages, etc. are transferred from the staging scope to the live site. If you've enabled remote staging, this process involves network communication with another remote site. From a low level perspective, staging is an equivalence relation where entities are mirrored to a different location. From a high level perspective, the staging process happens in three phases: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Export:} processes the publication configuration, which defines the site's content and apps. This phase also gathers the obligatory referenced entities that are required on the live site. Then everything according to the publication parameters is processed into the instance's own file format, and that file is stored locally or transferred to the remote live Liferay instance. \item \textbf{Validation:} determines if it's possible to start the import process. This phase verifies the file's version and its integrity, checks for additional system information like language settings, and validates there is no missing content referenced. \item \textbf{Import:} makes any necessary updates or additions to the site's content, layouts, and apps according to the publishing parameters. If everything is verified and correct, the staged content is published to your live Site. \end{enumerate} These phases are executed sequentially. A crucial factor for successfully publishing staged content is data integrity. If anything is not verified during the publication process, the transactional database reverts the site back to its original state, discarding the current publication. This is a necessary action to safeguard against publishing incomplete information, which could break an otherwise well-designed live Site. If the file system is not \emph{database-stored} (e.g., DBStore), it's not transactional and isn't reverted if a staging failure occurs. This could potentially cause a discrepancy between a file and its reference in the database. Because of this, administrators should take great care with staging the document library, making sure that regular backups of both database and file system are being maintained. Next, you'll learn about staging best practices and prerequisites to follow for a seamless staging experience. \section{Planning Ahead for Staging}\label{planning-ahead-for-staging} Staging is a complex subsystem that's flexible and scalable. Before advanced users and administrators begin using it for their site, it's important to plan ahead and remember a few tips for a seamless process. There are several factors to evaluate. \begin{itemize} \item \textbf{Content (amount, type, and structure):} Depending on the content in your site, you can turn on staging for only the necessary content types, leaving others turned off to avoid unnecessary work. Publication can also be configured to publish only certain types of content. See the \href{/docs/7-2/user/-/knowledge_base/u/managing-content-types-in-staging}{Managing Content Types} article for more information. \item \textbf{Hardware Environment:} You should plan your environment according to your content types. If your site operates on large images and video files, decide if a shared network drive is the best option. Storing many large images in the Document Library usually requires a faster network or local storage. If you're dealing with web content, however, these are usually smaller and take up very little disk space. \item \textbf{Customizations and Custom Logic for Your Staging Environment:} Your organization's business logic is most likely implemented in an app, and if you want to support staging for that app, you must \href{/docs/7-2/frameworks/-/knowledge_base/f/content-publication-management}{write some code} to accomplish this. You can also consider changing default UI settings by writing new JSP code if you want your staging environment's look and feel to change. \end{itemize} Once you've finished planning for your site, you should turn on staging at the very beginning of the site creation process. This allows the site creator to avoid waiting for huge publications that can take long periods to execute. Taking smaller steps throughout the publication process forms an iterative creative process as the site is built from the ground up, where content creators can publish their changes immediately, avoiding long wait times. Here are some JVM/network configuration recommendations for Staging: \begin{itemize} \item 4 GB of memory \item 20 MB/s transfer rate minimum (disk) \end{itemize} Now that you know how the staging environment works and how to enable it for your site, you'll begin using it in the next section. \chapter{Using the Staging Environment}\label{using-the-staging-environment} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} After \href{/docs/7-2/user/-/knowledge_base/u/enabling-staging}{enabling staging} (either Local Live or Remote Live) for a site, you'll notice additional options provided on the top Control Menu (Staging Bar) and also in the menu to the left. These new menus help you manage staged pages. Most of your page management options have also been removed; now you can't directly edit live pages. You now must use the staging environment to make changes. Click the \emph{Staging} button to view the staged area. Management options are restored and you can access some new options related to staging. \begin{figure} \centering \includegraphics{./images/staging-live-page.png} \caption{You can see the new staging options added to the top and left of your screen.} \end{figure} To test out the staging environment, add the Bookmarks widget and then click on \emph{Live} from the top menu. Notice that the Bookmarks widget isn't there. That's because you've staged a change to the page but haven't published that change yet to the live site. Next, you'll learn the basics of staging content. \chapter{Staging Content}\label{staging-content} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} When you're on the staged site, you have several options in the Staging Bar to help start your staging conquest. \textbf{Site Pages Variation:} lets you work in parallel on multiple versions of a staged site page. You can choose the site page variation from the dropdown menu or manage them from the \emph{Options} icon (\includegraphics{./images/icon-staging-bar-options.png}) in the Staging Bar. See the \href{/docs/7-2/user/-/knowledge_base/u/using-multi-and-single-page-variations}{Using Site Pages Variations} article for more information. \textbf{Page Variations:} lets you work in parallel on multiple versions of a staged page. You can choose the page variation from the dropdown menu or manage them from the \emph{Options} icon (\includegraphics{./images/icon-staging-bar-options.png}) in the Staging Bar. See the \href{/docs/7-2/user/-/knowledge_base/u/using-multi-and-single-page-variations}{Using Site Pages Variations} article for more information. \textbf{Undo/Redo:} steps back/forward through recent changes to a page, which can save you the time of manually adding or removing apps if you make a mistake. To access \emph{Undo}/\emph{Redo}, select the \emph{Options} icon (\includegraphics{./images/icon-staging-bar-options.png}) in the Staging Bar. \textbf{History:} shows the list of revisions of the page, based on publication dates. You can go to any change in the revision history and see how the pages looked at that point. To access \emph{History}, select the \emph{Options} icon (\includegraphics{./images/icon-staging-bar-options.png}) in the Staging Bar. \textbf{Ready for Publication:} After you're done making changes to the staged page, click this button. The status of the page changes from \emph{Draft} to \emph{Ready for Publication} and any changes you've made can be published to the Live Site. When you publish a page to live, only the version \emph{Marked as Ready for Publication} is published. Now you'll step through a brief example for using the Staging Bar to stage and publish content. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item On the staged site, navigate to the Product Menu and select \emph{Content \& Data} → \emph{Web Content}. \item Create a Basic Web Content article and save it. \item Go back to your staged site's main page and navigate to the \emph{Add} (\includegraphics{./images/icon-add-widget.png}) → \emph{Widgets} → \emph{Content Management} menu and drag the \emph{Web Content Display} widget to the page. \item Select the web content article you created to display in the new widget. \item Select the \emph{Ready for Publication} button to confirm you're ready to publish the content from the staged site to the live site. This prepares the staged content for publication. If workflow is enabled for any new resource, the resource must go through the workflow process before it can be published to the live site. \begin{figure} \centering \includegraphics{./images/staging-publish-bar.png} \caption{The staging toolbar indicates whether you're able to publish to the live site.} \end{figure} \item Click the \emph{Publish to Live} button. A pop-up window appears with configuration options for your publication. \item Enter the name of your publication. \item Observe the changes listed in the menu. This lists the changed content planned for publication. \begin{figure} \centering \includegraphics{./images/simple-staging-publication.png} \caption{The Simple Publication menu displays the changes since last publication and a way to name your publication.} \end{figure} \item Click the \emph{Publish to Live} button to publish your staged results to the live site. \end{enumerate} Awesome! You've created content on the staged site and published it to your live site. It's now available for all your site users to see! \noindent\hrulefill \textbf{Note:} Although publishing content is the more well-known function, publishing a portlet is also a viable option. You can publish portlets residing in the Control Panel and on pages. For example, you can modify a portlet's title and publish the change to live. This is possible because portlet configurations are always staged. To publish a portlet that is on a page, you must publish the page first. \noindent\hrulefill This example explored the Simple Publication menu. If your publication requires more advanced configuration like specifying specific content, dates, pages, etc., you should click the \emph{Switch to Advanced Publication} button. You'll explore the more advanced configuration options next. \chapter{Advanced Publication with Staging}\label{advanced-publication-with-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Once you've finished your changes on the staged site and want to publish them, select the \emph{Publish to Live} button from the Staging Bar. To configure advanced publication options, select the \emph{Switch to Advanced Publication} button. Opening the Advanced Publication menu presents options for scheduling a time to publish your content, editing the pages/content to include in the publication, managing permissions, etc. This lets you perform advanced editing to your publication process. \section{Date}\label{date} You have two options for the Date category: \textbf{Now:} immediately pushes any changes to the live Site. \textbf{Schedule:} set a specific date to publish or to set up recurring publishing. You could use this to publish all changes made during the week every Monday morning without any further intervention. These options let you plan staging schedules in advance. \section{Pages}\label{pages-1} This area of the menu gives you the option to choose which pages to include when you publish. You can choose the page group (Public or Private) to publish by selecting the \emph{Change to Public Pages} or \emph{Change to Private Pages}. You cannot publish both at the same time; you must complete their publication processes separately if you want to publish both page groups. \begin{figure} \centering \includegraphics{./images/staging-advanced-publication.png} \caption{You have several ways to specify the pages you want included in your publication.} \end{figure} You can also choose specific pages to publish, and the look and feel of those pages. \noindent\hrulefill \textbf{Note:} If you're publishing pages with a custom theme, you must check the \emph{Theme Settings} option under the Look and Feel heading for your staging configuration. If it's not checked, the default theme is always applied. \noindent\hrulefill The \emph{Delete Missing Pages} option under the Look and Feel heading deletes all pages from the live Site that are not present on the staging Site. You can choose specific pages to publish (and remove) by manually selecting them under the Pages to Publish heading. \noindent\hrulefill \textbf{Note:} The Simple Publication menu displays the number of page deletions tracked by the Staging framework. Keep in mind that this number counts the page deletions on the staging Site, not how many pages will be deleted on the live Site. There could be an inconsistency between the number of page deletions to be published and the actual number of pages present on either of the staging and live Sites. For example, pages that were deleted on the staging Site before they were published. \noindent\hrulefill See the \hyperref[deletions]{Deletions} section for more information on content deletion. \section{Content}\label{content} The Content area lets you select the content to be published. If you choose a page to be published from the Pages menu, the portlets and their references are always published, even if you specify differently in the Content section. There are other filtering sub-options for certain content types. You first must choose what content to publish based on date. Specifying a date range lets you choose content to publish based on when it was created or last modified. Select the option that best fits your workflow. The available options are described in more detail below: \textbf{All:} publishes all content regardless of its creation or last modification date. \textbf{From Last Publish Date:} publishes content that was created or modified since the last publish date. This is the default option. \textbf{Date Range:} publishes content based on a specified date range. You can set a start and end date/time window. The content created or modified within that window of time is published. \textbf{Last:} publishes content based on a set amount of time since the current time. For example, you can set the date range to the past 48 hours, starting from the current time. Under the date options are the different types of content that can be published. This list is populated based on the provided date range. For example, if at least one article is created or modified in the given date range, a Web Content section appears in the list, and the number of articles is shown next to the Web Content label. Otherwise, the Web Content section is absent. The \emph{Categories} content type is not dependent on the date range and is always shown in the list. \noindent\hrulefill \textbf{Note:} Since comments and ratings are meant for the end user, they are not supported in staging and can only be added to the live site. \noindent\hrulefill Unchecking the checkbox next to a certain content type excludes it from the current publication to the live site. Some of the content types in the list, like Web Content and Documents and Media, have further filtering options. For instance, when the Web Content section is present and checked, it shows a comma-separated list of related items to be published, including the articles themselves. A sample list of related items for web content might look like this: \emph{Web Content(12), Structures(3), Referenced Content, Include Always, Version History}. You can remove items by clicking the \emph{Change} button next to the list. See the \href{/docs/7-2/user/-/knowledge_base/u/managing-content-types-in-staging}{Managing Content Types in Staging} article for more information on managing content during the publication process. \section{Deletions}\label{deletions} This portion of the menu lets you delete two things: \begin{itemize} \tightlist \item portlet metadata before publishing \item operations performed for content types \end{itemize} You have two options to manage for deletions: \textbf{Delete Application Data Before Importing:} all data created by the application is deleted before the import process. Ensure you understand the ramifications of this option before selecting it. Some applications in other pages may reference this data. This process cannot be undone. If you are unsure, complete an export first. \textbf{Replicate Individual Deletions:} operations performed for content in the staging environment are replicated to the target site. This does not include page deletions. \section{Permissions}\label{permissions} This area lets you include permissions for the pages and portlets when the changes are published. Select the \emph{Publish Permissions} checkbox to enable this functionality. Once you're finished configuring you advanced publication, select \emph{Publish to Live} to publish or schedule your publication. \chapter{Managing Content Types in Staging}\label{managing-content-types-in-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} When managing content in Staging's Advanced Publication menu, there are several factors to consider when preparing your content for publication. As described in \href{/docs/7-2/user/-/knowledge_base/u/advanced-publication-with-staging}{Advanced Publication with Staging}, you can navigate to the Content area of the Advanced Publication menu to select content you want to publish. There are options attached to each content group (e.g., Web Content) that you can manage too. \begin{figure} \centering \includegraphics{./images/web-content-version-history-box.png} \caption{Click the \emph{Change} button for a content group to manage its specific content.} \end{figure} You'll learn about some of these options and their best practices next. \section{Referenced Content}\label{referenced-content} This is represented by \begin{itemize} \tightlist \item Structures and templates included in web content. \item Documents and Media files (e.g., images) included in web content. \item etc. \end{itemize} You can exclude some of this content during publication or export to speed up the process. These references are validated during the publication process or an import, so the images must be published or imported first. \section{Version History}\label{version-history} Web content tends to be updated frequently, often more so than other kinds of content. Sometimes this can result in high numbers of versions. If there are hundreds of versions, it can take a long time to publish these articles. You can bypass this by choosing to not publish the \emph{Version History} (i.e., the past versions of the web content articles to be published). If you disable this, only the last \textbf{approved} version of each web content article is published to Live. This can significantly speed up the publication process. You can set this option globally. If you navigate to the Control Panel → \emph{Configuration} → \emph{System Settings} → \emph{Web Content} → \emph{Virtual Instance Scope} → \emph{Web Content}, you can toggle the \emph{Version History by Default Enabled} checkbox. This sets the default behavior. When publishing content, it is selected by default, so site administrators must manually uncheck the \emph{Version History} box to publish only the latest approved version of web content articles. To change the default behavior, enable the checkbox in System Settings. \section{Previews and Thumbnails}\label{previews-and-thumbnails} Previews and thumbnails are generated automatically for documents. Disabling this, though, can greatly increase your publishing speed in some cases. You should be careful about publishing previews and thumbnails to the live Site. Imagine a scenario where a site has approximately 4000 images or documents. If the previews and thumbnails are turned on, this could end up in 28000 physical files on the disk. If staging is set up to publish the previews and thumbnails, this would mean that instead of taking care of the 4000 images, it would process seven times more files! If you still want to use the previews on your live environment, you can set up that Liferay instance to generate them automatically. It depends on your environment for whether you can use the publishing of the previews and thumbnails. Publishing them is a heavy operation, and you must also transfer the LAR file over the network if you use remote staging. If you decide to generate them on the live Site, understand that this could take some time, since it's a CPU intense operation. \section{Vocabularies}\label{vocabularies} When working within a site, a user may select vocabularies from both the current site as well as the global site. While this doesn't pose an issue when creating content, it can cause issues when publishing. For environments that use both global and local vocabularies, note that global vocabularies must be published to the live site through global site staging. One way to avoid confusion with vocabularies is to keep all vocabularies local or global. If both must be used, you can resolve the issue by ensuring that dependencies (e.g., categories and vocabularies) are published before publishing the site that depends on them (whether the dependencies are local or global). Assets like tags, categories, structures, templates, widget templates, document types, and dynamic data lists can also be shared by a parent to its child sites. In this case, ensure that the ancestor's dependencies are published before the site in question. \section{Deletions}\label{deletions-1} The Staging framework gathers deletions (including trashed entities) in a site. These deletions can be published to clean up the live Site. If you plan to process it later, or if it's not a problem to have lingering data on live, this can be turned off as well to save execution time during the process. \chapter{Staging Processes and Templates}\label{staging-processes-and-templates} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} When you make a staging publication, it's captured as a staging process and stored for future reference. You can manage these processes by navigating to the \emph{Staging} option located in the Product Menu's Publishing tab. From there, you'll see a list of staging processes that have been completed. You can relaunch or clear any of these publications by clicking the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to a process. If you click the \emph{Scheduled} tab from above, you'll find staging processes that you've scheduled for future publication dates. \begin{figure} \centering \includegraphics{./images/staging-processes.png} \caption{Your staging processes can be viewed at any time.} \end{figure} If you find yourself repeatedly creating similar staging processes, you should think about using Publish Templates. Instead of manually having to customize a publication process every time you're looking to publish pages/content, you can use a publish template. With publish templates, you can select a custom template and immediately publish with the options you configured. Follow the steps below to create and use a publish template. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the \emph{Options} icon (\includegraphics{./images/icon-options.png}) from the top right corner of the Staging screen and select \emph{Publish Templates}. \item Click the \emph{Add} button (\includegraphics{./images/icon-add.png}) and assign the template a title and description, and then fill out the configuration options as you would during a custom publication process. \item Save your publish template. It's available to use from the \emph{Publish Templates} tab in the \emph{Publish to Live} menu's Advanced Publication area. \item To use the template, click the \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to the template and select \emph{Publish}. This automatically sets the options for publishing pages and their content. All you have to do is give the publication process a name. Once you confirm the configuration settings, your staging settings are published. \end{enumerate} \noindent\hrulefill \textbf{Note:} When staging is enabled, the options available from the \emph{Publishing} tab are modified. When in the Live environment, you can only access the \emph{Export} feature. When in the Staging environment, you can only access the \emph{Import} and \emph{Staging} features. The disabled features for each environment don't make sense in that context. For example, you shouldn't be able to import content when in the live environment; it must be imported into the staged environment and then published before it is available in the live site. \noindent\hrulefill Now you know how to reference stored/scheduled staging processes and create publish templates to streamline publication. \chapter{Disabling Staging}\label{disabling-staging} Disabling staging doesn't take a lot of steps, but should not be done lightly. It's important to know the consequences of turning the staging environment off so you can decide if your circumstances really warrant it. The consequences for disabling Local Live and Remote Live staging are slightly different, so you'll learn about both. \section{Disabling Local Live Staging}\label{disabling-local-live-staging} {This material has been updated and ported to Liferay Learn and is no longer maintained here.} Conceptually, the live site is the final approved version of your site, whereas the staging site is a temporary workspace containing information that is not finalized. Disabling local staging completely removes the staging environment, which means all content not published to your live site is erased. Therefore, before disabling staging, you must ensure all necessary information on the staged site is published or preserved elsewhere. Keep in mind that draft content types are not published, so they can be lost too. When you enabled staging there was an initial publication. Disabling staging does not start a publication; the staging site is deleted. If the staged site contains a large amount of content, however, those deletions could take a substantial amount of time to process. For this reason, don't disable staging when your portal instance is busy. \section{Disabling Remote Live Staging}\label{disabling-remote-live-staging} {This material has been updated and ported to Liferay Learn and is no longer maintained here.} Disabling remote staging does not delete the staged site; it only disables the connection between the live site and remote staging site. This means no data is deleted from the live or remote staging sites after the connection is disabled. Since no data is erased and no processes are started, disabling remote staging is almost instantaneous. When Remote Live staging is enabled, certain information (e.g., which portlet is being staged) is recorded on both the live and staged Sites. For this reason, when you disable remote staging, you must ensure the live Site is still accessible so both sides can communicate that it's disabled. Do not shut down your live Site and then attempt to disable remote staging from your staged Site; this results in errors. If there's ever a lost network connection between the remote staged Site and the live Site, a message appears, informing you of the error and a way to forcibly disable staging. This is only an option for the staged site; executing this option erases the staged site's staging information---not the content. On the contrary, the live site remains in a locked state. A possible workaround is to create a new live site and import content to it, if necessary. \section{Steps to Disable Staging}\label{steps-to-disable-staging} Follow the steps below to disable Local Live or Remote Live staging: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Publishing} → \emph{Staging} option, which is only available from the staged site. \item Click the \emph{Options} icon (\includegraphics{./images/icon-options.png}) from the upper right corner of the page and select \emph{Staging Configuration}. \item Select the \emph{None} radio button and click \emph{Save}. \end{enumerate} That's it! Your staging environment is now turned off. \chapter{Publishing Single Assets From a Staged Site}\label{publishing-single-assets-from-a-staged-site} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Sometimes, stepping through the entire publication process is not necessary and can be overkill. For example, \begin{itemize} \tightlist \item What if you created a web content article and want only to publish it and nothing else? \item What if you want to publish a new folder of articles and their dependencies, but don't want the hassle of the publication process? \item What if you found a typo in a document and want to fix it quickly and push to the live site? \end{itemize} You're in luck! You can publish certain single assets from the staged Site to the live Site without creating a staging publication process, from their respective app menus: \begin{itemize} \tightlist \item Web Content \begin{itemize} \tightlist \item Web Content \item Folder \end{itemize} \item Documents and Media \begin{itemize} \tightlist \item Document \item Folder \item Shortcut \item Document Type \end{itemize} \item Blogs \begin{itemize} \tightlist \item Blog \end{itemize} \item Bookmarks \begin{itemize} \tightlist \item Bookmark \item Folder \end{itemize} \end{itemize} \textbf{Important:} Single asset publication is not supported for page-scoped content. \noindent\hrulefill \textbf{Note:} When publishing a Web Content or Bookmarks folder, their respective entries and subfolders are included. Publishing a Documents and Media folder works the same way, but also includes shortcuts. \noindent\hrulefill You'll step through an example to see how this is done. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Make sure the Staging framework is \href{/docs/7-2/user/-/knowledge_base/u/enabling-staging}{enabled} and you're on the staged Site. \item Create a Web Content Article in the Product Menu's \emph{Content \& Data} → \emph{Web Content} menu. \item Once you've saved the new Web Content Article, select its \emph{Actions} button (\includegraphics{./images/icon-actions.png}) next to the article and select \emph{Publish to Live}. \begin{figure} \centering \includegraphics{./images/single-asset-publish.png} \caption{You can publish the single web content article to the live site.} \end{figure} \item You're presented a Process Details page where you can view the progress of your single asset publication request. Ensure the Web Content Article is published successfully. \end{enumerate} \noindent\hrulefill \begin{verbatim} **Note:** Sometimes the publication process doesn't start immediately (e.g., if there's another publication running). You can check a specific asset's publication progress by navigating to the *Options* (![Options](./images/icon-options.png)) → *Staging* → *Current and Previous* tab in its Site Admin app. \end{verbatim} \noindent\hrulefill There you have it! If you navigate to your live site's Web Content section, the new article is available. Similar to the regular staging publication process, your single asset publications also include associated dependencies. For example, if your web content article contains an image, custom structure, and custom template, they are all published together. The same concept applies for folders---if you publish a folder containing several web content articles, all the articles and their associated dependencies are published too. By default, only those with permissions to publish widgets can publish single assets. Follow the steps below to modify these permissions for a Role: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Control Panel → \emph{Users} → \emph{Roles}. \item Select the Role you're updating. \item Click the \emph{Define Permissions} tab. \item In the left menu, navigate to \emph{Control Panel} → \emph{Sites} → \emph{Sites}. \item Under the Resource Permissions heading, select the \emph{Export/Import Application Info} option. \end{enumerate} Also, make sure the \emph{Publish Staging} permission is granted to the role. This is required to publish assets with staging. See the \href{/docs/7-2/user/-/knowledge_base/u/managing-permissions}{Managing Permissions} article for more information. Great! Now you know how to publish single assets and manage the permissions for who can do it. \chapter{Organizing Pages for Staging}\label{organizing-pages-for-staging} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Say you're working on a product-oriented Site where several major changes are required for a page or a set of pages over a short period of time. You must work on multiple versions of the Site at the same time to ensure everything has been properly reviewed before it goes live. With staging, you can do this using \emph{Page Variations}. In this section, you'll explore page variations and how they're useful. \chapter{Using Multi and Single Page Variations}\label{using-multi-and-single-page-variations} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} There are two page variation options available from the \href{/docs/7-2/user/-/knowledge_base/u/using-the-staging-environment}{Staging Bar}: \begin{itemize} \tightlist \item \emph{Site Pages Variation:} Different variations for a set of Site pages. For instance, you could use this if you had three separate pages and wanted to modify these pages while keeping them together as a set. \item \emph{Page Variations:} Different variations for a single page. A page variation can only exist inside a Site pages variation. \end{itemize} You must enable page versioning in the Staging Configuration menu before the above options are available in the Staging Bar. You'll learn more about this later. Variations only affect pages and not the content, which means all the existing content in your staging Site is shared by all your variations. The content, however, can be displayed in many different ways for each page variation. For example, in different Site page variations, you can have different logos, a different look and feel for your pages, different applications on these pages, different configuration of these applications, and even different pages. One page can exist in just one Site page variation or in several of them. Modifying the layout type (e.g., Layout, Panel, Embedded, etc.) or friendly URL of a page, however, \textbf{does} affect every Site page variation. For example, if a page template is modified, those modifications are propagated to the pages configured to inherit changes from the template, overriding Staging's Page Variations and Site Pages Variations. \noindent\hrulefill \textbf{Note:} Page templates are not recognized by the Staging framework. This means that existing page templates are not viewable or editable on a staged Site. If they're created on a staged Site, they cannot be preserved once staging is disabled. You can, however, export and import page templates. \noindent\hrulefill You'll learn about enabling page versioning next. \section{Enabling Page Versioning}\label{enabling-page-versioning} Page Versioning is enabled on the Staging Configuration screen when first \href{/docs/7-2/user/-/knowledge_base/u/enabling-staging}{enabling staging}. \begin{figure} \centering \includegraphics{./images/page-versioning.png} \caption{You can enable page versioning for public and/or private pages.} \end{figure} You can enable page versioning for public and private pages. When page versioning is enabled, the page variation options are available in the Staging Bar. By default, you only have one Site pages variation and page variation which are both called \emph{Main Variation}. If you did not enable page versioning during the initial setup of your staging environment, navigate to the Product Menu → \emph{Publishing} → \emph{Staging} → \emph{Options} (\includegraphics{./images/icon-options.png}) → \emph{Staging Configuration}. You can enable the page versioning options there. \section{Using Site Pages Variations}\label{using-site-pages-variations} Site pages variations are useful when you must plan multiple page sets for your Site at once. For example, consider this scenario: If there were separate teams in your company that needed to create three drastically different page sets for your Site at the same time, they would need to create three Site pages variations. For example, \begin{itemize} \tightlist \item The marketing team can give your Site a completely different look and feel for the Holidays. \item The product management team can work on a version that is planned to publish on the first of the year for a new product launch. \item The developer relations team can work on a version that is planned to publish on the upcoming Hack-a-thon day. \end{itemize} With this use case, having a Site pages variation for each planned page set allows three ideas to be fully planned and implemented before publication. Another option for this scenario is to let the product management team experiment with two different ideas for the home page of the Site. This can be done by creating several page variations within the current Site pages variation of their product launch Site. You'll learn more about page variations later. Once you've \href{/docs/7-2/user/-/knowledge_base/u/creating-multi-and-single-page-variations}{created a Site pages variation}, you can now navigate to its home page and change the logo, apply a new theme, move applications around, change the order of the pages, configure different apps, and more. The other variations aren't affected. You can even delete existing pages or add new ones (remember to \emph{Mark as Ready for Publication} when you are finished with your changes). When you delete a page, it is deleted only in the current variation. The same happens when you add a new page. If you try to access a page that was deleted in the current variation, Liferay informs you this page is not \emph{enabled} in this variation and you must enable it. \begin{figure} \centering \includegraphics{./images/enable-unavailable-page.png} \caption{Select the \emph{Enable} button to create a missing page in the current Site pages variation.} \end{figure} To publish a variation to the live Site, click on \emph{Publish to Live} in the staging menu and then select \emph{Publish to Live}. Publications can also be scheduled independently for different variations. For example, you could have a variation called \emph{Mondays} which is published to the live Site every Monday and another one called \emph{Day 1} which is published to the live Site every first day of each month. \section{Using Page Variations}\label{using-page-variations} You can also have variations for a single page inside a Site pages variation, which lets you work in parallel on different versions of a page. For example, you might work on two different proposals for the design of the home page for a Holidays Site pages variation. Page variations only exist inside a Site pages variation. Once you've \href{/docs/7-2/user/-/knowledge_base/u/creating-multi-and-single-page-variations}{created a page variation}, you can choose it from the dropdown menu on the Staging Bar. You can always switch between different variations by clicking on them from the Staging Bar. Once you've modified the page variation to the way you want, mark it as \emph{Ready for Publication}. Only one page variation can be marked as ready for publication and that is the one that gets published to the live Site. To publish a variation to the live Site, click on \emph{Publish to Live} in the staging menu and then select \emph{Publish to Live}. \section{Managing Variation Permissions}\label{managing-variation-permissions} It's also possible to set permissions on each variation, so certain users have access to manage some, but not all variations. To do this, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Staging Bar's \emph{Options} button (\includegraphics{./images/icon-staging-bar-options.png}) and select the variation type you want to configure. \item Select the desired variation's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) and select \emph{Permissions}. \item Configure the variation's permissions and then click \emph{Save}. \end{enumerate} \begin{figure} \centering \includegraphics{./images/page-variation-permissions.png} \caption{Configure the roles that can access and modify your variation.} \end{figure} Awesome! You now know how to manage variation permissions! \chapter{Creating Multi and Single Page Variations}\label{creating-multi-and-single-page-variations} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} You can create two types of page variations: \begin{itemize} \tightlist \item Site Pages Variation (multiple pages) \item Page Variation (single page) \end{itemize} You can learn more about these variations in the \href{/docs/7-2/user/-/knowledge_base/u/using-multi-and-single-page-variations}{Using Multi and Single Page Variations} article. As an example, you'll step through creating a Site pages variation next. Both variation processes, however, are similar. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Select the \emph{Options} icon (\includegraphics{./images/icon-staging-bar-options.png}) in the Staging Bar and select the variation option. For example, select the \emph{Site Pages Variation} option. This brings you to a list of the existing Site page variations for your Site. \item Click \emph{Add Site Pages Variation} to create a new one. \begin{figure} \centering \includegraphics{./images/staging-page-variations.png} \caption{When selecting the \emph{Site Pages Variation} link from the Staging Bar, you're able to add and manage your Site pages variations.} \end{figure} \item Set a name and description for your new Site pages variation. \item Set how you want your variation created. From the \emph{Copy Pages from Site Page Variation} field, you can copy content from an existing variation to create your new one. There are several options to choose in this selector. \textbf{All Site Pages Variations:} Creates a new variation that contains the last version marked as ready for publication from any single page existing in any other variation. \textbf{None (Empty Site Pages Variation):} Creates a new, empty variation. \textbf{{[}Existing Variations{]}:} Creates a new Site page variation that contains only the last version of all the pages that exist in a specific variation (e.g., \emph{Main Variation}). The current variation must be marked as ready for publication. The copy option is not available when creating a page variation. A new page variation is a copy of the current Site pages variation. \item Click \emph{Add} to create the Site pages variation. \end{enumerate} \noindent\hrulefill \textbf{Note:} You can rename any variation after it's created, if necessary. For example, edit the Main Variation and change its name to something that makes more sense in your Site, such as \emph{Basic}, \emph{Master}, or \emph{Regular}. \noindent\hrulefill Awesome! Your Site pages variation is created and available for modification. \chapter{Merging Site Pages Variations}\label{merging-site-pages-variations} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Another powerful feature of Staging's Page Versioning framework is the possibility of \emph{merging} Site Pages Variations. To merge two Site Pages Variations, follow the instructions below. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the Staging Bar's \emph{Options} button (\includegraphics{./images/icon-staging-bar-options.png}) and select \emph{Site Pages Variation}. \item Click the Site Pages Variation's \emph{Actions} button (\includegraphics{./images/icon-actions.png}) you want to use as the base for merging and select \emph{Merge}. \item Select the Site Pages Variation to merge on top of the base Site Pages Variation. \begin{figure} \centering \includegraphics{./images/merge-site-pages-variation.png} \caption{Select the site pages variation you'd like to merge with your base variation.} \end{figure} Merging works like this: \begin{itemize} \tightlist \item New pages that don't exist in the base variation are added. \item If a page exists in both Site Pages Variations, and at least one version of the page was marked as ready for publication, the latest version marked as ready is added as a new page variation in the target page of the base variation. Note that older versions or page variations not marked as ready for publication aren't copied. Merging can be executed, however, as many times as needed and creates the needed page variations in the appropriate page of the base site pages variation. \item Merging does not affect content and doesn't overwrite anything in the base variation; it adds more versions, pages, and page variations as needed. \end{itemize} \end{enumerate} Great! You've merged site pages variations! \chapter{Managing Permissions}\label{managing-permissions} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} The staging environment has many different options for building and managing a Site and its pages. Sometimes administrators want to limit access to staging's subset of powerful features. A Role with permissions can accomplish this. To create/modify a Role, complete the following steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the \emph{Control Panel} → \emph{Users} → \emph{Roles}. \item To create a new Role, select the \emph{Add} button (\includegraphics{./images/icon-add.png}) and complete the New Role menu. Once you have a new Role created, or you've decided on the Role you want to modify, select the Role's \emph{Actions} icon (\includegraphics{./images/icon-actions.png}) and select \emph{Edit}. \item From the top menu, select \emph{Define Permissions}. \end{enumerate} The most obvious permissions for staging are the general permissions that look similar to the permissions for most Liferay apps. These permissions are in the \emph{Site Administration} → \emph{Publishing} → \emph{Staging} section of the Define Permissions menu. They include \begin{itemize} \tightlist \item \emph{Access in Site Administration} \item \emph{Add to Page} \item \emph{Configuration} \item \emph{Permissions} \item \emph{Preferences} \item \emph{View} \end{itemize} Also, there are some Site resource permissions that deal directly with staging. These permissions are in the \emph{Control Panel} → \emph{Sites} → \emph{Sites} section in the Define Permissions menu. The relevant Site resource permissions related to staging are listed below: \textbf{Add Page Variation:} Hides/shows the \emph{Add Page Variation} button on the Staging Bar's Manage Page Variations screen. \textbf{Add Site Pages Variation:} Hides/shows the \emph{Add Site Pages Variation} button on the Staging Bar's Manage Site Page Variations screen. \textbf{Export/Import Application Info:} If the Publish Staging permission is not granted, hides/shows the application level Export/Import menu. The Configuration permission for the Export/Import app is also required. \textbf{Export/Import Pages:} If the Publish Staging permission is not granted, hides/shows the Export/Import app in the Site Administration menu. \textbf{Manage Staging:} Hides/shows the Staging Configuration menu in the Site Administration → \emph{Publishing} → \emph{Staging} → \emph{Options} (\includegraphics{./images/icon-options.png}) menu. \textbf{Publish Application Info:} Hides/shows the application level Staging menu. \textbf{Publish Staging:} Hides/shows the \emph{Publish to Live} button on the Staging Bar and hides/shows the \emph{Add Staging Process} button (\includegraphics{./images/icon-add.png}) in the Site Administration menu's Staging app. This permission automatically applies the \emph{Export/Import Application Info}, \emph{Export/Import Pages}, and \emph{Publish Application Info} permission functionality regardless of whether they're unselected. \textbf{View Staging:} If Publish Staging, Manage Pages, Manage Staging, or Update permissions are not granted, hides/shows the Site Administration menu's Staging app. Notice that some of the permissions above are related to the export/import functionality. Since these permissions are directly affected by the Publish Staging permission, they are important to note. Visit the \href{/docs/7-2/user/-/knowledge_base/u/importing-exporting-pages-and-content}{Importing/Exporting Pages and Content} section for more details on importing/exporting Site and page content. \chapter{Scheduling Web Content Publication}\label{scheduling-web-content-publication} Liferay's Web Content framework lets you define when your content goes live. You can determine when the content is displayed, expired, and/or reviewed. This is an excellent way to keep your Site current and free from outdated (and perhaps incorrect) information. The scheduler is built right into the Properties menu your users access when adding web content. To access this menu, click the \emph{Options} gear (\includegraphics{./images/icon-gear.png}) and open the Schedule dropdown menu. \begin{figure} \centering \includegraphics{./images/web-content-schedule.png} \caption{The web content scheduler can be easily accessed from the right panel of the page.} \end{figure} The scheduler offers several configurable options: \textbf{Display Date:} Sets (within a minute) when content will be displayed. \textbf{Expiration Date:} Sets a date to expire the content. The default is one year. \textbf{Never Expire:} Sets your content to never expire. \textbf{Review Date:} Sets a content review date. \textbf{Never Review:} Sets the content to never be reviewed. As an example, you'll step through the process of scheduling a web content article. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Navigate to the Product Menu → \emph{Content \& Data} → \emph{Web Content}. \item Create a new web content article by selecting the \emph{Add Web Content} button (\includegraphics{./images/icon-add.png}) → \emph{Basic Web Content}. \item Add content for your web content article. \item Select the \emph{Schedule} tab from the web content's Properties menu. Configure the publication schedule. \item Click \emph{Publish}. Your web content article is now created and abides by the scheduling parameters you've set. \end{enumerate} When you set a Display Date for an existing article it does not affect previous versions of the article. If a previous version is published, it remains the same until the new version is scheduled to display. However, the expiration date affects all versions of the article. Once an article has expired, no version of that article appears. \noindent\hrulefill \textbf{Tip:} If you want only the latest version of articles to expire, and not every past version, go to \emph{Control Panel} → \emph{Configuration} → \emph{System Settings} → \emph{Web Content} → \emph{Virtual Instance Scope} → \emph{Web Content} and uncheck \emph{Expire All Article Versions Enabled}. This makes the previously approved version of an article appear if the latest version expires. \noindent\hrulefill The scheduling feature gives you great control in managing when, and for how long, your web content is displayed on your Site. Additionally, you can determine when your content should be reviewed for accuracy and/or relevance. This makes it possible to manage your growing inventory of content. \chapter{Managing Apps}\label{managing-apps} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Apps run on the platform Liferay DXP provides. The web experience management, collaboration, and business productivity features all consist of apps. Even the Control Panel consists of configuration apps. You can also add to or change built-in functionality by installing other apps. There are several ways to manage, find, and install apps. This section covers these topics and more. \chapter{Managing and Configuring Apps}\label{managing-and-configuring-apps} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay DXP is a platform for deploying apps that comprise modules and components. It has conveniences for managing apps and app management best practices for maximizing stability. Best practices in production environments involve stopping the server before applying changes, but in cases where this isn't feasible, you can ``auto deploy'' changes several different ways. There are two places in the Control Panel where you can manage and configure apps: the \emph{App Manager} and the \emph{Components} listing. The App Manager manages apps in the OSGi framework. You can use the App Manager to install, activate, deactivate, and delete apps. You can manage apps at the app and module levels. The Components listing views and manages apps at the OSGi component level. It differs from the App Manager by showing apps by type (portlet, theme, and layout template), and setting app permissions. You can use the Components listing to activate and deactivate apps, but it can't install or delete apps. Start with learning app management best practices in production, or wherever you want to maximize stability. \section{Managing Apps in Production}\label{managing-apps-in-production} Not all apps are designed to be ``auto deployed''---deployed while the server is running. Deploying that way can cause instabilities, such as class loading leaks and memory leaks. On production systems, avoid ``auto deploying'' apps and configurations whenever possible. If you're installing an app or a component configuration on a production system and stopping the server is feasible, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Stop your server. \item Copy your app (\texttt{.lpkg}, module \texttt{.jar}, or plugin \texttt{.war}) to your \texttt{{[}Liferay\ \ \ \ \ Home{]}/deploy} folder, or copy your component configuration (\texttt{.config} file) to the \texttt{{[}Liferay\ Home{]}/osgi/configs} folder. The \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder is typically the app server's parent folder. \item Start your server. \end{enumerate} If you're running in cluster, follow the instructions for \href{/docs/7-2/deploy/-/knowledge_base/d/updating-a-cluster}{updating a cluster}. \noindent\hrulefill \textbf{Warning:} Avoid repeatedly ``auto deploying'' new versions of apps that aren't designed for ``auto deployment''. \noindent\hrulefill If it's not feasible to stop your server or you're app \emph{is} designed for ``auto deployment'', Liferay DXP provides several ``auto deployment'' conveniences. Except where stopping/starting the server is explicitly mentioned, the practices described in the rest of this article and in the following articles involve ``auto deployment''. \section{Using the App Manager}\label{using-the-app-manager} Access the App Manager by selecting \emph{Control Panel} → \emph{Apps} → \emph{App Manager}. The App Manager lists your apps. The \emph{Filter and Order} menu lets you filter and order by category, status, or title. Click the up or down arrows to perform an ascending or descending sort, respectively. To search for an app or module, use the search bar. This is often the quickest way to find something. \begin{figure} \centering \includegraphics{./images/app-manager.png} \caption{The App Manager lets you manage the apps, modules, and components installed in your Liferay DXP instance.} \end{figure} Each item listed in the table contains a description (if available), version, and status. Here are the statuses: \begin{itemize} \tightlist \item \textbf{Installed:} The item is installed to Liferay DXP. \item \textbf{Resolved:} The item's dependencies are active. Resolved items can typically be activated. Some items, however, can't be activated and are intended to remain in the Resolved state (e.g., WSDD modules containing SOAP web services). \item \textbf{Active:} The item is running in Liferay DXP. \end{itemize} Clicking each item's Actions button (\includegraphics{./images/icon-actions.png}) brings up a menu that lets you activate, deactivate, or uninstall that item. To view an item's contents, click its name in the table. If you click an app, the app's modules are listed. If you click a module, the module's components and portlets appear. The component level is as far down as you can go without getting into the source code. At any level in the App Manager, a link trail appears that lets you navigate back in the hierarchy. For information on using the App Manager to install an app, see \href{/docs/7-2/user/-/knowledge_base/u/installing-apps-manually}{Installing Apps Manually}. Next, you'll learn how to use the Components listing. \section{Using the Components Listing}\label{using-the-components-listing} Access the components listing by selecting \emph{Control Panel} → \emph{Configuration} → \emph{Components}. The components listing first shows a table containing a list of installed portlets. Select the type of component to view---portlets, themes, or layout templates---by clicking the matching tab on top of the table. To configure a component, select its name in the table or select \emph{Edit} from its Actions button (\includegraphics{./images/icon-actions.png}). Doing either opens the same configuration screen. \begin{figure} \centering \includegraphics{./images/components-list.png} \caption{The components listing lets you manage the portlets, themes, and layout templates installed in your Liferay DXP instance.} \end{figure} The configuration screen lets you view a component's module ID and plugin ID, activate or deactivate the component, and change the component's Add to Page permission. The component's module ID and plugin ID appear at the top of the screen. You can activate or deactivate a component by checking or unchecking the \emph{Active} checkbox, respectively. To change a component's Add to Page permission for a role, select the role's \emph{Change} button in the permissions table. This takes you to \emph{Control Panel} → \emph{Users} → \emph{Roles}, where you can change the component's permissions for the selected role. \begin{figure} \centering \includegraphics{./images/components-configuration.png} \caption{You can activate or deactivate a component, and change its permissions.} \end{figure} \chapter{Using the Liferay Marketplace}\label{using-the-liferay-marketplace} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Liferay Marketplace is a hub for sharing, browsing, and downloading apps. Marketplace leverages the entire Liferay ecosystem to release and share apps in a user-friendly, one-stop shop. There are two ways to access the Marketplace. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item \textbf{Via the website:} Using your favorite browser, navigate to the Marketplace at \href{https://web.liferay.com/marketplace}{web.liferay.com/marketplace}. If you're new to Marketplace, this is the easiest way to access it. You can browse Marketplace without signing in with your liferay.com account. \item \textbf{Via the Control Panel:} In the Control Panel, navigate to \emph{Apps} → \emph{Store}. To view Marketplace, you must sign in with your liferay.com account. \end{enumerate} No matter how you access Marketplace, you'll see the same content. Note that to download apps, you must have a \href{https://www.liferay.com}{liferay.com} account and agree to the Marketplace Terms of Use. Here you'll learn how to, \begin{itemize} \item \hyperref[finding-and-purchasing-apps]{Find and purchase apps} \item \hyperref[managing-purchased-apps]{Manage purchased apps} \item \hyperref[renewing-a-purchased-app]{Renew purchased apps} \end{itemize} Start with finding and purchasing the apps you want. \section{Finding and Purchasing Apps}\label{finding-and-purchasing-apps} If you've used an app store before, Marketplace should be familiar. You'll see apps in the center of the page, in the following sections: \begin{itemize} \item Featured Apps: Liferay features a different set of apps each month. \item New and Interesting: The latest apps added to Marketplace. \item Most Viewed in the Past Month: The top 5 most viewed apps in the last month. \item Themes / Site Templates: Apps that change your Liferay instance's look and feel. \item App categories: Communication, productivity, security, etc. \item Weekly Stats: The newest apps, latest apps updated, and trend chart for app downloads and views. \end{itemize} Each section's \emph{See All} link shows more section info. At the top of the page, you can search Marketplace by category, Liferay DXP version, and price. To browse by category, click the \emph{Categories} menu at the top of the page. \begin{figure} \centering \includegraphics{./images/marketplace-homepage.png} \caption{The Liferay Marketplace home page lets you browse and search for apps.} \end{figure} Click an app to view its details. This includes its description, screenshots, price, latest version, number of downloads, a link to the developer's website, a link to the app's license agreement, and a purchase button (labeled Free or Buy, depending on the price). You can also view the app's version history, read reviews, or write your own review. The purchase button prompts you to choose a purchase type. You can purchase an app for your personal account, or for a Liferay project associated with your company. If you have the necessary permissions, you can also create a new project for your company. Once you select a purchase type, accept the EULA and Terms of Service, and click \emph{Purchase}. \begin{figure} \centering \includegraphics{./images/marketplace-app-details.png} \caption{Click an app to view its details.} \end{figure} Once you purchase an app, you can download and install it. \noindent\hrulefill \textbf{Warning:} Not all apps are designed to be ``auto deployed''---deployed while the server is running. Deploying that way can cause instabilities, such as class loading leaks and memory leaks. On production systems, avoid ``auto deploying'' apps whenever possible. See the \href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps\#managing-apps-in-production}{best practices for managing apps in production}. \noindent\hrulefill An app downloads and installs immediately if you purchase it from the Control Panel. If you purchase the app on the Marketplace website, however, your receipt is displayed immediately after purchase. To download the app, click the \emph{See Purchased} button on the bottom of the receipt, and then click the \emph{App} button to start the download. You must then \href{/docs/7-2/user/-/knowledge_base/u/installing-apps-manually}{install the app manually}. Alternatively, you can use Marketplace from the Control Panel to download and install the app after purchase on the Marketplace website. The next section shows you how to do this. Note that sometimes administrators disable automatic app installations so they can manage installations manually. In this case, Marketplace apps downloaded from the Control Panel are placed in the \texttt{deploy} folder in \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home}. Administrators must then manually install the app from this folder. Manual install is also required if the server is behind a corporate firewall or otherwise lacks direct Marketplace access. Regardless of how the app is downloaded, the manual install process is the same. For details, see the article \href{/docs/7-2/user/-/knowledge_base/u/installing-apps-manually}{Installing Apps Manually}. \section{Managing Purchased Apps}\label{managing-purchased-apps} \noindent\hrulefill \textbf{Important}: When uninstalling an app or module, make sure to use the same agent you used to install the app. For example, if you installed it with Marketplace, uninstall it with Marketplace. If you installed it with the file system, use the \href{/docs/7-2/user/-/knowledge_base/u/installing-apps-manually}{file system} to uninstall it. If you installed it with the App Manager, however, use \href{/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components}{Blacklisting} to uninstall it. \noindent\hrulefill There are two places to manage your purchased apps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Your \href{https://www.liferay.com}{liferay.com} account's home page. After signing in, click the user menu at the top-right and select \emph{Account Home}. Note that your home page is distinct from your profile page. Your home page is private, while your profile page is public. On your home page, select \emph{Apps} from the menu on the left to view your projects. Select a project to view its registered apps. Clicking an app lets you view its versions. You can download the version of the app that you need. This is especially useful if you need a previous version of the app, or can't download the app from the Control Panel. \begin{figure} \centering \includegraphics{./images/marketplace-project-apps.png} \caption{You can manage your purchased apps from your liferay.com account's home page.} \end{figure} \item From the Control Panel. Navigate to \emph{Apps} → \emph{Purchased} to see your purchased apps. A button next to each app lets you install or uninstall the app. If the app isn't compatible with your Liferay DXP version, \emph{Not Compatible} is displayed in place of the button. Additional compatibility notes are also shown, such as whether a newer version of the app is available. You can also search for an app here by project, category, and title. Clicking the app takes you to its Marketplace entry. \begin{figure} \centering \includegraphics{./images/marketplace-purchased.png} \caption{You can also manage your purchased apps from within a running Liferay instance.} \end{figure} \end{enumerate} \section{Renewing a Purchased App}\label{renewing-a-purchased-app} To continue using a purchased app whose license terms are non-perpetual, you must renew your app subscription, register your server to use the app, and generate a new activation key to use on your server. Here are the steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to \url{https://web.liferay.com/marketplace}. \item Click your profile picture in the upper right corner and select \emph{Purchased Apps}. The Purchased Apps page appears and shows your app icons organized by project. \item Click your app's icon. Your app's details page appears. \item Click \emph{Manage Licenses}. \item Select \emph{Register New Server}. \item Select the most recent \emph{Order ID} (typically the order that has no registered servers). \item Fill in your server's details. \item Click \emph{Register}. \item Click \emph{Download}. The new app activation key to use on your server downloads. \item Copy the activation key file to your \texttt{deploy/} folder in your \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{\texttt{{[}Liferay\ \ \ \ \ Home{]}}}. \end{enumerate} You can continue using the application on your server. \chapter{Installing Apps Manually}\label{installing-apps-manually} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} By default, apps you download from the Control Panel via Liferay Marketplace install automatically. But what if the app you want to install isn't on Marketplace? What if all you have is the app's file? In this case, you must install the app manually. Here you'll learn how to install any app manually. \noindent\hrulefill \textbf{Warning:} Not all apps are designed to be ``auto deployed''---deployed while the server is running. Deploying that way can cause instabilities, such as class loading leaks and memory leaks. On production systems, avoid ``auto deploying'' apps whenever possible. See the \href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps\#managing-apps-in-production}{best practices for managing apps in production}. \noindent\hrulefill \noindent\hrulefill \textbf{Important}: When uninstalling an app or module, make sure to use the same agent you used to install the app. For example, if you installed it with Marketplace, uninstall it with \href{/docs/7-2/user/-/knowledge_base/u/using-the-liferay-marketplace}{Marketplace}. If you installed it with the file system, use the file system to uninstall it. If you installed it with the App Manager, however, use \href{/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components}{Blacklisting} to uninstall it. \noindent\hrulefill \section{Using the Control Panel to Install Apps}\label{using-the-control-panel-to-install-apps} To install an app manually from the Control Panel, navigate to \emph{Control Panel} → \emph{Apps} → \emph{App Manager}, and select \emph{Upload} from the options button (\includegraphics{./images/icon-options.png}). In the Upload dialog, choose the app on your machine and then click \emph{Install}. When the install completes, close the dialog and you're ready to roll! \section{Using Your File System to Install Apps}\label{using-your-file-system-to-install-apps} To install an app manually on the Liferay DXP server, put the app in the \texttt{{[}Liferay\ Home{]}/deploy} folder (the \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder is typically the app server's parent folder). That's it. The auto deploy mechanism takes care of the rest. You might now be thinking, ``Whoa there! What do you mean by `the rest?' What exactly happens here? And what if my app server doesn't support auto deploy?'' These are fantastic questions! When you put an app in the \texttt{{[}Liferay\ Home{]}/deploy} folder, the OSGi container deploys the app to the appropriate subfolder in \texttt{{[}Liferay\ Home{]}/osgi}. By default, the following subfolders are used for apps matching the indicated file type: \begin{itemize} \tightlist \item \texttt{marketplace}: Marketplace LPKG packages \item \texttt{modules}: OSGi modules \item \texttt{war}: WAR files \end{itemize} You can, however, change these subfolders by setting the properties \texttt{module.framework.base.dir} and \texttt{module.framework.auto.deploy.dirs} in a \href{/docs/7-2/deploy/-/knowledge_base/d/portal-properties}{\texttt{portal-ext.properties}} file. These properties define the \texttt{{[}Liferay\ Home{]}/osgi} folder and its auto deploy subfolders, respectively. The default settings for these properties in the \href{https://docs.liferay.com/dxp/portal/7.2-latest/propertiesdoc/portal.properties.html}{\texttt{portal.properties}} file are as follows: \begin{verbatim} module.framework.base.dir=${liferay.home}/osgi module.framework.auto.deploy.dirs=\ ${module.framework.base.dir}/configs,\ ${module.framework.base.dir}/marketplace,\ ${module.framework.base.dir}/modules,\ ${module.framework.base.dir}/war \end{verbatim} Note that the \texttt{configs} subfolder isn't for apps: it's for configuration files \href{/docs/7-2/user/-/knowledge_base/u/system-settings\#exporting-and-importing-configurations}{imported from other Liferay DXP instances}. But what happens if your app server doesn't support ``hot deploy''? No problem! Liferay DXP's module framework (OSGi) enables auto deploy. Any app server running Liferay DXP therefore also supports this auto deploy mechanism. \section{Manually Deploying an LPKG App}\label{manually-deploying-an-lpkg-app} When manually installing an LPKG app, the installation may hang with a server log message like this: \begin{verbatim} 14:00:15,789 INFO [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:252] Processing Liferay Push 2.1.0.lpkg \end{verbatim} This happens when LPKG apps have the \texttt{restart-required=true} property in their \texttt{liferay-marketplace.properties} file (inside the LPKG file). This property setting specifies that a server restart is required to complete the installation. \chapter{App Types}\label{app-types} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} There are several different kinds of apps. Some apps can even contain other apps. The types of apps you can install include: \begin{itemize} \tightlist \item OSGi Modules \item Portlets \item Web Plugins \item Templates \item Themes \end{itemize} Read on to learn about these app types. \section{OSGi Modules}\label{osgi-modules} Since Liferay DXP runs on OSGi, apps can be implemented as OSGi modules. An OSGi module is a JAR file adapted to run on OSGi. Although it's possible for a single module to implement a single app, an app typically consists of multiple modules that are packaged together. Also note that apps in OSGi modules aren't required to have a UI. For example, Liferay DXP can run OSGi modules that expand built-in APIs without requiring any user interaction. This is crucial for developers that must leverage custom APIs. By providing such an API via one or more OSGi modules, you can let developers leverage your API. OSGi modules can also contain apps that have a UI: portlets. The next section discusses these. \section{Portlets}\label{portlets} \href{/docs/7-2/frameworks/-/knowledge_base/f/portlets}{Portlets} are small web applications that run in a portion of a web page. For example, the built-in Blogs app is a portlet. Portlet applications, like servlet applications, are a Java standard implemented by various portal server vendors. The JSR-168 standard defines the portlet 1.0 specification, the JSR-286 standard defines the portlet 2.0 specification, and the JSR-362 standard defines the portlet 3.0 specification. A Java standard portlet should be deployable on any portlet container that supports the standard. Portlets are placed on the page in a certain order by the end user and are served up dynamically by the portal server. This means certain things that apply to servlet-based projects, such as control over URLs or access to the \texttt{HttpServletRequest} object, don't apply in portlet projects because the portal server generates these objects dynamically. Portlets can be composed of OSGi modules (recommended), or contained in WAR files. For information on developing portlets see \href{/docs/7-2/appdev/-/knowledge_base/a/web-front-ends}{Web Front-ends}. \section{Web Plugins}\label{web-plugins} Web plugins are regular Java EE web modules designed to work with Liferay DXP. You can integrate with various Enterprise Service Bus (ESB) implementations, as well as Single Sign-On implementations, workflow engines, and so on. These are implemented as web modules used by Liferay DXP portlets to provide functionality. \section{Templates and Themes}\label{templates-and-themes} \href{/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro}{Templates} and \href{/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction}{themes} are plugins that change Liferay DXP's appearance. Templates (layout templates) control how you can arrange portlets on a page. They make up a page's body (the large area into which you can drag and drop portlets). There are several built-in layout templates. If you have a complex page layout (especially for your home page), you may wish to create a custom layout template of your own. Themes can completely transform Liferay DXP's look and feel. Most organizations have their own look and feel standards that apply to all of their web sites and applications. By using a theme plugin, an organization can apply these standards on Liferay DXP. There are many available theme plugins on Liferay's web site and more are being added every day. This makes it easy for theme developers, as they can customize existing themes instead of writing a new one from scratch. \section{Liferay Marketplace App Packages}\label{liferay-marketplace-app-packages} Regardless of app type, each \href{https://web.liferay.com/marketplace}{Liferay Marketplace} app is distributed in an LPKG package. The LPKG package contains Marketplace metadata and the files the app needs to run. Note that it's possible for an LPKG package to contain multiple apps. For example, a single LPKG package can contain several portlets. This is common in cases where an app requires a Control Panel portlet for administrators, and another portlet for end users. \chapter{Blacklisting OSGi Bundles and Components}\label{blacklisting-osgi-bundles-and-components} {This document has been updated and ported to Liferay Learn and is no longer maintained here.} Blacklists are used for good and evil. An evil blacklist penalizes unfairly; a good blacklist protects. Liferay DXP's OSGi bundle and component blacklists are files that prevent particular bundles from installing and particular components from enabling. This saves you the trouble of uninstalling and disabling them individually with the Application Manager, Components list, or Gogo shell. \section{Blacklisting Bundles}\label{blacklisting-bundles} Liferay DXP removes any installed OSGi bundles on the blacklist. Blacklisted bundles therefore can't be installed. The log reports each bundle uninstallation. Follow these steps to blacklist bundles: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Control Panel, navigate to \emph{Configuration} → \emph{System Settings} → \emph{Module Container}. \item In the Bundle Blacklist screen, add the bundle symbolic names (see the table below) for the Module JARs, LPKG files, or WARs to uninstall. Click the \emph{Save} button when you're finished. \begin{figure} \centering \includegraphics{./images/bundle-blacklist-configuration.png} \caption{This blacklist uninstalls the \texttt{com.liferay.docs.greeting.api} bundle, Liferay Marketplace LPKG, and \texttt{classic-theme} WAR.} \end{figure} \item To export the blacklist, click its Actions button (\includegraphics{./images/icon-actions.png}) and then click \emph{Export}. The blacklist configuration file then downloads (\texttt{com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config}). Here are contents from an example file: \begin{verbatim} blacklistBundleSymbolicNames=["com.liferay.docs.greeting.api","Liferay\ Marketplace","classic-theme"] \end{verbatim} \item Add the bundle symbolic names of any bundles not already listed that you want to prevent from installing. \textbf{Important}: Configuration values can't contain extra spaces. Extra spaces can short-circuit lists or invalidate the configuration entry. \item To deploy the configuration file, copy it into the folder \texttt{{[}Liferay\_Home{]}/osgi/configs}. The \href{/docs/7-2/deploy/-/knowledge_base/d/liferay-home}{Liferay Home} folder is typically the app server's parent folder. \end{enumerate} \noindent\hrulefill \textbf{Note}: Blacklisting an LPKG uninstalls all of its internal bundles. \noindent\hrulefill \textbf{Blacklist Bundle Symbolic Names} \noindent\hrulefill \begin{longtable}[]{@{} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.4167}} >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5833}}@{}} \toprule\noalign{} \begin{minipage}[b]{\linewidth}\raggedright Type \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright Bundle Symbolic Name \end{minipage} \\ \midrule\noalign{} \endhead \bottomrule\noalign{} \endlastfoot Module JAR & \texttt{Bundle-SymbolicName} in \texttt{bnd.bnd} or \texttt{MANIFEST.MF} file \\ LPKG & LPKG file name without the \texttt{.lpkg} extension \\ WAR & Servlet context name in \texttt{liferay-plugin-package.properties} file or the WAR file name (minus \texttt{.war}), if there is no servlet context name property \\ \end{longtable} \noindent\hrulefill \section{Reinstalling Blacklisted Bundles}\label{reinstalling-blacklisted-bundles} To reinstall and permit installation of blacklisted OSGi bundles, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the configuration file \texttt{com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config}. \item Remove the symbolic names of the module JARs, LPKGs, or WARs from the \texttt{blacklistBundleSymbolicNames} list and save the file. \end{enumerate} To reinstall \emph{all} the blacklisted bundles execute one of these options: \begin{itemize} \tightlist \item Remove the configuration file. \item Uninstall the bundle \texttt{com.liferay.portal.bundle.blacklist} using the \href{/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps\#using-the-app-manager}{Application Manager} or \href{/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell}{Felix Gogo Shell}. \end{itemize} \noindent\hrulefill \textbf{Note}: To temporarily reinstall a bundle that's been blacklisted, you can remove its symbolic name from the Bundle Blacklist module in \emph{System Settings} and click the \emph{Update} button. If you want the bundle to install on subsequent server startup, make sure to remove the bundle's symbolic name from any existing blacklist configuration file in the \texttt{{[}Liferay\_Home{]}/osgi/configs} folder. \noindent\hrulefill The log reports each bundle installation. \section{Blacklisting Components}\label{blacklisting-components} Follow these steps to blacklist components: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item In the Control Panel, navigate to \emph{Configuration} → \emph{System Settings} → \emph{Module Container}. \item In the Component Blacklist screen, add the names of components to disable and click the \emph{Save} button. \end{enumerate} \begin{figure} \centering \includegraphics{./images/component-blacklist-configuration.png} \caption{This blacklist disables the components \texttt{com.liferay.portal.security.ldap.internal.authenticator.LDAPAuth} and \texttt{com.liferay.ip.geocoder.sample.web.internal.portlet.IPGeocoderSamplePortlet}.} \end{figure} \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \setcounter{enumi}{2} \item To export the blacklist, click on the Component Blacklist module's Actions button (\includegraphics{./images/icon-actions.png}) and then click \emph{Export}. The blacklist configuration file then downloads (\texttt{com.liferay.portal.component.blacklist.internal.ComponentBlacklistConfiguration.config}). Here are contents from an example file: \begin{verbatim} blacklistComponentNames=["com.liferay.portal.security.ldap.internal.authenticator.LDAPAuth","com.liferay.ip.geocoder.sample.web.internal.portlet.IPGeocoderSamplePortlet "] \end{verbatim} \item Add the names of any components not already listed (e.g., components of bundles not yet installed) that you want to prevent from activating. \textbf{Important}: Configuration values can't contain extra spaces. Extra spaces can short-circuit lists or invalidate the configuration entry. \item To deploy the configuration file, copy it into the folder \texttt{{[}Liferay\_Home{]}/osgi/configs}. The Liferay Home folder is typically the app server's parent folder. \end{enumerate} \section{Re-enabling Blacklisted Components}\label{re-enabling-blacklisted-components} To re-enable and permit enabling of blacklisted components, follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Open the configuration file \texttt{com.liferay.portal.component.blacklist.internal.ComponentBlacklistConfiguration.config}. \item Remove the names of the components from the \texttt{blacklistComponentNames} list and save the file. \end{enumerate} To enable \emph{all} the blacklisted components, remove the configuration file. \noindent\hrulefill \textbf{Note}: To temporarily reactivate a blacklisted component, remove its name from the Component Blacklist Configuration module in System Settings and click \emph{Update}. If you want the component to activate on subsequent server startup, make sure to remove the component's name from any existing component blacklist configuration file in the \texttt{{[}Liferay\_Home{]}/osgi/configs} folder. \chapter{Polls}\label{polls} How can The Lunar Resort stay connected with its earthbound clientèle from 239,000 miles away? Make them feel really involved and enthusiastic about the resort by asking them for feedback. You're not just creating a poll, you're making connections. Use Polls to find out what your site visitors are thinking and keep them engaged with your site's content. Two applications make and display a poll: the \emph{Polls} application in the Site Menu and the \emph{Polls Display} widget you add to a page. \section{Creating a Poll}\label{creating-a-poll} From the Site Menu, go to \emph{Content \& Data} → \emph{Polls}. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click the \includegraphics{./images/icon-add.png} button and fill out the form. \begin{figure} \centering \includegraphics{./images/polls-add-new-question.png} \caption{Besides the Title and the Polls Question, you must enter data for each of the Choices fields when creating a new poll.} \end{figure} \textbf{Title:} (Required) Enter the name of the poll question. \textbf{Polls Question:} (Required) Enter the text of the poll question. \textbf{Expiration Date:} Enter the date and time you want the poll to expire. \textbf{Choices:} Enter at least two options for the poll question. \textbf{Add Choice:} Enter additional answer options for the poll question. \textbf{Permissions:} Manage who can view and edit the poll. \item Click \emph{Save} to add the poll to the Polls application. \end{enumerate} Once a poll is created, the Polls Display portlet publishes it until it expires or is deleted. Set an expiration date for a poll by selecting the day and time in the Add Poll form. The default is set to \emph{Never Expire}. When a published poll expires, the poll results are displayed, but users can't add new entries. To remove an expired poll from a page, remove the Poll Display portlet or configure it to show another poll question. See the section below for more details about the Polls Display portlet. \emph{Permissions} are set on individual polls. Use permissions, for example, to allow some privileged users to vote on a certain poll question, while others can only view it. Creating a poll is fairly straightforward. Next, complete the two-step process and put your poll on a page. \section{Adding a Poll to a Page}\label{adding-a-poll-to-a-page} Now that you have created your poll question, you can present it to your users: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to a page and add a Polls Display widget from \emph{Add} → \emph{Widgets} → \emph{Content Management}. \item Click \emph{Please configure this portlet to make it visible to all users.} \item In the dialog box that appears, select the poll to display. \item Click \emph{Save}. \end{enumerate} Once the poll question has been placed on the page, perform other tasks using the icons at the bottom of the portlet. \begin{figure} \centering \includegraphics{./images/poll-buttons.png} \caption{These buttons provide shortcuts to the widget's configuration, as well as to some of the Polls Application's functionality.} \end{figure} \textbf{Edit Question:} Displays a similar dialog box to the one used to create the poll. \textbf{Select Poll:} Displays the same dialog box as Configuration, allowing you to choose different polls from the drop-down menu. \textbf{Add:} Allows you to create a new poll. \section{Viewing Poll Results}\label{viewing-poll-results} All the polls you create appear in the Polls portlet in the Site Menu → \emph{Content \& Data} → \emph{Polls}. When users vote in the poll, the data is collected here. Click on a poll to see a breakdown of the results. \begin{figure} \centering \includegraphics{./images/polls-results.png} \caption{Selecting a poll in the Polls portlet puts the data at your fingertips.} \end{figure} If you click on one of the listed \emph{Charts}, the portlet generates an appropriate visualization of the data. Below this is an item called \emph{Charts}. This option shows the poll results represented in various graphs. The graphs are \emph{Area}, \emph{Horizontal Bar}, \emph{Line}, \emph{Pie}, and \emph{Vertical Bar}. \begin{figure} \centering \includegraphics{./images/polls-results-vertical-bar.png} \caption{This is what the vertical bar graph for the Lunar Resort poll results looks like.} \end{figure} There is also a listing of the users who voted in the poll, how they voted, and a time/date stamp of when their votes were cast. Registered users are represented by name. Guest users have a blank \emph{User} field. \chapter{Using the Calendar}\label{using-the-calendar} The Calendar widget is an updated, digitized, 3D-printed sundial. Okay, it's really a tool for storing and sharing scheduled events. It's a personal planner for individual users, a shared calendar for an entire site, or both at the same time. It can be used to create multiple calendars for a single Site or User, to overlay the events stored in multiple calendars for simultaneous view, to send email reminders to users, and more. \noindent\hrulefill \textbf{Note:} The calendar supports social activities. Whenever a calendar event is added or updated, a corresponding social activity notification is created. If the event was added or updated in a calendar that the current user has permission to view, the social activity is viewable in the Activities widget. \noindent\hrulefill \section{Configuring the Calendar}\label{configuring-the-calendar} Once the Calendar widget is on a page, open the \includegraphics{./images/icon-app-options.png} menu in the widget's header and click \emph{Configuration}. \begin{figure} \centering \includegraphics{./images/new-calendar-configuration.png} \caption{The Setup → User Settings tab provides the options you need to get started quickly.} \end{figure} From the \emph{User Settings} tab, customize the calendar's default view and settings. \textbf{Time Format:} Choose \emph{Locale}, \emph{AM/PM}, or \emph{24 Hour}. \emph{Locale} is a dynamic setting that chooses whether to display the time in \emph{AM/PM} or \emph{24 Hour} format, based on the preferences set by the User's locale. \emph{AM/PM} displays times such as 8AM or 11PM. The \emph{24 Hour} time format displays times such as 08:00 and 23:00. \textbf{Default Duration:} Choose an event duration. When you add a new event to the calendar, the time you set here specifies how long events last by default. \textbf{Default View:} Choose \emph{Day}, \emph{Week}, \emph{Month} or \emph{Agenda}. This sets the default for when the calendar is first displayed, but the view can be changed by clicking the appropriate button at the top-right of the widget. \textbf{Week Starts On:} Choose \emph{Sunday}, \emph{Monday}, or \emph{Saturday}. \textbf{Time Zone:} Choose a time zone or check the \emph{Use Global Time Zone} box. If you check \emph{Use Global Time Zone}, the time displayed depends on whether it's being viewed by a logged-in user or a guest. If a user is logged in, the Calendar displays events using the time zone set for the user in \emph{User Personal Menu} → \emph{Account Settings} → \emph{Preferences} → \emph{Display Settings} → \emph{Time Zone}. If the Calendar is viewed by a guest or a user who is not logged in, the Calendar displays events using the time zone set by the portal administrator in \emph{Control Panel} → \emph{Configuration} → \emph{Instance Settings} → \emph{Platform} → \emph{Localization} → \emph{VIRTUAL INSTANCE SCOPE} → \emph{Time Zone}. From the \emph{Display Settings} tab, set the display behavior for the calendar. \textbf{Display Scheduler Only:} By default, the list of calendars and a mini-calendar view (used for quickly navigating to a particular date) are displayed. Check this to display only the scheduler (the large calendar view showing the calendar and scheduled events). \textbf{Display User Events:} Turns off the display of the current, logged in User's personal calendar and events. \textbf{Display Scheduler's Header:} If disabled, removes the ability to toggle through the calendar views (for example, Day/Week/Month/Agenda) and access to the Add Event button. \textbf{Enabled Views:} If one of the available views is disabled (Day, Week, Month, Agenda), it disappears from the scheduler's header. \textbf{Maximum Days to Display:} Set the maximum number of days to display in the Agenda view. \textbf{Maximum Events to Display:} Set the maximum number of events to display in the Agenda view. Use the \emph{RSS} tab to disable RSS subscription or configure the RSS behavior. Enough with configuration. Next you'll learn how to use it. \chapter{Using the Calendar Widget}\label{using-the-calendar-widget} The calendar widget displays a small monthly calendar showing an overview of upcoming events. A larger area shows the Scheduler, a more detailed calendar with a number of options: you can set it to to display a \emph{Day}, \emph{Week}, or \emph{Month}, or choose a more event-oriented \emph{Agenda} setting. \begin{figure} \centering \includegraphics{./images/calendar-view.png} \caption{The default view is set in configuration, but a user can change it at any time.} \end{figure} Two calendars are included by default when the widget is first added to a page: a personal calendar for the current user and a Site calendar for the current Site. These are displayed in the widget's lower left. Next to each calendar is a colored box: click it to show/hide that calendar's events in the main viewing area. \section{Adding New Calendars}\label{adding-new-calendars} To create a new personal calendar, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the arrow to the right of the \emph{My Calendars} header and select \emph{Add Calendar} from the menu. \item Fill in the \emph{Add Calendar} form. Give the calendar a name and description, set a time zone, and decide if it's your user's \emph{default calendar}---the one that is shown automatically whenever the widget is displayed. You can also pick a color, which color codes events whenever multiple calendar's events are displayed at once. You can also decide to enable ratings or comments on the calendar's events, and configure permissions. \end{enumerate} To edit an existing calendar instead of adding a new one, select \emph{Manage Calendars} from the menu. To add or edit a Site calendar, open the menu next to the header with the Site's name. \begin{figure} \centering \includegraphics{./images/new-calendar-manage-calendars.png} \caption{Personal and Site calendars are shown in the lower left. This image shows calendars belonging to User \emph{Test Test} and Site \emph{Liferay DXP}.} \end{figure} \section{Adding Events to a Calendar}\label{adding-events-to-a-calendar} To add events to a calendar, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on any day in the main viewing area to open an event creation pop up. If you've selected the \emph{Day} or \emph{Week} view, you can click on the specific time when your event begins. \begin{figure} \centering \includegraphics{./images/new-calendar-event-popup.png} \caption{When you click anywhere on the calendar, you'll see the event creation pop up appear. Click \emph{Edit} to specify details for your event.} \end{figure} \item Name your event and assign it to a calendar. Click \emph{Save} to create the event immediately or \emph{Edit} to enter additional information. \begin{figure} \centering \includegraphics{./images/new-calendar-event-details.png} \caption{You can specify event details such as the event title, start date, end date, description, location, and more.} \end{figure} \item If you clicked \emph{Edit}, complete the \emph{Edit Event} form. Enter start and end times and enter a description. To schedule an event that reoccurs, check the \emph{Repeat} box and fill in the \emph{Repeat} pop up. \begin{figure} \centering \includegraphics{./images/new-calendar-event-repeat.png} \caption{The \emph{Repeat} box allows you to specify whether an events repeats daily, weekly, monthly, or yearly, how often it repeats, and when (or if) it ends.} \end{figure} \end{enumerate} \section{Additional Event Functions}\label{additional-event-functions} At the bottom of the \emph{Edit Event} form, there are several collapsed sections: \emph{Details}, \emph{Invitations}, \emph{Reminders}, \emph{Categorization}, and \emph{Related Assets}. \subsection{Details}\label{details-2} In the Details section, you can move the event to another calendar and enter a location. \subsection{Invitations}\label{invitations} In the invitations section, invite Users, Sites, or Calendar Resources (see the next tutorial for more on resources: in brief, a resource is anything you might need for an event---a conference room, a vehicle, etc.). Follow these steps: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Enter the name of an invitee (User, Site, or Resource) in the \emph{Invitations} field. Hit \emph{Enter} to add them to the \emph{Pending} column. \item Check the availability of invitees by clicking the arrow next to the their names and selecting \emph{Check Availability}. This displays their calendars (assuming you have permission to view them). \end{enumerate} An automated email is sent to invitees who must navigate to the calendar widget to respond. See below to customize the content of the invitation. When invitees respond to the invitation, their names move to the \emph{Accepted}, \emph{Declined}, or \emph{Maybe} columns. \subsection{Reminders}\label{reminders} Schedule up to two email reminders to send to attendees. Reminders translate the time of the event into the recipients own time zone. See below to customize the content of the reminder email. \subsection{Categorization}\label{categorization-2} Tag your event or assign it to a category so it appears in appropriate search results and is published by any asset publisher set to publish content assigned to the same category. \subsection{Related Assets}\label{related-assets} List an asset---such as an agenda or supplementary material for a meeting---as related to your event. Links to related assets are displayed in the \emph{Event Details} window. \subsection{Saving and Drafting Changes and Updating Permissions}\label{saving-and-drafting-changes-and-updating-permissions} At the very bottom of the Edit form is a set of buttons that let you publish the changes, save the changes as a draft, and configure the event's permissions. Giving a user permission to add, delete, or update discussion allows them to make, edit and remove comments on the event. The \emph{Permissions} permission allows a Role to update permissions for the event. \section{Customizing Email Notifications}\label{customizing-email-notifications} To customize email notifications for event invitations and reminders, \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the Calendar widget, click on the arrow next to a calendar and select \emph{Calendar Settings}. \begin{figure} \centering \includegraphics{./images/calendar-email-note.png} \caption{Email templates apply to a single calendar and all its events.} \end{figure} \item Click on the \emph{Notification Templates} tab. Then select either the \emph{Invite Email} or the \emph{Reminder Email} sub-tab. \item Edit the email as desired. At the bottom of the screen is a glossary that specifies variables for terms that were set when you created the event. Use these variables to refer to event-specific information, such as the event's name, date or location. It's a good idea to include a link to the event (use the variable \texttt{{[}\$EVENT\_URL\${]}}) as users must navigate to the calendar widget in order to respond. \end{enumerate} Click \emph{Save}. Now your notifications contain the proper text. The next article covers setting up calendar resources and porting data from one installation to another. \chapter{Calendar Resources and Porting}\label{calendar-resources-and-porting} With calendar resources, you can invite entities other than people to your events. This is beneficial for finding the availability of important resources your event requires, like a conference room, laptop, or, at The Lunar Resort, the Sasquatch Space Suit used to scare guests out on Lunar hikes. Another important topic is porting your calendar's data from one installation of Liferay DXP to another. \section{Calendar Resources}\label{calendar-resources} Follow these steps to add a new calendar resource: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Click on the \emph{Resources} tab and click the \includegraphics{./images/icon-add.png} button to add a new resource. \begin{figure} \centering \includegraphics{./images/calendar-resources.png} \caption{Resources are accessed from the tab menu at the top of the widget.} \end{figure} \item Fill in the \emph{New Resource} form. Enter a name, give it a description, and choose whether to set it as active. You can also tag it, assign it to categories, and configure its permissions. Click \emph{Save}. \end{enumerate} The resource has its own calendar that was generated automatically (this is how users can check its availability when creating events). Just as with Users, however, resources can have more than one calendar. Follow these steps to assign a new calendar to the resource: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item Go to the widget's \emph{Resources} tab, click the \includegraphics{./images/icon-actions.png} button next to the resource, and select \emph{View Calendars}. \item Click \emph{Add Calendar} and continue just as if you were creating a calendar for a user or a Site. \end{enumerate} Once a resource is created, invite it to your events just as you would an attendee. \section{Exporting and Importing Calendar Data}\label{exporting-and-importing-calendar-data} Like other Liferay Applications, the calendar allows data to be exported or imported as \href{/docs/7-2/user/-/knowledge_base/u/exporting-importing-widget-data}{LAR files}. As with all LAR files, data can only be ported between installations of the same version. \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the calendar widget, click the \includegraphics{./images/icon-app-options.png} button in the widget header and select \emph{Export/Import}. \item Enter a name for the LAR file (or use the default). Under \emph{Application}, choose whether to include the widget's configuration in the LAR. Under \emph{Content}, choose how much historical data to export and select the content types (calendars, resources, and events) to include. You can also choose whether to include comments and ratings. Check the appropriate boxes to select whether to include deletions and permissions in the LAR. \item Click \emph{Export}. When a success message displays (this may take a few moments) you can click on the LAR's filename to download it. \begin{figure} \centering \includegraphics{./images/calendar-lar.png} \caption{This LAR is ready to be downloaded.} \end{figure} \end{enumerate} Follow these steps to import a LAR: \begin{enumerate} \def\labelenumi{\arabic{enumi}.} \item From the calendar widget, click the \includegraphics{./images/icon-app-options.png} button in the widget header and select \emph{Export/Import}. \item Click the \emph{Import} tab. \item Click \emph{Choose File} or else drag-and-drop a LAR into the area surrounded by a dotted line. Click \emph{Continue}. \item Decide how much data you want to import: Under \emph{Application}, check the box to import the configuration stored in the LAR or leave in unchecked to keep your current configuration. Under \emph{Content}, decide which content types (calendars, resources, and events) to import, and whether to include comments and ratings. Choose whether to import permissions and deletions, and decide whether to delete your widget's existing data before the import. \item In the collapsible \emph{Update Data} section, choose how data will be updated. \textbf{Mirror:} The data will be imported along with a reference to its source. This allows data to be updated rather than duplicated if the same LAR is imported more than once. \textbf{Copy as New:} All data is imported as new entries. Repeat imports will produce duplicates. \item In the \emph{Authorship of the Content} section, choose whether to keep the original author of the imported content (where available) or to list the current user as the author. \item Click \emph{Import}. \end{enumerate} Your calendar is set up and ready to go! Better check it to see what's next on the agenda. ================================================ FILE: book/using-liferay-dxp-72.tex ================================================ \documentclass[11pt,openright,twoside]{memoir} % \usepackage{ucs} \usepackage[english]{babel} \usepackage{fontspec} \usepackage{graphicx} \usepackage{calc} \usepackage{hyperref} \usepackage{enumerate} \usepackage{ctable} %\usepackage[labelformat=empty,font=small]{caption} \usepackage{titlesec} \usepackage{float} \usepackage{morefloats} \usepackage{wrapfig} \usepackage{longtable} \usepackage{geometry} \usepackage{framed} \usepackage{animate} \defaultfontfeatures{Ligatures=TeX} % Begin stuff from Pandoc template \usepackage{amsmath,amssymb} \usepackage{iftex} \ifPDFTeX \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \usepackage{textcomp} % provide euro and other symbols \else % if luatex or xetex \usepackage{unicode-math} % this also loads fontspec \defaultfontfeatures{Scale=MatchLowercase} \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1} \fi \usepackage{lmodern} \ifPDFTeX\else % xetex/luatex font selection \fi % Use upquote if available, for straight quotes in verbatim environments \IfFileExists{upquote.sty}{\usepackage{upquote}}{} \IfFileExists{microtype.sty}{% use microtype if available \usepackage[]{microtype} \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts }{} \makeatletter \@ifundefined{KOMAClassName}{% if non-KOMA class \IfFileExists{parskip.sty}{% \usepackage{parskip} }{% else \setlength{\parindent}{0pt} \setlength{\parskip}{6pt plus 2pt minus 1pt}} }{% if KOMA class \KOMAoptions{parskip=half}} \makeatother \usepackage{xcolor} \usepackage{color} \usepackage{fancyvrb} \newcommand{\VerbBar}{|} \newcommand{\VERB}{\Verb[commandchars=\\\{\}]} \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}} % Add ',fontsize=\small' for more characters per line \newenvironment{Shaded}{}{} \newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} \newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{#1}} \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\BuiltInTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{#1}} \newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{#1}}} \newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{#1}} \newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} \newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{#1}} \newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{#1}}} \newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{#1}}} \newcommand{\ExtensionTok}[1]{#1} \newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{#1}} \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{#1}} \newcommand{\ImportTok}[1]{\textcolor[rgb]{0.00,0.50,0.00}{\textbf{#1}}} \newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{#1}}} \newcommand{\NormalTok}[1]{#1} \newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} \newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{#1}} \newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{#1}} \newcommand{\RegionMarkerTok}[1]{#1} \newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{#1}} \newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{#1}} \newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{#1}} \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{#1}}}} \makeatletter \def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi} \def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi} \makeatother % Scale images if necessary, so that they will not overflow the page % margins by default, and it is still possible to overwrite the defaults % using explicit options in \includegraphics[width, height, ...]{} \setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio} % Set default figure placement to htbp \makeatletter \def\fps@figure{htbp} \makeatother \setlength{\emergencystretch}{3em} % prevent overfull lines \providecommand{\tightlist}{% \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}} \setcounter{secnumdepth}{-\maxdimen} % remove section numbering \ifLuaTeX \usepackage{selnolig} % disable illegal ligatures \fi \IfFileExists{bookmark.sty}{\usepackage{bookmark}}{\usepackage{hyperref}} \IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available \urlstyle{same} \makeatletter \newsavebox\pandoc@box \newcommand*\pandocbounded[1]{% scales image to fit in text height/width \sbox\pandoc@box{#1}% \Gscale@div\@tempa{\textheight}{\dimexpr\ht\pandoc@box+\dp\pandoc@box\relax}% \Gscale@div\@tempb{\linewidth}{\wd\pandoc@box}% \ifdim\@tempb\p@<\@tempa\p@\let\@tempa\@tempb\fi% select the smaller of both \ifdim\@tempa\p@<\p@\scalebox{\@tempa}{\usebox\pandoc@box}% \else\usebox{\pandoc@box}% \fi% \makeatother } % End stuff from Pandoc template %\geometry{paperwidth=191mm,paperheight=235mm, % hmargin={20mm,20mm},vmargin={20mm,20mm}} \geometry{letterpaper, hmargin={1in, 1in}, vmargin={1in,1in}} \setmainfont{Source Serif Pro} \setsansfont{Source Sans Pro} \setmonofont{IosevkaTermSlab Nerd Font} \newfontfamily\sectionfont{Source Sans Pro} \newfontfamily\subsectionfont{Source Sans Pro} \newfontfamily\subsubsectionfont{Source Sans Pro} \newfontfamily\captionfont{Source Sans Pro} \newcommand{\hruleafter}[1]{#1\hrule} \titleformat{\section}{\large\bfseries\sffamily\sectionfont}{\thesection}{1em}{\hruleafter} \titleformat*{\subsection}{\bfseries\sffamily\subsectionfont} \titleformat*{\subsubsection}{\itshape\subsubsectionfont} \titlespacing*{\section} {0pt}{10ex}{2ex} \captionnamefont{\small\sffamily} \captiontitlefont{\small\sffamily} \aliaspagestyle{part}{empty} \makeatletter \g@addto@macro\@verbatim\scriptsize \makeatother \newlength{\imgwidth} \newlength{\drop}% for my convenience \newcommand*{\titleGM}{\begingroup% Gentle Madness \drop = 0.1\textheight %\vspace*{\baselineskip} \vfill \hbox{% \hspace*{0.2\textwidth}% \rule{1pt}{\textheight} \hspace*{0.05\textwidth}% \parbox[b]{0.75\textwidth}{ \vbox{% \vspace{\drop} {\noindent\HUGE\bfseries Using\\[0.5\baselineskip] Liferay DXP 7.2}\\[2\baselineskip] {\Large\itshape A Complete Guide}\\[4\baselineskip] {\Large THE LIFERAY DOCUMENTATION TEAM}\par {\small Richard Sezov, Jr.}\par {\small Jim Hinkey}\par {\small Stephen Kostas}\par {\small Jesse Rao}\par {\small Cody Hoag}\par {\small Nicholas Gaskill}\par {\small Michael Williams}\par \vspace{0.25\textheight} {\noindent Liferay Press}\\[\baselineskip] }% end of vbox }% end of parbox }% end of hbox \vfill \null \endgroup} \makeatletter \newcommand\thickhrulefill{\leavevmode \leaders \hrule height 1ex \hfill \kern \z@} \setlength\midchapskip{10pt} \makechapterstyle{VZ14}{ \renewcommand\chapternamenum{} \renewcommand\printchaptername{} \renewcommand\chapnamefont{\sffamily\Large\scshape} \renewcommand\printchapternum{% \chapnamefont\null\thickhrulefill\quad \@chapapp\space\thechapter\quad\thickhrulefill} \renewcommand\printchapternonum{% \par\thickhrulefill\par\vskip\midchapskip \hrule\vskip\midchapskip } \renewcommand\chaptitlefont{\sffamily\Huge\scshape\centering} \renewcommand\afterchapternum{% \par\nobreak\vskip\midchapskip\hrule\vskip\midchapskip} \renewcommand\afterchaptertitle{% \par\vskip\midchapskip\hrule\nobreak\vskip\afterchapskip} } \makeatother \newcommand\scalegraphics[1]{% \settowidth{\imgwidth}{\includegraphics{#1}}% \setlength{\imgwidth}{\minof{\imgwidth}{\textwidth}}% \includegraphics[width=\imgwidth]{#1}% } \usepackage{fancybox} \newenvironment{roundedframe}{% \def\FrameCommand{% \cornersize*{20pt}% \setlength{\fboxsep}{5pt}% \ovalbox}% \MakeFramed{\advance\hsize-\width \FrameRestore}}% {\endMakeFramed} \author{Richard L. Sezov, Jr. } \title{Using Liferay DXP} \date{12/12/2020} \begin{document} \pagestyle{empty} \titleGM Using Liferay DXP 7.2 by The Liferay Documentation Team Copyright \copyright 2020 by Liferay, Inc.\\[2\baselineskip] This work is offered under the following license: \\[2\baselineskip] Creative Commons Attribution-Share Alike Unported \scalegraphics{./images/cc-by-sa.png} You are free: \begin{enumerate}[1.] \item to share---to copy, distribute, and transmit the work \item to remix---to adapt the work \end{enumerate} Under the following conditions: \begin{enumerate}[1.] \item Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work). \item Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. \end{enumerate} The full version of this license is here: \href{http://creativecommons.org/licenses/by-sa/3.0}{http://creativecommons.org/licenses/by-sa/3.0} This book was created out of material from the \href{https://github.com/liferay/liferay-docs}{Liferay Docs} repository. Where the content of this book and the repository differ, the site is more up to date. \clearpage \frontmatter \pagestyle{plain} \pagenumbering{roman} \chapterstyle{VZ14} \tableofcontents \chapter{Preface} Welcome to the world of Liferay DXP! This book was written for anyone who has any part in setting up, using, or maintaining a web site built on Liferay DXP. For the end user, it contains everything you need to know about using the applications included with Liferay. For the administrator, you'll learn all you need to know about setting up your site with users, sites, organizations, and user groups, as well as how to manage your site's security with roles. For server admins, it guides you step-by-step through the installation, configuration, and optimization of Liferay DXP, including setting it up in a clustered, enterprise-ready environment. Use this book as a handbook for everything you need to do to get your Liferay DXP installation running smoothly, and then keep it by your side as you configure and maintain your Liferay-powered web site. \section{Conventions} The information contained herein has been organized in a way that makes it easy to locate information. The book has two parts. The first part, \textit{Using Liferay Portal}, describes how to configure and use a freshly installed Liferay Portal. The second part, \textit{Deploying Liferay Portal}, is for administrators who want to install Liferay Portal and optimize its performance. Sections are broken up into multiple levels of headings, and these are designed to make it easy to find information. Source code and configuration file directives are presented monospaced, as below. \begin{verbatim} Source code appears in a non-proportional font. \end{verbatim} \textit{Italics} represent links or buttons to be clicked on in a user interface. \texttt{Monospaced type} denotes Java classes, code, or properties within the text. \textbf{Bold} describes field labels and portlets. Page headers denote the chapters and the section within the chapter. \section{Publisher Notes} It is our hope that this book is valuable to you, and that it becomes an indispensable resource as you work with Liferay DXP. If you need assistance beyond what is covered in this book, Liferay offers training\footnote{https://learn.liferay.com}, consulting\footnote{https://www.liferay.com/consulting}, and support\footnote{https://help.liferay.com} services to fill any need that you might have. For up-to-date documentation on the latest versions of Liferay, please see the documentation pages on Liferay Learn.\footnote{https://learn.liferay.com} As always, we welcome feedback. If there is any way you think we could make this book better, please feel free to mention it on our forums or in the feedback on Liferay Learn. You can also use any of the email addresses on our Contact Us page.\footnote{\href{http://www.liferay.com/contact-us}{https://www.liferay.com/contact-us}} We are here to serve you, our users and customers, and to help make your experience using Liferay DXP the best it can be. \mainmatter \pagenumbering{gobble} \part{Using Liferay DXP} \pagestyle{headings} \pagenumbering{arabic} \include{user/user} \part{Deploying Liferay DXP} \include{user/deployment} \end{document} ================================================ FILE: build-common.xml ================================================ base.source.url=https://github.com/${repo.owner}/${repo.name}/blob/${repo.branch}/${language.dir}/${purpose.dir}/${base.filepath} ================================================ FILE: build.properties ================================================ ## ## DO NOT EDIT THIS FILE. ## ## To update the properties of this file, create a separate properties file ## named "build.${user.name}.properties" with the properties to overwrite. ## ## ## System settings ## lang=en build.dir=./build ## ## Pandoc ## pandoc.app=/home/${user.name}/.cabal/bin/pandoc ## ## Operating Systems ## os.apple=Mac OS X os.unix=Linux,FreeBSD,Solaris,SunOS os.windows=Windows 95,Windows 98,Windows NT,Windows 2000,Windows 2003,Windows XP,Windows Vista,Windows 7 ## ## Markdown Metadata ## title= author= date= ## ## Repository ## repo.owner=liferay repo.name=liferay-docs repo.branch=master ================================================ FILE: build.xml ================================================ ================================================ FILE: code/liferay-book-utils/.pydevproject ================================================ /${PROJECT_DIR_NAME} python 3.0 Default ================================================ FILE: code/liferay-book-utils/src/fix-latex.py ================================================ #!/usr/bin/python # encoding: utf-8 ''' -- is a description It defines classes_and_methods @author: Rich Sezov @copyright: 2014 Liferay, Inc. All rights reserved. @license: license ''' import argparse parser = argparse.ArgumentParser() parser.add_argument("output", help="The file name to be written") parser.add_argument("file", help="file to be fixed") args = parser.parse_args() outfile = open(args.output, "w") infile = open(args.file, "r") content = infile.readlines() for i in content: if i.startswith("\caption"): eff = i.find("F") prefix = i[:eff] suffix = i[eff:] i = prefix + "\\" + "\\" + suffix if i.startswith("+sidebar"): i = "\\begin{roundedframe}\n\\begin{wrapfigure}{l}{0.12\\textwidth}\n\\includegraphics{../../images/01-tip.png}\n\end{wrapfigure}\n" if i.startswith("-sidebar"): i = "\end{roundedframe}" #if i.startswith("\href"): # print (i) # print("Found href; ignoring.") # i = "}" if i.startswith("\index"): i="" outfile.write(i) ================================================ FILE: code/liferay-book-utils/src/format-ebook.py ================================================ ''' Created on Jan 23, 2015 @author: rsezov ''' import argparse parser = argparse.ArgumentParser() parser.add_argument("output", help="The file name to be written") parser.add_argument("file", help="file to be fixed") args = parser.parse_args() outfile = open(args.output, "w") infile = open(args.file, "r") content = infile.readlines() count = 0 for i in content: if i.startswith("+sidebar"): i = '\n' outfile.write(i) count = count + 1 infile.close() outfile.close() ================================================ FILE: code/liferay-book-utils/src/merge-books.py ================================================ import argparse parser = argparse.ArgumentParser() parser.add_argument("output", help="The file name to be written") parser.add_argument("files", nargs="+", help="files to be merged") args = parser.parse_args() outfile = open(args.output, "w") chapterCount = 0 for i in args.files: infile = open(i, "r") content = infile.readlines() if chapterCount == 0: # We're in the first file and we only need to count the chapters for j in content: if j.startswith("![Figure"): # find the chapter number colon = j.find(":") e = j.find("e") dot = j.find(".") prefix = j[e+1:dot] suffix = j[dot+1:colon] chapter = prefix.strip(" ") print ("Chapter: " + chapter) chapterCount = int(chapter) outfile.writelines(content) else: # We're in another file and we need to increment the chapters for j in content: whiteLength = len(j) - len(j.lstrip()) whiteSpace="" if (whiteLength > 0) and (j != "\n"): for k in range(whiteLength): whiteSpace = whiteSpace + " " j = j.lstrip() print (str(whiteLength) + " white space characters found; " + str(len(whiteSpace)) + " characters added.") if j.startswith("![Figure"): colon = j.find(":") e = j.find("e") dot = j.find(".") chNum = j[e+1:dot] figNum = j[dot+1:colon] chapter = int(chNum.strip(" ")) prefix = j[:e+1] suffix = j[colon:] if chapter < chapterCount: chapter = chapter + chapterCount #imageLocation = suffix.index("(../../") #imageDir = suffix.index("/images/") #newLocation = "(../../../deployment" #beginSuffix = suffix[:imageLocation] #endSuffix = suffix[imageDir:] #suffix = beginSuffix + newLocation + endSuffix j = prefix + " " + str(chapter) + "." + figNum + suffix j = whiteSpace + j outfile.write(j) infile.close() outfile.close() ================================================ FILE: code/liferay-doc-utils/.gitignore ================================================ /dist/ /bin/ ================================================ FILE: code/liferay-doc-utils/build.xml ================================================ ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/movedclassreporter/BasicClassInfo.java ================================================ package com.liferay.documentation.movedclassreporter; import java.io.File; public class BasicClassInfo { public BasicClassInfo(File file, String pkg) { this.file = file; this.name = file.getName(); this.pkg = pkg; this.pkgEnd = pkg; int lastDot = pkg.lastIndexOf("."); if (lastDot != -1) { this.pkgEnd = pkg.substring(lastDot); } } public File getFile() { return file; } public String getName() { return name; } public String getPkg() { return pkg; } public String getPkgEnd() { return pkgEnd; } private File file; private String name; private String pkg; private String pkgEnd; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/movedclassreporter/MovedClassInfo.java ================================================ package com.liferay.documentation.movedclassreporter; public class MovedClassInfo { public MovedClassInfo(BasicClassInfo basicClassInfo) { this.basicClassInfo = basicClassInfo; this.name = basicClassInfo.getName(); this.packageOld = basicClassInfo.getPkg(); } public BasicClassInfo getBasicClassInfo() { return this.basicClassInfo; } public String getGroup( ) { return group; } public void setGroup(String group) { this.group = group; } public String getPackageNew() { return packageNew; } public void setPackageNew(String packageNew) { this.packageNew = packageNew; } public String getModule() { return module; } public void setModule(String module) { this.module = module; } public String getModuleVersion() { return moduleVersion; } public void setModuleVersion(String moduleVersion) { this.moduleVersion = moduleVersion; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPackageOld() { return packageOld; } BasicClassInfo basicClassInfo; private String name; private String packageOld; private String packageNew; private String module; private String moduleVersion; private String group; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/movedclassreporter/MovedClassReporterMain.java ================================================ package com.liferay.documentation.movedclassreporter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; public class MovedClassReporterMain { private static final String PORTAL_KERNEL_JAR = "portal-kernel.jar"; private static final String PORTAL_SERVICE_JAR = "portal-service.jar"; private static final String USAGE = "Usage: MovedClassReporter oldLiferayDir newLiferayDir movedClassesOutputFile"; public static void main(String[] args) { if (args == null || args.length < 3) { System.out.println(USAGE); return; } File oldDir = new File(args[0]); File newDir = new File(args[1]); File movedClassesOutputFile = new File(args[2]); if (!oldDir.isDirectory()) { System.out.println("oldLiferayDir is not a directory: " + args[0]); } if (!newDir.isDirectory()) { System.out.println("newLiferayDir is not a directory: " + args[1]); } // Create a list of all portal-kernel classes File newKernelSrcDir = new File(newDir + "/" + PORTAL_KERNEL_SRC); if (!newKernelSrcDir.exists()) { System.out.println("Expected folder not found: " + newKernelSrcDir.getPath()); System.out.println(USAGE); return; } System.out.println("New kernel dir: " + newKernelSrcDir.getPath()); ArrayList newKernelClasses = new ArrayList(); MovedClassReporterMain.listJavaFiles(newKernelSrcDir, newKernelClasses); System.out.println("New kernel dir class count: " + newKernelClasses.size()); // Create list of portal-service/portal-kernel class files File oldKernelSrcDir = new File(oldDir.getPath() + "/" + PORTAL_SERVICE_SRC); String oldJar = PORTAL_SERVICE_JAR; // If portal-service doesn't exist, use old portal-kernel if (!oldKernelSrcDir.exists()) { oldKernelSrcDir = new File(oldDir.getPath() + "/" + PORTAL_KERNEL_SRC); oldJar = PORTAL_KERNEL_JAR; if (!oldKernelSrcDir.exists()) { System.out.println("No portal-service or portal-kernel source files in oldLiferayDir: " + oldDir.getPath()); System.out.println(USAGE); return; } } System.out.println("Old kernel dir: " + oldKernelSrcDir.getPath()); ArrayList oldKernelClasses = new ArrayList(); MovedClassReporterMain.listJavaFiles(oldKernelSrcDir, oldKernelClasses); System.out.println("Old kernel dir class count: " + oldKernelClasses.size()); // Create list of service class BasicClassInfo objects List serviceBasicClassInfos = new ArrayList(); for (File file : oldKernelClasses) { String pkg = ""; try { pkg = getPackage(file); BasicClassInfo info = new BasicClassInfo(file, pkg); serviceBasicClassInfos.add(info); } catch (Exception e) { e.printStackTrace(); } } // Add to a new list of BasicClassInfo objects all service classes not in new kernel List classesNotInKernel = new ArrayList(); for (BasicClassInfo serviceInfo: serviceBasicClassInfos) { String serviceFileName = serviceInfo.getName(); boolean matched = false; for (File newKernelClass : newKernelClasses) { if (serviceFileName.equals(newKernelClass.getName())) { matched = true; break; } } if (!matched) { MovedClassInfo formerClass = new MovedClassInfo(serviceInfo); classesNotInKernel.add(formerClass); } } System.out.println("classesNotInKernel: " + classesNotInKernel.size()); ArrayList moduleFiles = new ArrayList(); File modulesAppsDir = new File(newDir + "/" + MODULES_APPS); listJavaFiles(modulesAppsDir, moduleFiles); File modulesCoreDir = new File(newDir + "/" + MODULES_CORE); listJavaFiles(modulesCoreDir, moduleFiles); File modulesUtilDir = new File(newDir + "/" + MODULES_UTIL); listJavaFiles(modulesUtilDir, moduleFiles); System.out.println("moduleFiles: " + moduleFiles.size()); // Create list of module class BasicClassInfo objects List moduleBasicClassInfos = new ArrayList(); for (File file : moduleFiles) { String pkg = ""; try { pkg = getPackage(file); BasicClassInfo info = new BasicClassInfo(file, pkg); moduleBasicClassInfos.add(info); } catch (Exception e) { e.printStackTrace(); } } // Find the module class info for each moved class and store in MovedClassInfo objects List movedClasses = new ArrayList(); List removedClasses = new ArrayList(); final int portletPkgLen = "com.liferay.portlet.".length(); for (MovedClassInfo classNotInKernel : classesNotInKernel) { final String className = classNotInKernel.getName(); final String oldPackageName = classNotInKernel.getPackageOld(); // Construct expected new prefix String newPrefix = ""; if (oldPackageName.startsWith("com.liferay.portlet.dynamicdatalists")) { newPrefix = "com.liferay.dynamic.data.lists"; } else if (oldPackageName.startsWith("com.liferay.portlet.dynamicdatamapping")) { newPrefix = "com.liferay.dynamic.data.mapping"; } else if (oldPackageName.startsWith("com.liferay.portlet.")) { // construct new prefix using portlet type String newPrefixEnd = ""; int end = oldPackageName.indexOf(".", portletPkgLen); if (end != -1) { newPrefixEnd = oldPackageName.substring(portletPkgLen, end); } else { newPrefixEnd = oldPackageName.substring(portletPkgLen); } newPrefix = "com.liferay." + newPrefixEnd; } // If expecting a new prefix, match it boolean matched = false; List matchingClassInfos = new ArrayList(); for (BasicClassInfo moduleClassInfo : moduleBasicClassInfos) { if (moduleClassInfo.getName().equals(className)) { matchingClassInfos.add(moduleClassInfo); if (!newPrefix.isEmpty()) { if (moduleClassInfo.getPkg().startsWith(newPrefix)) { matched = true; classNotInKernel.setPackageNew(moduleClassInfo.getPkg()); extractModuleInfo(moduleClassInfo, classNotInKernel); movedClasses.add(classNotInKernel); break; } } } } if (!matched) { if (matchingClassInfos.isEmpty()) { removedClasses.add(classNotInKernel); } else if (matchingClassInfos.size() == 1) { // Match it with the only other class with the same name matched = true; BasicClassInfo moduleClassInfo = matchingClassInfos.get(0); classNotInKernel.setPackageNew(moduleClassInfo.getPkg()); extractModuleInfo(moduleClassInfo, classNotInKernel); movedClasses.add(classNotInKernel); } else { // Find the best match for (BasicClassInfo moduleClassInfo : matchingClassInfos) { if (moduleClassInfo.getPkgEnd().equals(classNotInKernel.getBasicClassInfo().getPkgEnd())) { // It's a match since it has the same ending package name matched = true; classNotInKernel.setPackageNew(moduleClassInfo.getPkg()); extractModuleInfo(moduleClassInfo, classNotInKernel); movedClasses.add(classNotInKernel); } } if (!matched) { removedClasses.add(classNotInKernel); } } } } System.out.println("movedClasses: " + movedClasses.size()); sortByClassName(movedClasses); removeJavaSuffix(movedClasses); System.out.println("removedClasses: " + removedClasses.size()); String oldDirName = oldDir.getName(); String newDirName = newDir.getName(); TemplateProcessor movedClassesTemplateProcessor = new TemplateProcessor(); movedClassesTemplateProcessor.processMovedClassesTemplate(movedClasses, movedClassesOutputFile, oldJar, oldDirName, newDirName); System.out.println("Reported moved classes to HTML file: " + movedClassesOutputFile.getPath()); // Write moved classes to XML Element root = new Element("moved-class-report"); Element oldVersion = new Element("old-source-version"); oldVersion.addContent(oldDir.getPath()); root.addContent(oldVersion); Element newVersion = new Element("new-source-version"); newVersion.addContent(newDir.getPath()); root.addContent(newVersion); for (MovedClassInfo movedClass : movedClasses) { Element movedClassElem = new Element("moved-class"); Element className = new Element("class-name"); className.addContent(movedClass.getName()); movedClassElem.addContent(className); Element oldStuff = new Element("old"); Element oldPackage = new Element("package"); oldPackage.addContent(movedClass.getPackageOld()); oldStuff.addContent(oldPackage); movedClassElem.addContent(oldStuff); Element newStuff = new Element("new"); Element newPackage = new Element("package"); newPackage.addContent(movedClass.getPackageNew()); newStuff.addContent(newPackage); Element newArtifactGroup = new Element("artifact-group"); newArtifactGroup.addContent(movedClass.getGroup()); newStuff.addContent(newArtifactGroup); Element newArtifactId = new Element("artifact-id"); newArtifactId.addContent(movedClass.getModule()); newStuff.addContent(newArtifactId); Element newArtifactVersion = new Element("artifact-version"); newArtifactVersion.addContent(movedClass.getModuleVersion()); newStuff.addContent(newArtifactVersion); movedClassElem.addContent(newStuff); root.addContent(movedClassElem); } Document doc = new Document(); doc.setRootElement(root); String movedClassesXMLFilePath; int dotIndex = movedClassesOutputFile.getPath().indexOf(".htm"); if (dotIndex != -1) { movedClassesXMLFilePath = movedClassesOutputFile.getPath().substring(0, dotIndex) + ".xml"; } else { movedClassesXMLFilePath = movedClassesOutputFile.getPath() + ".xml"; } File movedClassesXMLFile = new File(movedClassesXMLFilePath); XMLOutputter outter = new XMLOutputter(); outter.setFormat(Format.getPrettyFormat()); try { outter.output(doc, new FileWriter(movedClassesXMLFile)); System.out.println("Reported moved classes to XML file: " + movedClassesXMLFile.getPath()); } catch (IOException e) { e.printStackTrace(); } } private static void extractModuleInfo(BasicClassInfo classInfo, MovedClassInfo formerClass) { String bundleName = ""; String bundleVersion = ""; File moduleFile = classInfo.getFile(); File currentDir = moduleFile.getParentFile(); while (currentDir != null) { if (currentDir.getName().equals("src")) { File moduleDir = currentDir.getParentFile(); FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name.equals("bnd.bnd")) { return true; } return false; } }; File[] bndFiles = moduleDir.listFiles(filter); if ((bndFiles != null) && bndFiles.length > 0) { File bndFile = bndFiles[0]; try { LineNumberReader in = new LineNumberReader(new FileReader(bndFile)); String line; while ((line = in.readLine()) != null) { if (line.startsWith("Bundle-SymbolicName:")) { String[] tokens = line.split(" "); if (tokens.length > 1) { bundleName = tokens[1].trim(); } } else if (line.startsWith("Bundle-Version:")) { String[] tokens = line.split(" "); if (tokens.length > 1) { bundleVersion = tokens[1].trim(); } } if (!bundleName.isEmpty() && !bundleVersion.isEmpty()) { break; } } in.close(); } catch (IOException e) { e.printStackTrace(); } if (bundleName.isEmpty()) { System.out.println("ERROR: Could not fill bundle info for " + bndFile.getPath()); } } else { System.out.println("Couldn't find a parent folder with a bnd.bnd file."); } break; } currentDir = currentDir.getParentFile(); } formerClass.setGroup(GROUP_COM_LIFERAY); formerClass.setModule(bundleName); formerClass.setModuleVersion(bundleVersion); } private static void sortByClassName( List formerPortalServiceClasses) { formerPortalServiceClasses.sort(new Comparator() { @Override public int compare(MovedClassInfo classA, MovedClassInfo classB) { return classA.getName().compareTo(classB.getName()); } } ); } private static void sortByPackageName( List formerPortalServiceClasses) { formerPortalServiceClasses.sort(new Comparator() { @Override public int compare(MovedClassInfo classA, MovedClassInfo classB) { int rval = classA.getPackageOld().compareTo(classB.getPackageOld()); if (rval == 0) { rval = classA.getName().compareTo(classB.getName()); } return rval; } } ); } private static void removeJavaSuffix( List formerPortalServiceClasses) { for (MovedClassInfo movedClass : formerPortalServiceClasses) { String name = movedClass.getName(); int x = name.indexOf(".java"); if (x != -1) { name = name.substring(0, x); movedClass.setName(name); } } } private static String getPackage(File serviceClass) throws Exception { String pkgStr = "package"; int pkgStrLen = pkgStr.length(); String pkg = ""; try { LineNumberReader in = new LineNumberReader(new FileReader(serviceClass)); String line; while ((line = in.readLine()) != null) { if (line.startsWith("package")) { String str = line.substring(pkgStrLen); str = str.trim(); int semiIndex = str.indexOf(';'); if (semiIndex > 0) { str = str.substring(0, semiIndex); } pkg = str; break; } } in.close(); } catch (IOException e) { throw new Exception(e.getLocalizedMessage()); } return pkg; } private static void listBndFiles(String directoryName, ArrayList files) { File directory = new File(directoryName); // get all the files from a directory File[] fList = directory.listFiles(); if (fList == null) { return; } for (File file : fList) { if (file.isFile()) { if (file.getName().equals("bnd.bnd")) { files.add(file); } } else if (file.isDirectory()) { listBndFiles(file.getAbsolutePath(), files); } } } private static void listJavaFiles(File directory, ArrayList files) { // get all the files from a directory File[] fList = directory.listFiles(); if (fList == null) { return; } for (File file : fList) { if (file.isFile()) { if (file.getName().endsWith(".java")) { files.add(file); } } else if (file.isDirectory()) { listJavaFiles(file, files); } } } public static final String GROUP_COM_LIFERAY = "com.liferay"; public static final String MODULES_APPS = "modules/apps"; public static final String MODULES_CORE = "modules/core"; public static final String MODULES_UTIL = "modules/util"; public static final String PORTAL_KERNEL_SRC = "portal-kernel/src"; public static final String PORTAL_SERVICE_SRC = "portal-service/src"; public static final String SRC_MAIN_JAVA = "src/main/java"; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/movedclassreporter/TemplateProcessor.java ================================================ package com.liferay.documentation.movedclassreporter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; public class TemplateProcessor { public void processMovedClassesTemplate(List movedClasses, File article, String oldJar, String oldDirName, String newDirName) { // Write report using FreeMarker template Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); try { cfg.setDefaultEncoding("UTF-8"); ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), TPL_ROOT); cfg.setTemplateLoader(ctl); Map root = new HashMap(); root.put("oldJar", oldJar); root.put("oldSrc", oldDirName); root.put("newSrc", newDirName); root.put("movedClasses", movedClasses); Template temp = cfg.getTemplate("moved-classes.ftl"); FileWriter out = new FileWriter(article); temp.process(root, out); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (TemplateException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static final String TPL_ROOT = "/com/liferay/documentation/movedclassreporter/dependencies/"; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/movedclassreporter/dependencies/moved-classes.ftl ================================================ To leverage the benefits of modularization, many classes from ${oldJar} 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. <#list movedClasses as movedClass>
    Classes Moved from ${oldJar} to Modules

    This information was generated based on comparing classes in ${oldSrc} to classes in ${newSrc}.

    Class Package Group ID, Artifact ID,
    and Version
    ${movedClass.name} Old: ${movedClass.packageOld}
    New: ${movedClass.packageNew}
    ${movedClass.group}
    ${movedClass.module}
    ${movedClass.moduleVersion}
    ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/util/AddTOCTask.java ================================================ package com.liferay.documentation.util; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class AddTOCTask extends Task { @Override public void execute() throws BuildException { String docDir = _docdir; String productType = _productType; List dirTypes = new ArrayList(); dirTypes.add(""); if (productType.equals("dxp")) { dirTypes.add("-dxp"); } for (String dirType : dirTypes) { List fileList = DocsUtil.getMarkdownFileList(docDir, dirType); List fileListNoTOC = new ArrayList(); try { fileListNoTOC = getFilesWithNoTOC(fileList); addTOCs(fileListNoTOC); } catch (IOException e) { throw new BuildException(e.getLocalizedMessage()); } } } private static void addTOCs(List fileListNoTOC) throws IOException { for (int i = 0; i < fileListNoTOC.size(); i++) { String filenameNoTOC = fileListNoTOC.get(i); File inFile = new File(filenameNoTOC); File outFile = new File(filenameNoTOC); String outFileTmp = outFile + ".tmp"; System.out.println("Adding TOC syntax for " + filenameNoTOC); LineNumberReader in = new LineNumberReader(new FileReader(inFile)); BufferedWriter out = new BufferedWriter(new FileWriter(outFileTmp)); String line; boolean tocAdded = false; while ((line = in.readLine()) != null) { if (line.startsWith("#") && !line.startsWith("##") && !tocAdded) { out.append(line); out.append("\n\n"); out.append(tocSyntax); out.append("\n"); tocAdded = true; } else { out.append(line); out.append("\n"); } } in.close(); out.flush(); out.close(); // Replace original file with modified temp file FileUtils.copyFile( new File(outFileTmp), new File(filenameNoTOC)); FileUtils.forceDelete(new File(outFileTmp)); } } private static List getFilesWithNoTOC(List fileList) throws IOException { List fileListNoTOC = new ArrayList(); for (int i = 0; i < fileList.size(); i++) { String filename = fileList.get(i); File inFile = new File(filename); LineNumberReader in = new LineNumberReader(new FileReader(inFile)); String line; boolean tocExists = false; //Integer tocLineNum = null; int tocLineNum = -2; while ((line = in.readLine()) != null) { if (line.startsWith(tocSyntax)) { tocExists = true; tocLineNum = in.getLineNumber(); } if (in.getLineNumber() == (tocLineNum + 1) && tocExists) { if (!line.equals("")) { in.close(); throw new BuildException("Filename: " + filename + ":" + in.getLineNumber() + "The line following the TOC syntax should " + "be blank."); } } } if (!tocExists) { fileListNoTOC.add(filename); } in.close(); } return fileListNoTOC; } public void setDocdir(String docdir) { _docdir = docdir; } public void setProductType(String productType) { _productType = productType; } private static String tocSyntax = "[TOC levels=1-4]"; private String _docdir; private String _productType; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/util/CheckArticleImagesTask.java ================================================ package com.liferay.documentation.util; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.List; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class CheckArticleImagesTask extends Task { @Override public void execute() throws BuildException { List errors = new ArrayList(); System.out.println("Start checking image sources ..."); File dir = new File("../" + _docdir); if (!dir.exists()) { throw new BuildException("docdir " + dir.getAbsolutePath() + " could not be found"); } if (!dir.isDirectory()) { throw new BuildException("docdir " + dir.getAbsolutePath() + " is not a directory"); } if (!_article.endsWith(".markdown") && !_article.endsWith(".md")) { throw new BuildException("Article isn't a Markdown file"); } File article = new File("../" + _docdir + "/" + _article); if (!article.exists()) { throw new BuildException("Article doesn't exist: " + article.getPath()); } int dirsUp = 0; File tmpFile = article.getParentFile(); String fileName = article.getName(); while (!_docdir.equals(tmpFile.getName())) { tmpFile = tmpFile.getParentFile(); dirsUp++; } if (!_docdir.equals(tmpFile.getName())) { throw new BuildException("File not in docdir"); } try { System.out.println("inside " + article.getPath()); LineNumberReader in = new LineNumberReader(new FileReader(article)); String line; try { while ((line = in.readLine()) != null) { List newErrors = CheckImageUtil.checkImgSrc( line, _article, in.getLineNumber(), dirsUp, _imagedir); if (!newErrors.isEmpty()) { errors.addAll(newErrors); } } } catch (IOException e) { throw new BuildException(e.getLocalizedMessage()); } } catch (FileNotFoundException e) { throw new BuildException(e.getLocalizedMessage()); } // Check for errors if (!errors.isEmpty()) { // Print errors System.out.println("ERROR - Missing image source files ..."); for (String error : errors) { System.out.println(error); } throw new BuildException("Missing image source files"); } System.out.println("Finished checking image sources."); } public void setArticle(String article) { _article = article; } public void setDocdir(String docdir) { _docdir = docdir; } public void setImagedir(String imagedir) { _imagedir = imagedir; } private String _docdir; private String _article; private String _imagedir = "images"; } ================================================ FILE: code/liferay-doc-utils/src/com/liferay/documentation/util/CheckHeadersTask.java ================================================ package com.liferay.documentation.util; import java.io.File; import java.io.FileReader; import java.io.FilenameFilter; import java.io.IOException; import java.io.LineNumberReader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.regex.Pattern; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; public class CheckHeadersTask extends Task { @Override public void execute() throws BuildException { String docDir = _docdir; String productType = _productType; List dirTypes = new ArrayList(); dirTypes.add(""); if (productType.equals("dxp")) { dirTypes.add("-dxp"); } for (String dirType : dirTypes) { File articlesDir = new File("../" + docDir + "/articles" + dirType); File docSetDir = new File("../" + docDir); if (!articlesDir.exists()) { if (!dirType.contains("dxp")) { throw new BuildException("FAILURE - no articles directory " + articlesDir); } else { continue; } } List docSetDirFolders = new ArrayList(); Queue q = new LinkedList(); File articlesDirContents[] = articlesDir.listFiles(); for (File f : articlesDirContents) { if (f.isDirectory()) { q.add(f); } } while (!q.isEmpty()) { File f = q.remove(); docSetDirFolders.add(f); File[] files = f.listFiles(); for (File file : files) { if (file.isDirectory()) { q.add(file); } } } docSetDirFolders.add(articlesDir); docSetDirFolders.add(docSetDir); File docSetDirFoldersArray[] = docSetDirFolders.toArray( new File[docSetDirFolders.size()]); List fileList = new ArrayList(); for (int i = 0; i < docSetDirFoldersArray.length; i++) { File files[] = docSetDirFoldersArray[i].listFiles( new FilenameFilter() { String filePatternArg = "([^\\\\\\[\\]\\|:;%<>]+).markdown"; Pattern fileNamePattern = Pattern.compile( filePatternArg); public boolean accept(File file, String name) { return (fileNamePattern.matcher(name).matches()); } } ); for (int j = 0; j < files.length; j++) { fileList.add(files[j].getPath()); } } for (int i = 0; i < fileList.size(); i++) { String filename = fileList.get(i); File inFile = new File(filename); try { LineNumberReader in = new LineNumberReader( new FileReader(inFile)); String line; String titleLine = null; String titleLineError1 = null; String titleLineError2 = null; int counter = 0; boolean headerSyntaxExists = false; while ((line = in.readLine()) != null) { if (counter == 2) { headerSyntaxExists = true; titleLine = Files.readAllLines(Paths.get(filename)).get(in.getLineNumber()); titleLineError1 = Files.readAllLines(Paths.get(filename)).get(in.getLineNumber() - 1); titleLineError2 = Files.readAllLines(Paths.get(filename)).get(in.getLineNumber() + 1); break; } if (line.startsWith("---")) { counter++; } } if (titleLine != null) { // Check whether the markdown file starts with the proper single # // header. // If it doesn't, throw an exception identifying the file if (!titleLine.startsWith("# ")) { String message; if (titleLineError1.startsWith("# ") || titleLineError2.startsWith("# ")) { message = "FAILURE - " + filename + ": File's single # header is spaced incorrectly."; } else { message = "FAILURE - " + filename + ": File does not start with a single # for a header"; } if (titleLine.startsWith(" ================================================ FILE: en/build.xml ================================================ ================================================ FILE: en/deployment/articles/01-deploying-liferay/01-deploying-liferay-intro.markdown ================================================ --- header-id: deploying-product --- # Deploying @product@ [TOC levels=1-4] @product@ is one of the most flexible applications on the market today with respect to database and application server support. It supports a wide variety of databases and application servers, freeing you to use the ones you want. @product@ also scales very well. You can install it on a shared hosting account, on a multi-node cluster running a commercial application server, or on anything in between. In fact, @product@ is used successfully in all of these scenarios every day. You'll find that because of @product@'s flexibility in its deployment options, it is also easy to install. If you already have an application server, you can use your application server's deployment tools to install @product@. If you don't already have an application server, Liferay provides several application server bundles from which to choose. These are pre-configured application servers with @product@ already installed on them. With a small amount of configuration, these can be made into production-ready systems. There are some preparations to make before installing. You must create a database and install a supported Java Development Kit (JDK). It can also be worthwhile to pre-configure or gather information for configuring a data source, mail session, and more. You'll get guidance for these preparations. Lastly, you'll install and deploy @product@ for the first time and then set up Marketplace. You can continue configuring and tuning as you desire. Read on to obtain the @product@ installer that's right for you. ================================================ FILE: en/deployment/articles/01-deploying-liferay/02-obtaining-liferay.markdown ================================================ --- header-id: obtaining-product --- # Obtaining @product@ [TOC levels=1-4] Before you begin, you should answer a few questions. - Which version of @product@ will you install? - Is your installation for development purposes or are you preparing to run in production? - Are you installing @product@ in a clustered environment? - Which application server do you want @product@ to run on? - Are you installing on an existing application server? Here you'll determine the installation that's best for you and download it. Anyone can download @product@ from [liferay.com](https://www.liferay.com). Clicking *Resources → Community Downloads* takes you to the [Community Downloads page](https://www.liferay.com/downloads-community), where you can access Liferay Portal CE or a trial of the enterprise supported Liferay DXP. The installers are available in several different formats. The formats include a Liferay Tomcat bundle (@product@ bundled with Tomcat) as well as a @product@ `.war` (WAR) file for installing @product@ on an existing application server of choice. Liferay enterprise subscribers can download Liferay DXP from the [Help Center](https://help.liferay.com/hc). @product@ runs on a wide variety of application servers (Check the [Support page](https://help.liferay.com/hc/categories/360000894391-Product-Support) for a complete listing). Here are the ways to install @product@: - [Install a Liferay Tomcat bundle](#liferay-tomcat-bundle) (Tomcat application server with @product@ pre-installed). - [Install the Liferay WAR](#downloading-the-liferay-war-and-dependency-jars) (and supporting libraries) onto an existing application server. Since the Liferay Tomcat bundle is the easiest way, it's described first. ## Liferay Tomcat Bundle The Liferay Tomcat bundle includes the Tomcat application server with @product@ pre-installed. If you prefer using another application server with @product@, you must install it manually. If you don't currently have an application server preference, consider starting with the Tomcat bundle. Tomcat is one of the most lightweight and straightforward bundles to configure. | **Note:** Application server bundles for proprietary application servers such | as WebLogic or WebSphere aren't available because the licenses for these | servers don't allow for redistribution. @product@'s commercial offering, | however, runs just as well on these application servers as it does on the | others. Bundles are released as 7-Zip (`.7z`) and gzipped (`.tar.gz`) compressed file archive formats. [Installing @product@](/docs/7-2/deploy/-/knowledge_base/d/installing-product) demonstrates installing @product@ from a bundle. Follow its instructions once you've prepared for installing @product@ (see the next article). @product@ bundles might not be appropriate for you. Here are some reasons for installing the @product@ WAR manually instead of using a bundle. - There is no @product@ bundle for the application server you want to use. - You're installing @product@ in a clustered environment. - You're installing to an existing application server. Manual installation is described next. ## Downloading the Liferay WAR and Dependency JARs Manual installation requires installing the @product@ WAR and dependency JARs onto the application server. These files are available to download for [DXP](https://customer.liferay.com/downloads) or [Portal CE](https://www.liferay.com/downloads-community): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file After preparing for install (next), follow the @product@ installation steps for your application server. There are specific configuration and script changes required on each application. The installation article titles follow this format, with *[Application Server]* replaced by the application server name. *Installing @product@ on [Application Server]* ================================================ FILE: en/deployment/articles/01-deploying-liferay/03-preparing-for-install.markdown ================================================ --- header-id: preparing-for-install --- # Preparing for Install [TOC levels=1-4] @product@ doesn't require much to deploy. You need a Java Development Kit (JDK) and a database. Several configuration topics (e.g., [search engine integration](/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine), [document repository configuration](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration), [security management](/docs/7-2/deploy/-/knowledge_base/d/securing-product), [clustering](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering), and more) can be addressed *after* deploying @product@. | **Note:** If you are installing @product@ to multiple machines (e.g., in a | [cluster](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering)) | or prefer centralizing configuration in a file, using portal properties in a | [`[LIFERAY_HOME]/portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) | is the recommended way to configure. The install preparation topics here and | the configuration topics throughout this guide demonstrate using applicable | portal properties. | **Note:** `LIFERAY_HOME` is the location from which @product@ launches | applications, applies configurations, loads JAR files, and generates logs. | Liferay Home is customizable and can differ between application servers. The | [Liferay Home reference](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) | describes its folder structure. Start preparing for @product@ install by installing a supported Java Development Kit. ## JDK Requirements @product@ deployment requires a JDK. The [Support page](https://help.liferay.com/hc/categories/360000894391-Product-Support) lists the supported JDKs from various vendors. You must use one of these JDK versions: - JDK 8 - JDK 11 JDK 11 is backwards compatible with JDK 8 applications. Applications and customizations developed on JDK 8 run on JDK 8 or JDK 11 runtimes. This makes JDK 8 best for developing on @product-ver@. ## JVM Requirements @product@ requires that the application server JVM use the GMT time zone and UTF-8 file encoding. Include these JVM arguments to set the required values. ```properties -Dfile.encoding=UTF8 -Duser.timezone=GMT ``` On JDK 11, it's recommended to add this JVM argument to display four-digit years. ```properties -Djava.locale.providers=JRE,COMPAT,CLDR ``` | **Note:** Since JDK 9, the Unicode Common Locale Data Repository (CLDR) is the | default locales provider. CLDR, however, is not providing years in a | four-digit format (see | [LPS-87191](https://issues.liferay.com/browse/LPS-87191)). | The setting `java.locale.providers=JRE,COMPAT,CLDR` works around this issue by | using JDK 8's default locales provider. The recommended maximum heap size is 2GB. Setting the minimum heap size to the maximum heap size value minimizes garbage collections. ```properties -Xms2560m -Xmx2560m ``` If you're using JDK 11, you may see *Illegal Access* warnings like these: ``` WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.liferay.petra.reflect.ReflectionUtil (file:/Users/malei/dev/project/bundles/master-bundles/tomcat-9.0.10/lib/ext/com.liferay.petra.reflect.jar) to field java.lang.reflect.Field.modifiers WARNING: Please consider reporting this to the maintainers of com.liferay.petra.reflect.ReflectionUtil WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release ``` This is a known issue: [LPS-87421](https://issues.liferay.com/browse/LPS-87421). As a workaround, you can eliminate these warnings by adding these properties after your application server JVM options: ```properties --add-opens=java.base/java.io=ALL-UNNAMED \ --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-opens=java.base/java.net=ALL-UNNAMED \ --add-opens=java.base/java.nio=ALL-UNNAMED \ --add-opens=java.base/java.text=ALL-UNNAMED \ --add-opens=java.base/java.util=ALL-UNNAMED \ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \ --add-opens=java.desktop/java.awt.font=ALL-UNNAMED \ --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED \ --add-opens=java.xml/com.sun.org.apache.xerces.internal.parsers=ALL-UNNAMED ``` If you're using JDK 11 on Linux or UNIX and are activating @product@ using an LCS 5.0.0 client, you may see an error like this: ``` ERROR [LCS Worker 2][BaseScheduledTask:92] java.lang.reflect.InaccessibleObjectException: Unable to make public long com.sun.management.internal.OperatingSystemImpl.getOpenFileDescriptorCount() accessible: module jdk.management does not "opens com.sun.management.internal" to unnamed module @1a3325e5 java.lang.reflect.InaccessibleObjectException: Unable to make public long com.sun.management.internal.OperatingSystemImpl.getOpenFileDescriptorCount() accessible: module jdk.management does not "opens com.sun.management.internal" to unnamed module @1a3325e5 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java: at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java: at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:198) at java.base/java.lang.reflect.Method.setAccessible(Method.java:192) ``` To workaround this issue, add this property after your application server JVM options: ```properties --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED ``` It's time to prepare your database. ## Preparing a Database The recommended way to set up your @product@ database is also the simplest. @product@ takes care of just about everything. Here are the steps: 1. Create a blank database encoded with the character set UTF-8. @product@ is a multilingual application and needs UTF-8 encoding to display all of its supported character sets. | **Note:** If you plan to migrate from one database vendor to another, | [configure the database to use the default query result order you expect for entities @product@ lists](/docs/7-2/deploy/-/knowledge_base/d/sort-order-changed-with-a-different-database). 2. Create a database user for accessing this database. Grant this database user all rights, including the rights to create and drop tables, to the blank @product@ database. @product@ uses this database user's credentials to connect to the database either [directly](#using-the-built-in-data-source) or [through its application server](#using-a-data-source-on-your-application-server). After you've configured the database connection, @product@ creates its tables in the database automatically, complete with indexes. This is the recommended way to set up @product@. It enables @product@ to maintain its database automatically during upgrades or when various @product@ plugins that create database tables of their own are installed. This method is by far the best way to set up your database. | **Warning:** If you're using an Oracle database, use the `ojdbc8.jar` driver | library with at least Oracle 12.2.0.1.0 JDBC 4.2 versioning because | [data truncation issues](https://issues.liferay.com/browse/LPS-79229) | have been detected reading data from CLOB columns. You can connect @product@ with your database using @product@'s built-in data source (recommended) or using a data source you create on your app server. ### Using the Built-in Data Source You can configure the built-in data source from the [Basic Configuration page](/docs/7-2/deploy/-/knowledge_base/d/installing-product#using-the-setup-wizard) (available when @product@ starts up the first time) or by specifying it using portal properties. Here's how set it using portal properties: 1. Create a [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) if you haven't created one already. 2. Copy a set of `jdbc.*` properties from one of the [JDBC templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates) into your `portal-ext.properties` file. 3. Modify the `jdbc.*` property values to specify your database and database user credentials. 4. Put the `portal-ext.properties` file into your [LIFERAY_HOME](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) once you've established it based on your installation. @product@ connects to the data source on startup. As an alternative to the built-in data source, you can use your application server's data source. ### Using a Data Source on Your Application Server Here's how to use your application server's data source: 1. Create your data source based on the instructions in the *Installing @product@ on [Application Server]* article (for your application server) and your application server's documentation. 2. Create a [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties), if you haven't created one already. 3. Add the `jdbc.default.jndi.name` property set to the data source's JNDI name. Here's an example: ```properties jdbc.default.jndi.name=jdbc/LiferayPool ``` 4. Put the `portal-ext.properties` file into your [LIFERAY_HOME](/docs/7-2/deploy/-/knowledge_base/d/liferay-home), once you've established your LIFERAY_HOME based on your installation. @product@ connects to your data source on startup. Allowing the database user you're using to initialize the @product@ database to continue with all database rights is recommended. If you're fine with that user having the recommended permissions, skip the next section on limiting database access. ### Limiting Database Access | **Warning:** The instructions below are not ideal for @product@ installations | The following procedure is documented so that enterprises with more | restrictive standards can install @product@ with stricter (but sub-optimal) | database settings. If it's at all possible, allow the database user that | initializes the database to continue using the database with the same | recommended permissions. The start of this section | ([Database Preparation](#preparing-a-database)) | describes the recommended procedure for initializing the @product@ database | and preserving that user's permissions for maintaining the @product@ database | and updating the database as plugin installations and plugin updates require. Even though it's recommended for @product@ to use the same database user to create and maintain its database automatically, your organizations might insist on revoking database initialization and maintenance permissions from that user once the database is initialized. If permissions for Select, Insert, Update and Delete operations are the only ones you allow for that user, you must initialize and maintain the database manually (even though it's not recommended). Here is the manual procedure: 1. Create a new, blank, database for @product@. 2. Grant full rights for the @product@ database user to do anything to the database. 3. Install @product@ and start it so that it automatically populates the database. 4. Once the database has been populated with the @product@ tables, remove all permissions from that user except permissions to perform Select, Insert, Update and Delete operations. There are some caveats to running @product@ like this. Many plugins create new tables when they're deployed. Additionally, you must run the database upgrade function to upgrade @product@. If the @product@ database user doesn't have adequate rights to create/modify/drop tables in the database, you must grant those rights to that user before you deploy one of these plugins or start upgrading @product@. Once the tables are created or the upgrade completes, you can remove those rights until the next deploy or upgrade. Additionally, your own developers might create plugins that must create their own tables. These are just like @product@ plugins that do the same thing, and they can only be installed if @product@ can create database tables. Installing these plugins requires granting the @product@ database user rights to create tables in the database before you attempt to install the plugins. @product@ has many more configurable features; but they can wait until *after* deployment. The [Configuring @product@](/docs/7-2/deploy/-/knowledge_base/d/configuring-product) section explains them. Now it's time to install @product@. ================================================ FILE: en/deployment/articles/01-deploying-liferay/04-installing-liferay.markdown ================================================ --- header-id: installing-product --- # Installing @product@ [TOC levels=1-4] Now that you've [prepared for installing @product@](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install), you can install and deploy it! This involves uncompressing the archive (the 7-Zip or gzip bundle file), possibly copying a JDBC driver to your application server, and starting the application server. Lastly, @product@ provides a setup wizard to configure portal essentials. | **Note:** Since bundles are the easiest way to complete an installation, the | installation steps here assume you're installing a @product@ bundle. If you | plan to install @product@ manually, please refer to the *Installing @product@ | on [Application Server]* article for the application server you're | installing on. ## Extracting a @product@ Bundle Extract your @product@ bundle to the appropriate location on your server. This folder is the [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). ## Installing the JDBC Driver If you're using a supported open source database or if you're setting up @product@ to use the embedded HSQL database for demo purposes, you can skip this step. Otherwise, copy your database's JDBC driver `.jar` file to the folder your application server documentation specifies. On Tomcat, for example, the driver belongs in the `[Tomcat]/lib/ext` folder. ## Running @product@ for the First Time Start your application server using the start script bundled with your application server. For example, the Tomcat bundle provides the `startup.sh` script in `$CATALINA_HOME/bin`. | **Note:** @product@ writes log files to folder `[Liferay Home]/logs`. | **Important:** @product@ requires that the application server JVM use the GMT | time zone and UTF-8 file encoding. They're preset in your @product@ bundle. The first time @product@ starts, it creates all of its database tables. On completing startup, it launches a web browser that displays the Basic Configuration page (the setup wizard). If for some reason your browser doesn't load the Basic Configuration page automatically, open your browser and navigate to your app server's address and port (for example, http://localhost:8080). ## Using the Setup Wizard The Basic Configuration page provides a convenient way to configure these things: - Portal name and default locale - Administrator user - Database ![Figure 1: Supply the information for your portal and your portal's default administrator user on the Basic Configuration page.](../../images/basic-configuration1.png) ### Portal Supply this basic portal information: **Portal Name:** name the installation you're powering with @product@. **Default Language:** choose your portal's default locale and click the *Change* button. This immediately localizes your portal content, including the Basic Configuration page. **Time Zone:** select your @product@ instance's default time zone. ### Administrator User For the administrator, supply the following information: **First Name:** the default administrator user's first name **Last Name:** the default administrator user's last name **Email:** the default administrator user's email address | **Note:** the administrator user's email domain is used as the @product@ | instance's default domain (i.e., the | [`company.default.web.id`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Company) [portal property](/docs/7-2/deploy/-/knowledge_base/d/portal-properties)). ### Database This section lets you connect to @product@'s built-in data source. | **Important:** If you haven't created a database for @product@, create one | now following | [database preparation instructions](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#preparing-a-database) | in the preceding article. HSQL is selected as the default database, but it's primarily for demonstration or trial purposes. Click the *Change* link if you want to use @product@'s built-in data source and configure it to use the [database you created earlier](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#preparing-a-database). The database configuration section also has an *Add Sample Data* checkbox for adding sample data to your database. This data includes Users, Sites, and Organizations for demo purposes. If you're installing @product@ on your own machine to explore features, the sample data may be useful. If, however, you're installing @product@ on a real server, start with a clean system by leaving this checkbox unselected. | **Warning:** HSQL should not be used in production @product@ instances. | Configure @product@ to use a different database; specify that database via the | Basic Configuration page here or using portal properties. See | [Database Preparation](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#preparing-a-database) | for details. Once you've filled out the Basic Configuration form, click *Finish Configuration*. The setup wizard creates a `[LIFERAY_HOME]/portal-setup-wizard.properties` file which stores the settings that you entered. When you begin customizing your portal's configuration, however, use a [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties). The [Portal properties reference documentation](@platform-ref@/7.2-latest/propertiesdoc) lists the default properties and describes all the properties you can set for @product@. | **Tip:** The wizard is a helpful tool, especially if you're setting | up @product@ for the first time. If you're a veteran and you already have your | various properties set up, you can disable the setup wizard. If you disable | the setup wizard, you must configure everything manually from a portal | properties file (e.g., `[LIFERAY_HOME]/portal-ext.properties`). To disable the | setup wizard, set `setup.wizard.enabled=false` in your portal properties file. | Note that property values in `portal-setup-wizard.properties` (the file the | setup wizards creates in Liferay Home) override property values in | `portal-ext.properties`. On finishing basic configuration, @product@ prompts you to restart your server. When you restart your application server, @product@ initializes the database you specified. Now that @product@ is up and running, you can continue configuring it as desired. Here are some suggestions: 1. [Configure your mail session](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail), if you haven't already configured it. 2. Install the Marketplace plugin, if it isn't already installed. If your machine has restricted access to the public network or if you restricted the @product@ database user's permissions after initializing the database (not recommended), you can still set up Marketplace by following the [Marketplace setup instructions](/docs/7-2/deploy/-/knowledge_base/d/setting-up-marketplace-and-portal-security). 3. Read the [Configuring @product@](/docs/7-2/deploy/-/knowledge_base/d/configuring-product) articles for guidance in configuring @product@'s default time zone, locales, logging, search engine, document repository, and more. You're on your way to providing your organization with terrific experiences on @product@. ================================================ FILE: en/deployment/articles/01-deploying-liferay/05-installing-liferay-on-tomcat.markdown ================================================ --- header-id: installing-product-on-tomcat --- # Installing @product@ on Tomcat [TOC levels=1-4] @product-ver@ bundled with Tomcat 9 is available on the [Help Center](https://customer.liferay.com/downloads) (DXP) or the [Community Downloads page](https://www.liferay.com/downloads-community) (Portal CE). The Tomcat bundle contains JARs, scripts, and configuration files required for deploying @product-ver@. Copying these files from the @product@ Tomcat bundle facilitates installing @product@ on an existing Tomcat application server. Whether you copy bundle files (recommended) or download and create the files, you must download these files for [DXP](https://customer.liferay.com/downloads) or [Portal CE](https://www.liferay.com/downloads-community): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file | **Important:** | [Prepare for the install](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install) | before continuing. Here are the basic steps for installing @product@ on Tomcat: - [Installing dependencies to your application server](#installing-dependencies) - [Configuring your application server for @product@](#configuring-tomcat) - [Deploying the @product@ WAR file to your application server](#deploying-product) [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) is Tomcat server's parent folder. `$TOMCAT_HOME` refers to your Tomcat server folder. It is usually named `tomcat-[version]` or `apache-tomcat-[version]`. ## Installing Dependencies @product@ depends on many JARs included by @product@ Tomcat bundle. Some of the bundle's JARs are not strictly required but can still be useful. If you don't have a bundle, you can download the Liferay JARs by downloading the *Dependencies* archive and the *OSGi Dependencies* archive, and you can download the third-party JARs as described below. 1. Create the folder `$TOMCAT_HOME/lib/ext` if it doesn't exist and extract the JARs from the dependencies ZIP to it. 2. Copy the following JARs from a @product@ Tomcat bundle (or download them) to the `$TOMCAT_HOME/lib/ext` folder: - [`activation.jar`](http://www.oracle.com/technetwork/java/javase/jaf-136260.html) - [`ccpp.jar`](http://mvnrepository.com/artifact/javax.ccpp/ccpp/1.0) - [`jms.jar`](http://www.oracle.com/technetwork/java/docs-136352.html) - [`jta.jar`](http://www.oracle.com/technetwork/java/javaee/jta/index.html) - [`jutf7.jar`](http://mvnrepository.com/artifact/com.beetstra.jutf7/jutf7) - [`mail.jar`](http://www.oracle.com/technetwork/java/index-138643.html) - [`persistence.jar`](http://mvnrepository.com/artifact/org.eclipse.persistence/javax.persistence/2.1.1) - [`support-tomcat.jar`](http://mvnrepository.com/artifact/com.liferay.portal/com.liferay.support.tomcat) 3. Copy the JDBC driver for your database to the `$CATALINA_BASE/lib/ext` folder. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. 4. Create an `osgi` folder in your Liferay Home. Extract the folders (i.e., `configs`, `core`, and more) from OSGi ZIP file to the `osgi` folder. The `osgi` folder provides the necessary modules for @product@'s OSGi runtime. ## Configuring Tomcat Configuring Tomcat to run @product@ includes - Setting environment variables - Specifying a web application context for @product@ - Setting properties and descriptors Optionally, if you're not using @product@'s built-in data source or mail session, you can configure Tomcat to manage them: - [Data source](#database-configuration) - [Mail session](#mail-configuration) Start with configuring Tomcat to run @product@. 1. If you have a @product@ Tomcat bundle, copy the `setenv.bat`, `setenv.sh`, `startup.bat`, `startup.sh`, `shutdown.bat`, and `shutdown.sh` files from it to your `$CATALINA_BASE/bin` folder. If not, create the `setenv.bat` and `setenv.sh`scripts. The scripts set JVM options for Catalina, which is Tomcat's servlet container. Among these options is the location of the Java runtime environment. If this environment is not available on your server globally, you must set its location in in these files so Tomcat can run. Do this by pointing the `JAVA_HOME` environment variable to a @product@-supported JRE: ```bash export JAVA_HOME=/usr/lib/jvm/java-8-jdk export PATH=$JAVA_HOME/bin:$PATH ``` Then configure Catalina's JVM options to support @product@. Unix: ```bash CATALINA_OPTS="$CATALINA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" ``` Windows: ```bash set "CATALINA_OPTS=%CATALINA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" ``` This sets the file encoding to UTF-8, prefers an IPv4 stack over IPv6, prevents Tomcat from working around garbage collection bugs relating to static or final fields (these bugs don't exist in @product@ and working around them causes problems with the logging system), sets the time zone to GMT, gives the JVM 2GB of RAM, and limits Metaspace to 512MB. | **Important:** @product@ requires that the application server JVM use the | GMT time zone and UTF-8 file encoding. | **Note:** On JDK 11, it's recommended to add this JVM argument to display | four-digit years. | | ```properties | -Djava.locale.providers=JRE,COMPAT,CLDR | ``` After installation, tune your system (including these JVM options) for performance. 2. If you have a @product@ Tomcat bundle, copy its `$CATALINA_BASE/conf/Catalina/localhost/ROOT.xml` file to the corresponding location in your application server. Create the file path if it doesn't exist. If you don't have a @product@ Tomcat bundle, create a `ROOT.xml` file. The `ROOT.xml` file specifies a web application context for @product@. `ROOT.xml` looks like this: ```xml ``` Setting `crossContext="true"` lets multiple web applications use the same class loader. This configuration includes commented instructions and tags for configuring a JAAS realm, disabling persistent sessions, and disabling sessions entirely. 3. Provide Catalina access to the JARs in `$CATALINA_BASE/lib/ext` by opening your `$CATALINA_BASE/conf/catalina.properties` file and appending this value to the `common.loader` property: ```properties ,"${catalina.home}/lib/ext/global","${catalina.home}/lib/ext/global/*.jar","${catalina.home}/lib/ext","${catalina.home}/lib/ext/*.jar" ``` 4. Make sure to use UTF-8 URI encoding consistently. If you have a @product@ Tomcat bundle, copy the `$CATALINA_BASE/conf/server.xml` file to your server. If not, open your `$CATALINA_BASE/conf/server.xml` file and add the attribute `URIEncoding="UTF-8"` to HTTP and AJP connectors that use `redirectPort=8443`. Here are examples: Old: ```xml ``` New: ```xml ``` Old: ```xml ``` New: ```xml ``` 5. Refrain from writing access logs (optional) by commenting out the access log `Valve` element in `$CATALINA_BASE/conf/server.xml`. It's commented out here: ```xml ``` 6. Optionally, set the following log levels in your `$CATALINA_HOME/conf/logging.properties` file: ```properties org.apache.catalina.startup.Catalina.level=INFO org.apache.catalina.startup.ClassLoaderFactory.level=SEVERE org.apache.catalina.startup.VersionLoggerListener.level=WARNING org.apache.level=WARNING ``` 7. In `$CATALINA_HOME/conf/web.xml`, set the JSP compiler to Java 8 and set @product@'s `TagHandlerPool` class to manage the JSP tag pool. Do this by adding the following elements above the `jsp` servlet element's `` element. ```xml compilerSourceVM 1.8 compilerTargetVM 1.8 tagpoolClassName com.liferay.support.tomcat.jasper.runtime.TagHandlerPool ``` 8. In `$CATALINA_HOME/conf/web.xml`, specify whether the application server should look for extra metadata, such as annotations in the application's JARs and classes. Setting `web-app` element's attribute `metadata-complete="true"` tells the application server there's no extra metadata. The application server starts up faster this way. The default is to check for extra metadata. 9. If you're on Unix, Linux, or Mac OS, make the shell scripts in your `$CATALINA_HOME/bin` and `$CATALINA_BASE/bin` folders executable by running this command in each folder: ```bash chmod a+x *.sh ``` **Checkpoint:** Your application server is configured to run @product@. 1. The file encoding, user time-zone, and preferred protocol stack are set in your `setenv.sh`. 2. The default memory available and Metaspace limit are set. 3. `$CATALINA_BASE/conf/Catalina/localhost/ROOT.xml` declares the web application context. 4. The `common.loader` property in `$CATALINA_BASE/conf/catalina.properties` grants Catalina access to the JARs in `$CATALINA_BASE/lib/ext`. 5. `$CATALINA_BASE/conf/server.xml` sets UTF-8 encoding. 6. `$CATALINA_BASE/conf/server.xml` doesn't declare any valve for writing host access logs. (optional) 7. `$CATALINA_HOME/conf/logging.properties` sets the desired log levels. 8. `$CATALINA_HOME/conf/web.xml` sets the tag handler pool and sets Java 8 as the JSP compiler. 9. `$CATALINA_HOME/conf/web.xml` specifies for the application server to refrain from looking for extra metadata. (optional) 10. The scripts in Tomcat's `bin` folders are executable. ### Database Configuration The easiest way to handle your database configuration is to let @product@ manage your data source. If you want to use the [built-in data source (recommended)](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#using-the-built-in-data-source), skip this section. If you want Tomcat to manage your data source, follow these steps: 1. Make sure your database server is installed and working. If it's installed on a different machine, make sure your @product@ machine can access it. 2. Open `$CATALINA_BASE/conf/Catalina/localhost/ROOT.xml` and add your data source as a `Resource` in your web application `Context`: ```xml ... ``` Make sure to replace the database URL, user name, and password with the appropriate values. For example JDBC connection values, please see [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates) 3. In a `portal-ext.properties` file in your Liferay Home, specify your data source: ```properties jdbc.default.jndi.name=jdbc/LiferayPool ``` You created a data source for Tomcat to manage and configured @product@ to use it. Mail session configuration is next. ### Mail Configuration As with database configuration, the easiest way to configure mail is to let @product@ handle your mail session. If you want to use @product@'s [built-in mail session](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail), skip this section. If you want to manage your mail session with Tomcat, follow these steps: 1. Open `$CATALINA_BASE/conf/Catalina/localhost/ROOT.xml` and add your mail session as a `Resource` in your web application `Context`. Make sure to replace the example mail session values with your own. ```xml ... ``` 2. In your `portal-ext.properties` file in Liferay Home, reference your mail session: ```properties mail.session.jndi.name=mail/MailSession ``` You've created a mail session for Tomcat to manage and configured @product@ to use it. ## Deploying @product@ Now you're ready to deploy @product@ using the @product@ WAR file. 1. If you are manually installing @product@ on a clean Tomcat server, delete the contents of the `$CATALINA_BASE/webapps/ROOT` folder. This removes the default Tomcat home page. 2. Extract the @product@ `.war` file contents to `$CATALINA_BASE/webapps/ROOT`. It's time to launch @product@ on Tomcat! 3. Start Tomcat by navigating to `$CATALINA_HOME/bin` and executing `./startup.sh`. Alternatively, execute `./catalina.sh run` to tail @product@'s log file. The log audits startup activities and is useful for debugging deployment. Congratulations on successfully installing and deploying @product@ on Tomcat! ================================================ FILE: en/deployment/articles/01-deploying-liferay/06-installing-liferay-portal-on-wildfly.markdown ================================================ --- header-id: installing-product-on-wildfly --- # Installing @product@ on Wildfly [TOC levels=1-4] Installing @product@ on Wildfly 11 takes three steps: - [Installing dependencies to your application server](#installing-dependencies) - [Configuring your application server for @product@](#configuring-wildfly) - [Deploying the @product@ WAR file to your application server](#deploying-product) | **Important:** Before installing @product@, familiarize yourself with | [preparing for install](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install). Now, [download the @product@ WAR and Dependency JARs](/docs/7-2/deploy/-/knowledge_base/d/obtaining-product#downloading-the-liferay-war-and-dependency-jars): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file Note that [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) is the folder containing your Wildfly server folder. After installing and deploying @product@, the Liferay Home folder contains the Wildfly server folder as well as `data`, `deploy`, `logs`, and `osgi` folders. `$WILDFLY_HOME` refers to your Wildfly server folder. It is usually named `wildfly-[version]`. ## Installing Dependencies @product@ depends on a driver for your database and the JARs in the Dependencies ZIP and OSGi Dependencies ZIP files you downloaded. Here's how to install them: 1. Create the folder `$WILDFLY_HOME/modules/com/liferay/portal/main` if it doesn't exist and extract the Dependencies ZIP JARs to it. 2. Download your database driver `.jar` file and copy it into the same folder. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. 3. Create the file `module.xml` in the `$WILDFLY_HOME/modules/com/liferay/portal/main` folder and insert this configuration: ```xml ``` Replace `[place your database vendor's jar here]` with the driver JAR for your database. 4. Create an `osgi` folder in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder. Extract the OSGi Dependencies ZIP file that you downloaded into the `[Liferay Home]/osgi` folder. The `osgi` folder provides the necessary modules for @product@'s OSGi runtime. ## Running @product@ on Wildfly in Standalone Mode vs. Domain Mode Wildfly can be launched in either *standalone* mode or *domain* mode. Domain mode allows multiple application server instances to be managed from a single control point. A collection of such application servers is known as a *domain*. For more information on standalone mode vs. domain mode, please refer to the section on this topic in the [Wildfly Admin Guide](https://docs.jboss.org/author/display/WFLY/Admin+Guide#AdminGuide-Operatingmodes). @product@ fully supports Wildfly in standalone mode but not in domain mode. You can run @product@ on Wildfly in domain mode, but this method is not fully supported. In particular, @product@'s hot-deploy does not work with a managed deployment, since Wildfly manages the content of a managed deployment by copying files (exploded or non-exploded). This prevents JSP hooks and Ext plugins from working as intended. For example, JSP hooks don't work on Wildfly running in managed domain mode, since @product@'s JSP override mechanism relies on the application server. Since JSP hooks and Ext plugins are deprecated, however, you may not be using them. The command line interface is recommended for domain mode deployments. | **Note:** This does not prevent @product@ from running in a clustered | environment on multiple Wildfly servers. You can set up a cluster of @product@ | instances running on Wildfly servers running in standalone mode. Please refer | to the [@product@ clustering articles](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) | for more information. ## Configuring Wildfly Configuring Wildfly to run @product@ includes these things: - Setting environment variables - Setting properties and descriptors - Removing unnecessary configurations Optionally, you can configure Wildfly to manage @product@'s data source and mail session. Start with configuring Wildfly to run @product@. Make the following modifications to `$WILDFLY_HOME/standalone/configuration/standalone.xml`: 1. Locate the closing `` tag. Directly beneath that tag, insert the following system properties: ```xml ``` 2. Add the following `` tag within the `` tag, directly below the `` tag: ```xml ``` 3. Add a timeout for the deployment scanner by setting `deployment-timeout="600"` as seen in the excerpt below. ```xml ``` 4. Add the following JAAS security domain to the security subsystem `` defined in element ``. ```xml ``` 5. Remove the welcome content code snippets: ```xml ``` and ```xml ``` 6. Find the `` tag and set the `development`, `source-vm`, and `target-vm` attributes in the tag. Once finished, the tag should look like this: ```xml ``` **Checkpoint:** Before continuing, verify the following properties have been set in the `standalone.xml` file: 1. The new `` is added. 2. The new `` is added. 3. The `` is set to `600`. 4. The new `` is created. 5. Welcome content is removed. 6. The `` tag contains its new attributes. Now you must configure your JVM and startup scripts. In the `$WILDFLY_HOME/bin/` folder, modify your standalone domain's configuration script file `standalone.conf` (`standalone.conf.bat` on Windows): - Set the file encoding to `UTF-8` - Set the user time zone to `GMT` - Set the preferred protocol stack - Increase the default amount of memory available. | **Important:** For @product@ to work properly, the application server JVM must | use the `GMT` time zone and `UTF-8` file encoding. Make the following edits as applicable for your operating system: **Windows:** 1. Comment out the initial `JAVA_OPTS` assignment like this: ```bash rem set "JAVA_OPTS=-Xms64M -Xmx512M -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m" ``` 2. Add the following `JAVA_OPTS` assignment one line above the `:JAVA_OPTS_SET` line found at end of the file: ```bash set "JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=200m" ``` **Unix:** 1. Below the `if [ "x$JAVA_OPTS" = "x" ];` statement, replace this `JAVA_OPTS` statement: ```bash JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" ``` with this: ```bash JAVA_OPTS="-Djava.net.preferIPv4Stack=true" ``` 2. Add the following statement to the bottom of the file: ```bash JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=200m" ``` This sets the file encoding to UTF-8, prefers an IPv4 stack over IPv6, sets the time zone to GMT, gives the JVM 2GB of RAM, and limits Metaspace to 512MB. On JDK 11, it's recommended to add this JVM argument to display four-digit years. ```bash -Djava.locale.providers=JRE,COMPAT,CLDR ``` After installation, tune your system (including these JVM options) for performance. | **Important:** For @product@ to work properly, the application server JVM must | use the `GMT` time zone and `UTF-8` file encoding. | **Note:** If you plan on using the IBM JDK with your Wildfly server, you must | complete some additional steps. First, navigate to the | `$WILDFLY_HOME/modules/com/liferay/portal/main/module.xml` file and insert the | following dependency within the `` element: | | | | Then navigate to the | `$WILDFLY_HOME/modules/system/layers/base/sun/jdk/main/module.xml` file and | insert the following path names inside the `...` element: | | | | | | | The added paths resolve issues with deployment exceptions and image uploading | problems. **Checkpoint:** You've configured the application server's JVM settings. 1. The file encoding, user time-zone, preferred protocol stack have been set in the `JAVA_OPTS` in the `standalone.conf.bat` file. 2. The default amount of memory available has been increased. The prescribed script modifications are now complete for your @product@ installation on Wildfly. Next you'll configure your database. ### Database Configuration The easiest way to handle database configuration is to let @product@ manage your data source. The [Basic Configuration](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#using-the-built-in-data-source) page lets you configure @product@'s built-in data source. If you want to use the built-in data source, skip this section. If using WildFly to manage the data source, follow these steps: 1. Add the data source inside the `$WILDFLY_HOME/standalone/configuration/standalone.xml` file's `` element: ```xml [place the URL to your database here] [place your driver name here] [place your user name here] [place your password here] ``` Make sure to replace the database URL, user name, and password with the appropriate values. For example JDBC connection values, please see [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates) | **Note:** If the data source `jndi-name` must be changed, edit the `datasource` element in the `` tag. 1. Add the driver to the `standalone.xml` file's `` element also found within the `` element. ```xml [JDBC driver class] ``` A final data sources subsystem that uses MySQL should look like this: ```xml jdbc:mysql://localhost/lportal mysql root root com.mysql.cj.jdbc.Driver ``` 3. In a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file in your Liferay Home, specify your data source: ```properties jdbc.default.jndi.name=java:jboss/datasources/ExampleDS ``` Now that you've configured your data source, the mail session is next. ### Mail Configuration As with database configuration, the easiest way to configure mail is to let @product@ handle your mail session. If you want to use @product@'s built-in mail session, skip this section and [configure the mail session](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail) in the Control Panel. If you want to manage your mail session with Wildfly, follow these steps: 1. Specify your mail subsystem in the `$WILDFLY_HOME/standalone/configuration/standalone.xml` file like this: ```xml ... ... ``` 2. In your [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file in Liferay Home, reference your mail session: ```properties mail.session.jndi.name=java:jboss/mail/MailSession ``` Next, you'll deploy @product@ to your Wildfly app server. ## Deploying @product@ Now you're ready to deploy @product@ using the @product@ WAR file. 1. If the folder `$WILDFLY_HOME/standalone/deployments/ROOT.war` already exists in your Wildfly installation, delete all of its subfolders and files. Otherwise, create a new folder called `$WILDFLY_HOME/standalone/deployments/ROOT.war`. 2. Unzip the @product@ `.war` file into the `ROOT.war` folder. 3. To trigger deployment of `ROOT.war`, create an empty file named `ROOT.war.dodeploy` in your `$WILDFLY_HOME/standalone/deployments/` folder. On startup, Wildfly detects this file and deploys it as a web application. 4. Start the Wildfly application server by navigating to `$WILDFLY_HOME/bin` and running `standalone.bat` or `standalone.sh`. Congratulations; you've deployed @product@ on Wildfly! | **Note:** After deploying @product@, you may see excessive warnings and log | messages, such as the ones below, involving `PhaseOptimizer`. These are | benign and can be ignored. Make sure to adjust your app server's logging level | or log filters to avoid excessive benign log messages. | | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass gatherExternProperties | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass checkControlFlow | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] | current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] ================================================ FILE: en/deployment/articles/01-deploying-liferay/12-setting-up-marketplace-and-portal-security.markdown ================================================ --- header-id: setting-up-marketplace-and-portal-security --- # Setting Up Marketplace [TOC levels=1-4] [Liferay Marketplace](https://www.liferay.com/marketplace) is more than just a store for Liferay applications. Under the hood, it provides both the store and @product@'s application deployment features. For this reason, you must ensure that Marketplace can run and configure itself. Here are some scenarios to work around to ensure Marketplace works successfully: - Server is Firewalled without Access to the Internet - Limited Database Access The firewall scenario is discussed first. ## Server is Firewalled without Access to the Internet Your server might be behind a firewall that prevents access to the Internet. Or your security policy might not allow direct download and installation from the Internet. In these cases, you have two options: 1. From an Internet-enabled computer, download the [Marketplace plugin](https://www.liferay.com/marketplace/download). Then deploy the plugin (`.lpkg` file) by copying it into the `deploy` folder in [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). 2. Alternately, once you have the downloaded `.lpkg` file, deploy it using the [App Manager](/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps). Next you'll learn how to work around database access restrictions. ## Limited Database Access Some production environments do not have the necessary database permissions for @product@, apps, modules, and plugins to maintain their tables. In these cases: 1. Grant the @product@ database user temporary full rights to the database. 2. Install @product@ and start it so that it populates its database. 3. Once the database is created, remove the permissions for creating tables and dropping tables from the @product@ database user. See the [database preparation instructions](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#limiting-database-access) for more information. Note that many sophisticated @product@ apps---not just the Marketplace app---require new tables when deployed. If your environment restricts database access, you may need to repeat the above steps whenever you deploy a new app. You've prepared @product@ for installing Marketplace and additional apps. ================================================ FILE: en/deployment/articles/01-deploying-liferay/14-document-repository-configuration/00-document-repository-configuration-intro.markdown ================================================ --- header-id: document-repository-configuration --- # Document Repository Configuration [TOC levels=1-4] You can configure file storage in several ways. Each option is a *store* which can be configured through the [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file by setting the [`dl.store.impl=` property](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Document%20Library%20Service). The default store is called Simple File Store. It stores [documents and media](/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media) files on a file system (local or mounted). The store's default root folder is `[Liferay Home]/data/document_library`. You can specify a different root directory from within [System Settings](/docs/7-2/user/-/knowledge_base/u/system-settings). 1. Access System Settings by opening the *Menu* (![Menu](../../../images/icon-menu.png)) and navigating to *Control Panel → Configuration → System Settings*. 2. In the *Platform* section, click *File Storage*. The File Storage page appears. 3. Click *Simple File System Store*. 4. For the store's *Root directory* value, specify its absolute path or its path relative to [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). 5. Click the *Save* button. The document library store switches immediately to the new folder. ![Figure 1: The File Storage page in System Settings lets you configure document repository storage.](../../../images/file-storage.png) You can also use an entirely different method for storing documents and media files: **Simple File System Store**: uses the file system (local or a mounted share) to store files. **Advanced File System Store**: in addition to using the file system (local or a mounted share) to store files, Advanced File System Store nests the files into folders by version, for faster performance and to store more files. **S3 Store (Amazon Simple Storage)**: uses Amazon's cloud-based storage solution. **DBStore (Database Storage)**: stores the files in the database. DBStore's file (stored as a blob) size is 1 gigabyte. To store files larger than 1 gigabyte, use Simple File System Store or Advanced File System Store. These articles explain details for each one. ================================================ FILE: en/deployment/articles/01-deploying-liferay/14-document-repository-configuration/01-using-simple-file-system-store.markdown ================================================ --- header-id: using-the-simple-file-system-store --- # Using the Simple File System Store [TOC levels=1-4] The simple file storage implementation is the default store. It uses a local folder to store files. You can use the file system for your [clustered](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) configuration, but the folder you're pointing to must be shared by all nodes and handle concurrent requests and file locking. For this reason, you need to use a Storage Area Network or a clustered file system. The file system store was the first store used in @product@ and is heavily bound to the @product@ database. By default, documents are stored in a `document_library` subfolder of the `data` folder. Of course, you can change this path to anything you want in [System Settings](/docs/7-2/user/-/knowledge_base/u/system-settings). The Simple File System store uses this folder path format for storing documents: ```bash /companyId/folderId/numericFileEntryName/versionNumber ``` The first folder name is the site's company ID. The second folder name is the Documents and Media folder's ID where the document resides. The third folder name is the document's numeric file entry name. Finally, the fourth name is a version number used for storing multiple versions of the document. | **Note:** A document's numeric file entry name is distinct from the document | ID; don't confuse the two! Each has an independent counter. The numeric file | entry name is used in the folder path for storing the document but the | document ID is not. The numeric file entry name is in the `name` column of the | `DLFileEntry` table in @product@'s database; the document ID is in the | `fileEntryId` column of the same table. | **Warning:** If a database transaction rollback occurs in the Document | Library, file system changes that have occurred since the start of the | transaction aren't reversed. Inconsistencies between Document Library files | and those in the file system store can occur and may require manual | synchronization. The Simple File System Store binds documents very closely to @product@, and may not be exactly what you want. If you've been using the default settings for a while and must migrate your documents, there's a migration utility in the Control Panel in *Server Administration* → *Data Migration*. The utility facilitates moving documents from one store implementation to another. ================================================ FILE: en/deployment/articles/01-deploying-liferay/14-document-repository-configuration/02-using-advanced-file-system-store.markdown ================================================ --- header-id: using-the-advanced-file-system-store --- # Using the Advanced File System Store [TOC levels=1-4] The advanced file system store is similar to the simple file system store (the default store). Like that store, it saves files to the local file system---which, of course, could be a remote file system mount. It uses a slightly different folder structure to store files, which is pictured below. ![Figure 1: The advanced file system store creates a more nested folder structure than the file system store.](../../../images/enterprise-adv-file-system-store.png) So what makes the advanced file system store *advanced*? Several operating systems have limitations on the number of files that can be stored in a particular folder. The advanced file system store overcomes this limitation by programmatically creating a structure that can expand to millions of files, by alphabetically nesting the files in folders. This not only allows for more files to be stored, but also improves performance as there are fewer files stored per folder. The same rules apply to the advanced file system store as apply to the simple file system store. To [cluster](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) this, you must point the store to a network mounted file system that all the nodes can access, and that networked file system must support concurrent requests and file locking. Otherwise, you may experience data corruption issues if two users attempt to write to the same file at the same time from two different nodes. To use the advanced file system store, follow these steps: 1. Configure `portal-ext.properties` with this property: ```properties dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore ``` 2. Restart @product@. 3. In the Control Panel, navigate to *Configuration* → *System Settings* → *File Storage*. 4. In the *Advanced File System Store* screen, configure the store your way. 5. Click *Save*. @product@ is using the advanced file system store. | **Warning:** If a database transaction rollback occurs in the Document | Library, file system changes that have occurred since the start of the | transaction aren't reversed. Inconsistencies between Document Library files | and those in the file system store can occur and may require manual | synchronization. You may decide the advanced file system store for whatever reason doesn't serve your needs. If this is the case, you can of course mount other file systems into the documents and media library. In addition to this, you can also redefine the @product@ store to use one of the other supported protocols. S3 store is next. ================================================ FILE: en/deployment/articles/01-deploying-liferay/14-document-repository-configuration/03-using-s3-store.markdown ================================================ --- header-id: using-amazon-simple-storage-service --- # Using Amazon Simple Storage Service [TOC levels=1-4] Amazon's simple storage service (S3) is a cloud-based storage solution that you can use with Documents and Media. All you need is an account, and you can store your documents to the cloud from all nodes, seamlessly. When you sign up for the service, Amazon assigns you unique keys that link you to your account. In Amazon's interface, you can create "buckets" of data optimized by region. Here are the steps for configuring @product@ to use your S3 account for file storage: 1. Amazon S3 requires a `SAXParser` from the application server to operate. If you are using an app server like Apache Tomcat that have one, you must include this property in a [`system-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/system-properties) file: ```properties org.xml.sax.driver=com.sun.org.apache.xerces.internal.parsers.SAXParser ``` 2. Place your `system-ext.properties` file in a folder that resides in your @product@ installation's class path (e.g., `/WEB-INF/classes/`). 3. Set the following property in a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder: ```properties dl.store.impl=com.liferay.portal.store.s3.S3Store ``` 4. Restart @product@. 5. In the Control Panel, navigate to *Configuration* → *System Settings* → *File Storage*. 6. In the *S3 Store Configuration* screen, configure the store your way. 7. Click *Save*. Your @product@ instance is using the Amazon S3 store. | **Warning:** If a database transaction rollback occurs in a Document Library | that uses a file system based store, file system changes that have occurred | since the start of the transaction aren't reversed. Inconsistencies between | Document Library files and those in the file system store can occur and may | require manual synchronization. All stores except DBStore are vulnerable to | this limitation. | **Note:** No action is required to support AWS Signature Version 4 request | authorization. Consult the Amazon Simple Storage documentation for additional details on using Amazon's service. ================================================ FILE: en/deployment/articles/01-deploying-liferay/14-document-repository-configuration/04-using-the-dbstore.markdown ================================================ --- header-id: using-the-dbstore --- # Using the DBStore [TOC levels=1-4] You can store Documents and Media files in your @product@ database using DBStore. DBStore's maximum file (stored as a blob) size is 1 gigabyte. To store files larger than that, use Simple File System Store or Advanced File System Store. Here are the DBStore configuration steps: 1. Set the following property in a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder: ```properties dl.store.impl=com.liferay.portal.store.db.DBStore ``` 2. Restart @product@. Documents and Media now uses @product@'s database via DBStore. ================================================ FILE: en/deployment/articles/02-configuring-liferay/01-configuring-liferay-portal-intro.markdown ================================================ --- header-id: configuring-product --- # Configuring @product@ [TOC levels=1-4] Once you have @product@ installed, it's time to configure it to the specifics of your environment. This means doing things like setting the time zone and language, configuring mail, configuring a cluster, configuring a Content Delivery Network, tuning, and more. These topics and more are discussed here. ================================================ FILE: en/deployment/articles/02-configuring-liferay/02-configuring-mail.markdown ================================================ --- header-id: configuring-mail --- # Configuring Mail [TOC levels=1-4] @product@ uses a mail server and SMTP to email notifications. @product@'s built-in mail session is the easiest way to configure mail and it's recommended. You can configure the built-in mail session before or after deploying @product@. Alternatively, you can configure @product@ to use a mail session on the application server. Creating a mail session in @product@ or on the application server requires this information: - Incoming POP Server and port - POP User Name - POP Password - Outgoing SMTP Server and port - SMTP User Name - SMTP Password - All JavaMail properties you want to use Built-in mail session setup is recommended and easiest. ## Configuring @product@'s Built-in Mail Session The built-in mail session setup can be done using either of these methods: - Control Panel - Portal properties ### Built-in Mail Session in the Control Panel After deploying @product@, you can configure the mail session from the Control Panel. 1. Sign in as the administrative user (the user you specified on the [Basic Configuration page](/docs/7-2/deploy/-/knowledge_base/d/installing-product#using-the-setup-wizard)). 2. Navigate to *Control Panel → Configuration → Server Administration → Mail*. 3. Fill out the form. You're asked for the following information: **Incoming POP Server:** The hostname for a server running the Post Office Protocol. @product@ checks this mailbox for incoming messages, such as message board replies. **Incoming Port:** The port on which the POP server is listening. **Use a Secure Network Connection:** Use an encrypted connection when connecting to the POP server. **User Name:** The user ID @product@ should use to log into the POP server. **Password:** The password @product@ should use to log into the POP server. **Outgoing SMTP Server:** The hostname for a server running the Simple Mail Transfer Protocol. @product@ uses this server to send emails, such as password change emails and other notifications. **Outgoing Port:** The port on which the SMTP server is listening. **Use a Secure Network Connection:** Use an encrypted connection when connecting to the SMTP server. **User Name:** The user ID @product@ should use to log into the SMTP server. **Password:** The password @product@ should use to log into the SMTP server. **Manually specify additional JavaMail properties to override the above configuration:** If there are additional properties you need to specify, supply them here. 4. Click *Save*. @product@ connects to the mail session immediately. ### Built-in Mail Session Portal Properties If you prefer specifying your mail session offline or before deploying @product@, use portal properties. 1. Create a [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties), if you haven't already created one. 2. Copy these default property settings into your `portal-ext.properties` file: ```properties mail.session.mail=false mail.session.mail.pop3.host=localhost mail.session.mail.pop3.password= mail.session.mail.pop3.port=110 mail.session.mail.pop3.user= mail.session.mail.smtp.auth=false mail.session.mail.smtp.host=localhost mail.session.mail.smtp.password= mail.session.mail.smtp.port=25 mail.session.mail.smtp.user= mail.session.mail.store.protocol=pop3 mail.session.mail.transport.protocol=smtp ``` 3. Replace the default mail session values with your own. 4. Put the `portal-ext.properties` file into your [LIFERAY_HOME](/docs/7-2/deploy/-/knowledge_base/d/liferay-home), once you've established it based on your installation. @product@ connects to the mail session on the next startup. ## Configuring a Mail Session on the Application Server You can manage a mail session for @product@ on your application server. Here's how: 1. Create a mail session on your application server, following your application server documentation. 2. Point @product@ to that mail session using the Control Panel or portal properties. Here are instructions for both: - Configure the JNDI name in the *Mail* page at *Control Panel → Configuration → Server Administration → Mail*. - Set a `mail.session.jndi.name` portal property in a `[LIFERAY_HOME]/portal-ext.properties` file. Here's an example property: ```properties mail.session.jndi.name=mail/MailSession ``` Lastly, configure your instance's email senders. ## Configuring default email senders Email senders are the default name and email address @product@ uses to send administrative emails and announcement emails. Default email senders are configured in the [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties). - Admin email configuration: ```properties admin.email.from.name=Joe Bloggs admin.email.from.address=test@domain.invalid ``` - Announcements email configuration: ```properties announcements.email.to.name= announcements.email.to.address=noreply@domain.invalid ``` 1. Replace the names and email addresses above with your values. | **Note:** Following emails are blacklisted by default and cannot be used | in any @product@ installation: | | - `noreply@liferay.com` | - `test@liferay.com` | - `noreply@domain.invalid` | - `test@domain.invalid` | | If you use them, @product@ logs a `WARN` trace: | | `Email xxx will be ignored because it is included in mail.send.blacklist` 2. Restart your server. Congratulations on configuring mail for @product@. ================================================ FILE: en/deployment/articles/02-configuring-liferay/03-locales-and-encoding.markdown ================================================ --- header-id: locales-and-encoding-configuration --- # Locales and Encoding Configuration [TOC levels=1-4] You can display content based on language, time zone, "right to left" (that is, languages such as Hebrew, Arabic, and Persian), and you can localize user names and titles. Administrators can localize specific core UI messages so that the messages display in certain languages. ## Time Zones You can set time zones in the Control Panel and theoretically in the JVM (but this must be set to GMT: see below). Time zone configuration and default language customization are done in the Control Panel, at the Instance level. 1. Navigate to the *Control Panel* → *Configuration*. 2. Click *Instance Settings*. 3. Click on the *Miscellaneous* tab. ![Figure 1: You can change the default and available languages and the time zone in Instance Settings.](../../images/instance-locales.png) The central left and right arrows let you add or remove available languages and locales. You can also set these as properties in your `portal-ext.properties` file in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder. The `portal.properties` reference document's [Company](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Company) section defines the default locale. The [Languages and Time Zones](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Languages%20and%20Time%20Zones) section defines the available and current locales. ```properties company.default.locale=en_GB ``` | **Note:** The `company.default.locale` portal property is only intended for | use on initial startup. To change the language settings on an existing | instance, open the Control Panel and navigate to *Configuration* → | *Instance Settings* and select the Localization category under the PLATFORM | heading. Under the Language entry you can change the default language, as well | as define the current locales. As an example, the above property changes the locale to English, Great Britain. ## Set the JVM Time Zone to GMT If you set the time zone in the JVM, it causes issues such as Calendar Events and Web Content articles displaying the wrong dates. This happens because the system assumes each date stored in the database is stored in GMT time. When the system needs to display one stored date to the end users, the display date is calculated by the application server's current date. This date is affected by the configured JVM level time zone and the stored GMT format date. To make sure the display date is calculated correctly, the time zone must be configured to GMT at the JVM level. Otherwise, an incorrect time zone offset at the JVM level causes the display date to be wrongly calculated and displayed. ## Friendly URLs and Locales In addition to configuring instance settings, you can also define unique URLs for specific languages using the `I18nServlet` by editing Portal's `web.xml` file: ```xml I18n Servlet /ar/* . . . I18n Servlet /de/* ``` The defaults should be sufficient for nearly all circumstances. Because `web.xml` changes require stopping and possibly redeploying @product@ (depending on your app server), test the defaults and make sure you really need to modify these settings. If you're clustered, you must make these changes on all nodes. ## Modifying Language Keys Developers can add or modify certain core UI messages (e.g. *Your request completed successfully.*) by [modifying the language keys](/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys) that ship by default. ### Right to Left For languages that are displayed right to left, use the following language properties settings: ```properties lang.dir=rtl lang.line.begin=right lang.line.end=left ``` To display right to left by default, [override these properties globally](/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys). ### Localizing User Names Users can change the prefix and suffix values for a locale. For example, for Spanish, the `language_es.properties` file contains these values: ```properties 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 ``` For more information, see [Using Liferay Language Settings](/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings). ## Related Topics [Using Liferay Language Settings](/docs/7-2/frameworks/-/knowledge_base/f/using-liferays-localization-settings) [Overriding Global Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys) [Overriding a Module's Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys) ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/01-intro.markdown ================================================ --- header-id: liferay-clustering --- # @product@ Clustering [TOC levels=1-4] @product@ can serve everything from the smallest to the largest web sites. Out of the box, it's configured optimally for a single server environment. If one server isn't sufficient to serve your site's high traffic needs, @product@ scales to the size you need. ![Figure 1: @product@ is designed to scale to as large an installation as you need.](../../../images/clustering-enterprise-configuration.png) @product@ works well in clusters of multiple machines (horizontal cluster) or in clusters of multiple VMs on a single machine (vertical cluster), or any mixture of the two. Once you have @product@ installed on more than one application server node, there are several optimizations that must be made. At a minimum, @product@ should be configured in the following way for a clustered environment: 1. [All nodes should point to the same database or database cluster.](/docs/7-2/deploy/-/knowledge_base/d/point-all-nodes-to-the-same-database) 2. [Documents and Media repositories must have the same configuration and be accessible to all nodes of the cluster.](/docs/7-2/deploy/-/knowledge_base/d/configure-documents-and-media-the-same-for-all-nodes) 3. [Search should be on a separate search server that is optionally clustered.](/docs/7-2/deploy/-/knowledge_base/d/clustering-search) 4. [Cluster Link must be enabled so the cache replicates across all nodes of the cluster.](/docs/7-2/deploy/-/knowledge_base/d/enabling-cluster-link) 5. [Applications must be auto-deployed to each node individually.](/docs/7-2/deploy/-/knowledge_base/d/auto-deploy-to-all-nodes) Many of these configuration changes can be made by adding or modifying properties in your `portal-ext.properties` file. Remember that this file overrides the [defaults](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) in the `portal.properties` file. It's a best practice to copy the relevant section you want to modify from `portal.properties` into your `portal-ext.properties` file, and then modify the values there. | **Note:** This documentation describes a @product@-specific cluster | configuration without getting into specific implementations of third party | software, such as Java EE application servers, HTTP servers, and load | balancers. Please consult your documentation for those components of your | cluster to configure those components. Before creating a @product@ cluster, | make sure your OS is not defining the hostname of your box to the local | network at 127.0.0.1. Each step defined above is covered below to give you a step by step process for creating your cluster. Start with making all Nodes point to the same database. ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/02-point-all-nodes-to-the-same-liferay-portal-database.markdown ================================================ --- header-id: point-all-nodes-to-the-same-database --- # Point all Nodes to the Same @product@ Database [TOC levels=1-4] Each node should have a data source that points to one @product@ database (or a database cluster) that all the nodes share. This means, of course, @product@ cannot (and should not) use the embedded HSQL database that is shipped with the bundles (but you already knew that, right?). And, of course, the database server should be on a separate system from the @product@ server. ## Read-Writer Database Configuration To improve database performance, you can use a read-writer database configuration. Instead of using the same data source for read and read-write operations, this strategy uses a separate data source for each operation type. DXP's Aspect Oriented Programming (AOP) transaction infrastructure directs read transactions to the read data source and read-write transactions to the write data source. Connections to separate read and read-write [data sources](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#JDBC) are configured using JDBC or JNDI [Portal Properties](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) (e.g., in a [`portal-ext.properties` file](/docs/7-2/deploy/-/knowledge_base/d/portal-properties)), as explained in the following sections. The data sources should use separate instances of the DXP database, where the read-write database instance is replicated to the read database instance. ### JDBC Edit your `portal-ext.properties` file following these steps to connect directly to your separate read and write data sources using [JDBC](/docs/7-2/deploy/-/knowledge_base/d/database-templates): 1. Set the default connection pool provider. For provider information, see the [JDBC properties reference](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#JDBC). The default setting specifies [HikariCP](https://github.com/brettwooldridge/HikariCP) as the pool provider: ```properties jdbc.default.liferay.pool.provider=hikaricp ``` 1. Configure JDBC connections to your separate read and write data sources. Here's an example: ```properties jdbc.read.driverClassName=[place your driver name here] jdbc.read.url=[place the URL to your "read" database here] jdbc.read.username=[place your user name here] jdbc.read.password=[place your password here] jdbc.write.driverClassName=[place your driver name here] jdbc.write.url=[place the URL to your "read-write" database here] jdbc.write.username=[place your user name here] jdbc.write.password=[place your password here] ``` For example JDBC connection values, please see [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). 1. Configure DXP to use the write data source (the data source whose prefix is `jdbc.write.`) to create the [Counter](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Counter) data source. A separate data source is always dedicated to the counter. ```properties counter.jdbc.prefix=jdbc.write. ``` 1. Optionally validate the data connections to make sure bad connections are handled gracefully. Some connection pools used with JDBC4 (check your driver's JDBC version) validate connections automatically. Other connection pools may require additional, vendor-specific connection validation properties---specify them in a Portal Properties file. Refer to your connection pool provider documentation for connection validation details. 1. Enable the read-writer database configuration by copying the default [`spring.configs` and `spring.infrastructure.configs` Portal Properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Spring) to your `portal-ext.properties` file and adding the following Spring configuration file paths to them. Add to `spring.configs`: ``` META-INF/dynamic-data-source-spring.xml ``` Add to `spring.infrastructure.configs`: ``` META-INF/dynamic-data-source-infrastructure-spring.xml ``` For more information, see the [Spring configuration Portal Properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Spring). ### JNDI Edit your `portal-ext.properties` file following these steps to connect to your read and write data sources on your app server using JNDI: 1. Set your read and write JNDI data source user names and passwords. ```properties jdbc.read.jndi.name=[place your "read" data source JNDI name here] jdbc.read.username=*[place your user name here] jdbc.read.password=[place your password here] jdbc.write.jndi.name=[place your "read-write" data source JNDI name here] jdbc.write.username=[place your user name here] jdbc.write.password=[place your password here] ``` 1. Configure DXP to use the write data source (the data source whose prefix is `jdbc.write.`) to create the [Counter](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Counter) data source. A separate data source is always dedicated to the counter. ```properties counter.jdbc.prefix=jdbc.write. ``` 1. Optionally validate the data connections to make sure bad connections are handled gracefully. Some connection pools used with JDBC4 (check your driver's JDBC version) validate connections automatically. Other connection pools may require additional, vendor-specific connection validation properties---specify them in a Portal Properties file. Refer to your connection pool provider documentation for connection validation details. 1. Enable the read-writer database configuration by copying the default [`spring.configs` and `spring.infrastructure.configs` Portal Properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Spring) to your `portal-ext.properties` file and add the following Spring configuration file paths to them. Add to `spring.configs`: ``` META-INF/dynamic-data-source-spring.xml ``` Add to `spring.infrastructure.configs`: ``` META-INF/dynamic-data-source-infrastructure-spring.xml ``` For more information, see the [Spring configuration Portal Properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Spring). DXP uses a read data source, a write data source, and a counter data source the next time it starts. ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/03-configure-documents-and-media-the-same-for-all-nodes.markdown ================================================ --- header-id: configure-documents-and-media-the-same-for-all-nodes --- # Configure Documents and Media the Same for all Nodes [TOC levels=1-4] In a cluster, Documents and Media must use the same [document repository configuration](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration) on all nodes. Note if you are using the `File System` or `Advanced File System` stores, the file system must be accessible from all nodes (i.e., a network share), support concurrent requests, and file locking. **Checkpoint**: Verify sharing works by executing these steps: 1. On Node 1 upload a document to the Documents and Media. 2. On Node 2 download the document. The download should be successful. 3. Repeat the test with reversed roles. ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/04-clustering-search.markdown ================================================ --- header-id: clustering-search --- # Clustering Search [TOC levels=1-4] Search should always run on a separate environment from your @product@ server. @product@ supports [Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch), which can also be clustered. For more information on how to cluster Elasticsearch, see [Elasticsearch's distributed cluster setup](https://www.elastic.co/guide/en/elasticsearch/guide/current/distributed-cluster.html). Once @product@ servers have been properly configured as a cluster and the same for Elasticsearch, change @product@ from *embedded* mode to *remote* mode. On the first connection, the two sets of clustered servers communicate with each other the list of all IP addresses; in case of a node going down, the proper failover protocols enable. Queries and indexes can continue to be sent for all nodes. ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/05-enabling-cluster-link.markdown ================================================ --- header-id: enabling-cluster-link --- # Enabling Cluster Link [TOC levels=1-4] Enabling Cluster Link automatically activates distributed caching. The cache is distributed across multiple @product@ nodes running concurrently. Cluster Link does [Ehcache](http://www.ehcache.org) replication. The Ehcache global settings are in the [`portal.properties` file](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Ehcache). By default Liferay does not copy cached entities between nodes. If an entity is deleted or changed, for example, Cluster Link sends a *remove* message to the other nodes to invalidate this entity in their local caches. Requesting that entity on another node results in a cache *miss*; the entity is then retrieved from the database and put into the local cache. Entities added to one node's local cache are not copied to local caches of the other nodes. An attempt to retrieve a new entity on a node which doesn't have that entity cached results in a cache *miss*. The miss triggers the node to retrieve the entity from the database and store it in its local cache. ![Figure 1: @product@'s cache algorithm is extremely efficient. ](../../../images/clustering-cache-efficient-algorithm.png) Here are the Cluster Link topics: - [Enabling Cluster Link](#enabling-cluster-link) - [Multicast Over UDP](#multicast-over-udp) - [Unicast Over TCP](#unicast-over-tcp) - [Using Different Control and Transport Channel Ports](#using-different-control-and-transport-channel-ports) - [Modifying the Cache Configuration with a Module](#modifying-the-cache-configuration-with-a-module) - [Conclusion](#conclusion) ## Enabling Cluster Link To enable Cluster Link, add this [portal property](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) to a `portal-ext.properties` file: ```properties cluster.link.enabled=true ``` The [Cluster Link portal properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Cluster%20Link) provide a default configuration that you can override to fit your needs. Many of the defaults use `localhost`, instead of a real address. In some configurations, however, `localhost` is bound to the internal loopback network (`127.0.0.1` or `::1`), rather than the host's real address. If for some reason you need this configuration, you can make DXP auto detect the real address with this property: ```properties cluster.link.autodetect.address=www.google.com:80 ``` Set it to connect to some other host that's contactable by your server. By default, it points to Google, but this may not work if your server is behind a firewall. If you use each host's real address, you don't need to set the auto-detect address. Cluster Link depends on [JGroups](http://www.jgroups.org) and provides an API for nodes to communicate. It can - Send messages to all nodes in a cluster - Send messages to a specific node - Invoke methods and retrieve values from all, some, or specific nodes - Detect membership and notify when nodes join or leave Cluster Link contains an enhanced algorithm that provides one-to-many type communication between the nodes. This is implemented by default with JGroups's UDP multicast, but unicast and TCP are also available. ## Multicast Over UDP When you enable Cluster Link, @product@'s default clustering configuration is enabled. This configuration defines IP multicast over UDP. @product@ uses two groups of [channels from JGroups](http://www.jgroups.org/manual4/index.html#_channel) to implement this: a control group and a transport group. If you want to customize the [channel properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Cluster%20Link), you can do so in `portal-ext.properties`: ```properties cluster.link.channel.name.control=[your control channel name] cluster.link.channel.properties.control=[your control channel properties] ``` Please see [JGroups's documentation](http://www.jgroups.org/manual4/index.html#protlist) for channel properties. The default configuration sets many properties whose settings are discussed there. Multicast broadcasts to all devices on the network. Clustered environments on the same network communicate with each other by default. Messages and information (e.g., scheduled tasks) sent between them can lead to unintended consequences. Isolate such cluster environments by either separating them logically or physically on the network, or by configuring each cluster's `portal-ext.properties` to use different sets of [multicast group address and port values](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Multicast). JGroups sets a bind address automatically. If you want to set a manual address, you can do this. By default, these are set to `localhost`: ```properties cluster.link.bind.addr["cluster-link-control"]=localhost cluster.link.bind.addr["cluster-link-udp"]=localhost ``` In some configurations, however, `localhost` is bound to the internal loopback network (`127.0.0.1` or `::1`), rather than the host's real address. If for some reason you need this configuration, you can make @product@ auto detect its real address with this property: ```properties cluster.link.autodetect.address=www.google.com:80 ``` Set it to connect to some other host that's contactable by your server. By default, it points to Google, but this may not work if your server is behind a firewall. If you set the address manually using the properties above, you don't need to set the auto-detect address. Your network configuration may preclude the use of multicast over TCP, so below are some other ways you can get your cluster communicating. Note that these methods are all provided by JGroups. ### Checkpoint: 1. If you are binding the IP address instead of using `localhost`, make sure the right IP addresses are declared using these properties: ```properties cluster.link.bind.addr["cluster-link-control"]=localhost cluster.link.bind.addr["cluster-link-udp"]=localhost ``` 3. Test your load and then optimize your settings if necessary. ## Unicast over TCP If your network configuration or the geographical distance between nodes prevents you from using UDP Multicast clustering, you can configure TCP Unicast. You must use this if you have a firewall separating any of your nodes or if your nodes are in different geographical locations. 1. Add a parameter to your app server's JVM on each node: ```bash -Djgroups.bind_addr=[node_ip_address] ``` Use the node's IP address. 2. Select a discovery protocol for the nodes to use to find each other. Here are the protocol choices: - TCPPing - JDBCPing - S3_Ping - Rackspace_Ping If you aren't sure which one to choose, use TCPPing. The rest of these steps use TCPPing 3. Extract the `tcp.xml` file from `$LIFERAY.HOME/osgi/marketplace/Liferay Foundation - Liferay Portal - Impl.lpkg/com​.​liferay​.​portal​.​cluster​.​multiple​-​[version].​jar/lib​/​jgroups​-​[version].​Final​.​jar/tcp.xml` to a location accessible to DXP, such as a folder called `jgroups` in the DXP web application's `WEB-INF/classes` folder. ``` WEB-INF/classes/jgroups/tcp.xml ``` 4. In the `tcp.xml` file, set the TCP bind port to an unused port on your node. Here's an example: ```xml ``` 5. In the `tcp.xml` file, make each node discoverable to TCPPing by specifying its IP address and an unused port on that node. Building off of the previous step, here's an example `` element: ```xml ``` **Regarding Initial Hosts:** - An alternative to specifying initial hosts in a TCP XML file is to specify them to your app server using a JVM argument like this: `-Djgroups.tcpping.initial_hosts=192.168.224.154[7800],192.168.224.155[7800]`. - Make sure the initial hosts value accounts for all your nodes. If `initial_hosts` is not specified in a TCP XML file or in a JVM argument, `localhost` is the initial host. 6. Copy your `tcp.xml` file to each node, making sure to set the TCP bind port to the node's bind port. On the node with IP address `192.168.224.155`, for example, configure TCPPing like this: ```xml ``` 7. Modify the [Cluster Link properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Cluster%20Link) in the node's `portal-ext.properties` file to enable Cluster Link and point to the TCP XML file for each Cluster Link channel: ```properties cluster.link.enabled=true cluster.link.channel.properties.control=/jgroups/tcp.xml cluster.link.channel.properties.transport.0=/jgroups/tcp.xml ``` The JGroups configuration demonstrated above is typically all that Unicast over TCP requires. However, in a very specific case, if *(and only if)* cluster nodes are deployed across multiple networks, then the parameter `external_addr` must be set on each host to the external (public IP) address of the firewall. This kind of configuration is usually only necessary when nodes are geographically separated. By setting this, clustered nodes deployed to separate networks (e.g. separated by different firewalls) can communicate together. This configuration may be flagged in security audits of your system. See [JGroups documentation](http://www.jgroups.org/manual4/index.html#_transport_protocols) for more information. > **Note:** The `singleton_name` TCP attribute was deprecated in JGroups v4.0.0 and has therefore been removed since Liferay DXP 7.2 SP1 and Liferay Portal CE GA2 which use JGroups v 4.1.1-Final. You're now set up for Unicast over TCP clustering! Repeat either TCPPING process for each node you want to add to the cluster. ### JDBC Ping Rather than use TCP Ping to discover cluster members, you can use a central database accessible by all the nodes to help them find each other. Cluster members write their own and read the other members' addresses from this database. To enable this configuration, replace the `TCPPING` tag with the corresponding `JDBC_PING` tag: ```xml ``` For example JDBC connection values, please see [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). For further information about JDBC Ping, please see the [JGroups Documentation](http://www.jgroups.org/manual4/index.html#DiscoveryProtocols). ### S3 Ping Amazon S3 Ping can be used for servers running on Amazon's EC2 cloud service. Each node uploads a small file to an S3 bucket, and all the other nodes read the files from this bucket to discover the other nodes. When a node leaves, its file is deleted. To configure S3 Ping, replace the `TCPPING` tag with the corresponding `S3_PING` tag: ```xml ``` Supply your Amazon keys as values for the parameters above. For further information about S3 Ping, please see the [JGroups Documentation](http://www.jgroups.org/manual4/index.html#_s3_ping). ### Other Pings JGroups supplies other means for cluster members to discover each other, including Rackspace Ping, BPing, File Ping, and others. Please see the [JGroups Documentation](http://www.jgroups.org/manual4/index.html#DiscoveryProtocols) for information about these discovery methods. The control and transport channels can be configured to use different ports. ## Using Different Control and Transport Channel Ports Using separate control and transport channel ports lets you monitor control and transport traffic and helps you isolate information to diagnose problems. These steps use Unicast over TCPPing to demonstrate the approach. 1. Add a parameter to your app server's JVM on each node: ```bash -Djgroups.bind_addr=[node_ip_address] ``` 2. Extract the `tcp.xml` file from `$LIFERAY.HOME/osgi/marketplace/Liferay Foundation - Liferay Portal - Impl.lpkg/com​.​liferay​.​portal​.​cluster​.​multiple​-​[version].​jar/lib​/​jgroups​-​[version].​Final​.​jar/tcp.xml` to a location accessible to DXP, such as a folder called `jgroups` in the DXP web application's `WEB-INF/classes` folder. 3. Make a copy of the `tcp.xml` in the same location and rename both files, designating one for the control channel and the other for the transport channel. For example, you could use these file names: - `tcp-control.xml` - `tcp-transport.xml` 5. Modify the [Cluster Link properties](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#Cluster%20Link) in the node's `portal-ext.properties` file to enable Cluster Link and point to the TCP XML file for each Cluster Link channel: ```properties cluster.link.enabled=true cluster.link.channel.properties.control=/jgroups/tcp-control.xml cluster.link.channel.properties.transport.0=/jgroups/tcp-transport.xml ``` 6. Modify each `tcp-*.xml` file's the TCP and TCPPing elements to account for each node's IP address and bind port. If you're vertically clustering (i.e., you have multiple servers running on the same physical or virtual system), every channel must use a unique unused bind port for discovery communication. In each `tcp-*.xml` file, assign the TCP tag's `bind_port` attribute to a unique, unused port. For example, your first two nodes might assign these bind ports: | Node | Properties File | Port | | :----- | :------------------ | :----- | | Node 1 | `tcp-control.xml` | `7800` | | Node 1 | `tcp-transport.xml` | `7801` | | Node 2 | `tcp-control.xml` | `7802` | | Node 2 | `tcp-transport.xml` | `7803` | Here are example TCP and TCPPing elements using the bind ports on nodes running on the same system (i.e., same IP address): **Node 1 `tcp-control.xml`** ```xml ``` **Node 1 `tcp-transport.xml`** ```xml ``` **Node 2 `tcp-control.xml`** ```xml ``` **Node 2 `tcp-transport.xml`** ```xml ``` If you have added entities that can be cached or you want to tune the cache configuration for your system, you can do so using a module. ## Modifying the Cache Configuration with a Module It's recommended to test your system under a load that best simulates the kind of traffic your system must handle. If you serve a lot of message board messages, your script should reflect that. If web content is the core of your site, your script should reflect that too. As a result of a load test, you may find that the default distributed cache settings aren't optimized for your site. In this case, tweak the settings using a module. Follow instructions for [Overriding Cache](/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache). You can install the module on each node and change the settings without taking down the cluster. This is a great benefit, but beware: since Ehcache doesn't allow for changes to cache settings while the cache is alive, reconfiguring a cache while the server is running flushes the cache. ## Conclusion Once you've configured your cluster, you can start it. A log file message shows your cluster's name (e.g., `cluster=liferay-channel-control`): ```bash ------------------------------------------------------------------- GMS: address=oz-52865, cluster=liferay-channel-control, physical address=192.168.1.10:50643 ------------------------------------------------------------------- ``` Congratulations! Your cluster is using Cluster Link. ================================================ FILE: en/deployment/articles/02-configuring-liferay/04-liferay-portal-clustering/06-auto-deploy-to-all-nodes.markdown ================================================ --- header-id: auto-deploy-to-all-nodes --- # Auto Deploy to All Nodes [TOC levels=1-4] All modules and WAR files you deploy onto the cluster must be deployed to all cluster nodes. Because @product@ [installs applications as OSGi bundles](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator), you cannot rely on your application server's means of installing WAR files (even if you only intend to install WAR files) to deploy an application to the entire cluster. Instead, place the application in each node's auto deploy folder (e.g., `[Liferay Home]/deploy`). This, as you might imagine, can be done with a script. Write a shell script that uploads applications to each node using sftp or some other service. This way, when you deploy an application, it uploads to each node's auto deploy folder and installs to @product@ on each node. ================================================ FILE: en/deployment/articles/02-configuring-liferay/05-updating-a-cluster/01-intro.markdown ================================================ --- header-id: updating-a-cluster --- # Updating a Cluster [TOC levels=1-4] Maintaining a [cluster](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) is a big responsibility. It includes deploying new and updated plugins and modules, applying [fix packs](/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay), making configuration changes, and more. Maximizing server uptime and minimizing risks take priority when applying changes. @product@ supports using standard cluster maintenance techniques. - [Rolling restarts](/docs/7-2/deploy/-/knowledge_base/d/using-rolling-restarts): Nodes are shut down and updated one at a time. - [Blue-green deployment](/docs/7-2/deploy/-/knowledge_base/d/other-cluster-update-techniques): Blue-green involves duplicating the current environment (*blue* environment), updating the duplicate (*green* environment), and cutting over users to the updated environment (green). The techniques are compared below. **Cluster Update Techniques** | Update |  Rolling Restart|  Blue-green | | ------ | :------------------- | :--------------- | | Plugin/module installation | Supported | Supported | | Plugin/module update (backward-compatible data/schema changes) | Supported | Supported | | Plugin/module update (non-backward-compatible data/schema changes) [1](#one) | Not supported | Supported | | Fix pack installation and removal (revertable fix pack) | Supported | Supported | | Fix pack installation (non-revertible fix pack) | Not supported | Supported | | Cluster code changes [2](#two) | Not supported | Supported | | Portal property changes | Supported | Supported | | System Setting changes via configuration admin files | Supported | Supported | | Application server updates | Supported | Supported | | JVM setting changes | Supported | Supported | | New Java version (minor) | Supported | Supported | | New Java version (major) | Not supported | Supported | [1] Data and data schema changes that are not backward-compatible include, but are not limited to these: - Modifying data in existing columns - Dropping columns - Changing column types - Changing data formats used in columns (such as changing from XML to JSON) - Updating a Service Builder service module's data schema to a version outside of the module's required data schema range. A module's `Liferay-Require-SchemaVersion` (specified in its `bnd.bnd`) must match the module's schema version value in the `Release_` table. Installing a module with a new schema version updates the `Release_` table with that schema version and triggers a data upgrade process. If you install such a module on one node, the schema version in the `Release_` table no longer matches the `Liferay-Require-SchemaVersion` of the modules on the other nodes, and the module's Service Builder services become unavailable until the module is installed on the other nodes. Such changes cannot be reverted: the database must be restored from a backup. These schema version changes must be applied while all nodes are shut down. [2] Cluster communication must stay intact. For this reason, cluster code must not be updated in rolling restarts. The Customer Portal identifies DXP fix packs that contain such changes as non-revertible. Here are packages you must not change in rolling restarts: - `com.liferay.portal.kernel.cluster` - `com.liferay.portal.kernel.cluster.*` - `com.liferay.portal.kernel.exception.NoSuchClusterGroupException` - `com.liferay.portal.kernel.scheduler.multiple` - `com.liferay.portal.kernel.scheduler.multiple.*` - `com.liferay.portal.cache.multiple` - `com.liferay.portal.cache.multiple.*` - `com.liferay.portal.scheduler.multiple` - `com.liferay.portal.scheduler.multiple.*` Since eligible changes should be done with rolling restarts, it's explained first. ================================================ FILE: en/deployment/articles/02-configuring-liferay/05-updating-a-cluster/02-using-rolling-restarts.markdown ================================================ --- header-id: using-rolling-restarts --- # Rolling Restarts [TOC levels=1-4] The rolling restart cluster maintenance process involves shutting down and updating nodes one at a time (while the other nodes are running) until they're all updated. It maximizes uptime while you update your cluster. Rolling restarts can be used in container and image based environments. | **Note:** Rolling restart does not include concepts for blue-green (separate, | but identical environments) architectures, as these concepts specifically | address multi-cluster style developments. Here are the rolling restart steps: 1. Shut down one cluster node (JVM instance). 2. Update/modify the deployment for that node (see the maintenance scenarios that follow). 3. Start the node. 4. Repeat these steps for all other cluster nodes. Maintenance scenarios vary in how they behave in rolling restarts. For example, UI changes in a plugin update are only visible on the updated nodes. Users on nodes that haven't been updated don't see the UI changes. Maintenance scenarios might have specific cases that cannot be performed in rolling restarts---the scenario descriptions mention these cases. The maintenance scenarios eligible for rolling restart are described below. ## New Modules and Plugins For a new plugin or module (one that does not already exist in the cluster) to be eligible for rolling restart it must not modify data, or delete or rename database columns in a way that breaks compatibility with existing plugins or modules. ## Updating Existing Modules and Plugins For a new version of an existing plugin or module to be eligible for rolling restart, it must not modify data or delete or rename database columns in a way that breaks compatibility with the existing version of the plugin or module. ## Applying Fix Packs (DXP only) The Customer Portal identifies [fix packs](/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay) that are not revertible, and therefore ineligible for rolling restart. All other fix packs are eligible. ## Reverting Fix Packs (DXP only) Revertible fix packs can be removed in rolling restarts. ## Portal Properties controlled by `portal-ext.properties` [Portal Properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) file changes can be applied in rolling restarts. ## System Settings controlled by Configuration Admin Files [System configuration](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) files can be applied in rolling restarts. ## Application Server or JVM setting modifications Modifications to application server and JVM settings can be done in rolling restarts. ## Java Version Updates Minor version updates of Java can be applied in rolling restarts. Major version updates are not supported in rolling restarts, and should instead be done when all cluster nodes are shut down. All rolling restart eligible updates can be applied using the rolling restart steps listed earlier. Other updates must be done differently as described next. ## Related Topics [@product@ Clustering](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) [Maintaining @product@](/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay) ================================================ FILE: en/deployment/articles/02-configuring-liferay/05-updating-a-cluster/03-other-cluster-update-techniques.markdown ================================================ --- header-id: other-cluster-update-techniques --- # Blue-Green Deployment [TOC levels=1-4] Blue-green is a deployment technique in which you duplicate your production environment (the *blue* environment) and modify the duplicate (the *green* environment) with software and data changes. When you've successfully tested the green environment, you cut users over from the blue environment to the green environment. Blue-green eliminates system down time. Data schema and data changes require special attention. Custom plugin/module data schema changes that break compatibility with existing code must be introduced over several releases in which the data is transitioned and maintained in old and new columns until the old columns are unnecessary. Data and schema changes require these steps: 1. Create a new column. 2. Copy the data to the new column. 3. Maintain both columns until the old column is no longer used by any cluster nodes. 4. Delete the column in the next release. For more information, refer to these blue-green deployment articles: - [BlueGreenDeployment](http://martinfowler.com/bliki/BlueGreenDeployment.html) - [Implementing Blue-Green Deployments with AWS](https://www.thoughtworks.com/insights/blog/implementing-blue-green-deployments-aws) ## Related Topics [Rolling Restarts](/docs/7-2/deploy/-/knowledge_base/d/using-rolling-restarts) [@product@ Clustering](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) [Maintaining @product@](/docs/7-2/deploy/-/knowledge_base/d/maintaining-liferay) ================================================ FILE: en/deployment/articles/02-configuring-liferay/06-configuring-remote-staging-clustered.markdown ================================================ --- header-id: configuring-remote-staging-in-a-clustered-environment --- # Configuring Remote Staging in a Clustered Environment [TOC levels=1-4] If you're running @product@ as a [clustered environment](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) and you want to use remote staging, you must configure it properly for a seamless experience. In this tutorial, you'll learn how to set up remote staging in an example clustered environment scenario. The example environment assumes you have - a Staging instance with database configurations and a file repository different from the cluster nodes. - a balancer responsible for managing the traffic flow between the cluster's nodes. - two nodes that call two Liferay app servers (e.g., *App Server 1* and *App Server 2*), both of which are connected to the same database. ![Figure 1: This is the assumed setup for your clustered environment.](../../images/remote-staging-clustering.png) The steps below also assume your web tier, application tier, and cluster environment are already configured. You may need to adjust the configurations in this tutorial to work with your specific configuration. Let's begin! 1. You must secure the communication made between your nodes and Staging server. Add the following property to both app servers' and Staging server's `portal-ext.properties` file: tunneling.servlet.shared.secret=[secret] This secret key denies other portals access to your configured portal servers. If you'd like to set your secret key using hexadecimal encoding, also set the following property in your `portal-ext.properties` files: tunneling.servlet.shared.secret.hex=true | **Note:** The following key lengths are supported by the available | encryption algorithms: | | - *AES:* 128, 192, and 256-bit keys | - *Blowfish:* 32-448 bit keys | - *DESede (Triple DES):* 56, 112, or 168 bit keys (Liferay places an | artificial limit on the minimum key length and does not support the 56-bit | key length) | | For example, you can use [OpenSSL](https://www.openssl.org/) to generate a | 128-bit AES key: | | openssl enc -aes-128-cbc -k abc123 -P -md sha1 2. You must allow the connection between the configured IPs of your app servers and the Staging server. Open your remote Liferay server's `portal-ext.properties` file and add the following properties: tunnel.servlet.hosts.allowed=127.0.0.1,SERVER_IP,[STAGING_IP] tunnel.servlet.https.required=false The `[STAGING_IP]` variable must be replaced by the staging server's IP addresses. The `SERVER_IP` constant can remain set for this property; it's automatically replaced by the Liferay server's IP addresses. 3. If you're validating IPv6 addresses, you must configure the app server's JVM to not force the usage of IPv4 addresses. For example, if you're using Tomcat, add the following attribute in the `$TOMCAT_HOME\bin\setenv.[bat|sh]` file. `-Djava.net.preferIPv4Stack=false` 4. Restart both app servers for the new properties to take effect. 5. Configure the *TunnelAuthVerifier* property for your nodes' app servers. There are two ways to do this: - **Use a `.config` file (recommended):** In the `$LIFERAY_HOME/osgi/configs` folder of one of your node @product@ instances, create (if necessary) a `com.liferay.portal.security.auth.verifier.tunnel.module.configuration.TunnelAuthVerifierConfiguration-default.config` file and insert the properties listed below. Creating one `.config` file configures all cluster nodes the same way. For more information on `.config` files, see the [Understanding System Configuration Files](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) article. enabled=true hostsAllowed=127.0.0.1,SERVER_IP,STAGING_IP serviceAccessPolicyName=SYSTEM_USER_PASSWORD urlsIncludes=/api/liferay/do - **Via System Settings:** Navigate to the *Control Panel* → *Configuration* → *System Settings* → *Foundation* → *Tunnel Auth Verifiers*. Click on the */api/liferay/do* configuration entry and add the Staging IP address to the *Hosts allowed* field. If you choose to configure the *TunnelAuthVerifier* this way, you **must** do this for all nodes (e.g., App Server 1 and App Server 2). 6. On your Staging instance, navigate to the Site Administration portion of the Product Menu and select *Publishing* → *Staging*. Then select *Remote Live*. ![Figure 2: When selecting the Remote Staging radio button, you're given a list of options to configure.](../../images/remote-staging-menu.png) 7. For the Remote Host/IP field, insert the balancer's IP of your web tier. Configuring the Staging instance with the balancer's IP ensures the availability of the environment at the time of publication from staging to live. 8. Enter the port on which the balancer is running into the Remote Port field. 9. Insert the remote site ID of your app servers into the Remote Site ID field. The site ID of all your app servers are the same since they are configured for the same database and are shared between nodes. Navigate to the Site Administration portion of the Product Menu and select *Configuration* → *Site Settings* to find the site ID. 10. Save the Remote Live settings. That's it! You've configured remote staging in your clustered environment. ================================================ FILE: en/deployment/articles/02-configuring-liferay/07-content-delivery-network.markdown ================================================ --- header-id: content-delivery-network --- # Content Delivery Network [TOC levels=1-4] A Content Delivery Network (CDN) is an network of servers deployed in multiple data centers that contain your static content. When users hit your site, that static content is loaded from a server with geographical proximity to the user, speeding up requests. Here, you'll first discover the perks of using a CDN and learn about general guidelines for using a CDN with @product@. Then, you'll configure a CDN. It's time to distribute your content around the world! ## Using CDN for Performance Enhancements A CDN serves static web resources to users. These resources (images, CSS files, JavaScript files, etc.) are stored on multiple servers around the world. When requested, the resources are retrieved from the server nearest to the user. The CDN functions as a caching proxy. This means that once static content is copied to a local server, it is stored in a cache for quick and easy retrieval. This drastically improves latency time, because browsers can download static resources from a local server down the street instead of halfway around the world. A user's request to the CDN for content is directed to specific server machine based on an algorithm that finds the server closest to the user. The figure below shows a visual representation of using geographical proximity to improve latency. ![Figure 1: The red lines on the map represent the required distances traveled by requests from a server to the user. Using CDN allows a user to request static resources from a much closer local server, improving download times.](../../images/cdn-map.png) Because of the reduced wait time for requests and reduced load on your application server, a CDN is a great option to improve performance. Using a CDN with @product@, however, has some restrictions. ## Liferay CDN Requirements @product@ only works with CDNs that can dynamically retrieve requested resources. Dynamic resources change over time or via interaction with end users and thus cannot be cached. For this reason, check with your CDN provider to make sure you don't have to upload anything manually in order for the CDN to work. The CDN must automatically fetch the content. The CDN must work like a transparent proxy. A request first goes to the CDN. If the CDN doesn't have the requested resource, the CDN makes an identical request back to the origin (@product@), caches the resource, then serves the resource. Once you're using a CDN (see below), it serves both portal resources and plugin resources (e.g., theme resources or JavaScript files referenced from a plugin's `liferay-portlet.xml` file). The CDN only serves resources that are included in a plugin. It does not serve resources that are dynamically loaded from external sources. To get the CDN URL for a resource, developers should replace the portal host in the resource path with `themeDisplay.getCDNDynamicResourcesHost()`. Prefix resources with the CDN host name. Don't manually upload any resources to the CDN or put anything on the CDN which requires permission checking or complex policy access. There are several portal properties for configuring your CDN to suit your needs. You'll learn how to do this next. ## Configuring @product@ to Use a CDN Now that you understand what a CDN accomplishes and how it's used, it's time to set one up for yourself. You can set your CDN and its properties using two different methods: 1. By editing your portal properties file 2. By using the Control Panel To configure your CDN via a properties file, create a `portal-ext.properties` file in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder and set the appropriate [Content Delivery Network properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Content%20Delivery%20Network). Once you configure your CDN host, @product@ generates URLs to the static assets that replace the old host with your new CDN host so they are automatically cached and served afterwards by the CDN. To configure your CDN in the Control Panel, navigate to *Control Panel* → *Configuration* → *Instance Settings*. In the main configuration, there are three fields related to CDNs: - *CDN Host HTTP* - *CDN Host HTTPS* - *Enable CDN Dynamic Resources* ![Figure 2: The Control Panel lets you configure your portal's CDN.](../../images/cdn-control-panel.png) These properties are exactly the same as the ones you can specify in your `portal-ext.properties`. Make sure to visit the Content Delivery Network section of the [portal.properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Content%20Delivery%20Network) reference document if you don't know how to fill in the CDN fields. Once you're finished, click *Save* and your old host is replaced with your new CDN host for static content. As you can see, configuring a CDN is easy and can drastically reduce latency time and improve performance. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/01-intro.markdown ================================================ --- header-id: installing-a-search-engine --- # Installing a Search Engine [TOC levels=1-4] A search engine is a critical component of your @product@ installation. If you're here, you probably know the basics already and want to configure a search engine for your @product@ deployment. Elasticsearch, a highly scalable, full-text search engine, is installed by default, as an embedded server. Elasticsearch is well-supported and almost certainly meets any search and indexing need you have, but you must not use the [embedded version in your production deployment](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch#embedded-vs-remote-operation-mode). Learn to configure a remote Elasticsearch server or cluster [here](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch). [Solr](http://lucene.apache.org/solr) is another capable and popular search engine supported in @product@. Learn to configure a remote Solr server or cluster [here](/docs/7-2/deploy/-/knowledge_base/d/installing-solr). But first, make sure you understand the disparity in functionality between the supported search engines. ## Choosing a Search Engine Elasticsearch and Solr are both supported, but there are limitations to Liferay's Solr integration. To make use of some features, you must choose Elasticsearch. ### End User Feature Limitations of Liferay's Solr Integration - [Liferay Commerce](https://learn.liferay.com/commerce-2.x/index.html) - [Workflow Metrics](https://help.liferay.com/hc/en-us/articles/360029042071-Workflow-Metrics-The-Service-Level-Agreement-SLA-) - [Custom Filter search widget](/docs/7-2/user/-/knowledge_base/u/filtering-search-results-with-the-custom-filter-widget) - [The Low Level Search Options widget](/docs/7-2/user/-/knowledge_base/u/low-level-search-options-searching-additional-or-alternate-indexes) - [Search Tuning: Customizing Search Results](https://help.liferay.com/hc/en-us/articles/360034473872-Search-Tuning-Customizing-Search-Results) - [Search Tuning: Synonyms](https://help.liferay.com/hc/en-us/articles/360034473852-Search-Tuning-Synonym-Sets) ### Developer Feature Limitations of Liferay's Solr Integration Implementation for the following APIs may be added in the future, but they are not currently supported by Liferay's Solr connector. - From Portal Core (Module: `portal-kernel`, Artifact: `com.liferay.portal.kernel`): - `com.liferay.portal.kernel.search.generic.NestedQuery` - `com.liferay.portal.kernel.search.filter`: - `ComplexQueryPart` - `GeoBoundingBoxFilter` - `GeoDistanceFilter` - `GeoDistanceRangeFilter` - `GeoPolygonFilter` - From the Portal Search API (Module: `portal-search-api`, Artifact: `com.liferay.portal.search.api`): - `com.liferay.portal.search.filter`: - `ComplexQueryPart` - `TermsSetFilter` - `com.liferay.portal.search.geolocation.*` - `com.liferay.portal.search.highlight.*` - `com.liferay.portal.search.query.function.*` - `com.liferay.portal.search.query.*`: - `com.liferay.portal.search.script.*` - `com.liferay.portal.search.significance.*` - `com.liferay.portal.search.sort.*`: only `Sort`,`FieldSort`, and `ScoreSort` are supported - Portal Search Engine Adapter API (Module: `portal-search-engine-adapter-api`, Artifact: `com.liferay.portal.search.engine.adapter.api`) - `com.liferay.portal.search.engine.adapter.cluster.*` - `com.liferay.portal.search.engine.adapter.document.UpdateByQueryDocumentRequest` - `com.liferay.portal.search.engine.adapter.index.*`: only `RefreshIndexRequest` is supported - `com.liferay.portal.search.engine.adapter.search.*`: - `MultisearchSearchRequest` - `SuggestSearchRequest` - `com.liferay.portal.search.engine.adapter.snapshot.*` Liferay Commerce requires the `TermsSetFilter` implementation, only available in the Elasticsearch connector. ### Elasticsearch Java Distribution Compatibility Another factor to consider in your search engine selection is JDK version. The search engine and @product@ must use the same Java version and distribution (e.g., Oracle Open JRE 1.8.0_201). Consult the [Elasticsearch compatibility matrix](https://www.elastic.co/support/matrix#matrix_jvm) and the [@product@ compatibility matrix](https://help.liferay.com/hc/en-us/sections/360002103292-Compatibility-Matrix) to learn more about supported JDK distributions and versions. This consideration is not necessary for Solr, because no JVM level serialization happens between the servers. All communication occurs at the HTTP level. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/01-elasticsearch-intro.markdown ================================================ --- header-id: elasticsearch --- # Elasticsearch [TOC levels=1-4] Elasticsearch is an open source, highly scalable, full-text search and analytics engine. By default, Elasticsearch runs as an embedded search engine, which is useful for development and testing but is not supported in production. In production environments you must run Elasticsearch in remote mode, as a separate server or cluster. This guide walks you through the process of configuring Elasticsearch in remote mode. ![Figure 1: To see information about the currently connected search engine, go to _Control Panel_ → _Configuration_ → _Search_.](../../../images/search-admin-engineinfo.png) | **Note:** Although Elasticsearch 6.5 is shipped as the embedded Elasticsearch | server version, Elasticsearch 7 is the most recent supported Elasticsearch | version for @product-ver@. Installing Elasticsearch 7 requires that you are | running Service Pack 1/Fix Pack 2 or later (GA2 or later for CE users). | Elasticsearch 6.8.x is also supported. See the [compatibility matrix for exact versions](https://help.liferay.com/hc/en-us/articles/360016511651). If you'd rather use Solr, it's also supported. See the documentation on [Installing Solr](/docs/7-2/deploy/-/knowledge_base/d/installing-solr) if you're interested. To get up and running quickly with Elasticsearch as a remote server, refer to the [Installing Elasticsearch article](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch). Included there are basic instructions for installing and configuring Elasticsearch in a single server environment. Additional articles include more details and information on configuring and tuning Elasticsearch. These terms are useful to understand as you read this guide: - *Elasticsearch Home* refers to the root folder of your unzipped Elasticsearch installation (for example, `elasticsearch-7.4.1`). - [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) refers to the root folder of your @product@ installation. It contains the `osgi`, `deploy`, `data`, and `license` folders, among others. ## Embedded vs. Remote Operation Mode When you start @product@, this message is displayed in the log: 2019-04-29 09:59:02.276 WARN [Elasticsearch initialization thread][EmbeddedElasticsearchConnection:288] Liferay is configured to use embedded Elasticsearch as its search engine. Do NOT use embedded Elasticsearch in production. Embedded Elasticsearch is useful for development and demonstration purposes. Refer to the documentation for details on the limitations of embedded Elasticsearch. Remote Elasticsearch connections can be configured in the Control Panel. When you start @product@, Elasticsearch is already running in embedded mode. @product@ runs an Elasticsearch node in the same JVM so it's easy to test-drive with minimal configuration. Running both servers in the same process has drawbacks: - Elasticsearch must use the same JVM options as @product@. - @product@ and Elasticsearch compete for the same system resources. | **Note:** While it's not a supported production configuration, installing | Kibana to monitor the embedded Elasticsearch server is useful during | development and testing. Just be aware that you must install the | [OSS only Kibana build](https://www.elastic.co/downloads/kibana-oss). You wouldn't run an embedded database like HSQL in production, and you shouldn't run Elasticsearch in embedded mode in production either. Instead, run Elasticsearch in *remote operation mode*, as a standalone server or cluster of server nodes. ## Troubleshooting Elasticsearch Integration Sometimes things don't go as planned. If you've set up @product@ with Elasticsearch in remote mode, but @product@ can't connect to Elasticsearch, check these things: **Cluster name:** : The value of the `cluster.name` property in `elasticsearch.yml` must match the `clusterName` property configured in the @product@ Elasticsearch connector. **Transport address:** : The value of the `transportAddresses` property in the Elasticsearch connector configuration must contain at least one valid host and port where an Elasticsearch node is running. If @product@ is running in embedded mode, and you start a standalone Elasticsearch node or cluster, it detects that port `9300` is taken and switches to port `9301`. If you then set Liferay's Elasticsearch connector to remote mode, it continues to look for Elasticsearch at the default port (`9300`). The following articles cover the Liferay Connector to Elasticsearch's configuration options in more detail. **Cluster Sniffing (Additional Configurations):** : Elasticsearch clusters can have multiple node [types](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-node.html#modules-node). [Cluster sniffing](https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html), enabled by default in the @product@ connector, looks for `data` nodes configured in the `transportAddresses` property. If none are available, the connector may throw a `NoNodeAvailableException` in the console log. If cluster sniffing is to remain enabled, be sure that your configuration allows for at least one `data` node's transport address to be "sniffable" at all times to avoid this error. To disable cluster sniffing, add `clientTransportSniff=false` to the `.config` file or un-check the Client Transport Sniff property in System Settings. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/02-preparing-to-install.markdown ================================================ --- header-id: preparing-to-install-elasticsearch --- # Preparing to Install Elasticsearch [TOC levels=1-4] By default, @product-ver@ and its [embedded Elasticsearch engine](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch#embedded-vs-remote-operation-mode) run in the same JVM. Although this enables out-of-the-box search, it's only supported for development. For production use, Elasticsearch must run in a separate JVM. See the [installation guide](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch) for information on installing a remote Elasticsearch cluster. Because search engines benefit heavily from caching, their JVM memory profiles differ substantially from those of a JVM running @product@. Therefore, the two applications should always be kept separate in production environments. The following sections provide a synopsis of Elasticsearch configurations for @product-ver@. Prior to deployment, we strongly recommend reading [Elastic's documentation on production deployment](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html). ## Sizing Your Deployment When sizing your Elasticsearch deployment, carefully consider CPU, memory, disk, and network capacity. To scale effectively and avoid using lots of machines, deploy Elasticsearch on medium to large machines (for example, machines with two to eight CPUs). Avoid running multiple Elasticsearch JVMs on the same operating system. ## CPU We recommend allocating at least eight total CPU cores to the Elasticsearch engine, assuming only one Elasticsearch JVM is running on the machine. ## Memory At least 16 GB of memory is recommended, with 64 GB preferred. The precise memory allocation required depends on how much data is indexed. For index sizes 500 GB to 1 TB, 64 GB of memory suffices. ## Disk Search engines store their indexes on disk, so disk I/O capacity can impact search performance. Deploy Elasticsearch on SSD whenever possible. Otherwise use high-performance traditional hard disks (for example, 15k RPM). In either case, consider using RAID 0. Avoid using Network Attached Storage (NAS) whenever possible as the network overhead can be large. If you're using public cloud infrastructure like Amazon Web Services, use instance local storage instead of network storage, such as Elastic Block Store (EBS). Maintain 25 percent more disk capacity than the total size of your indexes. If your index is 60 GB, make sure you have at least 75 GB of disk space available. To estimate the disk space you need, you can index a representative sample of your production content and multiply that size by the fraction of your production content that it represents. For example, index 25 percent of your production content and then multiply the resulting index size by four. Keep in mind that indexing a 1 MB file doesn't result in 1 MB of disk space in the search index. ## Cluster Size While @product@ can work with an Elasticsearch cluster comprised of one or two nodes, the minimum cluster size recommended by Elastic for fault tolerance is three nodes. ## Networking Elasticsearch relies on clustering and sharding to deliver fast, accurate search results, and thus requires a fast and reliable network. Most modern data centers provide 1 GbE or 10 GbE between machines. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/03-installing-elasticsearch.markdown ================================================ --- header-id: installing-elasticsearch --- # Installing Elasticsearch [TOC levels=1-4] @product@ uses Elasticsearch to index its content. By default, it's installed as an embedded service. It works, but it's not a supported configuration for a production server. Feel free to use it while testing or developing, but when you're ready to put your site in production, you must run Elasticsearch as a standalone process. This is better anyway, because it frees you to design your infrastructure the way you want it. If you've got hardware or a VM to spare, you can separate your search infrastructure from @product@ and reap some performance gains by putting search on a separate box. If you're more budget-conscious, you can still increase performance by running Elasticsearch in a separate, individually tunable JVM on the same box. Before installing Elasticsearch, refer to [Preparing to Install Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-install-elasticsearch) for guidance on configuring the servers to support an Elasticsearch deployment properly. Here's an overview of the installation steps: 1. Download a supported version of Elasticsearch. See [Elastic's](https://www.elastic.co) website. 2. Install Elasticsearch by extracting its archive to the system where you want it to run. 3. Install some required Elasticsearch plugins. 4. Name your Elasticsearch cluster. 5. Configure @product@ to connect to your Elasticsearch cluster. 6. Restart @product@ and reindex your search and spell check indexes. | **Prerequisites:** Before continuing, make sure you have set the | [`JAVA_HOME` environment variable](https://docs.oracle.com/cd/E19182-01/820-7851/inst_cli_jdk_javahome_t/). | | If you have multiple JDKs installed, make sure Elasticsearch and @product@ are | using the same version and distribution (e.g., Oracle Open JDK 1.8.0_201). You | can specify this in `[Elasticsearch Home]/bin/elasticsearch.in.sh`: | `JAVA_HOME=/path/to/java`. | **Replacing the Default Elasticsearch 6 Connector:** If you're installing | Elasticsearch 6, use the connector application installed by default. If you're installing | Elasticsearch 7, you'll need to download the connector from Liferay Marketplace | for either | [CE](https://web.liferay.com/en/marketplace/-/mp/application/170642090) and | [DXP](https://web.liferay.com/en/marketplace/-/mp/application/170390307). | Always refer to the [compatibility matrix to find the exact versions | supported](https://www.liferay.com/documents/10182/246659966/Liferay+DXP+7.2+Compatibility+Matrix.pdf/ed234765-db47-c4ad-7c82-2acb4c73b0f9). | Before installing the connector, blacklist the Elasticsearch 6 connector and | APIs. The [upgrade | documentation](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-elasticsearch-7#blacklisting-elasticsearch-6) | holds detailed blacklisting steps. When you perform these steps, you'll have a basic, production-ready instance of @product@ and Elasticsearch up and running. But that's just the beginning of your server/connector configuration: - Read about [Configuring Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector) for @product@ in more detail. - Learn how to [Secure Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-security). - [Liferay Enterprise Search] Learn how to configure [Monitoring](/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-monitoring). For complete information on compatibility, check the [@product@ compatibility matrix](https://help.liferay.com/hc/en-us/articles/360028982631-Liferay-DXP-7-2-Compatibility-Matrix) and the [Liferay Enterprise Search compatibility matrix](https://help.liferay.com/hc/en-us/articles/360016511651#Liferay-Enterprise-Search) if you have a subscription. ### Step One: Download a Supported Version of Elasticsearch If @product@ isn't running, start it. Visit port 9200 on localhost to access the embedded Elasticsearch: http://localhost:9200 A JSON document is returned that looks similar to this: ```json { "name" : "01BT8H4", "cluster_name" : "LiferayElasticsearchCluster", "cluster_uuid" : "ziPGEBeSToGHc7lVqaYHnA", "version" : { "number" : "6.5.0", "build_flavor" : "unknown", "build_type" : "unknown", "build_hash" : "816e6f6", "build_date" : "2018-11-09T18:58:36.352602Z", "build_snapshot" : false, "lucene_version" : "7.5.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" } ``` The version of Elasticsearch that's running is the value of the `number` field. In this example, it's 6.5.0. You can install the embedded version, but it might not be the most up-to-date version of Elasticsearch that's supported with @product@. Consult the [Compatibility Matrix](https://help.liferay.com/hc/en-us/articles/360016511651) for definitive information on what's supported. | **Note:** Although the embedded server uses Elasticsearch 6.5, Elasticsearch | 6.8.x has been tested with @product-ver@ GA1, and is fully supported. If you've | upgraded to @product-ver@ Service Pack 1/Fix Pack 2 (or GA2 for CE users), | Elasticsearch 7 is supported through the Liferay Connector to Elasticsearch | 7, which can be downloaded from Liferay Marketplace for both | [CE](https://web.liferay.com/en/marketplace/-/mp/application/170642090) and | [DXP](https://web.liferay.com/en/marketplace/-/mp/application/170390307). | Always refer to the [compatibility matrix to find the exact versions supported](https://help.liferay.com/hc/en-us/articles/360016511651). Shut down the @product@ server. In a local, single-machine testing environment, if you continue without shutting down, the Elasticsearch server you're about to install and start throws errors in the log if its cluster name and HTTP port match the already-running embedded Elasticsearch server. An alternative to shutting down @product@ is to use a different cluster name (i.e., not `LiferayElasticsearchCluster`) and HTTP port (i.e., not `9200`) in the remote Elasticsearch server. When you know the version of Elasticsearch you need, go to [Elastic's](https://www.elastic.co) website and download that version. ### Step Two: Install Elasticsearch Most of this step entails deciding where you want to run Elasticsearch. Do you want to run it on the same machine as @product@, or do you want to run it on its own hardware? The answer to this question comes down to a combination of the resources you have available and the size of your installation. Regardless of what you decide, either way you get the benefit of a separately tunable search infrastructure. Once you have a copy of the right version of Elasticsearch, extract it to a folder on the machine where you want it running. That's it! ### Step Three: Install Elasticsearch Plugins Install the following required Elasticsearch plugins: - `analysis-icu` - `analysis-kuromoji` - `analysis-smartcn` - `analysis-stempel` To install these plugins, navigate to Elasticsearch Home and enter ./bin/elasticsearch-plugin install [plugin-name] Replace *[plugin-name]* with the Elasticsearch plugin's name. ### Step Four: Name Your Elasticsearch Cluster A *cluster* in Elasticsearch is a collection of nodes (servers) identified as a cluster by a shared cluster name. The nodes work together to share data and workload. A one node cluster is discussed here; to create a multi-node cluster, please refer to [Elastic's documentation](https://www.elastic.co/guide/index.html). Now that you've installed Elastic, it sits in a folder on your machine, which is referred to here as `[Elasticsearch Home]`. To name your cluster, you'll define the cluster name in both Elasticsearch and in @product@. First, define it in Elasticsearch. Edit the following file: [Elasticsearch Home]/config/elasticsearch.yml Uncomment the line that begins with `cluster.name`. Set the cluster name to whatever you want to name your cluster: ```yml cluster.name: LiferayElasticsearchCluster ``` Of course, this isn't a very imaginative name; you may choose to name your cluster `finders_keepers` or something else you can remember more easily. Save the file. | **Elasticsearch 6.x:** On Elasticsearch 6.x, you must also disable X-Pack | Security unless you have a Liferay Enterprise Search subscription. Add this to | `elasticsearch.yml`: `xpack.security.enabled: false`. Now you can start Elasticsearch. Run the executable for your operating system from the `[Elasticsearch Home]/bin` folder: ```bash ./elasticsearch ``` Elasticsearch starts, and one of its status messages includes a transport address: ```sh [2019-04-01T16:55:50,127][INFO ][o.e.t.TransportService ] [HfkqdKv] publish_address {127.0.0.1:9300}, bound_addresses {[::1]:9300}, {127.0.0.1:9300} ``` Take note of this address; you'll need to give it to your @product@ server so it can find Elasticsearch on the network. ### Step Five: Configure @product@ to Connect to your Elasticsearch Cluster Now that you're ready to configure @product@, start it if you haven't already, log in, and then click on *Control Panel* → *Configuration* → *System Settings* → *Search*. Enter the term *elasticsearch* in the search bar and click the *Elasticsearch [Version]* entry from the list of settings (at the time of writing, the version will either be *6* or *7*). Now you can configure it. Here are the configuration options to change: **Cluster Name:** Enter the name of the cluster as you defined it in Elasticsearch. **Operation Mode:** Defaults to EMBEDDED. Change it to REMOTE to connect to a standalone Elasticsearch. **Transport Addresses:** Enter a delimited list of transport addresses for Elasticsearch nodes. Here, you'll enter the transport address from the Elasticsearch server you started. The default value is `localhost:9300`, which will work. When finished, click *Save*. You're almost done. ### Step Six: Restart @product@ and Reindex If you're doing a local test installation, you probably only changed the Operation Mode in the connector configuration, so there's no need to restart; skip to re-indexing. If you've made more configuration changes in the connector's configuration, stop and restart @product@. When it's back up, log in as an administrative user and click on *Control Panel* → *Configuration* → *Search* and click the *Execute* button for *Reindex all search indexes* and then *Reindex all spell check indexes*. When you do that, you'll see some messages scroll up in the Elasticsearch log. When restarting @product@, `update_mappings` messages will appear in the Elasticsearch logs: ```sh [2019-04-01T17:08:57,462][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:57,474][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:58,393][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:08:58,597][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-0/m27eNsekTAyP27zDOjGojw] update_mapping [LiferayDocumentType] [2019-04-01T17:09:07,040][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/OJidpYkgR5OcCD5dgWB8Aw] update_mapping [LiferayDocumentType] ``` Once you reindex, more log messages appear in Elasticsearch: ```sh [2019-04-01T17:11:17,338][INFO ][o.e.c.m.MetaDataDeleteIndexService] [HfkqdKv] [liferay-20101/OJidpYkgR5OcCD5dgWB8Aw] deleting index [2019-04-01T17:11:17,389][INFO ][o.e.c.m.MetaDataCreateIndexService] [HfkqdKv] [liferay-20101] creating index, cause [api], templates [], shards [1]/[0], mappings [LiferayDocumentType] [2019-04-01T17:11:17,471][INFO ][o.e.c.r.a.AllocationService] [HfkqdKv] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[liferay-20101][0]] ...]). [2019-04-01T17:11:17,520][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,047][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,133][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,204][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:19,249][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,215][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,262][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,268][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,275][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,282][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] [2019-04-01T17:11:21,373][INFO ][o.e.c.m.MetaDataMappingService] [HfkqdKv] [liferay-20101/Meacn_uxR06g0tCJonS4eA] update_mapping [LiferayDocumentType] ``` Reindexing the spell check dictionaries produces log messages like these: ```sh 2019-04-29 14:02:22.034 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:278] Start indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/en_US.txt 2019-04-29 14:02:34.166 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:299] Finished indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/en_US.txt 2019-04-29 14:02:34.167 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:278] Start indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/es_ES.txt 2019-04-29 14:02:39.379 INFO [liferay/search_writer/SYSTEM_ENGINE-11][BaseSpellCheckIndexWriter:299] Finished indexing dictionary for com/liferay/portal/search/dependencies/spellchecker/es_ES.txt ``` For additional confirmation that @product@ recognizes the remote search engine, navigate to the Search Control Panel application and note the subtle change there: the vendor name is now simply _Elasticsearch_, whereas prior to the installation of the remote Elasticsearch server, it said _Elasticsearch (Embedded)_. ![Figure 1: To see information about the currently connected search engine, go to _Control Panel → Configuration → Search_.](../../../images/search-admin-engineinfo-remote.png) For additional details refer to the [Elasticsearch installation guide](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/getting-started-install.html). ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/04-configuring-the-connector.markdown ================================================ --- header-id: configuring-the-liferay-elasticsearch-connector --- # Configuring the Liferay Elasticsearch Connector [TOC levels=1-4] For detailed Elasticsearch configuration information, refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/settings.html). The name of your Elasticsearch cluster is important. When you're running Elasticsearch in remote mode, the cluster name is used by @product@ to recognize the Elasticsearch cluster. To learn about setting the Elasticsearch cluster name on the @product@ side, refer below to the section called Configuring the Liferay Elasticsearch Connector. | **Note:** The `http.enabled` setting in Elasticsearch corresponds to the | `httpEnabled` setting in the Liferay Connector to Elasticsearch 6 application. | As this setting was [deprecated in Elasticsearch | 6.3](https://www.elastic.co/guide/en/elasticsearch/reference/6.5/release-notes-6.3.0.html), | the connector's corresponding setting is now also deprecated. This setting was | only used for configuring the embedded Elasticsearch server, so its deprecation | should have minimal impact to production deployments. Elasticsearch's configuration files are written in [YAML](http://www.yaml.org) and kept in the `[Elasticsearch Home]/config` folder. The main configuration file is `elasticsearch.yml`, used for configuring Elasticsearch modules. To set the name of the Elasticsearch cluster, open `[Elasticsearch Home]/config/elasticsearch.yml` and specify ```yml cluster.name: LiferayElasticsearchCluster ``` Since `LiferayElasticsearchCluster` is the default name given to the cluster in the @product@ Elasticsearch connector, this works just fine. Of course, you can name your cluster whatever you want (we humbly submit the recommendation `clustery_mcclusterface`).[1](#footnote1) Configure your node name using the same syntax (setting the `node.name` property). There's no client setting for this, it exists only in each Elasticsearch node's `elasticsearch.yml` file. If you'd rather work from the command line than in the configuration file, navigate to Elasticsearch Home and enter ```bash ./bin/elasticsearch --cluster.name clustery_mcclusterface --node.name nody_mcnodeface ``` Feel free to change the node name or the cluster name. Once you configure Elasticsearch to your liking, start it up. ## Starting Elasticsearch Start Elasticsearch by navigating to Elasticsearch Home and typing ```bash ./bin/elasticsearch ``` if you run Linux, or ```bash \bin\elasticsearch.bat ``` if you run Windows. To run as a daemon in the background, add the `-d` switch to either command: ```bash ./bin/elasticsearch -d ``` Once both Elasticsearch and @product@ are installed and running, introduce them to each other. ## Configuring the Liferay Elasticsearch Connector The Elasticsearch connector provides integration between Elasticsearch and the portal. Before configuring the connector, make sure Elasticsearch is running. There are two ways to configure the connector: 1. [Use the System Settings application in the Control Panel.](#configuring-the-connector-in-the-control-panel) 2. [Manually create an OSGi configuration file.](#configuring-the-connector-with-an-osgi-config-file) It's convenient to configure the Elasticsearch connector from System Settings, but this is often only possible during development and testing. If you're not familiar with System Settings, read about it [here](/docs/7-2/user/-/knowledge_base/u/system-settings). Remember that you can generate configuration files for deployment to other systems by configuring System Settings, and then exporting the `.config` file with your configuration. ### Configuring the Connector in the Control Panel To configure the Elasticsearch connector from the System Settings application, 1. Start @product@. 2. Navigate to *Control Panel* → *Configuration* → *System Settings* → *Platform*. 3. Find the *Elasticsearch* entry (scroll down and browse to it or use the search box) and click the Actions icon (![Actions](../../../images/icon-actions.png)), then *Edit*. ![Figure 1: Use the System Settings application in @product@'s Control Panel to configure the Elasticsearch connector.](../../../images/cfg-elasticsearch-sys-settings.png) 4. Make any edits to the configuration and click *Save*. ![Figure 2: Configure the Elasticsearch connector's settings. Make sure you set the Operation Mode to *Remote*.](../../../images/cfg-elasticsearch-sys-settings2.png) | **Note:** If you switch operation modes (`EMBEDDED` → `REMOTE`), you must | trigger a re-index. Navigate to *Control Panel* → *Configuration* → | *Search*, and click *Execute* next to *Reindex all search indexes.* ### Configuring the Connector with an OSGi `.config` File When preparing a system for production deployment, you want to use a repeatable deployment process. Therefore, it's best to use the OSGi configuration file, where your configuration is maintained in a controlled source. Follow these steps to configure the Elasticsearch connector using a configuration file: 1. Create the following file: [Liferay_Home]/osgi/configs/com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config | **Elasticsearch 6:** The name of the `.config` file for the Elasticsearch | 6 connector is | `com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config` 2. Add configurations to the file, in the format `propertyName="Value"`. For example, operationMode="REMOTE" # If running Elasticsearch from a different computer: #transportAddresses="ip.of.elasticsearch.node:9300" # Highly recommended for all non-production usage (e.g., practice, tests, diagnostics): #logExceptionsOnly="false" 3. Start @product@ or re-index if already running. As you can see from the System Settings entry for Elasticsearch, there are a lot more configuration options available that help you tune your system for optimal performance. What follows here are some known good configurations for clustering Elasticsearch. These, however, can't replace the manual process of tuning, testing under load, and tuning again, so we encourage you to examine the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/important-settings.html) and go through that process once you have a working configuration. ## Configuring a Remote Elasticsearch Host In production systems Elasticsearch and @product@ are installed on different servers. To make @product@ aware of the Elasticsearch cluster, set transportAddresses=[IP address of Elasticsearch Node]:9300 Here's an example that sets the IP address of two nodes in the Elasticsearch cluster: transportAddresses=["192.168.1.1:9300","192.168.1.2:9300"] Set this in the Elasticsearch connector's OSGi configuration file. List as many or as few Elasticsearch nodes in this property as you want. This tells @product@ the IP address or host name where search requests should be sent. If using System Settings, set the value in the *Transport Addresses* property. | **Note:** In an Elasticsearch cluster you can list the transport addresses for | multiple Elasticsearch nodes as a comma-separated list in the | `transportAddresses` property. If you set only one transport address, @product@ | loses contact with Elasticsearch if that node goes down. On the Elasticsearch side, set the `network.host` property in your `elaticsearch.yml` file. This property simultaneously sets both the *bind host* (the host where Elasticsearch listens for requests) and the *publish host* (the host name or IP address Elasticsearch uses to communicate with other nodes). See [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html) for more information. ## Clustering Elasticsearch in Remote Operation Mode To cluster Elasticsearch, first set `node.max_local_storage_nodes` to be something greater than `1`. When you run the Elasticsearch start script, a new local storage node is added to the cluster. If you want four nodes running locally, for example, run `./bin/elasticsearch` four times. See [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-node.html#max-local-storage-nodes) for more information. Configure the number of shards and replicas in the Elasticsearch 6 connector, using the `indexNumberOfShards` and `indexNumberOfReplicas` properties to specify the number of primary shards and number of replica shards, respectively. Elasticsearch's default configuration works for a cluster of up to ten nodes, since the default number of shards is `5` and the default number of replica shards is `1`. | **Note:** Elasticsearch uses the [Zen Discovery | Module](https://www.elastic.co/guide/en/elasticsearch/reference/6.x/modules-discovery-zen.html) | by default, which provides unicast discovery. Additionally, nodes in the cluster | communicate using the [Transport | Module](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-transport.html), | through TCP. See the Elasticsearch documentation for the available properties | (to be set in the `elasticsearch.yml` file), and the @product@ Elasticsearch | connector's settings for the connector's available settings. | | At a minimum, provide the list of hosts (as `host:port`) to act as gossip | routers during unicast discovery in the `elasticsearch.yml`: | | discovery.zen.ping.unicast.hosts: ["node1.ip.address", "node2.ip.address"] | | For example, | | discovery.zen.ping.unicast.hosts: ["10.10.10.5", "10.10.10,.5:9305"] | | For more information on configuring an Elasticsearch cluster, see the | documentation on | [Elasticsearch Index Settings](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html). ## Elasticsearch Connector System Settings, By Operation Mode Some of the settings available for the Elasticsearch connector are applicable for only one operation mode (REMOTE or EMBEDDED). Refer to the table below: Connector Setting/Operation Mode | EMBEDDED | REMOTE | ------------------------------| :----: | :----: | `clusterName` | x | x `operationMode` | x | x `indexNamePrefix` | x | x `indexNumberOfReplicas*` | x | x `indexNumberOfShards*` | x | x `bootstrapMlockAll` | x | \- `logExceptionsOnly` | x | x `retryOnConflict` | x | x `discoveryZenPingUnicastHostsPort` | x | \- `networkHost` | x | \- `networkBindHost` | x | \- `networkPublishHost` | x | \- `transportTcpPort` | x | \- `transportAddresses` | \- | x `clientTransportSniff` | \- | x `clientTransportIgnoreClusterName` | \- | x `clientTransportPingTimeout*` | \- | x `clientTransportNodesSamplerInterval` | \- | x `httpEnabled` | x | \- `httpCORSEnabled` | x | \- `httpCORSAllowOrigin` | x | \- `httpCORSConfigurations` | x | \- `additionalConfigurations` | x | x `additionalIndexConfigurations` | x | x `additionalTypeMappings` | x | x `overrideTypeMappings` | x | x 1 This is, of course, a nod to all those fans of [Boaty Mcboatface](http://www.theatlantic.com/international/archive/2016/05/boaty-mcboatface-parliament-lessons/482046). ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/05-advanced-configuration.markdown ================================================ --- header-id: advanced-configuration-of-the-liferay-elasticsearch-connector --- # Advanced Configuration of the Liferay Elasticsearch Connector [TOC levels=1-4] The default configuration for Liferay's Elasticsearch connector module is set in a Java class called `ElasticsearchConfiguration`. While the Elasticsearch connector has a lot of configuration options out of the box, you might find an Elasticsearch configuration you need that isn't provided by default. In this case, add the configuration options you need. If something is configurable for Elasticsearch, it's configurable using the Elasticsearch connector. ## Adding Settings and Mappings to the Liferay Elasticsearch Connector Think of the available configuration options as being divided into two groups: the most common ones that are easily configured, and more complex configurations requiring a more brute-force approach: these include the `additionalConfigurations`, `additionalIndexConfigurations`, `additionalTypeMappings`, and `overrideTypeMappings` settings. [Figure 1: You can add Elasticsearch configurations to the ones currently available in System Settings.](../../../images/cfg-elasticsearch-additional-configs.png) ### Additional Configurations The `additionalConfigurations` configuration defines extra settings (in YAML) for the embedded Elasticsearch. This is only useful for testing environments using the embedded Elasticsearch server. Any node settings normally set in `elasticsearch.yml` can be declared here. See the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index.html) for a description of all possible node settings. ### Adding Index Configurations The `additionalIndexConfigurations` configuration defines extra settings (in JSON or YAML) that are applied to the @product@ index when it's created. For example, you can create custom analyzers and filters using this setting. For a complete list of available settings, see the [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html). Here's an example that shows how to configure [analysis](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules-analysis.html) that can be applied to a field or a dynamic template (see [below](#overriding-type-mappings) for an example application to a dynamic template). ```json { "analysis": { "analyzer": { "kuromoji_liferay_custom": { "filter": [ "cjk_width", "kuromoji_baseform", "pos_filter" ], "tokenizer": "kuromoji_tokenizer" } }, "filter": { "pos_filter": { "type": "kuromoji_part_of_speech" } } } } ``` ### Adding Type Mappings `additionalTypeMappings` defines extra mappings for the `LiferayDocumentType` type definition. These are applied when the index is created. Add the mappings using JSON syntax. For more information see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping.html) and [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/indices-put-mapping.html). Use `additionalTypeMappings` for new field (`properties`) mappings and new dynamic templates, but don't try to override existing mappings. If any of the mappings set here overlap with existing mappings, index creation fails. Use `overrideTypeMappings` to replace default mappings. As with dynamic templates, you can add sub-field mappings to @product@'s type mapping. These are referred to as [properties](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/properties.html) in Elasticsearch. ```json { "LiferayDocumentType": { "properties": { "fooName": { "index": "true", "store": "true", "type": "keyword" } } } } ``` See [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/mapping-types.html) for more details on Elasticsearch's field datatypes. The above example shows how a `fooName` field might be added to @product@'s type mapping. Because `fooName` is not an existing property in the mapping, it works fine. If you try to override an existing property mapping, index creation fails. Instead use the `overrideTypeMappings` setting to override `properties` in the mapping. To see that your additional mappings have been added to the `LiferayDocumentType`, use `curl` to access this URL after saving your additions and re-indexing: ```bash curl http://[HOST]:[ES_PORT]/liferay-[COMPANY_ID]/_mapping/LiferayDocumentType?pretty ``` Here's what it would look like for an Elasticsearch instance running on `localhost:9200`, with a @product@ Company ID of `20116`: ```bash curl http://localhost:9200/liferay-20116/_mapping/LiferayDocumentType?pretty ``` In the above URL, `liferay-20116`is the index name. Including it indicates that you want to see the mappings that were used to create the index with that name. ### Overriding Type Mappings Use `overrideTypeMappings` to override @product@'s default type mappings. This is an advanced feature that should be used only if strictly necessary. If you set this value, the default mappings used to define the Liferay Document Type in @product@ source code (for example, `liferay-type-mappings.json`) are ignored entirely, so include the whole mappings definition in this property, not just the segment you're modifying. To make a modification, find the entire list of the current mappings being used to create the index by navigating to the URL http://[HOST]:[ES_PORT]/liferay-[COMPANY_ID]/_mapping/LiferayDocumentType?pretty Copy the contents in as the value of this property (either into System Settings or your OSGi configuration file). Leave the opening curly brace `{`, but delete lines 2-4 entirely: ```json "liferay-[COMPANY_ID]": { "mappings" : { "LiferayDocumentType" : { ``` Then, from the end of the mappings, delete the concluding three curly braces. } } } Now modify whatever mappings you'd like. The changes take effect once you save the changes and trigger a re-index from Server Administration. Here's a partial example, showing a [dynamic template](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/dynamic-templates.html) that uses the analysis configuration from `additionalIndexConfigurations` to analyze all string fields that end with `_ja`. You'd include this with all the other default mappings, replacing the provided `template_ja` with this custom one: ```json { "LiferayDocumentType": { "dynamic_templates": [ { "template_ja": { "mapping": { "analyzer": "kuromoji_liferay_custom", "index": "analyzed", "store": "true", "term_vector": "with_positions_offsets", "type": "string" }, "match": "\\w+_ja\\b|\\w+_ja_[A-Z]{2}\\b", "match_mapping_type": "string", "match_pattern": "regex" } ... } ] } } ``` ## Multi-line YAML Configurations If you configure the settings from the last section using an OSGi configuration file, you might find yourself needing to write YAML snippets that span multiple lines. The syntax for that is straightforward and just requires appending each line with `\n\`, like this: ```yaml additionalConfigurations=\ cluster.routing.allocation.disk.threshold_enabled: false\n\ cluster.service.slow_task_logging_threshold: 600s\n\ index.indexing.slowlog.threshold.index.warn: 600s\n\ index.search.slowlog.threshold.fetch.warn: 600s\n\ index.search.slowlog.threshold.query.warn: 600s\n\ monitor.jvm.gc.old.warn: 600s\n\ monitor.jvm.gc.young.warn: 600s ``` From simple configurations to overriding existing type mappings, Elasticsearch and Liferay's connector to Elasticsearch are configurable. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/06-elasticsearch-connector-settings-reference.markdown ================================================ --- header-id: elasticsearch-connector-settings-reference --- # Elasticsearch Connector Settings: Reference [TOC levels=1-4] Elasticsearch is the default search engine for @product-ver@. The connection is managed through the *Liferay Connector to Elasticsearch [Version]*, and is configurable through System Settings or an OSGi configuration file named com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.config If you are using Elasticsearch 7, your configuration file must be named com.liferay.portal.search.elasticsearch7.configuration.ElasticsearchConfiguration.config Deploy the file to `[Liferay_Home]/osgi/configs` and a listener auto-detects it. The list below is all the configuration settings for Liferay's default Elasticsearch connector, in the order they appear in the System Settings application (The _Elasticsearch [Version]_ entry under the _Search_ category): `clusterName=LiferayElasticsearchCluster` : A String value that sets the name of the cluster to integrate with. This name should match the remote cluster when Operation Mode is set to remote. (See also: remote operation mode) `operationMode=EMBEDDED` : There are two operation modes you can choose from: EMBEDDED or REMOTE. Set to REMOTE to connect to a remote standalone Elasticsearch cluster. Set to EMBEDDED to start Liferay with an internal Elasticsearch instance. Embedded operation mode is unsupported for production environments. `indexNamePrefix=liferay-` : Set a String value to use as the prefix for the search index name. The default value should not be changed under normal conditions. If you change it, you must also perform a *reindex all* operation for the portal and then manually delete the old index using the Elasticsearch administration console. `indexNumberOfReplicas=` Set the number of replicas for each index. If left unset, no replicas are used. A full reindex is required to make changes take effect. `indexNumberOfShards=` Set the number of index shards to use when a Liferay index is created. If left unset, a single shard is used. A full reindex is required to make changes take effect. `bootstrapMlockAll=false` : A boolean setting that, when set to `true`, tries to lock the process address space into RAM, preventing any Elasticsearch memory from being swapped out (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-configuration-memory.html#bootstrap-memory_lock)) for more information) `logExceptionsOnly=true` : A boolean setting that, when set to true, only logs exceptions from Elasticsearch, and does not rethrow them. `retryOnConflict=5` : Set an int value for the number of retries to attempt if a version conflict occurs because the document was updated between getting it and updating it (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/docs-update.html#docs-update-api-query-params) for more information). `discoveryZenPingUnicastHostsPort=9300-9400` : Set a String value for the range of ports to use when building the value for discovery.zen.ping.unicast.hosts. Multiple Elasticsearch nodes on a range of ports can act as gossip routers at the same computer (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-discovery-hosts-providers.html) for more information). `networkHost=` : Set this String value to instruct the node to bind to this hostname or IP address and publish (advertise) this host to other nodes in the cluster. This is a shortcut which sets the bind host and the publish host at the same time (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html#common-network-settings) for more information). `networkBindHost=` : Set the String value of the network interface(s) a node should bind to in order to listen for incoming requests (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html#advanced-network-settings) for more information). `networkPublishHost=` : Set the String value of a single interface that the node advertises to other nodes in the cluster, so that those nodes can connect to it (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-network.html#advanced-network-settings) for more information). `transportTcpPort=` : Set the String value for the port to bind for communication between nodes. Accepts a single value or a range (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-transport.html#_tcp_transport) for more information). `transportAddresses=localhost:9300` : Set the String values for the addresses of the remote Elasticsearch nodes to connect to. This value is required when Operation Mode is set to remote (see [here](https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html) for more information). Specify as many or few nodes as you see fit. `clientTransportSniff=true` : Set this boolean to true to enable cluster sniffing and dynamically discover available data nodes in the cluster (see [here](https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html) for more information). `clientTransportIgnoreClusterName=false` : Set this boolean to true to ignore cluster name validation of connected nodes (see [here](https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html) for more information). `clientTransportPingTimeout=` The time (in seconds) the client node waits for a ping response from a node. If unset, the default Elasticsearch `client.transport.ping_timeout` is used. `clientTransportNodesSamplerInterval=` : Set this String value to instruct the client node on how often to sample / ping the nodes listed and connected (see [here](https://www.elastic.co/guide/en/elasticsearch/client/java-api/7.x/transport-client.html) for more information). `httpEnabled=true` : Set this boolean to false to disable the http layer entirely on nodes which are not meant to serve REST requests directly. As this setting was [deprecated in Elasticsearch 6.3](https://www.elastic.co/guide/en/elasticsearch/reference/6.7/release-notes-6.3.0.html#deprecation-6.3.0), the connector's corresponding setting is now also deprecated. This setting was only used for configuring the embedded Elasticsearch server, so its deprecation should have minimal impact to production deployments. `httpCORSEnabled=true` : Set this boolean to false to disable cross-origin resource sharing, i.e. whether a browser on another origin can do requests to Elasticsearch. If disabled, web front end tools like elasticsearch-head may be unable to connect (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html#_settings) for more information). `httpCORSAllowOrigin=/https?:\\/\\/localhost(:[0-9]+)?/` : Set the String origins to allow when HTTP CORS is enabled (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html#_settings) for more information). `httpCORSConfigurations=` : Set the String values for custom settings for HTTP CORS, in YML format (`elasticsearch.yml`) (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/modules-http.html#_settings) for more information). `additionalConfigurations=` : Set the String values for custom settings for embedded Elasticsearch, in YML format. See: Adding Settings to the Liferay Elasticsearch Connector `additionalIndexConfigurations=` : Set the String values for custom settings for the Liferay index, in JSON or YML format (refer to the Elasticsearch Create Index API for more information). See: Adding Settings to the Liferay Elasticsearch Connector `additionalTypeMappings=` : Set the String values for custom mappings for the `LiferayDocumentType`, in JSON format (refer to the Elasticsearch Put Mapping API for more information) See: Adding Settings to the Liferay Elasticsearch Connector `overrideTypeMappings=` Settings here override @product@'s default type mappings. This is an advanced feature that should be used only if strictly necessary. If you set this value, the default mappings used to define the Liferay Document Type in @product@ source code (for example, `liferay-type-mappings.json`) are ignored entirely, so include the whole mappings definition in this property, not just the segment you're modifying. ## Configurations only Affecting the Embedded Elasticsearch Server These settings (defined above) are only meant to use while configuring the embedded Elasticsearch server. Configuring these will elicit no effect on remote Elasticsearch installations: - `bootstrapMlockAll` - `discoveryZenPingUnicastHostsPort` - `networkHost` - `networkBindHost` - `networkPublishHost` - `transportTcpPort` - `httpEnabled` - `httpCORSEnabled` - `httpCORSAllowOrigin` - `httpCORSConfigurations` You can easily configure these settings in the System Setting application, or as mentioned above, you can specify them in a deployable OSGi `.config` file. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/07-security.markdown ================================================ --- header-id: installing-liferay-enterprise-search-security --- # Installing Liferay Enterprise Search Security [TOC levels=1-4] The very first thing you must do to secure Elasticsearch is enable X-Pack Security. After that you can begin configuring authentication and Transport Layer Security. | **Elasticsearch 6.x:** If you're using Elasticsearch 6, you'll need a Liferay | Enterprise Search (LES) subscription to use X-Pack. Starting with the Liferay | Connector to Elasticsearch 7 (available on Liferay Marketplace), X-Pack security | is included by default. X-Pack monitoring still requires LES. ## Enabling X-Pack Security To enable security, add this setting in `elasticsearch.yml`: ```yaml xpack.security.enabled: true ``` Now you can set up X-Pack users. ## Setting Up X-Pack Users In a system using X-Pack Security and X-Pack Monitoring, these built-in X-Pack users are important: - `kibana` - `elastic` Set the passwords for all X-Pack's [built-in users](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/built-in-users.html). The `setup-passwords` command is the simplest method to set the built-in users' first-use passwords for the first time. To update a password subsequently, use Kibana's UI or the [Change Password API](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/security-api-change-password.html). The `interactive` argument sets the passwords for all built-in users. The configuration shown in these articles assumes you set all passwords to *liferay*. Of course, that's not recommended for production systems. ```bash ./bin/elasticsearch-setup-passwords interactive ``` Elastic's [setup-passwords command](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-passwords.html) documentation describes additional options. Since you're securing Elasticsearch, remember the `elastic` user's password. Enable transport layer security on each node. ## Enabling Transport Layer Security The following instructions for enabling TLS use `liferay` as the password whenever one is needed. Use your own passwords for your installation. | **Important:** Elasticsearch and @product@ must share the keys and certificates | used to configure TLS. Copy them between servers and point to the local copy in | the corresponding configuration files. ### Generate Node Certificates [Generate a node certificate](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html#node-certificates) for each node. Alternatively, use a Certificate Authority to obtain node certificates. 1. Create a certificate authority, using [X-Pack's `certutil`](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/certutil.html) command: ```bash ./bin/elasticsearch-certutil ca --pem --ca-dn CN=localhost ``` This generates a ZIP file. Unzip the contents in the `[Elasticsearch Home]/config/certs` folder. 2. Generate X.509 certificates and private keys using the CA from Step 1: ```bash ./bin/elasticsearch-certutil cert --pem --ca-cert /path/to/ca.crt --ca-key /path/to/ca.key --dns localhost --ip 127.0.0.1 --name localhost ``` This generates another ZIP file. Extract the contents in the `[Elasticsearch Home]/config/certs` folder. | **Note:** The `certutil` command defaults to using the *PKSC#12* format for | certificate generation. Since Kibana does not work with PKSC#12 certificates, | the `--pem` option (generates the certificate in PEM format) is important if | you're using X-Pack monitoring. **Checkpoint:** You now have the following files in your `[Elasticsearch Home]/config/certs` folder: ```bash ca.crt ca.key localhost.crt localhost.key ``` ### Enable TLS for Elasticsearch 7 [Enable TLS](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html#enable-ssl) on each node via its `elasticsearch.yml`. 1. Enable transport layer TLS with these settings in `elasticsearch.yml` for inter-node communication: ```yaml xpack.security.transport.ssl.enabled: true ``` 2. Add the certificate, key and certificate authority paths to each node's `elasticsearch.yml`: ```yaml xpack.security.transport.ssl.certificate: certs/localhost.key xpack.security.transport.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.security.transport.ssl.key: certs/localhost.crt xpack.security.transport.ssl.verification_mode: certificate ``` The example paths above assume you added the certificate to `Elasticsearch Home/config/`. 3. Enable TLS on the HTTP layer to encrypt client communication: ```yaml xpack.security.http.ssl.enabled: true ``` 4. Configure the certificate, key, and certificate authority paths to each node's `elasticsearch.yml`: ```yaml xpack.security.http.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.security.http.ssl.certificate: certs/localhost.crt xpack.security.http.ssl.key: certs/localhost.key xpack.security.http.ssl.verification_mode: certificate ``` ### Elasticsearch 6 TLS The settings on Elasticsearch 6 were slightly different than those presented above for Elasticsearch 7. [Enable TLS](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-tls.html#enable-ssl) on each node via its `elasticsearch.yml`. 1. Add the certificate, key and certificate authority paths to each node's `elasticsearch.yml`: xpack.ssl.certificate: certs/localhost.crt xpack.ssl.certificate_authorities: [ "certs/ca.crt" ] xpack.ssl.key: certs/localhost.key xpack.ssl.verification_mode: certificate The example paths above assume you added the certificate to `Elasticsearch Home/config/`. 2. Enable transport layer TLS with these settings in `elasticsearch.yml`: xpack.security.transport.ssl.enabled: true 3. Enable TLS on the HTTP layer to encrypt client communication: xpack.security.http.ssl.enabled: true After X-Pack is installed and TLS is enabled, configure the X-Pack Security adapter in @product@. ### Example Elasticsearch Security Configuration For ease of copying and pasting, here is the complete Elasticsearch configuration (`elasticsearch.yml`) used in this guide (with the Elasticsearch 6 example commented out): ```yaml # For Elasticsearch 7.3/7.4 cluster.name: LiferayElasticsearchCluster # X-Pack Security xpack.security.enabled: true ## TLS/SSL settings for Transport layer xpack.security.transport.ssl.enabled: true xpack.security.transport.ssl.verification_mode: certificate xpack.security.transport.ssl.key: certs/localhost.key xpack.security.transport.ssl.certificate: certs/localhost.crt xpack.security.transport.ssl.certificate_authorities : [ "certs/ca.crt" ] # TLS/SSL settings for HTTP layer xpack.security.http.ssl.enabled: true xpack.security.http.ssl.verification_mode: certificate xpack.security.http.ssl.key: certs/localhost.key xpack.security.http.ssl.certificate: certs/localhost.crt xpack.security.http.ssl.certificate_authorities : [ "certs/ca.crt" ] # Comment out when Kibana and Liferay's X-Pack Monitoring are also configured #xpack.monitoring.collection.enabled: true # For Elasticsearch 6.5/6.8 #cluster.name: LiferayElasticsearchCluster # # X-Pack Security #xpack.security.enabled: true # # Enable TLS/SSL #xpack.security.transport.ssl.enabled: true # To enable Transport level SSL for internode-communication #xpack.security.http.ssl.enabled: true # To enable HTTP level SSL required by Kibana # ## General TLS/SSL settings for both Transport and HTTP levels #xpack.ssl.verification_mode: certificate #xpack.ssl.key: certs/localhost.key #xpack.ssl.certificate: certs/localhost.crt #xpack.ssl.certificate_authorities : [ "certs/ca.crt" ] # # Comment out when Kibana and Liferay's X-Pack Monitoring are also configured #xpack.monitoring.collection.enabled: true ``` For both Elasticsearch 6 and Elasticsearch 7, the Liferay Connector settings are the same. ## Install and Configure the Liferay Enterprise Search Security app If you have a Liferay Enterprise Search subscription, [download](https://web.liferay.com/group/customer/dxp/downloads/enterprise-search) the Liferay Enterprise Search Security app . Install the LPKG file by copying it into the `Liferay Home/deploy` folder. To configure the X-Pack adapter, navigate to *Control Panel* → *Configuration* → *System Settings*. Find the *Search* category and click on the *X-Pack Security* entry. You can enter the property values here, but it's more common to use a [configuration file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) deployed to `[Liferay Home]/osgi/configs`. For the X-Pack security connector, create a file called ``` com.liferay.portal.search.elasticsearch7.configuration.XPackSecurityConfiguration.config ``` The exact contents of the file depend on your X-Pack setup. To configure the adapter according to the Elasticsearch setup documented here, populate the file like this: ```properties sslKeyPath="/path/to/localhost.key" sslCertificatePath="/path/to/localhost.crt" certificateFormat="PEM" requiresAuthentication="true" username="elastic" password="liferay" sslCertificateAuthoritiesPaths="/path/to/ca.crt" transportSSLVerificationMode="certificate" transportSSLEnabled="true" ``` The `password` should match what you set during the X-Pack password setup above. The certificate and key files referenced here are the same ones used on the Elasticsearch server. Copy them to the @product@ server and update their paths in the configuration accordingly. Enable authentication by setting `requiresAuthentication` to `true` and providing the credentials for the Elasticsearch user. For TLS, enable transport TLS, set the certificate verification mode and certificate format, and provide the path to the certificate, key, and certificate authority. Of course, the exact values depend on your X-Pack configuration. Here's the complete list of configuration options for the X-Pack Connector: - `sslKeyPath` - `sslCertificatePath` - `sslCertificateAuthoritiesPaths` - `certificateFormat` - `requiresAuthentication` - `username` - `password` - `transportSSLVerificationMode` - `transportSSLEnabled` - `sslKeystorePath` - `sslKeystorePassword` - `sslTruststorePath` - `sslTruststorePassword` When you're finished configuring X-Pack Security, restart Elasticsearch. These steps require a full cluster restart. ### Disabling Elasticsearch Deprecation Logging Some Elasticsearch APIs used by Liferay's Elasticsearch 6 connector were deprecated as of Elasticsearch 6.6 and 6.7. This can result WARN log entries in Elasticsearch's deprecation log when @product@ is configured with Elasticsearch 6.8.x and X-Pack Security is enabled: ```sh 2019-07-16T14:47:05,779][WARN ][o.e.d.c.j.Joda ] [ ode_name]'y' year should be replaced with 'u'. Use 'y' for year-of-era. Prefix your date format with '8' to use the new specifier. [2019-07-16T14:47:06,007][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.certificate] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,007][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.certificate_authorities] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,008][WARN ][o.e.d.c.s.Settings ] [ ode_name][xpack.ssl.key] setting was deprecated in Elasticsearch and will be removed in a future release! See the breaking changes documentation for the next major version. [2019-07-16T14:47:06,463][WARN ][o.e.d.x.c.s.SSLService ] [ ode_name]SSL configuration [xpack.http.ssl] relies upon fallback to another configuration for [key configuration, trust configuration], which is deprecated. [2019-07-16T14:47:06,464][WARN ][o.e.d.x.c.s.SSLService ] [ ode_name]SSL configuration [xpack.security.transport.ssl.] relies upon fallback to another configuration for [key configuration, trust configuration], which is deprecated. ``` These warnings do not signal any functional issues, and can be disabled (see [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/logging.html#deprecation-logging) to learn how). ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/08-backing-up-es.markdown ================================================ --- header-id: backing-up-elasticsearch --- # Backing Up Elasticsearch [TOC levels=1-4] [Elasticsearch replicas](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules.html#index-modules-settings) protect against a node going down, but they won't help you with a catastrophic failure. Only good backup practices can help you then. Back up and restore your Elasticsearch cluster in three steps: 1. Configure a repository 2. Make a snapshot of the cluster 3. Restore from the snapshot For more detailed information, refer to the [Elasticsearch administration guide](https://www.elastic.co/guide/en/elasticsearch/guide/master/administration.html), and in particular to the documentation on the [Snapshot and Restore module](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshot-restore.html). ## Creating a Repository First [create a repository](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-register-repository.html) to store your snapshots. Several repository types are supported: - Shared file system, such as a Network File System or NAS - Amazon S3 - HDFS (Hadoop Distributed File System) - Azure Cloud If using a shared file system repository type, first register the path to the shared file system in each node's `elasticsearch.yml` using [the path.repo setting](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-register-repository.html#snapshots-filesystem-repository). ```yaml path.repo: ["path/to/shared/file/system/"] ``` Once the path to the folder hosting the repository is registered (make sure the folder exists), create the repository with a PUT command. For example, ```bash curl -X PUT "localhost:9200/_snapshot/test_backup" -H 'Content-Type: application/json' -d' { "type": "fs", "settings": { "location": "/path/to/shared/file/system/" } }' ``` Replace `localhost:9200` with the proper `hostname:port` combination for your system, replace `test_backup` with the name of the repository to create, and use the absolute path to your shared file system in the `location`. If the repository is set up successfully, you see this message: ```json {"acknowledged":true} ``` Once the repository exists, you can start creating snapshots. ## Taking Snapshots of the Cluster The easiest snapshot approach is to create a [snapshot of all the indexes in your cluster](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-take-snapshot.html). For example, ```bash curl -XPUT localhost:9200/_snapshot/test_backup/snapshot_1 ``` If `{"accepted":true}` appears in the terminal, the snapshot was a success. It's possible to be more selective when taking snapshots. For example, if you use LES Monitoring, you can exclude the monitoring indexes. Explicitly declare the indexes to include in the snapshot. For example, ```bash curl -XPUT localhost:9200/_snapshot/test_backup/snapshot_2 { "indices": "liferay-0,liferay-20116" } ``` **Note:** For a list of all the Elasticsearch indexes, use this command: ```bash curl -X GET "localhost:9200/_cat/indices?v" ``` This shows the index metrics: ```bash health status index uuid pri rep docs.count docs.deleted store.size pri.store.size green open liferay-20099 obqiNE1_SDqfuz7rincrGQ 1 0 195 0 303.1kb 303.1kb green open liferay-47206 3YEjtye1S9OVT0i0EZcXcw 1 0 7 0 69.7kb 69.7kb green open liferay-0 shBWwpkXRxuAmGEaE475ug 1 0 147 1 390.9kb 390.9kb ``` It's important to note that Elasticsearch uses a *smart snapshots* approach. To understand what that means, consider a single index. The first snapshot includes a copy of the entire index, while subsequent snapshots only include the delta between the first, complete index snapshot and the current state of the index. Eventually you'll end up with a lot of snapshots in your repository, and no matter how cleverly you name the snapshots, you may forget what some snapshots contain. For this purpose, the Elasticsearch API provides getting information about any snapshot. For example: ```bash curl -XGET localhost:9200/_snapshot/test_backup/snapshot_1 ``` returns ```json {"snapshots":[ {"snapshot":"snapshot_1", "uuid":"WlSjvJwHRh-xlAny7zeW3w", "version_id":6.80399, "version":"6.8.2", "indices":["liferay-20099","liferay-0","liferay-47206"], "state":"SUCCESS", "start_time":"2018-08-15T21:40:17.261Z", "start_time_in_millis":1534369217261, "end_time":"2018-08-15T21:40:17.482Z", "end_time_in_millis":1534369217482, "duration_in_millis":221, "failures":[], "shards":{ "total":3, "failed":0, "successful":3 } } ]} ``` There's lots of useful information here, including which indexes were included in the snapshot. If you want to get rid of a snapshot, use the `DELETE` command. ```bash curl -XDELETE localhost:9200/_snapshot/test_backup/snapshot_1 ``` You might trigger creation of a snapshot and regret it (for example, you didn't want to include all the indexes in the snapshot). If your snapshots contain a lot of data, this can cost time and resources. To cancel the ongoing creation of a snapshot, use the same `DELETE` command. The snapshot process is terminated and the partial snapshot is deleted from the repository. ## Restoring from a Snapshot What good is a snapshot if you can't use it to [restore your search indexes](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshots-restore-snapshot.html) in case of catastrophic failure? Use the `_restore` API to restore all the snapshot's indexes: ```bash curl -XPOST localhost:9200/_snapshot/test_backup/snapshot_1/_restore ``` Restore only specific indexes from a snapshot by passing in the `indices` option, and rename the indexes using the `rename_pattern` and `rename_replacement` options: ```bash curl -XPOST localhost:9200/_snapshot/test_backup/snapshot_1/_restore { "indices": "liferay-20116", "rename_pattern": "liferayindex_(.+)", "rename_replacement": "restored_liferayindex_$1" } ``` This restores only the index named `liferay-20116index_1` from the snapshot. The `rename...` settings specify that the beginning `liferayindex_` are replaced with `restored_liferayindex_`, so `liferay-20116index_1` becomes `restored_liferay-20116index_1`. As with the process for taking snapshots, an errant restored index can be canceled with the `DELETE` command: ```bash curl -XDELETE localhost:9200/restored_liferay-20116index_3 ``` Nobody likes catastrophic failure on a production system, but Elasticsearch's API for taking snapshots and restoring indexes can help you rest easy knowing that your search cluster can be restored if disaster strikes. For more details and options, read Elastic's documentation on the [Snapshot and Restore Module](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/snapshot-restore.html). ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/09-upgrading-to-elasticsearch_65.markdown ================================================ --- header-id: upgrading-to-elasticsearch-6-5 --- # Upgrading to Elasticsearch 6.5 [TOC levels=1-4] Elasticsearch 6.5.x is the default, most up-to-date supported version of Elasticsearch for @product-ver@. If you're upgrading @product@ and still running Elasticsearch 6.1, it's time to upgrade your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6.1.x server, follow the [installation guide](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch) to install Elasticsearch and the [configuration guide](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector) to configure the Elasticsearch adapter. Here, you'll learn to upgrade an existing Elasticsearch 6.1.x server (or cluster) to Elasticsearch 6.5.x: 1. [Install and configure Elasticsearch 6.5.x](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch). 2. Disable X-Pack Security in `elasticsearch.yml` unless you have an Liferay Enterprise Search subscription, which gives you access to the Liferay Enterprise Search Security app: ```yml xpack.security.enabled: false ``` 3. Configure the bundled Liferay Connector to Elasticsearch 6. 4. Re-index all search and spell check indexes. | **Before Proceeding,** back up your existing data before upgrading | Elasticsearch. If something goes wrong during or after the upgrade, roll back | to the previous version using the uncorrupted index snapshots. See | [here](/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch) for more | information. Learn about configuring Elasticsearch [here](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector). ## Re-index Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to *Control Panel* → *Configuration* → *Server Administration*, and click *Execute* for the *Reindex all search indexes* entry. You must also re-index the spell check indexes. ## Reverting to Elasticsearch 6.1 Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 6.5, roll back to Elasticsearch 6.1 and regroup. Since your 6.1 and 6.5 are currently two separate installations, this procedure is straightforward: 1. Stop the Liferay Connector to Elasticsearch 6. 2. Stop Elasticsearch 6.5 and make sure that the Elasticsearch 6.1 `elasticsearch.yml` and the connector app are configured to use the same port (9200 by default). 3. Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/10-upgrading-to-elasticsearch_68.markdown ================================================ --- header-id: upgrading-to-elasticsearch-6-8 --- # Upgrading to Elasticsearch 6.8 [TOC levels=1-4] Elasticsearch 6.8.x is supported for @product-ver@. If you're upgrading @product@ and still running Elasticsearch 6.1, it's time to upgrade your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6.1.x server, follow the [installation guide](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch) to install Elasticsearch and the [configuration guide](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector) to configure the Elasticsearch adapter. Here, you'll learn to upgrade an existing Elasticsearch 6.1.x server (or cluster) to Elasticsearch 6.8.x: 1. [Install and configure Elasticsearch 6.8.x](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch). 2. Disable X-Pack Security in `elasticsearch.yml` unless you have an Liferay Enterprise Search subscription which gives you access to the LES Security app: ```yml xpack.security.enabled: false ``` 3. Configure the bundled Liferay Connector to Elasticsearch 6. 4. Re-index all search and spell check indexes. | **Before Proceeding,** back up your existing data before upgrading | Elasticsearch. If something goes wrong during or after the upgrade, roll back | to the previous version using the uncorrupted index snapshots. See | [here](/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch) for more | information. Learn about configuring Elasticsearch [here](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector). ## Re-index Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to *Control Panel* → *Configuration* → *Server Administration*, and click *Execute* for the *Reindex all search indexes* entry. You must also re-index the spell check indexes. ## Reverting to Elasticsearch 6.1 Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 6.8, roll back to Elasticsearch 6.1 and regroup. Since your 6.1 and 6.8 are currently two separate installations, this procedure is straightforward: 1. Stop the Liferay Connector to Elasticsearch 6. 2. Stop Elasticsearch 6.8 and make sure that the Elasticsearch 6.1 `elasticsearch.yml` and the connector app are configured to use the same port (9200 by default). 3. Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/02-elasticsearch/11-upgrading-to-elasticsearch-73.markdown ================================================ --- header-id: upgrading-to-elasticsearch-7 --- # Upgrading to Elasticsearch 7 [TOC levels=1-4] Elasticsearch 7 is supported for @product-ver@. If you're upgrading @product@ and still running Elasticsearch 6, consider upgrading your Elasticsearch servers too. If you're setting up a new system and not already running a remote Elasticsearch 6 server, follow the [installation guide](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch) to install Elasticsearch and the [configuration guide](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector) to configure the Elasticsearch adapter. | **Before Proceeding,** back up your existing data before upgrading | Elasticsearch. If something goes wrong during or after the upgrade, roll back | to the previous version using the uncorrupted index snapshots. See | [here](/docs/7-2/deploy/-/knowledge_base/d/backing-up-elasticsearch) for more | information. Here, you'll learn to upgrade an existing Elasticsearch 6 server (or cluster) to Elasticsearch 7: 1. [Install and configure Elasticsearch 7](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch). 2. Back up the application specific indexes for Workflow Metrics and Result Rankings. 3. In @product-ver@, security is now provided out of the box. If you're using X-Pack security, enable it (it's disabled by default): ```yml xpack.security.enabled: true ``` 4. Blacklist the bundled Liferay Connector to Elasticsearch 6. 5. Install and configure the Liferay Connector to Elasticsearch 7. 6. Re-index all search and spell check indexes. | **Known Issue:** See | [LPS-103938](https://issues.liferay.com/browse/LPS-103938). The Liferay | Connector to Elasticsearch 7 throws an exception in the log when the LPKG | file is deployed. There are no known functional impacts. If unexpected errors | occur, re-start the @product@ server. Learn about configuring Elasticsearch [here](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector). ## Backing up Application-Specific Indexes To preserve data stored in application-specific indexes, use a [rolling upgrade](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/rolling-upgrades.html) for each index you need to preserve across the upgrade. | **Synonym Sets:** If you follow the workaround for the bug | [LPS-100272](https://issues.liferay.com/browse/LPS-100272), your Synonym sets | are preserved across the upgrade, as they are stored in the index settings | directly, and not in their own index. ## Blacklisting Elasticsearch 6 To blacklist Elasticsearch 6, 1. Create a configuration file named ```bash com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config ``` 2. Give it these contents: ```properties blacklistBundleSymbolicNames=[ \ "com.liferay.portal.search.elasticsearch6.api", \ "com.liferay.portal.search.elasticsearch6.impl", \ "com.liferay.portal.search.elasticsearch6.spi", \ "com.liferay.portal.search.elasticsearch6.xpack.security.impl", \ "Liferay Connector to X-Pack Security [Elastic Stack 6.x] - Impl", \ "Liferay Enterprise Search Security - Impl" \ ] ``` ## Re-index Once the Elasticsearch adapter is installed and talking to the Elasticsearch cluster, navigate to *Control Panel* → *Configuration* → *Search*, and click *Execute* for the *Reindex all search indexes* entry. You must also re-index the spell check indexes. ## Reverting to Elasticsearch 6 Stuff happens. If that stuff involves an unrecoverable failure during the upgrade to Elasticsearch 7, roll back to Elasticsearch 6 and regroup. Since your Elasticsearch 6 and 7 are currently two separate installations, this procedure takes only a few steps: 1. Stop the Liferay Connector to Elasticsearch 6. 2. Stop Elasticsearch 7 and make sure that the Elasticsearch 6 `elasticsearch.yml` and the connector app are configured to use the same port (9200 by default). 3. Start the Elasticsearch server, and then restart the Liferay Connector to Elasticsearch 6. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/03-solr/01-installing-solr-intro.markdown ================================================ --- header-id: installing-solr --- # Installing Solr [TOC levels=1-4] Solr is a popular enterprise search platform built on Apache Lucene. It's reliable, scalable, and fault tolerant. Read more about it [here](http://lucene.apache.org/solr/). [Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-liferay-elasticsearch-connector) is the default search engine that ships with @product@, and some Liferay Search features are only available on Elasticsearch. It's valid, however, to use Solr instead. In particular, if you've already been using Solr with a previous version of @product@, or your platform (for example, your OS or JVM) [isn't supported by Elasticsearch](https://www.elastic.co/support/matrix), you might choose to use Solr to search and index your @product@ data. There are circumstances that force you to use Elasticsearch instead of Solr. Read [here](/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine#choosing-a-search-engine) for more information. Liferay DXP 7.2, Fix Pack 1, supports Solr 7.5.x through the Liferay Connector to Solr 7 application, version 2.0.0. Liferay DXP 7.2, Service Pack 1/Fix Pack 2 and later, supports Solr 7.5.x through the Liferay Connector to Solr 7 application, version 2.0.1. Liferay Portal CE 7.2, GA2 and later (not available at time of writing), support Solr 7.5.x through the Liferay CE Connector to Solr 7 application. | **Upgrading to Service Pack 1 or Fix Pack 2 (or later) requires installation of | a new Solr connector:** If you were running version 2.0.0 of the Liferay | Connector to Solr 7 application, and you want to install Service Pack 1/Fix Pack | 1 (or later), you must install version 2.0.1 of the Liferay Connector to Solr 7 | application. ## Blacklisting Elasticsearch-Only Features Before installing Solr, you must [blacklist](/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components) certain DXP [features that only work with Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/installing-a-search-engine#choosing-a-search-engine). 1. Create a configuration file named ```sh com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config ``` 2. Give it these contents: ```properties blacklistBundleSymbolicNames=["com.liferay.portal.search.tuning.web.api","com.liferay.portal.search.tuning.web","com.liferay.portal.search.tuning.synonyms.web","com.liferay.portal.search.tuning.rankings.web"] ``` 3. Place the file in `Liferay Home/osgi/configs`. It is required during the Solr installation process to also [stop the Elasticsearch Connectors](https://portal.liferay.dev/docs/7-2/deploy/-/knowledge_base/d/installing-solr-basic-installation#stopping-the-elasticsearch-connector) that ship with @product@. If you're ready to blacklist those bundles now, use these contents in the blacklist configuration file: ```properties blacklistBundleSymbolicNames=["com.liferay.portal.search.tuning.web.api","com.liferay.portal.search.tuning.web","com.liferay.portal.search.tuning.synonyms.web","com.liferay.portal.search.tuning.rankings.web","com.liferay.portal.search.elasticsearch6.spi","com.liferay.portal.search.elasticsearch6.api","com.liferay.portal.search.elasticsearch6.impl","Liferay Enterprise Search Monitoring ","Liferay Enterprise Search Security "] ``` The Liferay Enterprise Search bundles must be excluded if you don't have a LES subscription. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/03-solr/02-installing-solr-basic.markdown ================================================ --- header-id: installing-solr-basic-installation --- # Installing Solr: Basic Installation [TOC levels=1-4] There are two ways to install the Liferay Connector to Solr 7: 1. Navigate to [Liferay Marketplace](https://web.liferay.com/marketplace/) and download the app that corresponds to your portal. Once the app LPKG is downloaded, copy it to `Liferay_Home/osgi/marketplace`. 2. In your running portal instance, navigate to *Control Panel* → *Apps* → *Store*. Sign in using your credentials, search for Solr Search Engine, and purchase (it's free) the Liferay Connector to Solr 7 entry. As you proceed, remember these terms: *Solr Home*: The center of the Solr system (pun intended). This directory is `solr-[version]/server/solr`. *Liferay Home*: The root folder of your @product@ installation. It contains the `osgi`, `deploy`, `data`, and `license` folders, among others. There are two installation steps: 1. Installing and configuring Solr 7. 2. Installing and configuring the Solr 7 connector for @product@. Before configuring @product@ for Solr, install and set up Solr. ## Installing and Configuring Solr 7 To install and properly configure Solr for @product@: 1. Download [Solr](http://archive.apache.org/dist/lucene/solr/7.5.0/) and unzip it. 2. Navigate to `solr-[version]/server/solr`. This is Solr Home. 3. Create a new folder called `liferay`. 4. In the `liferay` folder, create two new folders: `conf` and `data`. 5. Copy the contents of `Solr_Home/configsets/_default/conf` to `Solr_Home/liferay/conf`. 6. Open the Liferay Connector to Solr 7's LPKG file with an archive manager. Open the `com.liferay.portal.search.solr7.impl.jar` file, and extract META-INF/resources/solrconfig.xml and META-INF/resources/schema.xml to Solr_Home/liferay/conf This replaces the current `solrconfig.xml` and `schema.xml` files with ones that tell Solr how to index data coming from @product@. 7. Create a `core.properties` file in `Solr_Home/liferay` and add this configuration: ```properties config=solrconfig.xml dataDir=data name=liferay schema=schema.xml ``` 8. Checkpoint: your `Solr_Home/liferay` folder now has this structure: ```sh liferay ├── conf │   ├── lang │   │   ├── contractions_ca.txt │   │   ├── ....txt │   ├── managed-schema │   ├── params.json │   ├── protwords.txt │   ├── schema.xml │   ├── solrconfig.xml │   ├── stopwords.txt │   └── synonyms.txt ├── core.properties └── data ``` 8. Start the Solr server by entering ./bin/solr start -f from the top-level folder of your Solr installation (`solr-[version]`). 9. The Solr server listens on port `8983` by default. Navigate to `http://localhost:8983/solr/#/~cores` (assuming you're testing locally with `localhost` as your host), and confirm that the `liferay` core is available. Solr is now installed. Next install and configure the Solr connector. ## Installing and Configuring the Liferay Solr Adapter Since Elasticsearch is the default search engine, the Elasticsearch connector is already installed and running. You must stop it before configuring the Solr connector. ### Stopping the Elasticsearch Connector Stop the Elasticsearch connector bundle using the App Manager, the Felix Gogo shell, or the bundle blacklist. If you're a Liferay DXP customer, use the blacklist feature as described below. The App Manager and Gogo shell rely on the `osgi/state` folder to "remember" the state of the bundle. If you delete this folder (recommended during patching) the Elasticsearch connector is reinstalled and started automatically. Navigate to Control Panel → Apps → App Manager. Once in the App Manager, search for *elasticsearch*. Find the Liferay Connector to Elasticsearch 6 module and click the Actions ((![Actions](../../../images/icon-actions.png))) button. Choose the Deactivate option. This leaves the bundle installed, but stops it in the OSGi runtime. Alternatively, use the [Felix Gogo shell](/developer/tutorials/-/knowledge_base/7-2/using-the-felix-gogo-shell) to stop the Elasticsearch connector. Enter lb elasticsearch You'll see two active bundles for the Liferay Connector to Elasticsearch 6: an API and an IMPL bundle. ```sh ID|State |Level|Name 476|Active | 10|Liferay (CE) Connector to Elasticsearch 6 - API (3.0.0) 478|Active | 10|Liferay Portal Search Elasticsearch 6 API (3.0.4) 480|Active | 10|Liferay Portal Search Elasticsearch 6 SPI (3.2.1) 706|Active | 10|Liferay (CE) Connector to Elasticsearch 6 - Impl (3.0.0) 707|Active | 10|Liferay Portal Search Elasticsearch 6 Implementation (3.0.15) ``` Stop the API bundle by entering stop [bundle ID] In the example above, the `[bundle ID]` is `476`. **Liferay DXP:** DXP customers should [blacklist](/docs/7-2/user/-/knowledge_base/u/blacklisting-osgi-bundles-and-components) the Elasticsearch, Shield, and Marvel plugins. 1. Create a com.liferay.portal.bundle.blacklist.internal.BundleBlacklistConfiguration.config file with these contents: ```properties blacklistBundleSymbolicNames=["com.liferay.portal.search.elasticsearch6.spi","com.liferay.portal.search.elasticsearch6.api","com.liferay.portal.search.elasticsearch6.impl","Liferay Enterprise Search Monitoring ","Liferay Enterprise Search Security "] ``` If the LES Security and Monitoring LPKG files are installed, you must blacklist these too. 2. Place the file in `Liferay Home/osgi/configs`. ### Install and Configure the Solr Connector Now you're ready to install the connector: 1. Start @product@, then deploy the Solr connector by copying the LPKG you downloaded to `Liferay_Home/deploy`. You'll see a `STARTED` message in your @product@ log once the Solr connector is installed. Here's what the log message looks like: ```sh 2018-11-06 19:59:49.396 INFO [pipe-start 943 944][BundleStartStopLogger:39] STARTED com.liferay.portal.search.solr7.api_2.0.5 [943] 2018-11-06 19:59:49.490 INFO [pipe-start 943 944][BundleStartStopLogger:39] STARTED com.liferay.portal.search.solr7.impl_2.0.11 [944] ``` 2. To re-index against Solr, navigate to *Control Panel* → *Configuration* → *Search*, and click *Execute* next to the *Reindex all search indexes* option. ![Figure 1: Once the Solr connector is installed, you can re-index your @product@ data against your Solr server.](../../../images/solr-reindex.png) In production deployments, specify your edits to the Solr connector's default configurations using a configuration file deployed to the `Liferay_Home/osgi/configs` folder. Name the file com.liferay.portal.search.solr7.configuration.SolrConfiguration.config During testing and development, use the Solr 7 System Settings entry Control Panel → Configuration → System Settings for editing the default configurations. ![Figure 2: You can configure Solr from @product@'s System Settings application. This is most useful during development and testing.](../../../images/solr-system-settings.png) The next article covers clustering Solr with SolrCloud. ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/03-solr/03-installing-solr-solrcloud.markdown ================================================ --- header-id: high-availability-with-solrcloud --- # Installing Solr: High Availability with SolrCloud [TOC levels=1-4] Use SolrCloud if you need a cluster of Solr servers. Note that to use SolrCloud in production, you should set up an [external ZooKeeper ensemble](https://cwiki.apache.org/confluence/display/solr/Setting+Up+an+External+ZooKeeper+Ensemble). [ZooKeeper](http://zookeeper.apache.org/) is a centralized coordination service for managing distributed systems like your SolrCloud cluster. The steps included here should be considered the bare minimum of what must be done to configure SolrCloud with @product@. For example, these instructions cover configuring SolrCloud on a single machine, whereas a production environment would feature multiple physical or virtual machines. These instructions also assume you've followed the earlier section on *Installing and Configuring Solr 7*. Refer to the [SolrCloud guide for more information](https://cwiki.apache.org/confluence/display/solr/SolrCloud). 1. Stop the Solr server if it's running. 2. Navigate to the `Solr_Home/configsets` folder and create a folder called liferay_configs 3. Copy the `conf` folder from `Solr_Home/liferay` to the `liferay_configs` folder you just created. The `configset/liferay_configs` folder contains the SolrCloud @product@ collection configuration and is uploaded to ZooKeeper. By copying the `conf` folder from the `liferay` server configured earlier, you're using the `schema.xml` and `solrconfig.xml` files provided with the Liferay Solr Adapter. 4. Next launch an interactive SolrCloud session to configure your SolrCloud cluster. Use this command: ./bin/solr -e cloud 5. Complete the setup wizard. These steps demonstrate creating a two-node cluster: - Enter `2` for the number of nodes. - Specify ports `8983` and `7574` (the defaults). Both nodes are started with the start commands printed in the log: ```sh Starting up Solr on port 8983 using command: "bin/solr" start -cloud -p 8983 -s "example/cloud/node1/solr" Waiting up to 180 seconds to see Solr running on port 8983 [|] [-] Started Solr server on port 8983 (pid=8846). Happy searching! Starting up Solr on port 7574 using command: "bin/solr" start -cloud -p 7574 -s "example/cloud/node2/solr" -z localhost:9983 Waiting up to 180 seconds to see Solr running on port 7574 [|] [/] Started Solr server on port 7574 (pid=9026). Happy searching! ``` - Name the collection *liferay*. - Split the collection into two shards. - Specify two replicas per shard. - When prompted to choose a configuration, enter *liferay_configs*. You should see a log message that concludes like this when the cluster has been started: ```sh SolrCloud example running, please visit http://localhost:8983/solr ``` Now you have a new collection called *liferay* in your local SolrCloud cluster. Verify its status by running the *status* command: ./bin/solr status You'll see log output like this: ```sh Found 2 Solr nodes: Solr process 12828 running on port 8983 INFO - 2019-07-18 16:46:35.137; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop { "solr_home":"/home/russell/liferay-bundles/liferay-portal-7.2.10-ga1/solr-7.5.0/example/cloud/node1/solr", "version":"7.5.0 b5bf70b7e32d7ddd9742cc821d471c5fabd4e3df - jimczi - 2018-09-18 13:07:55", "startTime":"2019-07-18T20:44:13.138Z", "uptime":"0 days, 0 hours, 2 minutes, 22 seconds", "memory":"56.4 MB (%11.5) of 490.7 MB", "cloud":{ "ZooKeeper":"localhost:9983", "liveNodes":"2", "collections":"1"}} Solr process 12995 running on port 7574 INFO - 2019-07-18 16:46:35.848; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop { "solr_home":"/home/russell/liferay-bundles/liferay-portal-7.2.10-ga1/solr-7.5.0/example/cloud/node2/solr", "version":"7.5.0 b5bf70b7e32d7ddd9742cc821d471c5fabd4e3df - jimczi - 2018-09-18 13:07:55", "startTime":"2019-07-18T20:44:16.847Z", "uptime":"0 days, 0 hours, 2 minutes, 19 seconds", "memory":"108.2 MB (%22.1) of 490.7 MB", "cloud":{ "ZooKeeper":"localhost:9983", "liveNodes":"2", "collections":"1"}} ``` To stop Solr while running in SolrCloud mode, use the *stop* command, like this: bin/solr stop -all ## Configure the Solr Adapter for SolrCloud There's only one thing left to do: specify the client type as *CLOUD* in Liferay's Solr connector. 1. From System Settings or your OSGi configuration file, set the *Client Type* to *CLOUD*. ```properties clientType="CLOUD" ``` 2. Start @product@ if it's not running already. ![Figure 1: From the Solr 7 System Settings entry, set the _Client Type_ to _Cloud_.](../../../images/solr-client-type.png) Now you can configure @product@ for Solr and Solr for @product@. Remember that Elasticsearch is the default search engine, so if you're not constrained to use Solr or already a Solr expert, consider Elasticsearch for your search engine requirements. If you do use Solr, tell all your colleagues that your @product@ installation's search capability is Solr powered (pun intended). ================================================ FILE: en/deployment/articles/03-installing-a-search-engine/03-solr/04-solr-settings.markdown ================================================ --- header-id: solr-connector-settings --- # Solr Connector Settings [TOC levels=1-4] Solr can be configured for use with @product-ver@. Liferay Marketplace includes a Solr connector app called the Liferay Connector to Solr 7. The connector is configurable through System Settings or an OSGi configuration file named `com.liferay.portal.search.solr7.configuration.SolrConfiguration.config` and deployed to `[Liferay_Home]/osgi/configs`. The list below is all the configuration settings for Liferay's Solr connector, in the order they appear in the System Settings application: ## Solr 7 `authenticationMode=BASIC` : A String with the value of *BASIC* or *CERT*. Use BASIC when connecting using the [Basic Authentication plugin](https://cwiki.apache.org/confluence/display/solr/Basic+Authentication+Plugin), otherwise select CERT to connect using [2-way SSL authentication](https://cwiki.apache.org/confluence/display/solr/Enabling+SSL). `clientType=REPLICATED` : A String with the value of *REPLICATED* or *CLOUD*. Use the default (REPLICATED) when connecting to a single-node Solr server. Specify CLOUD to connect to SolrCloud (see the next section, titled *High Availability with SolrCloud* for more information). `logExceptionsOnly=true` : A boolean value that, when set to true, only logs exceptions from Solr, without rethrowing them. `readURL=http://localhost:8983/solr/liferay` : A String array with the URLs to which Liferay will send search requests. This will be different from the `writeURL` if you use separate servers for indexing (write) and searching (read). `writeURL=http://localhost:8983/solr/liferay` : A String array with the URLs to which Liferay will send indexing requests. This is different from the `readURL` if you use separate servers for indexing (write) and searching (read). `zkHost=localhost:9983` : A String with the ZooKeeper host and port. This is required when using the adapter in CLOUD mode. ================================================ FILE: en/deployment/articles/04-securing-liferay/01-liferay-security-intro.markdown ================================================ --- header-id: securing-product --- # Securing @product@ [TOC levels=1-4] Liferay follows the OWASP Top 10 (2013) and CWE/SANS Top 25 lists to ensure @product@ is as secure as possible. Following these recommendations protects the product against known kinds of attacks and security vulnerabilities. For example, @product@'s persistence layer is generated and maintained by the Service Builder framework which prevents SQL Injection using Hibernate and parameter based queries. To prevent Cross Site Scripting (XSS), user-submitted values are escaped on output. To support integration features, @product@ doesn't encode input. Data is stored in the original form as submitted by the user. @product@ includes built-in protection against CSRF attacks, Local File Inclusion, Open Redirects, Uploading and serving files of dangerous types, Content Sniffing, Clickjacking, Path Traversal, and many other common attacks. To stay on top, @product@ also contains fixes for state-of-the-art attacks and techniques to improve product security. For example, @product@ uses PBKDF2 to store passwords. @product@ also contains mitigation for Quadratic Blowup XXE attack, Rosetta Flash vulnerability, Reflected File Download, and other kinds of attacks. This section of tutorials shows you how to configure various security and login features, such as LDAP, single sign-on, Service Access Policies, and more. What follows is an overview of what's available. ## Authentication Overview @product@ user authentication can take place using any of a variety of prepared solutions: - Form authentication using the Sign In Portlet with extensible adapters for checking and storing credentials (portal database, LDAP) - Single-Sign-On (SSO) solutions - NTLM, CAS, SiteMinder, OpenSSO, OpenID, Facebook - [SAML plugin ](https://www.liferay.com/marketplace/-/mp/application/15188711) - JAAS integration with application server Note: Although Liferay's SSO solutions are incompatible with WebDAV, they can be used with Liferay Sync. See the [Publishing Files](/docs/7-1/user/-/knowledge_base/u/publishing-files) article for more information on WebDAV and Liferay Sync. You can authenticate and authorize apps remotely using the `AuthVerifier` layer: - Password based HTTP Basic + Digest authentication - Token based OAuth plugin - Portal session based solution for JavaScript applications Both user authentication and remote application authentication are [extensible](/docs/7-2/frameworks/-/knowledge_base/f/authentication-pipelines). Developers can create custom Login portlets and plugins, extend the default Login portlet `auth.pipeline`, create `AutoLogin` extensions for SSO, or create custom `AuthVerifier` implementations. ## Authorization and Permission Checking There are several adjustable authorization layers in place to prevent unauthorized or unsecured access to data: - Remote IP and HTTPS transport check to limit access to @product@'s Java servlets - Extensible Access Control Policies layer to perform any portal service related authorization check - Extensible role-based permission framework for almost any portal entity or data (stored in the portal database or elsewhere) - Portlet Container security checks to control portlet access - Remote IP check for portal remote API authentication methods - Service Access Policies to control access to portal remote API ## Additional Security Features Users can be assigned to sites, teams, user groups, or organizations. Custom roles can be created, permissions can be assigned to those roles, and those roles can be applied to users. Roles are scoped to apply only with a specific context like a site, an organization, or globally. See the [Roles and Permissions](/docs/7-1/user/-/knowledge_base/u/roles-and-permissions) documentation for more information. | Note: @product@ relies on the application server for sanitizing CRLF in | HTTP headers. You should, therefore, make sure this is configured properly in | your application server, or you may experience false positives in reports from | automatic security verification software such as Veracode. There is one | exception to this for Resin, which does not have support for this feature. In | this case, @product@ sanitizes HTTP headers. There are additional security plugins available from [Liferay Marketplace](https://www.liferay.com/marketplace). For example, you can find an Audit plugin for tracking user actions or an AntiSamy plugin for clearing HTML from XSS vectors. There are many ways to fine-tune or disable various security features. Here are a few examples of these kinds of configuration actions: - Disable the Sign In portlet's *Create Account* link - Configure @product@'s HTTPS web server address - Configure the list of allowed servers to which users can be redirected - Configure the list of portlets that can be accessed from any page - Configure the file types allowed to be uploaded and downloaded ## Secure Configuration and Run Recommendations @product@ is built using the "secure by default" concept in mind. It's not recommended to disable built-in protections or to allow all values in security white-lists. Such acts may lead to security misconfiguration and an insecure deployment. Also, customers are advised to deploy security patches as described on the [customer portal](https://www.liferay.com/group/customer/products/portal/security-vulnerability). For community and CE deployments, the stay secure by always using the latest community version, which contains all previous security patches. ================================================ FILE: en/deployment/articles/04-securing-liferay/02-logging-in-to-liferay.markdown ================================================ --- header-id: logging-into-liferay --- # Logging into @product@ [TOC levels=1-4] One of the primary functions of a security system is to make pages, content, and web applications accessible only to the appropriate users. A student logging into a university portal should not be able to access the same resources a professor can. A patient logging into a health care portal should not be able to access a doctor's resources. Some content (at least a login page) should be available to everybody, including unauthenticated users (called *guest* users). To learn more about how @product@ restricts access to portal resources to different users, please see the [Roles and Permissions](/docs/7-2/user/-/knowledge_base/u/roles-and-permissions) documentation. ## Authentication Types There are three authentication types: by email address, screen name, or user ID. To choose an authentication type, navigate to the Control Panel, click on *Configuration* → *Instance Settings* → *Platform* → *User Authentication* and use the *How do users authenticate?* selector to make a selection. Alternatively, add the following lines to your `portal-ext.properties` file, uncomment the appropriate line, comment out the others, and restart your server. ```properties company.security.auth.type=emailAddress #company.security.auth.type=screenName #company.security.auth.type=userId ``` The default authentication type is by email address, but you can choose screen names or user IDs instead. Users choose screen names when they create their accounts or administrators can choose them. User IDs are auto-generated when the account is created. Regardless of which authentication type is configured, users must always enter a password. For information on adding restrictions on the kinds of passwords that are allowed or required (e.g., to require a minimum password length or require special characters), please see the [Password Policies](/docs/7-2/user/-/knowledge_base/u/password-policies) documentation. ## The Sign In Portlet Sign In portlet is how users log in. By default, the Sign In portlet can create new accounts or request a password reset. The default home page contains a Sign In portlet. You can access this page at [http://localhost:8080/web/guest/home](http://localhost:8080/web/guest/home). ![Figure 1: By default, the Sign In portlet allows users to log in, create a new account, or request a password reset.](../../images/sign-in-portlet.png) If the Sign In portlet doesn't appear on any page, you can still access it here: [localhost:8080/c/portal/login](localhost:8080/c/portal/login) By default, guest users can create accounts by clicking on the *Create Account* link in the Sign In portlet, completing the form, and submitting it. If a user has an account but has forgotten its password, the user can click the *Forgot Password* link to request a password reset. Both the *Create Account* form and the *Forgot Password* form include a CAPTCHA-based text verification field. Using [CAPTCHA](http://www.captcha.net) prevents bots from submitting these forms. You can use [reCAPTCHA](https://www.google.com/recaptcha/intro/index.html) instead of CAPTCHA. One advantage of reCAPTCHA is that it can allow visually impaired users to pass the test. To use reCAPTCHA, navigate to the Control Panel, then click on *Configuration* → *System Settings* → *CAPTCHA*. You can prevent guest users from creating new user accounts, if your site requires users be registered by administrators. Navigate to the Control Panel → *Configuration* → *Instance Settings* → *Platform* → *User Authentication* and uncheck the *Allow strangers to create accounts?* box. You can also prevent users from requesting forgotten passwords or from requesting password reset links by unchecking the appropriate boxes. With these options, the Create Account and Forgot Password links no longer appear in the Sign-In portlet. Remember that the Sign In portlet is the default way for users to log in, but it's not the only way. User accounts can be imported from and exported to LDAP directories. You can use single-sign-on (SSO) solutions or token-based authentication, which allows remote web applications to authenticate. Please refer to the other articles in this section for more information. Finally, remember that user authentication and remote application authentication mechanisms are [extensible](/docs/7-2/frameworks/-/knowledge_base/f/authentication-pipelines). ================================================ FILE: en/deployment/articles/04-securing-liferay/03-service-access-policies.markdown ================================================ --- header-id: service-access-policies --- # Service Access Policies [TOC levels=1-4] *Service access policies* comprise a layer of web service security that defines services or service methods that can be invoked remotely. You can apply many of them at once to produce a combined effect. To help you understand how service access policies fit into the big picture, here's a summary of @product@'s web service security layers: **IP permission layer:** The IP address from which a web service invocation request originates must be white-listed in the portal properties file. A web service invocation coming from a non-whitelisted IP address automatically fails. **Service access policy layer:** Methods corresponding to a web service invocation request must be whitelisted by each service access policy that's in effect. You can use wildcards to reduce the number of service classes and methods that must be explicitly whitelisted. **Authentication/verification layer (browser-only):** If a web service invocation request comes from a browser, the request must include an authentication token. This authentication token is the value of the `p_auth` URL parameter. The token is generated by @product@ and associated with your browser session. The `p_auth` parameter is automatically supplied when you invoke a @product@ web service via the JSON web services API page or via JavaScript using `Liferay.Service(...)`. If @product@ cannot associate the caller's authentication token with a User, the web service invocation request fails. **User permission layer:** Properly implemented web services have permission checks. The user invoking a web service must have permission to invoke the service. ![Figure 1: To get to a service, a request must pass through the door lock of user permissions, the padlock of the verification layer, the brick wall of service access policies, and finally the safe of predefined IP permissions.](../../images/service-access-policies-security-layers.png) Note that service access policies respect the permissions system. If a service access policy grants a user access to a remote service, the user must still have the appropriate permissions to invoke that service. Service access policies are especially useful when remote applications such as mobile devices or Liferay Sync instances must access web services. Administrators can use service access policies to ensure that these devices can only invoke remote services from approved lists that can be modified at runtime. ## Managing Service Access Policies Navigate to the Control Panel and click on *Service Access Policy* under the Configuration heading. Here, you can see the default service access policies and add new ones. When creating or editing service access policies, keep these points in mind: - Service access policy names must be unique per portal instance. - Service access policy names can include only these allowed characters: 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#:@-./_ - Service access policy titles can be localized; service access policy names cannot be localized. - Allowed service signatures must be entered one per line. Wildcards (`*`) are allowed for both class names and method names. The `#` symbol must be used to separate a class name from a method name. For example, `com.liferay.portal.kernel.service.UserService` allows any method from the `UserService` class to be invoked. `com.liferay.document.library.kernel.service.DLAppService#get*` allows any method from the `DLAppService` that starts with `get` to be invoked. Thus, ```java com.liferay.portal.kernel.service.UserService com.liferay.document.library.kernel.service.DLAppService#get* ``` allows any method from the `UserService` class to be invoked and any method from the `DLAppService` whose name starts with `get` to be invoked. There are 16 service access policies that are enabled by default. Six of these have to do with the system: **ASSET_ENTRY_DEFAULT:** Allows the view counter for assets to be updated when an asset is retrieved. **CALENDAR_DEFAULT:** Makes it possible to search public events in the calendar. **SYNC_DEFAULT:** Allows only the `com.liferay.sync.service.SyncDLObjectService.getSyncContext` method. It applies to every Liferay Sync request, including unauthenticated Sync requests. **SYNC_TOKEN:** Allows `com.liferay.sync.service.*`, meaning that any API function that's a method of a class in this package can be invoked. It applies to Sync requests which are accompanied by an authentication token. **SYSTEM_DEFAULT:** Allows access to country/region services by JavaScript calls, so users can switch languages on the fly. Applies to every request, including unauthenticated requests. **SYSTEM_USER_PASSWORD:** Allows any method to be invoked. Of course, since API functions include permission checks, this call works only if the user has the required permission. It applies to requests for which `AuthVerifierResult.isPasswordBasedAuthentication` is `true`: i.e., whenever user authentication took place using a password. If you want to completely disallow certain API functions from being invoked, you can change the `SYSTEM_USER_PASSWORD` policy to something more restrictive than `*`. `SYNC_DEFAULT` and `SYSTEM_DEFAULT`, as their names suggest, are default service access policies. Default service access policies are applied to all incoming requests, including unauthenticated requests. The other 10 policies have to do with OAuth and JSON web services: **OAUTH2_analytics.read/write:** Integrates with [Liferay Analytics Cloud](https://www.liferay.com/products/analytics-cloud), allowing it access to JSON web services. **OAUTH2_everything/read/documents/userprofile/write:** The Everything policies grant access to all the JSON web services for various reasons. Everything is everything: all JSON web services (matches `*`). The others match method signatures appropriate to their description. For example, OAUTH2_everything.read matches all methods starting with `fetch`, `get`, `has`, `is`, or `search`. **OAUTH_READ/WRITE:** These provide access to JSON web services via the OAuth 1.0a plugin. The default configuration makes available corresponding scopes that provide access to all web services shipped with the system. The scopes must be assigned to OAuth 1 or 2 applications before they become usable. Administrators should review the ones you want to use and disable the others. You can create new default service access policies: 1. Navigate to the *Configuration* → *Service Access Policy* section of the Control Panel. 2. Click *Add* (![add](../../images/icon-add.png)). 3. Give your policy a name. 4. Flip the switch to enable your policy. 5. If you want the policy applied to unauthenticated requests as well as authenticated requests, flip the switch labeled *Default*. 6. Give your policy a localized title. 7. Under Allowed Service Signatures, start typing the fully qualified name of a service class that's installed. Code completion helps you find the class. For example, if you're creating a policy for Liferay's Knowledge Base application, you could enter `com.liferay.knowledge.base.service.KBArticleService`. 8. Under Method Name, start typing a service method call. Again, code completion helps you. For Knowledge Base, you could enter `getKBArticle`. 9. To specify another service or method, click the plus icon to add another entry. 10. When done, click *Save*. | **Note:** If you know all the method signatures ahead of time, you can click | *Switch to Advanced Mode* and enter them all in one field on separate lines. Liferay applications can declare their own default policies (the `SYNC_DEFAULT` policy is a good example). This policy can then be changed or disabled by administrators. In this case, the plugin can still verify that the policy exists so there is no need to redefine or update it. By default, Liferay's tunneling servlet uses the `SYSTEM_USER_PASSWORD` service access policy. You can, however, create your own policy for the tunneling servlet and use the property `service.access.policy.name` for the `TunnelingServletAuthVerifier` to specify that your policy should be used instead. ## Service Access Policy Module Liferay's service access policy functionality is provided by the Service Access Policy module. This module includes the following important classes: - `com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicy`: defines the public interface for `ServiceAccessPolicy`. - `com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyManager`: defines the public interface for retrieving instances of `ServiceAccessPolicy`. - `com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyManagerUtil`: bridges service access policy functionality to the parts of Liferay's core that have not yet been modularized. - `com.liferay.portal.kernel.security.service.access.policy.ServiceAccessPolicyThreadLocal`: makes `ServiceAccessPolicy` instances active. Liferay's Service Access Policy module resides in the `modules/apps/service-access-policy` folder in the source code. When running, these three bundles provide the service access policy functionality (they're in the `[Liferay Home]/osgi/modules` folder): - `com.liferay.service.access.policy.api.jar` - `com.liferay.service.access.policy.service.jar` - `com.liferay.service.access.policy.web.jar` These modules provide the service access policy management UI that's accessible from the Control Panel. They also provide the interface and default implementation for `ServiceAccessPolicy`. To configure the Service Access Policy module, navigate to the Control Panel, click on *System Settings*, and find the *Service Access Policies* module in the Security section. Click on its name to edit it. Here, you can edit the default service access policy configuration. You can also force a default policy to be applied even when no policies are applied by the `AuthVerifier`. There's also an `AuthenticatedAccessControlPolicy`. This policy doesn't do anything if a `ServiceAccessPolicyManager` implementation is present. If the service access policy module is disabled, however, the `AuthenticatedAccessControlPolicy` provides a fallback that still requires authenticated access for web services. ## Summary Great! Now you know service access policies can restrict access to @product@'s web services. Custom service access policies can be created by portal administrators. They are applied by the portal's token authenticator, e.g., by OAuth. ## Related Topics [Creating Service Access Policies](/docs/7-2/frameworks/-/knowledge_base/f/service-access-policies) ================================================ FILE: en/deployment/articles/04-securing-liferay/04-auth-verifiers.markdown ================================================ --- header-id: authentication-verifiers --- # Authentication Verifiers [TOC levels=1-4] The Authentication Verification Layer is a centralized and extensible way to authenticate remote invocations of @product@'s API. The main responsibilities of the authentication verification layer are to 1. Verify provided credentials using registered `AuthVerifier` instances 2. Create portal authorization contexts based on verification results If no available `AuthVerifier` can verify request credentials, an authorization context supporting non-authenticated access is created for a guest user. This allows each API to expose only a single API endpoint. In contrast, legacy (prior to 6.2) versions of @product@ exposed two API endpoints for each API: the `/api/endpoint` URI was for non-authenticated access and the URI `/api/secure/endpoint` was for authenticated access. There are built-in `AuthVerifier` implementations for the most common situations, such as when remote clients use HTTP Basic or HTTP Digest authentication, send credentials in request parameters, send authenticated `JSESSIONID`s, or use shared secrets to establish trust. Other `AuthVerifier` implementations can be deployed as modules containing implementations of the `AuthVerifier` interface that are registered as services in the OSGi runtime. Note: The authentication verification layer's focus is on verifying authentication, not on providing credentials. It does NOT issue tokens, credentials, or display Sign In portlets. Instead, the layer verifies existing credentials and authenticated sessions and is therefore a complement to authentication endpoints. To ensure backwards compatibility, however, the default implementations support requests providing user name and password credentials. Thus, the authentication verification layer stands on the border between authentication and authorization. ## Authentication Verification Process Overview This layer and surrounding processes are provided by the `AuthVerifierFilter` class that implements the `javax.servlet.Filter` interface. **Step 1: Verify Request Credentials** The layer uses the chain of responsibility design pattern to support both built-in and third party `AuthVerifier` implementations. Each `AuthVerifier` can provide configurations where it specifies mapped URLs and other properties. Each incoming request is matched against all registered `AuthVerifier`s to select the final list of `AuthVerifier`s that is used to process the request. It's the responsibility of each `AuthVerifier` to verify the incoming request credentials. **Step 2: Create an Authorization Context** When a request is processed by all matching `AuthVerifier`s, @product@ creates an authorization context for the resolved user. This encompasses setting the `HttpServletRequest` `remoteUser` to return the resolved user ID setting `ThreadLocal`s to the resolved user. The resolved user can be the user returned by one of the `AuthVerifier` instances or a guest user if no instance was able to verify the provided credentials. `AuthVerifier`s are created by developers, and are processed automatically as long as they're registered in the OSGi runtime. Each Auth Verifier gets its own configuration in *Control Panel* → *System Settings* → *Security* → *API Authentication*. Configuration for Auth Verifiers that ship with the product include - Basic Auth Header - Digest Authentication - HTTP Tunnel Extender - Image Request - Portal Sessions - Request Parameter - Tunnel Auth The following Auth Verifiers are enabled by default and can be used to access remote API out-of-the-box: - Basic Auth Header - Portal Sessions ## Basic Auth Header This Auth Verifier allows the remote client to authenticate using [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). Configure it by providing URL paths that should be authenticated this way. When Force Basic Authentication field is checked then HTTP Basic Authentication is required. The default URLs are `/api/*,/xmlrpc*` for web services. The mapping excludes `/api/liferay*` to prevent accessing `TunnelServlet`. For more information please see Tunnel Authentication Verifiers. ## Digest Auth Header This Auth Verifier allows the remote client to authenticate using [HTTP Digest Authentication](https://en.wikipedia.org/wiki/Digest_access_authentication). Configure it by providing URL paths that should be authenticated this way. When Force Digest Authentication field is checked then HTTP Basic Authentication is required. This Auth Verifier is not enabled by default. ## HTTP Tunnel Extender As Liferay embraced modularity, this extender was written to enable modules to be part of `TunnelServlet`. It maps `TunnelServlet` and `TunnelingServletAuthVerifier` to the module servlet context. Modules with `Http-Tunnel` in the manifest can make use of the Tunnel Servlet, and can expose the API via `/o/_module_/api/liferay/do`. Configure it by setting client IP addresses allowed to tunnel. For more information, please see [the properties documentation](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#HTTP%20Tunneling) as well as [remote staging](/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging). Note that this is not a recommended way to export remote APIs; it's far better to expose remote services using JAX-RS or Liferay JSON Web Service technologies. ## Image Request Authentication Verifier When connected to LibreOffice/OpenOffice, the Office process must download images from @product@ to render docs with images. To do this, a [JWT Token](https://jwt.io) is created to access the images securely. Configure this by setting the Hosts Allowed, URLs included, and URLs excluded if necessary. This Auth Verifier is not enabled by default. ## Portal Sessions Auth Verifiers Enables JavaScript in a browser to access Liferay JSON Web Services using an existing portal session. In the default configuration, the URLs included field shields access to the legacy JSON remote services layer: `/api/json*,/api/jsonws*,/c/portal/json_service*`. ## Request Parameter Auth Verifiers For backwards compatibility with `RequestParameterAutoLogin` you can authenticate and access portal endpoints with credentials inside HTTP request parameters `parameterAutoLoginLogin` and `parameterAutoLoginPassword`. This Auth Verifier is not enabled by default. ## Tunnel Authentication Verifiers `TunnelServlet` is a legacy remote API endpoint mapped at `/api/liferay/do` to provide access to the portal remote services. The Tunnel Auth Verifier allows trusted remote clients authenticated access using any user ID provided, on behalf of the user. An example of a trusted remote client is the Staging remote publishing feature. Trusted remote clients authenticate using a shared secret stored in the portal property `tunneling.servlet.shared.secret`. The default value is empty and forbids all access. Even though the default configuration is enabled by default, access is limited to localhost only. Configure it by setting client IP addresses allowed to tunnel. For more information, please see [the properties documentation](https://docs.liferay.com/portal/7.2-latest/propertiesdoc/portal.properties.html#HTTP%20Tunneling) as well as [remote staging](/docs/7-2/user/-/knowledge_base/u/enabling-remote-live-staging). ## Related Topics [Service Access Policies](/docs/7-2/deploy/-/knowledge_base/d/service-access-policies) ================================================ FILE: en/deployment/articles/04-securing-liferay/05-ldap/01-ldap-servers-intro.markdown ================================================ --- header-id: ldap --- # LDAP [TOC levels=1-4] LDAP is a common user store for @product@. You can configure LDAP at the system scope in System Settings or at the instance scope in Instance settings. Users can be imported from LDAP or exported to LDAP. To access LDAP configuration settings, navigate to *Control Panel → Configuration* → *Instance Settings*. At the bottom of the list on the left, click *Servers*. Click the *Add* button to add an LDAP server connection. If you have more than one LDAP server, you can arrange the servers by order of preference using the up/down arrows. Regardless of how many LDAP servers you add, each server has the same configuration options. **Server Name:** Enter a name for your LDAP server. **Default Values:** Several common directory servers appear here. If you use one of these, select it. The rest of the form is populated with default values for that directory. ## Connection These settings cover the connection to LDAP. **Base Provider URL:** The link to the LDAP server. Make sure the @product@ server can communicate with the LDAP server. If there is a firewall between the two systems, make sure the appropriate ports are opened. **Base DN:** The Base Distinguished Name for your LDAP directory. It is usually modeled after your organization. It may look similar to this: `dc=companynamehere,dc=com`. **Principal:** The default LDAP administrator user ID is populated here. If your administrator ID differs, use that credential instead. You need an administrative credential because @product@ uses this ID to synchronize user accounts to and from LDAP. **Credentials:** This is the password for the LDAP administrative user. This is all you need to make a regular connection to an LDAP directory. The rest of the configuration, however, may need to be customized, as it represents "best guesses" as to correct defaults. The default attribute mappings usually provide enough data to synchronize back to the @product@ database when a user attempts to log in. To test the connection to your LDAP server, click the *Test LDAP Connection* button. ## Checkpoint Before proceeding to fine tune @product@'s LDAP connections, ensure the following steps have been taken: 1. The LDAP connection is enabled. Depending on your needs, LDAP authentication may be required so that only users who have been bound may log in. 2. *Export/Import*: for users in a clustered environment, Enable Import/Export on Startup should be disabled so that there are no massive imports on every node upon start up. 3. When adding the LDAP server, the *Server Name*, *Default Values*, *Connection* values are correct. It is always a good idea to click the *Test LDAP Connection* before saving. ## Instance Settings vs. System Settings You can define an LDAP server connection at the System Settings scope as well. Because this user interface is auto-generated, it's not as helpful as the one in Instance Settings. For this reason, you should define and troubleshoot your settings in Instance Settings first. If you decide you want your LDAP connection at the system scope, you can copy your configuration from Instance Settings and then delete the server from Instance Settings. Of course, you can also configure LDAP servers at the system scope using OSGi `.config` files. The easiest way to do this is to use the GUI and export the configuration. Then you can use the resulting `.config` file anywhere you need it (such as other nodes in a cluster). **Note:** To use `config` files for LDAP server configuration, you must specify the Virtual Instance ID (in the source, the variable name is `companyId`) in the exported configuration file, because servers are defined at the instance scope, not the system scope. To do this, specify the virtual instance ID somewhere in the file like this: ```properties companyId=1234 ``` You can find your Virtual Instance ID in Control Panel → Configuration → Virtual Instances. ## Security If you run your LDAP directory in SSL mode to encrypt credential information on the network, you must perform extra steps to share the encryption key and certificate between the two systems. For example, if your LDAP directory is Microsoft Active Directory on Windows Server 2003, you'd share the certificate like this: Click *Start* → *Administrative Tools* → *Certificate Authority*. Highlight the machine that is the certificate authority, right-click on it, and click *Properties*. From the General menu, click *View Certificate*. Select the Details view, and click *Copy To File*. Use the resulting wizard to save the certificate as a file. You must also import the certificate into the *cacerts keystore* like this: keytool -import -trustcacerts -keystore /some/path/java-8-jdk/jre/lib/security/cacerts -storepass changeit -noprompt -alias MyRootCA -file /some/path/MyRootCA.cer The `keytool` utility ships as part of the Java SDK. Once this is done, go back to the LDAP page in the Control Panel. Modify the LDAP URL in the Base DN field to the secure version by changing the protocol to `ldaps` and the port to `636` like this: ldaps://myLdapServerHostname:636 Save the changes. Communication to LDAP is now encrypted. ## Configuring LDAP Import/Export The other settings configure mappings between LDAP and @product@ so users and groups can be imported. ### Users This section contains settings for finding users in your LDAP directory. **Authentication Search Filter:** Use this search filter box to determine the search criteria for user logins. By default, @product@ uses users' email addresses for their login names. The value here must use the authentication method you use. For example, if you changed @product@'s authentication method to use screen names instead of the email addresses, you would modify the search filter so it can match the entered log in name: (cn=@screen_name@) **Import Search Filter:** Depending on the LDAP schema, there are different ways to identify the user. The default setting is usually fine: (objectClass=inetOrgPerson) If you want to search for only a subset of users or users that have different LDAP object classes, you can change this. **User Mapping:** Next, you can define mappings from LDAP attributes to Liferay fields. Though LDAP user attributes may be different from LDAP server to LDAP server, there are five fields @product@ requires to be mapped for the user to be recognized: - *Screen Name* (e.g., `uid` or `cn`) - *Password* (e.g., `userPassword`) - *Email Address* (e.g., `mail` or `email`) - *First Name* (e.g., `name` or `givenName`) - *Last Name* (e.g., `sn`) **Note:** If you intend to create or import users with no email addresses, you must set `users.email.address.required=false` in `portal-ext.properties`. With this set, Liferay auto-generates an email address combining the user ID plus the suffix defined in the property `users.email.address.auto.suffix=`. Finally, make sure to set Liferay and LDAP authentication to something other than email address. If you want to import LDAP groups as @product@ user groups, make sure define a mapping for the @product@ group field so that membership information is preserved: - *Group* (e.g., *member*) The other LDAP user mapping fields are optional. The Control Panel provides default mappings for commonly used LDAP attributes. You can also add your own mappings. **Test LDAP Users:** Once you have your attribute mappings set up (see above), click the *Test LDAP Users* button and @product@ attempts to pull LDAP users and match them with their mappings as a preview. ![Figure 1: You should see a list of users when you click the *Test LDAP Users* button.](../../../images/testing-ldap-users.png) ### Groups This section contains settings for mapping LDAP groups to @product@ user groups. **Import Search Filter:** This is the filter for mapping LDAP groups to @product@ user groups. For example, (objectClass=groupOfNames) Enter the LDAP group attributes you want retrieved for this mapping. The following attributes can be mapped. The *Group Name* and *User* fields are required, the *Description* is optional. - *Group Name* (e.g., `cn` or `o`) - *Description* (e.g., `description`) - *User* (e.g., `member`) **Test LDAP Groups:** Click the *Test LDAP Groups* button to display a list of the groups returned by your search filter. ### Export This section contains settings for exporting user data from LDAP. **Users DN:** Enter the location in your LDAP tree where the users are stored. @product@ exports the users to this location. **User Default Object Classes:** Users are exported with the listed default object classes. To find out what your default object classes are, use an LDAP browser tool such as Apache Directory Studio to locate a user and view the Object Class attributes stored in LDAP for that user. **Groups DN:** Enter the location in your LDAP tree where the groups are stored. When @product@ does an export, it exports the groups to this location. **Group Default Object Classes:** When a group is exported, the group is created with the listed default object classes. To find out what your default object classes are, use an LDAP browser tool such as [Apache Directory Studio](https://directory.apache.org/studio) to locate a group and view the Object Class attributes stored in LDAP for that group. When you've set all your options and tested your connection, click *Save*. | **Note:** If a user changes a value like a password in @product@, that change is | passed to the LDAP server, provided @product@ has enough schema access to make | the change. Now you know how to connect an LDAP server to @product@ and how to configure user import behavior, export behavior, and other LDAP settings. ## Related Topics [@product@ Security Overview](/docs/7-0/deploy/-/knowledge_base/d/liferay-portal-security-overview) [Logging into @product@](/docs/7-0/deploy/-/knowledge_base/d/logging-in-to-liferay) ================================================ FILE: en/deployment/articles/04-securing-liferay/05-ldap/02-configuring-ldap.markdown ================================================ --- header-id: configuring-ldap --- # Configuring LDAP [TOC levels=1-4] In this article, you'll learn how to configure import settings, export settings, and related LDAP configuration settings. To access LDAP configuration settings, navigate to *Control Panel → Configuration* → *Instance Settings* → *Security* → *LDAP*. There are four categories on the left: Export, General, Import, and Servers. The Servers category was covered in [the last article](/docs/7-2/deploy/-/knowledge_base/d/ldap). The rest are covered below. ## Export **Enable Export:** Check this box to export user accounts to LDAP. A listener tracks changes made to the `User` object and pushes updates to the LDAP server whenever a `User` object is modified. Note that by default on every login, fields such as `lastLoginDate` are updated. When export is enabled, this causes a user export every time the user logs in. You can prevent updates to users' `lastLoginDate` fields from triggering LDAP user exports by setting the following property in your `portal-ext.properties` file: users.update.last.login=false **Enable Group Export:** Export groups to LDAP. ## General **Enabled:** Check this box to enable LDAP Authentication. **Required:** Check this box if LDAP authentication is required. Users can't log in unless they can bind to the LDAP directory successfully. Uncheck this box if users with @product@ accounts but no LDAP accounts can log in. **Use LDAP Password Policy:** @product@ uses its own password policy by default. This can be configured on the Control Panel's Password Policies page. Check the *Use LDAP Password Policy* box if you want to use the password policies defined by your LDAP directory. Once this is enabled, the Password Policies tab states that you are not using a local password policy. You must now use your LDAP directory's mechanism for setting password policies. @product@ cannot enforce these policies; the best it can do is pass through the messages returned by your LDAP server. It does this by parsing the messages in the LDAP controls the server returns. By default, @product@ is configured to parse the messages returned by the Fedora Directory Server. If you use a different LDAP server, you must customize the messages in *System Settings* → *Security* → *LDAP* → *Connection*. **Method:** Choose *Bind* (the default) or *Password Compare*. Bind does a standard LDAP bind; Password Compare attempts to compare Liferay and LDAP passwords using the encryption algorithm specified in the field below. Password Compare is rarely used. **Password Encryption Algorithm:** Choose the password encryption algorithm your LDAP server uses to encrypt passwords so they can be compared if using the Password Compare bind method. This is rarely used. ## Import You can import user data from LDAP directories using the following options: **Enable Import:** Check this box to do a mass import from your LDAP directories. Otherwise, Users are imported as they log in. ![Figure 1: Ziltoid and Rex have been imported because they logged in.](../../../images/imported-ldap-users.png) **Enable Import on Startup:** Check this box to do the mass import when @product@ starts. Note: this box only appears if you check **Enable Import**, described above. Definitely leave this unchecked if you have a @product@ cluster, or all your nodes will do a mass import when each of them starts up. **Import Interval:** When mass importing users, import users every X minutes. **Import Method:** Set either User or Group. If you set this to User, @product@ imports all users from the location specified in the server connection. If you set this to Group, @product@ searches all the groups and imports the users in each group. If you have users who do not belong to any groups, they are not imported. **Lock Expiration Time:** Set the account lock expiration time for LDAP User import. The default is one day. **Import User Sync Strategy:** Set the strategy used to sync user accounts. Options are Auth Type (i.e., the way the user authenticates, like with screen name) and UUID (requires a UUID attribute in LDAP). **Enable User Password on Import:** Assign a default password (see below) when users are imported, so they can be synced between the two systems. **Autogenerate User Password on Import:** Create a random password on user import. **Default User Password:** Enter the default password users are assigned when they first log in via LDAP. **Enable Group Cache on Import:** Cache the imported groups so import isn't slowed by database access. **Create Role per Group on Import:** For every LDAP group, create a corresponding Liferay Role. ## Servers **LDAP Servers:** @product@ supports connections to multiple LDAP servers. Use the *Add* button to add LDAP servers. Each LDAP server has several configuration options [explained here](/docs/7-2/deploy/-/knowledge_base/d/ldap). Once you've finished configuring LDAP, click the *Save* button. ### LDAP Options Available in System Settings Although most LDAP configuration can be done from Instance Settings, there are several configuration parameters that are only available in System Settings. There are also settings duplicated from the ones in Instance Settings. These change the *default* settings for new virtual instances (see note below). | **Note**: When you make a change in System Settings, it takes effect for the | virtual instance you're in. If after changing a setting you create a new | virtual instance, that virtual instance inherits the settings of the one it was | created from as defaults. For example, say you have virtual instances named A, | B, and C. From A, you modify *Error password history keywords*. This change | appears only in A, not in B or C. Then from A, you create virtual instance D. | The change to *Error password history keywords* appears in D (not B or C), | since D defaults to A's settings because you created it from A. If you must change any of these options, navigate to *Control Panel* → *Configuration* → *System Settings*. Go to the *Security* section and find the entries with LDAP in the title. The only new settings here are in the *Connection* entry. Use the *Connection* entry to manage error properties like *Error password age keywords* which lets you set a list of phrases from error messages which can possibly be returned by the LDAP server. When a user binds to LDAP, the server returns *controls* with its response of success or failure. These controls contain a message describing the error or the information that is returned with the response. Though the controls are the same across LDAP servers, the messages can be different. The properties described here contain snippets of words from those messages and work with Red Hat's Fedora Directory Server. If you are not using that server, the word snippets may not work with your LDAP server. If they don't, you can replace the values of these properties with phrases from your server's error messages. This enables @product@ to recognize them. ================================================ FILE: en/deployment/articles/04-securing-liferay/06-token-based-authentication.markdown ================================================ --- header-id: token-based-single-sign-on-authentication --- # Token-based Single Sign On Authentication [TOC levels=1-4] Token-based SSO authentication was introduced in @product@ 7.0 to standardize support for Shibboleth, SiteMinder, Oracle OAM, or any other SSO product that works by propagating a token via one of the following mechanisms: - HTTP request parameter - HTTP request header - HTTP cookie - Session attribute Since these providers have a built-in web server module, you should use the Token SSO configuration. The authentication token contains the @product@ user's screen name or email address, whichever @product@ has been configured to use for the particular company (portal instance). Recall that @product@ supports three authentication methods: - By email address - By screen name - By user ID Token-based authentication only supports email address and screen name. If @product@ is configured to use user ID when a token-based authentication is attempted, the `TokenAutoLogin` class logs this warning: Incompatible setting for: company.security.auth.type Please note that the above sources are fully trusted. Furthermore, you must use a security mechanism external to @product@, such as a fronting web server like Apache. The chosen fronting solution must prevent malicious @product@ user impersonation that otherwise might be possible by sending HTTP requests directly to @product@ from the client's web browser. Token-based authentication is disabled by default. To manage token- based SSO authentication, navigate to Control Panel → *System Settings*, → *Security* → *SSO*. Token Based SSO appears in Virtual Instance Scope at the bottom. Here are the configuration options for the Token Based SSO module: **Enabled:** Check this box to enable token-based SSO authentication. **Import from LDAP:** Check this box to import users automatically from LDAP if they don't exist. **User token name:** Set equal to the name of the token. This is retrieved from the specified location. (Example: SM_USER) **Token location:** Set this to the location of the user token. As mentioned earlier, the options are: - HTTP request parameter - HTTP request header - HTTP cookie - Session attribute **Authentication cookies:** Set this to the cookie names that must be removed after logout. (Example: `SMIDENTITY`, `SMSESSION`) **Logout redirect URL:** When user logs out of @product@, the user is redirected to this URL. Remember to click *Save* to activate Token Based SSO. ## Required SiteMinder Configuration If you use SiteMinder, note that @product@ sometimes uses the tilde character in its URLs. By default, SiteMinder treats the tilde character (and others) as bad characters and returns an HTTP 500 error if it processes a URL containing any of them. To avoid this issue, change this default setting in the SiteMinder configuration to this one: BadUrlChars //,./,/.,/*,*.,\,%00-%1f,%7f-%ff,%25 The configuration above is the same as the default except the `~` was removed from the bad URL character list. Restart SiteMinder to make your configuration update take effect. For more information, please refer to SiteMinder's [documentation](https://support.ca.com/cadocs/0/CA%20SiteMinder%20r6%200%20SP6-ENU/Bookshelf_Files/HTML/index.htm?toc.htm?258201.html) ## Summary @product@'s token-based SSO authentication mechanism is highly flexible and compatible with any SSO solution that provides it with a valid @product@ user's screen name or email address. These include Shibboleth and SiteMinder. ================================================ FILE: en/deployment/articles/04-securing-liferay/07-openid-connect-authentication.markdown ================================================ --- header-id: authenticating-with-openid-connect --- # Authenticating with OpenID Connect [TOC levels=1-4] OpenID Connect is a lightweight authentication layer built on top of the [OAuth 2.0](/docs/7-2/deploy/-/knowledge_base/d/oauth-2-0) authorization protocol. It compliments local accounts by enabling users to authenticate using accounts they have on other systems. Users who avoid signing up for new accounts can then use an account they already have to sign into your website. By using OpenID Connect, you *delegate* user authentication to other providers, making it easy for users with existing accounts to authenticate to your system. | **Note:** You can add multiple providers to your installation, but @product@ | can't be an OpenID Connect provider. Because OpenID Connect is built on OAuth 2.0, its token flow is similar. OAuth 2.0 is only an authorization protocol, so it sends an *access token* that grants access to particular APIs. OpenID Connect adds to this an *identity token* that passes user information like name and email, provided the user has authenticated and granted permission. ## Creating a Client in OpenID Connect Provider To use OpenID Connect, you must first register it as a client in your provider. This is an OAuth 2.0 client. The process varies by provider: 1. Navigate to the provider's website and create a client. 2. During the creation process, you must supply an *authorized redirect URL* that can process the tokens sent from the provider. @product@'s URL is https://[server.domain]/c/portal/login/openidconnect 3. The provider sends several pieces of information. Some of these, like the Discovery Endpoint, Authorization Endpoint, or Issuer URL are the same regardless of the client. The two pieces of information unique to your request are the `client_id` and the `client_secret`. Collect the information from the provider. You'll need it create the provider next. ## Configuring an OpenID Connect Provider Connection Go to *Control Panel* → *Configuration* → *System Settings* → *Security* → *SSO* and select ***OpenID Connect Provider*** under the *System Scope* and follow these steps: 1. Add the provider by clicking the *Add* button. 2. Use the information you received from the provider to fill out the form: **Provider Name:** This name appears in the Sign-In Portlet when users use OpenID Connect to log in. **OpenID Client ID:** Provide the OAuth 2.0 Client ID you received from your provider. **OpenID Connect Client Secret:** Provide the OAuth 2.0 Client Secret you received from your provider. **Scopes:** Leave the default, which requests the user name and the email. Your provider may offer other scopes of user information. **Discovery Endpoint:** Other URLs may be obtained from this URL, and they vary by provider. **Discovery Endpoint Cache in Milliseconds:** Cache the endpoints (URLs) discovered for this amount of time. **Authorization Endpoint:** This URL points to the provider's URL for authorizing the user (i.e., signing the user in). **Issuer URL:** The provider's URL that points to information about the provider who is issuing the user information. **JWKS URI:** A URL that points to the provider's JSON Web Key Set that contains the public keys that can verify the provider's tokens. **ID Token Signing Algorithms:** Set the supported ID token algorithms manually. Normally, this is "discovered" at the discovery endpoint. You can add as many of these as you need. **Subject Types:** A Subject Identifier is a unique and never reassigned identifier the provider uses to establish who the user is, and is consumed by the client (i.e., @product@). There are two types: public (provides the same value to all clients) and private (provides a different value to each client). **Token Endpoint:** The provider's URL where tokens can be requested. **User Information Endpoint:** The OAuth 2.0 protected URL from which user information can be obtained. Once you've filled out the form, click *Save*, and you're ready to enable OpenID Connect authentication. | System Settings configuration file: | | com.liferay.portal.security.sso.openid.connect.internal.configuration.OpenIdConnectProviderConfiguration-[name].config | | where `[name]` is a descriptive, but unique name for example `provider1`. ## Enabling OpenID Connect Authentication 1. Go to *Control Panel* → *Configuration* → *System Settings* → *Security* → *SSO* and select ***OpenID Connect*** under *Virtual Instance Scope*. 2. Click the *Enabled* check box, and then click *Save*. **Note:** You can also enable OpenID Connect authentication for the given virtual instance through the *Control Panel* → *Configuration* → *Instance Settings* → *OpenID Connect* tab. | System Settings configuration file: | | com.liferay.portal.security.sso.openid.connect.configuration.OpenIdConnectConfiguration.config Now users can sign in with OpenID Connect. ## Signing In With OpenID Connect There's a new link in the Sign-In Portlet for signing in with OpenID Connect: 1. From the Sign-In Portlet, click the OpenID Connect link at the bottom. 2. Choose a provider and click *Sign In*. 3. This takes you to your provider's sign in page. Enter your credentials and log in. 4. Upon successful authentication, you're redirected back to @product@ in an authenticated state. OpenID is a standards-based, secure way to authenticate users from other systems. ================================================ FILE: en/deployment/articles/04-securing-liferay/08-openam-authentication.markdown ================================================ --- header-id: opensso-single-sign-on-authentication --- # OpenAM Single Sign On Authentication [TOC levels=1-4] OpenAM is an open source single sign-on solution that comes from the code base of Sun's System Access Manager product. @product@ integrates with OpenAM, allowing you to use OpenAM to integrate @product@ into an infrastructure that contains a multitude of different authentication schemes against different repositories of identities. Note that OpenAM relies on cookie sharing between applications. Thus, in order for OpenAM to work, **all applications that require SSO must be in the same web domain**. You should also add the following property if you have enabled HTTPOnly cookies due to the way some web containers (like Apache Tomcat™) parse cookies that contain special characters: ```properties com.iplanet.am.cookie.encode=true ``` You can install OpenAM on the same or different server as @product@. Be sure to review the context path and server hostname for your OpenAM server. If you want to install OpenAM on the same server as @product@, you must deploy the OpenAM `.war`, downloadable from [here](https://backstage.forgerock.com/downloads/browse/am/archive/productId:openam). Otherwise, follow the instructions at the [OpenAM 13 site](https://backstage.forgerock.com/docs/openam/13/install-guide/) to install OpenAM. | **Note**: OpenAM 12 and below work with @product@, but are at end of life. | Because of this, we recommend only OpenAM 13 for production use. Once you have it installed, create the @product@ administrative user in it. Users are mapped back and forth by screen names. By default, the @product@ administrative user has a screen name of *test*, so if you were to use that account, register the user in OpenAM with the ID of *test* and the email address specified in the [`admin.email.from.address`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Admin%20Portlet) [portal property](/docs/7-2/deploy/-/knowledge_base/d/portal-properties)). Once you have the user set up, log in to OpenAM using this user. In the same browser window, log in to @product@ as the administrative user (using the previous admin email address). Go to the Control Panel and click *Configuration* → *Instance Settings* → *Security* → *SSO*. Then choose *OpenSSO* in the list on the left. ![Figure 1: OpenSSO Configuration.](../../images/opensso-configuration.png) Modify the three URL fields (Login URL, Logout URL, and Service URL) so they point to your OpenAM server (in other words, only modify the host name portion of the URLs), check the *Enabled* box, and click *Save*. @product@ then redirects users to OpenAM when they request the `/c/portal/login` URL *for example, when they click on the *Sign In* link). @product@'s OpenAM configuration can be applied at either the system scope or at the instance scope. To configure the OpenAM SSO module at the system scope, navigate to the Control Panel, click on *Configuration* → *System Settings* → *Security* → *SSO* → *OpenSSO*. Click on it and you'll find these settings to configure. The values configured here provide the default values for all portal instances. Enter them in the same format as you would when initializing a Java primitive type with a literal value. Property Label | Property Key | Description | Type ----- | ----- | ----- | ----- **Version** | `version` | OpenAM version to use (12 and below or 13) | `String` **Enabled** | `enabled` | Check this box to enable OpenAM authentication. Note that OpenAM works only if LDAP authentication is also enabled and @product@'s authentication type is set to screen name. | `boolean` **Import from LDAP** | `importFromLDAP` | If this is checked, users authenticated from OpenAM that do not exist in @product@ are imported from LDAP. LDAP must be enabled. | `boolean` **Login URL** | `loginURL` | The URL to the login page of the OpenAM server | `String` **Logout URL** | `logoutURL` | The URL to the logout page of the OpenAM server | `String` **Service URL** | `serviceURL` | The URL by which OpenAM can be accessed to use the authenticated web services. If you are using OpenAM Express 8 or higher, you need to have the server running Java 6. | `String` **Screen Name Attribute** | `screenNameAttr` | The name of the attribute on the OpenAM representing the user's screen name | `String` **Email Address Attribute** | `emailAddressAttr` | The name of the attribute on the OpenAM representing the user's email address | `String` **First Name Attribute** | `firstNameAttr` | The name of the attribute on the OpenAM representing the user's first name | `String` **Last Name Attribute** | `lastNameAttr` | The name of the attribute on the OpenAM representing the user's last name | `String` To override these default settings for a particular portal instance, navigate to the Control Panel and click *Configuration* → *Instance Settings* → *Security* → *SSO*. Then choose *OpenSSO* in the list on the left. ================================================ FILE: en/deployment/articles/04-securing-liferay/09-cas-authentication.markdown ================================================ --- header-id: cas-central-authentication-service-single-sign-on-authentication --- # CAS (Central Authentication Service) Single Sign On Authentication [TOC levels=1-4] CAS is an authentication system originally created at Yale University. It is a widely used open source single sign-on solution and was the first SSO product to be supported by @product@. @product@'s CAS module includes the CAS client, so there's no need to install it separately. | **Note:** @product@ supports CAS 3.3.x. If you use a later version of CAS, it | is best to use CAS's support for standards such as OpenID Connect or SAML to | interface with @product@. The CAS Server application requires your server to have a properly configured Secure Socket Layer (SSL) certificate. To generate one yourself, use the `keytool` utility that comes with the JDK. First generate the key, then export the key into a file. Finally, import the key into your local Java key store. For public, Internet-based production environments, you must purchase a signed key from a recognized certificate authority or have your key signed by a recognized certificate authority. For Intranets, you should have your IT department pre-configure users' browsers to accept the certificate so they don't get warning messages about the certificate. To generate a key, use the following command: ```bash keytool -genkey -alias tomcat -keypass changeit -keyalg RSA ``` Instead of the password in the example (`changeit`), use a password you can remember. If you are not using Tomcat, you may want to use a different alias as well. For first and last names, enter `localhost` or the host name of your server. It cannot be an IP address. To export the key to a file, use the following command: ```bash keytool -export -alias tomcat -keypass changeit -file server.cert ``` Finally, to import the key into your Java key store, use the following command: ```bash keytool -import -alias tomcat -file server.cert -keypass changeit -keystore $JAVA_HOME/jre/lib/security/cacerts ``` If you are on a Windows system, replace `$JAVA_HOME` above with `%JAVA_HOME%`. Of course, all of this must be done on the system where CAS is running. Once your CAS server is up and running, configure @product@ to use it. CAS configuration can be applied either at the system scope or at the scope of a portal instance. To configure the CAS SSO module at the system or instance scope, navigate to the Control Panel, click on *Configuration* → *System Settings* (or *Instance Settings*) → *Security* → *SSO*. The values configured in System Settings provide the default values for all portal instances. Enable CAS authentication and then modify the URL properties to point to your CAS server. **Enabled:** Check this box to enable CAS single sign-on. **Import from LDAP:** A user may be authenticated from CAS and not yet exist in @product@. Select this to automatically import users from LDAP if they do not exist in @product@. For this to work, LDAP must be enabled. The rest of the settings are various URLs with defaults included. Change *localhost* in the default values to point to your CAS server. When you are finished, click *Save*. After this, when users click the *Sign In* link, they are directed to the CAS server to sign in to @product@. For some situations, it might be more convenient to specify the system configuration via files on the disk. To do so, create the following file: {LIFERAY_HOME}/osgi/configs/com.liferay.portal.security.sso.cas.configuration.CASConfiguration.cfg The format of this file is the same as any properties file. The key to use for each property that can be configured is shown below. Enter values in the same format as you would when initializing a Java primitive type with a literal value. Property Label | Property Key | Description | Type ----- | ----- | ----- | ----- **Enabled** | `enabled` | Check this box to enable CAS SSO authentication. | `boolean` **Import from LDAP** | `importFromLDAP` | Users authenticated from CAS that do not exist in @product@ are imported from LDAP. LDAP must be enabled separately. | `boolean` **Login URL** | `loginURL` | Set the CAS server login URL. | `String` **Logout on session expiration** | `logoutOnSessionExpiration` | If checked, browsers with expired sessions are redirected to the CAS logout URL. | `boolean` **Logout URL** | `logoutURL` | The CAS server logout URL. Set this if you want @product@'s logout function to trigger a CAS logout | `String` **Server Name** | `serverName` | The name of the @product@ instance (e.g., `liferay.com`). If the provided name includes the protocol (`https://`, for example) then this will be used together with the path `/c/portal/login` to construct the URL to which the CAS server will provide tickets. If no scheme is provided, the scheme normally used to access the @product@ login page will be used. | `String` **Server URL** | `serviceURL` | If provided, this will be used as the URL to which the CAS server provides tickets. This overrides any URL constructed based on the Server Name as above. | `String` **No Such User Redirect URL** | `noSuchUserRedirectURL` | Set the URL to which to redirect the user if the user can authenticate with CAS but cannot be found in @product@. If import from LDAP is enabled, the user is redirected if the user could not be found or could not be imported from LDAP. | `String` To override system defaults for a particular portal instance, navigate to the Control Panel, click on *Configuration* → *Instance Settings*, click on *Authentication* on the right and then on *CAS* at the top. ================================================ FILE: en/deployment/articles/04-securing-liferay/11-ntlm-authentication.markdown ================================================ --- header-id: ntlm-single-sign-on-authentication --- # NTLM Single Sign On Authentication [TOC levels=1-4] NTLM (NT LAN Manager) is a suite of Microsoft protocols that provide authentication, integrity, and confidentiality for users. Though Microsoft has adopted Kerberos in modern versions of Windows server, NTLM is still used when authenticating to a workgroup. @product@ now supports NTLM v2 authentication. NTLM v2 is more secure and has a stronger authentication process than NTLMv1. | **Note:** NTLM authentication was deprecated in @product-ver@ and was removed. | You can still install it from Marketplace [here](https://web.liferay.com/marketplace/-/mp/application/125668266) | or [here](https://web.liferay.com/marketplace/-/mp/application/125668305). Note that in order to use NTLM SSO, @product@'s portal instance authentication type must be set to screen name. | **Note:** To USE NTLM with @product@, you must configure your browser. Consult | your browser vendor's documentation for the details. Most importantly, all users *must* be imported from an Active Directory server. NTLM (and Kerberos) works only if the users are in the AD; otherwise any SSO requests initiated by @product@ fail. NTLM configuration can be applied either at the system scope or at the scope of a portal instance. To configure the NTLM SSO module at the system scope, navigate to the Control Panel, click on *Configuration* → *System Settings* → *Security* → *SSO* → NTLM. The values configured there provide the default values for all portal instances. Enter values in the same format as you would when initializing a Java primitive type with a literal value. Property Label | Property Key | Description | Type ---- | ---- | ---- | ---- **Enabled** | `enabled` | Check this box to enable NTLN SSO authentication. Note that NTLM will only work if @product@'s authentication type is set to screen name. | `boolean` **Domain Controller** | `domainController` | Enter the IP address of your domain controller. This is the server that contains the user accounts you want to use with @product@. | `String` **Domain Controller Name** | `domainControllerName` | Specify the domain controller NetBIOS name. | `String` **Domain** | `domain` | Enter the domain / workgroup name | `String` **Service Account** | `serviceAccount` | You need to create a service account for NTLM. This account will be a computer account, not a user account. | `String` **Service Password** | `serviceAccount` | Enter the password for the service account. | `String` **Negotiate Flags** | `negotiateFlags` | Only available at system level. Set according to the client's requested capabilities and the server's ServerCapabilities. See [here](http://msdn.microsoft.com/en-us/library/cc717152%28v=PROT.10%29.aspx) | `String` Note the AD's name and IP address correspond to the `domainControllerName` and `domainController` settings. The `Service Account` is for the _NTLM_ account (registered with NTLM), not the @product@ user account. To override system defaults for a particular portal instance, navigate to the Control Panel, click on *Configuration* → *Instance Settings*, click on *Authentication* and then on *NTLM*. ## Summary NTLM authentication is often highly desirable in Intranet scenarios where the IT department has control over what software is running on client devices and thus can ensure NTLM compatibility. In an Active Directory based network / domain, it is hard to beat the user experience that NTLM authentication can provide. Please remember that in order to use NTLM SSO, your @product@ instance authentication type must be set to screen name *and* that all users have been imported from your active directory. If this is not acceptable for your @product@ implementation, then another SSO solution (such as CAS) can be used as a broker between your portal and the NTLM authentication process. ================================================ FILE: en/deployment/articles/04-securing-liferay/12-openid-authentication.markdown ================================================ --- header-id: openid-single-sign-on-authentication --- # OpenID Single Sign On Authentication [TOC levels=1-4] OpenID is a single sign-on standard implemented by multiple vendors. Users can register for an ID with the vendor they trust. The credential issued by that vendor can be used by all the web sites that support OpenID. Some high profile OpenID vendors are Google, Paypal, Amazon, and Microsoft. Please see the [OpenID site](http://www.openid.net/) for a more complete list. | **Note:** OpenID is deprecated in @product-ver@ and has been removed. You can | still install it from Marketplace [here](https://web.liferay.com/marketplace/-/mp/application/125668346) | or [here](https://web.liferay.com/marketplace/-/mp/application/125668379). With OpenID, users don't have to register for a new account on every site which requires an account. Users register on *one* site (the OpenID provider's site) and then use those credentials to authenticate to many web sites which support OpenID. Web site owners sometimes struggle to build communities because users are reluctant to register for *another* account. Supporting OpenID removes that barrier, making it easier for site owners to build their communities. All the account information is kept with the OpenID provider, making it much easier to manage this information and keep it up to date. @product@ can act as an OpenID consumer, allowing users to automatically register and sign in with their OpenID accounts. Internally, the product uses [OpenID4Java](https://github.com/jbufu/openid4java) to implement the feature. ## OpenID at the System Scope OpenID is enabled by default in @product@ but can be disabled or enabled at either the system scope or portal instance scope. To configure the OpenID SSO module at the system level, navigate to the Control Panel and click on *Configuration* → *System Settings* → *Security* → *SSO*. There's only a single configuration setting. Check the *Enabled* box to enable OpenID at the system scope (for all portal instances), uncheck it to disable it at the system scope. ## OpenID at the Instance Scope To configure the OpenID SSO module at the portal instance scope, navigate to the Control Panel and click on *Configuration* → *Instance Settings*, then on *Authentication* → *OpenID*. There's only a single configuration setting. Check the *Enabled* box to enable OpenID for the current portal instance, or uncheck it to disable it for the current portal instance. Regardless of whether OpenID is enabled at the System or Instance scope, users can see the OpenID icon when they sign into @product@. Click *Sign In*. The OpenID icon is displayed at the lower left. ================================================ FILE: en/deployment/articles/04-securing-liferay/13-kerberos.markdown ================================================ --- header-id: authenticating-with-kerberos --- # Authenticating with Kerberos [TOC levels=1-4] You can use Kerberos to authenticate Microsoft Windows ™ accounts with @product@. This is done completely through configuration by using a combination of @product@'s LDAP support and a web server that supports the Kerberos protocol. Note that this configuration is preferred above [NTLM](/docs/7-1/deploy/-/knowledge_base/d/ntlm-single-sign-on-authentication) because security vulnerabilities persist. While it's beyond the scope of this article to explain how to set up Kerberos and Active Directory on a Windows ™ server, we can describe the minimum prerequisites for setting up Liferay authentication: 1. A Windows ™ server with Active Directory and DNS set up so the AD server and @product@ can resolve each other on the network. In other words, they must be able to ping each other *by name*. 2. An administrative user in AD @product@ can use to bind to AD. 3. A Kerberos keytab file exported via the `ktpass` command containing the cryptographic information the @product@ server needs to bind to AD. 4. A web server in front of @product@ that supports Kerberos, such as Apache, NGNIX, or IIS. The web server must also support injecting a header to be used as a token in the @product@ configuration (see below). 5. Of course, you need a @product@ installation that can also resolve by name the other servers. It should never run on the Active Directory server. When you have all of these prerequisites in place, you're ready to configure Kerberos authentication. ## How Kerberos Authentication Works From the prerequisites, you may be able to guess that there are several moving parts to how SSO works with Kerberos. ![Figure 1: Kerberos authentication requires a web server in front of your @product@ server.](../../images/kerberos.png) First, a properly configured web browser sends a negotiate request using encrypted Windows user data. To configure this, the browser must recognize the site as a trusted site (explained below). The web server's Kerberos module uses the keytab file to bind over the Kerberos protocol to AD and verify the user information. If all is okay, the AD server confirms the connection with a valid response. The web server you choose must support both the Kerberos protocol and the injection of a custom header into the request that @product@ can later read. When the web server forwards the request to @product@, it reads the header to obtain the user data and authenticate the user. Next, you'll learn how to get all of this working. ## Configuring Kerberos Authentication There are four components to configure: a user keytab from Active Directory, a web server in front of your application server, @product@, and your Windows™ clients. ### Creating the User Keytab 1. Create a user so @product@ can bind to Active Directory. 2. Generate a Kerberos keytab file using `ktpass`: ktpass -princ HTTP/[web server host name]@[domain] -mapuser [user name]@[domain] -crypto ALL -ptype KRB5_NT_PRINCIPAL -pass [password] -out c:\kerberos.keytab For example: ktpass -princ HTTP/mywebserver.intdomain.local@INTDOMAIN.LOCAL -mapuser Marta@INTDOMAIN.LOCAL -crypto ALL -ptype KRB5_NT_PRINCIPAL -pass password-for-Marta -out c:\kerberos.keytab 3. Ensure that the AD domain controller and the web server can see each other on the network via DNS configuration or `hosts` file. ### Configuring Your Web Server 1. Configure Kerberos authentication. On Linux, this involves installing `krb5` and configuring it to match your realm that's already configured for Active Directory. The example domain for the user configured in step two above would look like this: [libdefaults] default_realm = INTDOMAIN.LOCAL [domain_realm] mywebserver.intdomain.local = INTDOMAIN.LOCAL intdomain.local = INTDOMAIN.LOCAL .intdomain.local = INTDOMAIN.LOCAL [realms] INTDOMAIN.LOCAL = { admin_server = winserver.intdomain.local kdc = winserver.intdomain.local } 2. Copy the keytab file you generated on your AD server to your web server. 3. Configure your web server, making sure you set the correct server name, Kerberos service name, Kerberos authentication realms, and the path to the keytab file. For example, if you're using the Apache HTTP server, the configuration might look like this: ```apache LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so LoadModule proxy_ajp_module /usr/lib/apache2/modules/mod_proxy_ajp.so LoadModule auth_kerb_module /usr/lib/apache2/modules/mod_auth_kerb.so Order deny,allow Allow from all ProxyRequests Off ProxyPreserveHost On ProxyPass / ajp://localhost:8009/ ProxyPassReverse / ajp://localhost:8009/ ServerName mywebserver.intdomain.local Order allow,deny Allow from all AuthType Kerberos KrbServiceName HTTP/mywebserver.intdomain.local@INTDOMAIN.LOCAL AuthName "Domain login" KrbAuthRealms INTDOMAIN.LOCAL Krb5KeyTab /etc/apache2/kerberos.keytab require valid-user KrbMethodNegotiate On KrbMethodK5Passwd Off #KrbLocalUserMapping On # Below directives put logon name of authenticated user into http header X-User-Global-ID RequestHeader unset X-User-Global-ID RewriteEngine On RewriteCond %{LA-U:REMOTE_USER} (.+) RewriteRule /.* - [E=RU:%1,L,NS] RequestHeader set X-User-Global-ID %{RU}e # Remove domain suffix to get the simple logon name # RequestHeader edit X-User-Global-ID "@INTDOMAIN.LOCAL$" "" Listen 10080 ``` The last line is commented out based on user preference. If you want the domain removed from the user name when saved in @product@, uncomment it. Otherwise, leave it commented out to store the domain with the user name. ### Connecting @product@ to Active Directory over LDAP 1. Finally, configure @product@ to access Active Directory via the LDAP protocol. Change authentication to be by Screen Name by selecting it in Configuration → Instance Settings → Authentication → General. 2. Connect @product@ to AD over LDAP by going to Configuration → Instance Settings → Authentication → LDAP and adding an LDAP server. Provide the information appropriate to your installation: **Base Provider URL:** Your AD server on the proper port. **Base DN:** Your domain configuration. The example above might be `DC=INTDOMAIN.DC=LOCAL`. **Principal/Credentials:** Supply the credentials for the user exported to the keytab file. **Authentication Search Filter:** Supply the appropriate search filter to return user objects. For example, `(&(objectCategory=person)(sAMAccountName=*))` **UUID:** Supply what uniquely identifies a user, such as `sAMAccountName`. **Screen Name:** Supply the field that should be mapped to @product@'s screen name field, such as `sAMAccountName`. **Password:** Supply the field that contains the user's password, such as `userPassword`. 3. Be sure to test the connection, save, and enable the configuration. 4. Finally, configure the token for single sign-on at Configuration → System Settings → Security → SSO → Token Based SSO. Make sure the User Token Name matches *exactly* the token you configured in your web server. Click the *Enabled* and *Import from LDAP* boxes and click *Save*. Excellent! You've configured your servers. All that's left is to configure your clients. ### Configuring your Clients You must do two things: make your computer log into the domain and configure your @product@ server as a trusted Internet site. 1. Join your computer to your domain. In keeping with the example above, you'd make your computer a member of the `INTDOMAIN.LOCAL` domain. 2. Log in as a user in that domain. 3. Internet Explorer, Edge, and Chrome use the Windows™ settings for trusted sites. If you use these browsers, go to Internet Options → Security → Local Intranet Sites and add your @product@ server's URL. For example, add `http://mywebserver.intdomain.local:10080`. 4. Firefox can be configured by typing `about:config` in its address bar. Search for the below two preferences and add the @product@ server's URL as the value for both: - `network.negotiate-auth.delegation-uris` - `network.negotiate-auth.trusted-uris` After configuring these things, test your configuration by accessing @product@ through the web server's URL. Since you are already logged into your client machine, you should be automatically logged into @product@ without a user/password prompt. Congratulations on configuring Kerberos with @product@! ================================================ FILE: en/deployment/articles/04-securing-liferay/14-cors.markdown ================================================ --- header-id: configuring-cors --- # Configuring CORS [TOC levels=1-4] CORS stands for Cross-Origin Resource Sharing. An Origin is a web server at a different domain, and a Resource is some asset stored on the server, like an image, PDF, or HTML file. Sometimes you must request resources stored on another origin. This is called a cross-origin request, and web servers have policies to allow or deny such requests. For example, browsers themselves don't allow cross-origin AJAX-style requests from scripts to help mitigate [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks. These APIs follow a *same origin* policy. But for certain resources, it can be convenient to allow @product@ to serve them to different origins. For example, if you manage images in Docs & Media, you may want to allow cross-origin requests for them. You can enable CORS for matching URLs in @product@ or for JAX-RS application resources. ## Enabling CORS for @product@ Services You'll find the settings in Configuration → System Settings → Security → Security Tools → Portal Cross Resource Origin Sharing (CORS): 1. Click *Add* to create a configuration entry. 2. Fill out the fields on the form. When finished, click *Save*. ![Figure 1: The CORS system settings provide a way to configure CORS headers for Liferay services.](../../images/CORS-portal.png) **Enabled:** Check this box to enable the entry. **Name:** Give the configuration entry a name. **URL Pattern:** Use the Plus button to add as many patterns as you need. Define patterns that match URLs to the resources you want to share. For example, if you have many attachments in the Knowledge Base application, you could define this pattern: /knowledge_base/* This would define resources stored in the Knowledge Base as applicable to the policy you set in the response header below. **CORS Response Headers:** Use the Plus button to add as many headers as you need. Define policies for any of the [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS) here. You can also use a [configuration file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) to configure CORS. ## Enabling CORS for JAX-RS Applications You'll find the settings in Configuration → System Settings → Security → Security Tools → Web Contexts Resource Origin Sharing (CORS): 1. Click *Add* to create a configuration entry. 2. Fill out the fields on the form. When finished, click *Save*. ![Figure 2: There's a separate system settings category for CORS web contexts.](../../images/CORS-jax-rs.png) **Dynamic Web Context OSGi Filter:** Define an LDAP-style [filter](https://osgi.org/specification/osgi.cmpn/7.0.0/service.http.whiteboard.html) to define which JAX-RS whiteboard applications the CORS headers in this entry apply to. This is the default filter: ```properties (&(!(liferay.cors=false))(osgi.jaxrs.name=*)) ``` It applies CORS headers to all deployed JAX-RS whiteboard applications without a `liferay.cors=false` property. This helps during development, but in production you should use the narrowest configuration possible. **URL Pattern:** Use the Plus button to add as many patterns as you need. Define patterns that match URLs to the web services you want to access. **CORS Response Headers:** Use the Plus button to add as many headers as you need. Define policies for any of the [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS) here. [JAX-RS](/docs/7-2/frameworks/-/knowledge_base/f/jax-rs) developers can use the `@CORS` annotation to set policies for their deployed applications. ================================================ FILE: en/deployment/articles/04-securing-liferay/15-antisamy.markdown ================================================ --- header-id: antisamy --- # AntiSamy [TOC levels=1-4] @product@ includes an [AntiSamy](https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project) module that protects against user-entered malicious code. If your site allows users to post content, such as in message boards, blogs, or other applications, they could include malicious code either intentionally or unintentionally. The AntiSamy module filters HTML/CSS fragments and removes suspect JavaScript code from them. The module leverages the powerful [OWASP AntiSamy library](https://www.owasp.org/index.php/Category:OWASP_AntiSamy_Project) to enforce a content policy that's been effective for the auction site eBay. The AntiSamy module adds an OWASP AntiSamy implementation to your portal's list of existing sanitizer implementations. @product@ uses the AntiSamy sanitizer and any existing configured sanitizers to scrub user input to blogs entries, calendar events, message boards posts, wiki pages, and web content articles. AntiSamy is enabled by default. ![Figure 1: @product@'s AntiSamy configuration options allow you to specify both a blacklist and a whitelist.](../../images/antisamy.png) ## Configuring AntiSamy AntiSamy uses both a blacklist and a whitelist, so you can define subsets of entities that should be sanitized or not sanitized. The whitelist prevents content of that type from being filtered, while the blacklist filters content of that type. By default, everything is sanitized except for `JournalArticle`, `BlogsEntry`, and `FragmentEntry`. The assumption is that users posting these kinds of content are trusted, while users posting message boards or wiki articles may not be trusted. If this is not the configuration you want, you can change it: 1. Navigate to *Control Panel* → *System Settings* → *Security Tools* → *AntiSamy Sanitizer*. 2. Enter a package path you want to sanitize into the *Blacklist* field. 3. Use the plus (+) button to add further Blacklist fields if you need them. 4. Use the plus (+) button to add further Whitelist fields if you need them. 5. Enter a package path you don't want sanitized into a *Whitelist* field. 6. If you want to remove a package path from the configuration, click the trash can icon. 7. When finished, click *Save*. ## Using Wildcards You can use wildcards in the configuration. For example, if you only want to sanitize message board posts and nothing else, you can 1. Configure the whitelist to `*` 2. Configure the blacklist to `com.liferay.message.boards.*` The whitelist and the blacklist work together. Without the blacklist, the above configuration's whitelist must include every content type except `com.liferay.message.boards`, which would be a daunting task to configure. Use AntiSamy to ensure user-generated content stays safe for other users to view. ================================================ FILE: en/deployment/articles/04-securing-liferay/15-oauth2/01-oauth2-intro.markdown ================================================ --- header-id: oauth-2-0 --- # OAuth 2.0 [TOC levels=1-4] OAuth 2.0 is an industry-standard authorization protocol. Users can seamlessly share select credentials from your Liferay-based website with various clients. You've seen this before: any time you see a "This site wants to access:" button (followed by a list of things like email address, friends list, etc.) from Google or Facebook, or you authorize a third-party Twitter client, that's OAuth 2.0 in action. It works by authorizing password-less access to portions of user-owned resources (such as an email address, a user profile picture, or something else from your account) and other permissioned resources. OAuth 2.0's design encrypts all authorization transport through HTTPS, which prevents data passed between the systems from being intercepted. ## Flow of OAuth 2.0 OAuth 2.0 takes advantage of web standards wherever possible: transport is encrypted with HTTPS; tokens are implemented as HTTP headers; data is passed via web services. Here's how OAuth 2.0 works: ![Figure 1: OAuth 2.0 takes advantage of web standards.](../../../images/oauth-flow.png) 1. A user accesses a third-party application that supports authorization via credentials from a Liferay-based website. In the application (web or mobile), the user requests authorization via OAuth, sending the browser or app to the Liferay-based website. When using PKCE (explained below), the application also generates a code verifier and sends a code challenge that is created by applying a transformation to it. 2. The user authenticates and is shown the resources the application wants permission to access. When the user gives permission by clicking *Allow*, Liferay generates an authorization code that's sent to the application over HTTPS. 3. The application then requests a more permanent authorization token and sends the code with the request (along with the PKCE code verifier). 4. If the authorization code matches (and the transformed PKCE code verifier matches the previously sent code challenge), Liferay cryptographically generates an authorization token for this user and application combination. It sends the token to the application over HTTPS. Initial authorization is now complete! 5. When the application must retrieve data, it sends the token with the request to prove it's authorized to have that data. 6. Provided the token matches what Liferay has for that user and application, access is granted to retrieve the data. That description throws around a lot of terms. Definitions provided below. ## OAuth 2.0 Terminology **Authentication:** Providing credentials so a system can verify who you are by matching those credentials with what it has stored. OAuth is not an authentication protocol. **Authorization:** Granting access to resources stored on another system. OAuth is an authorization protocol. **Application:** Any client (web, mobile, etc.) that must be authorized to have access to resources. Applications must be registered by administrators before users can authorize access to their resources. **Client:** Almost synonymous with *application*, except that applications can have variants, such as web and mobile. These variants are clients. **Client ID:** An identifier given to a client so it can be recognized. **Client Secret:** A previously agreed-upon text string that identifies a client as a legitimate client. **Access Token:** A cryptographically generated text string that identifies a user/client combination for access to that User's resources. **Response Type:** OAuth 2.0 supports several response types. Pictured and described above is the most common: the authorization code. Other response types are *password* (logging in with a user name and password), and *client credentials* (headless predefined application access). **Scope:** A list of items that define what the application wants to access. This list is sent during the initial authorization request (or otherwise defaults to scopes selected in the application registration) so users can grant or deny access to their resources. **Callback URI:** Also called a Redirection Endpoint URI. After authorization is complete, the authorization server (i.e., Liferay) sends the client to this location. ## Creating an Application When you have an application that can use OAuth 2.0 for authorization, you must register that application so @product@ can recognize it. Do this by accessing *Control Panel* → *Configuration* → *OAuth2 Administration*: 1. Click the *Add* (![add](../../../images/icon-add.png)) button. 2. Fill out the form (description below). 3. Click *Save* to save the application. ![Figure 2: Adding an application registers it so users can authorize access to their data.](../../../images/oauth-new-application.png) **Name:** Give the application a recognizable title. **Website URL:** Add a link to the application's website. **Callback URIs:** Enter at least one (line-separated) URI where users should be redirected after they authorize (or refuse to authorize) access to their accounts. This should link to a handler for whichever Allowed Authorization Types you support (see below). **Client Profile:** Choose a template that filters the authorization types that are appropriate (secure) for that profile. For example, if your application is a web application, choose *Web Application*, and these authorization types are available and selected automatically: Authorization Code, Client Credentials, Refresh Token, and Resource Owner Password Credentials. These are OAuth 2 "flows" documented in the [OAuth2 RFC 6749 Standards Document](https://tools.ietf.org/html/rfc6749). If you want to select authorization types manually, select *Other*. **Allowed Authorization Types:** Select the defined OAuth 2 [protocol flows](https://tools.ietf.org/html/rfc6749#section-1.2) your application supports. Several common combinations are defined for you in the various Client Profiles above. After you save the form, it reappears with additional fields: **Client ID:** The system generates this for you; it's an identifier for your application, so that @product@ knows what application is being authorized to access user data. **Client Secret:** Click the *pencil* icon to generate a client secret. The secret identifies the client during the authorization process (see figure 1 above). Not all client profiles require a client secret, because some are incapable of keeping it secret! This is when the aforementioned PKCE code challenge and verifier is needed. **Icon:** Upload an icon that your application's users identify with your application. This is displayed on the authorization screen. **Privacy Policy URL:** Add a link to your application's privacy policy. **Token Introspection:** Allow your application to retrieve metadata from the token by requesting it from @product@. This implements [RFC 7662](https://tools.ietf.org/html/rfc7662). Excellent! Now you know how to add OAuth2 authorization for your application to @product@! Next, you must define scopes of user data the application can access. ================================================ FILE: en/deployment/articles/04-securing-liferay/15-oauth2/02-scopes.markdown ================================================ --- header-id: oauth2-scopes --- # OAuth2 Scopes [TOC levels=1-4] In OAuth 2.0, applications are granted access to limited subsets of user data. These are called *scopes* (not to be confused with Liferay scopes). They are created in two ways: 1. By administrators, by creating a Service Access Policy for the scope 2. By developers, by creating a JAX-RS endpoint. By default, scopes are generated based on the HTTP verbs supported by the JAX-RS endpoint. A special annotation overrides this behavior and registers specific scopes. ## Creating a Scope for a JSONWS Service The most common way to create a scope is to create a [Service Access Policy](/docs/7-2/deploy/-/knowledge_base/d/service-access-policies) prefixed with the name `OAUTH2_`. This naming convention causes the policy to appear in the OAuth application configuration screen as a scope. For example, say the application needs access to a user's profile information to retrieve the email address. To grant the application access to this, go to *Control Panel* → *Configuration* → *Service Access Policy*, and create the policy pictured below. ![Figure 1: A Service Access Policy defines a scope for OAuth 2.0 applications.](../../../images/oauth-service-access-policy.png) Note that the policy is not a default policy, and that it grants access only to one method in the `UserService`. This is a JSONWS web service generated by Service Builder. You can view a list of all available services in your installation at this URL: http://[host]:[port]/api/jsonws/ Once you create a policy and name it with the `OAUTH2_` prefix, it appears in the *Scopes* tab in OAuth2 Administration. ![Figure 2: Scopes named with the proper prefix appear in the Scopes tab of your application configuration.](../../../images/oauth-scopes-tab.png) Now you can select it and save your application. ## Creating the Authorization Page This step is optional. Users need an interface to authorize access to their accounts, and one is provided automatically. If, however, you want to customize the page, you can create an authorization page in your Site. 1. Go to *Control Panel* → *System Settings* → *Security* → *OAuth2*. Click the bottom item on the left, labeled *Authorize Screen*. 2. Two defaults appear. The first is the URL to the authorize page. By default, it's `/group/guest/authorize-oauth2-application`. This corresponds to the default site's URL and a page on that site called `authorize-oauth2-application`. 3. If you have customized the name and URL of your default site, make the appropriate change here so the URL matches the page you'll create in that site next. Click *Save*. 4. Go to your Site's *Build* → *Pages* screen. Click the ![add](../../../images/icon-add.png) button and choose *Private Page*. This forces users to log in. 5. Choose the *Full Page Application* type. 6. Give the page the same name you configured in step 2. 7. Uncheck the box labeled *Add this Page to the following Menus:*. You don't want this page showing up in your Site navigation. 8. On the page that appears next, verify the Friendly URL matches the URL you configured in step 2. 9. Under *Full Page Application*, choose *OAuth2 Authorize Portlet*. 10. Click *Save*. Excellent! Users can use the default or the UI of your design to go through the authorization process. Now that you have the UI and you understand scopes, it's time to make the authorization process happen in your application. ================================================ FILE: en/deployment/articles/04-securing-liferay/15-oauth2/03-authorizing-access.markdown ================================================ --- header-id: authorizing-account-access-with-oauth2 --- # Authorizing Account Access with OAuth2 [TOC levels=1-4] Once you have an application registered, you can start authorizing users. To do that, you must construct the URL to the authorization server (@product@). The authorization server asks users to authorize the requested permissions to their resources, defined as you saw in the previous tutorial as scopes. ## Authorization Code Flow The most common OAuth flow is the Authorization Code flow, used for web applications. The URL for this requires the following request parameters: - `response_type` - `client_id` To construct a URL for this authorization, follow this pattern: https://[hostname]/o/oauth2/authorize?response_type=code&client_id=[client ID] The client ID comes from registering the application. It's automatically generated (you can change it if you edit the application). IMPORTANT: Sometimes the phrase "web application" is used loosely, implying applications where the above URL is requested from the web browser directly. If this happens, you'd leak the client secret, compromising the security of the grant flow and the application. In such cases, select the "User Agent Application" client profile instead when registering your application. This makes a secure alternative available to your application: PKCE Extended Authorization Code flow (see below). Once the user has authorized the requested permissions to their resources, the authorization server returns an authorization code to your application at its registered callback URI (A.K.A. redirect URI) as a query string parameter. [your callback URI]?code=[authorization server generated code] Your application must then exchange this authorization code for an access token by sending a POST request following this pattern: http://localhost:8080/o/oauth2/token With the following parameters in the body (encoded as `application/x-www-form-urlencoded`): client_id=[client ID] client_secret=[client secret] grant_type=authorization_code code=[authorization server generated code] redirect_uri=[registered callback URI] In the body of HTTP response to this request, you receive JSON like this: ```json { "access_token": "[authorization server generated access token]", "token_type": "Bearer", "expires_in": 600, "scope": "[the scopes that were authorized by the user]", "refresh_token": "[authorization server generated refresh token]" } ``` From this you should extract and persist the access token. If you intend to use the token for an indefinite amount of time (beyond 600 seconds from the above example) you also need the refresh token. This can be used in conjunction with the Refresh Token Flow to obtain a new access token with the same permissions, without further user authorization. The authorization server only issues Refresh Tokens if your application registration is registered for this flow. ## PKCE Extended Authorization Code Flow This flow is the same as above with the addition of the Proof Key for Code Exchange (PKCE). It requires another request parameter: `code_challenge`. This flow is for clients like smartphone applications that may not have sole access to the URL (and thus the request parameters) redirected to by the authorization server after the user authorization. It protects against a malicious application on the same system authorizing itself by reading the response code. To do this, the client application sends a *code challenge* with the authorization request: a string it has generated and which it only knows. To generate this string it must first create another secret string known as the *Code Verifier*, and then apply a transformation to it. After authorization, the code verifier is sent with the authorization code, validating the client. For more detail on how to do this, please refer to the [PKCE specification](https://tools.ietf.org/html/rfc7636). To support this flow, you must have defined PKCE as an Allowed Authorization Type when you created the application. This is part of the Native Application and User Agent Application client profiles. To request an authorization code using PKCE, use a URL containing the `code_challenge` request parameter: https://[hostname]/o/oauth2/authorize?response_type=code&client_id=[client ID]&code_challenge=[PKCE code challenge] The rest of the process is identical to Authorization Code flow, except that when making the final request to get the access token, you must also provide the following parameter: code_verifier=[Code Verifier that was transformed and sent as code_challenge previously] ## Client Credentials and Resource Owner Flows There are two other, less used flows. If you have a scenario where two servers exchange agreed upon, non user-centric data, you can bypass the Allow/Deny screen for users and authorize the client. This is called the Client Credentials flow, and you'd use this URL pattern: https://[hostname]/o/oauth2/token?grant_type=client_credentials&client_id=[client ID]&client_secret=[client secret] A final flow, where users trust the application with their passwords is rare, but possible. This is called the Resource Owner Password flow, and its URL pattern looks like this: https://[hostname]/o/oauth2/token?grant_type=password&client_id=[client ID]&client_secret=[client secret]&username=[user@emailaddress.com]&password= Users are prompted for their passwords, and upon successful log in, receive an authorization code. ## Token Use All flows above result in an access token that's sent by the authorization server (@product@) to the client application. This token is sent in the response for the client application to store and send along with any future request for data. For example, say the authorization code `946856e2b5ddf0928f6fc55f657bab73` was sent to the client application. When the client requests data, this code must be sent in each request header. Using a command line HTTP client such as Curl, you could send a request like this: curl -H 'Authorization: Bearer 946856e2b5ddf0928f6fc55f657bab73' 'https://[hostname]/o/api/sample2' OAuth 2.0 provides a convenient way for client applications to be granted access to particular services (scopes) by users without sharing credential information. ## Revoking Access Once access is granted, users or administrators are free to revoke access whenever they wish. If this happens to a client, the token becomes invalid and the client must ask the user for authorization again. This puts users in control of what has access to their data, and they can exercise this control at any time. ![Figure 1: Users have complete control over what applications have access to their data in their account profiles.](../../../images/oauth-user-apps.png) In their account areas, users can click *OAuth2 Connected Applications* and see a list of applications they've allowed to access their accounts. From here, they can revoke access by clicking the *Remove Access* item in the Action menu or the *Remove Access* button in the detail screen for the application. Administrators can view the authorizations in the Authorizations tab of any app in *Control Panel* → *Configuration* → *OAuth2 Administration*. ![Figure 2: All authorizations for an app appear in the Authorizations tab for the app.](../../../images/oauth-revoke-access.png) Clicking the *Revoke* button on any listed authorization revokes that application's access to that user's account. ## Summary OAuth 2.0 provides a complete and secure authorization flow for users, without their having to share any credential information. Once applications are created in the system, secure tokens provide access to particular scopes of information, and this access can be revoked at any time, making OAuth 2.0 a convenient method for users and developers alike to access the information they need. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/01-upgrading-to-liferay-7-2-intro.markdown ================================================ --- header-id: upgrading-to-product-ver --- # Upgrading to @product-ver@ [TOC levels=1-4] Upgrading to @product-ver@ involves migrating your installation and [code (your theme and custom apps)](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver) to the new version. Here you'll learn how to upgrade your installation. Here are the installation upgrade paths: | Upgrade Path | Description | | ---------------------------------------------- | -------------------------- | | Liferay Portal 5.x and 6.0.x → Liferay Portal 6.2 → @product@ 7.2 | Support life ended for Liferay Portal 5.0, 5.1, 5.2, and 6.0 | | Liferay Portal 6.1.x → @product@ 7.1 → @product@ 7.2 | Support life ended for Liferay Portal 6.1 | | Liferay Portal 6.2+ → @product@ 7.2 | | | @product@ 7.0+ → @product@ 7.2 | | | **Note:** Themes and custom apps from Liferay Portal 6.0 through @product@ | 7.1 can be upgraded directly to @product@ 7.2. See the | [code upgrade instructions](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver) | for details. Here are the upgrade steps: | **Note:** You can | [prepare a new Liferay server for data upgrade](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database) | in parallel with the steps up to and including the step to | [upgrading the @product@ data](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data). 1. {.root}[If You're Upgrading to Liferay Portal 6.2, Follow the Liferay Portal 6.2 Upgrade Instructions First](/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay){.title} 2. [If You're Upgrading a Sharded Environment, Follow the Instructions for Upgrading It](/docs/7-2/deploy/-/knowledge_base/d/upgrading-a-sharded-environment){.title} Upgrading a sharded installation to @product-ver@ requires migrating it to as many non-sharded @product@ installations (servers) as you have shards.{.summary} 3. [If You're a Upgrading a Cluster, Read Those Instructions First](/docs/7-2/deploy/-/knowledge_base/d/updating-a-cluster){.title} If you're updating a cluster, read those instructions first and apply them to your upgrade.{.summary} 4. [Plan for Handling the Deprecated Applications](/docs/7-2/deploy/-/knowledge_base/d/planning-for-deprecated-applications){.title} Every application deprecation has different ramifications. Learn how the deprecations might affect your site and decide how to replace the functionality you use from those applications.{.summary} 5. [Test Upgrading a @product@ Backup Copy](/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy){.title} Here you'll prune a backup copy of your database and upgrade the data. You'll learn how to use the upgrade tool and resolve upgrade issues. The notes and scripts you assemble as you prune and upgrade the database copy are invaluable for correctly and efficiently upgrading the @product@ database you'll use with @product-ver@.{.summary} 1. [Copy the Production Installation to a Test Server](/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy#copy-the-production-installation-to-a-test-server){.title} You'll use the installation copy to test data changes.{.summary} 2. [Copy the Production Database Backup](/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy#copy-the-production-backup-to-the-test-database){.title} Copy the production backup to the test database and save the copy logs for analysis.{.summary} 3. [Remove Duplicate Web Content and Structure Field Names](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database){.title} 4. [Find and Remove Unused Objects](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database){.title} You may have intermediate versions of objects (e.g., `JournalArticle` objects) that you don't need. Remove them and objects that only reference them.{.summary} 5. [Test @product@ with its Pruned Database Copy](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database#test-with-the-pruned-database-copy){.title} Make sure @product@ continues to work successfully. If it's broken, start over with a fresh database backup and prune it more carefully.{.summary} 6. [Install the New @product@ Version on a Test Server](/docs/7-2/deploy/-/knowledge_base/d/upgrading-your-test-server-and-database){.title} Install the @product@ version you're upgrading to, to use its upgrade tool.{.summary} 7. [Tune Your Database for the Upgrade](/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade){.title} 8. [Upgrade the Liferay Data, then Return Here](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data){.title} 9. [If the Upgrade Took too Long, Prune a Fresh Database Backup More and Upgrade Its Data](/docs/7-2/deploy/-/knowledge_base/d/test-upgrading-a-product-backup-copy#copy-the-production-backup-to-the-test-database){.title} 10. [Test the Upgraded Instance](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database){.title} Make sure @product@ continues to work successfully. If it's broken, start over with a fresh database backup and prune it more carefully.{.summary} 11. Checkpoint: You've pruned and upgraded your production database copy. You're ready to prepare for upgrading the production database. 6. [Prepare to Upgrade the @product@ Database](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database){.title} Preparing for the production database upgrade involves pruning and testing it, upgrading your Marketplace apps, publishing staged changes, and synchronizing a complete data and configuration backup.{.summary} 1. [Remove All Noted Unused Objects](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#remove-all-unused-objects-you-identified-earlier){.title} Remove all unused objects you noted from pruning your test database.{.summary} 2. [Test @product@](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#test-using-the-pruned-database){.title} 3. [Upgrade Your Marketplace Apps](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#upgrade-your-marketplace-apps){.title} 4. [Publish All Staged Changes](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#publish-all-staged-changes-to-production){.title} 5. [Synchronize a Complete @product@ Backup](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#synchronize-a-complete-backup){.title} Synchronize a complete backup of your production @product@ server installation and pruned production database.{.summary} 6. Checkpoint: You're ready to prepare a @product-ver@ server for upgrading a production database. 7. [Prepare a New @product@ Server for Data Upgrade](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade){.title} Set up a production server with @product-ver@, configured to use your document repository and @product@ database. You'll migrate your portal and system properties too. (Note, this step can be done in parallel with any of the previous steps.){.summary} 1. [Request an Upgrade Patch From Liferay Support \(Liferay DXP Only\)](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#synchronize-a-complete-backup){.title} 2. [Install the @product@ Version You're Upgrading To](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#install-liferay){.title} 3. [Install the Latest Upgrade Patch or Fix Pack \(Liferay DXP Only\)](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#install-the-latest-upgrade-patch-or-fix-pack-liferay-dxp-only){.title} 4. [Migrate Your OSGi Configurations \(@product@ 7.0+\)](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#migrate-your-osgi-configurations-70){.title} 5. [Migrate Your Portal Properties](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#migrate-your-portal-properties){.title} Migrate your portal properties to your new @product-ver@ server.{.summary} 1. [Update Your Portal Properties](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#update-your-portal-properties){.title} Some of the portal properties have new values or have been removed or replaced. Update your properties for @product-ver@.{.summary} 2. [Convert Applicable Properties to OSGi Configurations](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#convert-applicable-properties-to-osgi-configurations){.title} Many applications are configured using OSGi Configuration (Config Admin) instead of portal properties. Convert your existing properties to their OSGi Configuration replacements.{.summary} 6. [Configure Your Documents and Media File Store](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#configure-your-documents-and-media-file-store){.title} The upgrade tool upgrades your Documents and Media file store too. Update your Documents and Media file store configuration and specify it for the upgrade tool.{.summary} 7. [Disable Indexing](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade#disable-indexing){.title} Improve the data upgrade performance by disabling indexing.{.summary} 8. Checkpoint: You've prepared a new @product@ server for executing the data upgrade 8. [Upgrade the @product@ data](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data){.title} This section explains the data upgrade options, upgrade configuration, and the upgrade process.{.summary} 1. [Tune Your Database for the Upgrade](/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade){.title} 2. [Configure the Data Upgrade](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade){.title} Configure the data upgrade, including the data store and whether to automatically upgrade the modules.{.summary} 3. [Upgrade the Core](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool){.title} 1. [Run the Data Upgrade Tool](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool#upgrade-tool-usage){.title} Run the data upgrade tool. Resolve any core upgrade issues.{.summary} 2. [Issues Upgrading to 7.0 or Lower? Restore the Database Backup](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database#synchronize-a-complete-backup){.title} If the issues were with upgrades to Liferay 7.0 or lower, get a clean start by restoring the pruned production database backup.{.summary} 3. [Upgrade Your Resolved Issues](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-core-using-the-upgrade-tool){.title} If there were issues upgrading to 7.2, resolve them and restart the data upgrade tool; continue if there were no issues.{.summary} 4. [Upgrade the Liferay Modules](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell){.title} Learn how to use Gogo Shell to upgrade the Liferay modules, if you didn't upgrade them automatically with the core.{.summary} 1. [Upgrade Modules that are Ready for Upgrade](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell#command-usage){.title} Discover which modules are ready for upgrade and upgrade them.{.summary} 2. [Check Module Upgrade Status and Resolve Any Module Upgrade Issues](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell#checking-upgrade-status){.title} 3. Checkpoint: You've completed upgrading the Liferay data. It's time to get your server ready for production.{.summary} 9. [Execute the Post-Upgrade Tasks](/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks){.title} Now that your database is upgraded, clean up remnants of upgrading by restoring your database optimizations, enabling and regenerating your search indexes, and more.{.summary} 1. [Remove the Database Tuning](/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks#tuning-your-database-for-production){.title} 2. [Re-enable and Re-Index the Search Indexes](/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks#re-enabling-search-indexing-and-re-indexing-search-indexes){.title} 3. [Update Web Content Permissions \(7.0 and lower\)](/docs/7-2/deploy/-/knowledge_base/d/executing-post-upgrade-tasks#enabling-web-content-view-permissions){.title} 4. [Address Any Deprecated Apps That Still Need Handling](/docs/7-2/deploy/-/knowledge_base/d/planning-for-deprecated-applications){.title} 10. Checkpoint: You've completed the upgrade and post-upgrade tasks Follow the steps above to upgrade your @product@ installation to @product-ver@. They link upgrade topic details to help complete a safe, successful upgrade. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/02-planning-for-deprecated-apps.markdown ================================================ --- header-id: planning-for-deprecated-applications --- # Planning for Deprecated Applications [TOC levels=1-4] As @product@ evolves so do Liferay applications. They may, for example, be deprecated in favor of newer and better Liferay applications. The deprecations might call for migrating application data to a new application or completely removing the application and data. Before upgrading, examine the deprecations: - [7.2 deprecations](/docs/7-2/deploy/-/knowledge_base/d/deprecated-apps-in-7-2-what-to-do) - [7.1 deprecations](/docs/7-1/deploy/-/knowledge_base/d/deprecated-apps-in-7-1-what-to-do) Determine how and when to address the deprecations in your upgrade plan. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/03-test-ugrading-a-liferay-backup-copy/01-test-upgrading-a-liferay-backup-copy-intro.markdown ================================================ --- header-id: test-upgrading-a-product-backup-copy --- # Test Upgrading a @product@ Backup Copy [TOC levels=1-4] Before upgrading your production Liferay instance, you should do a trial run (even multiple runs) to make sure that you upgrade successfully and efficiently. Here's the process: - [Preparing a test server and database](#preparing-a-test-server-and-database): This involves copying your current production installation to a test server and copying your production data backup to a test database. After you prune data from the test database (next step) you'll test against it. - [Pruning the database](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database): Free your database of duplicate and unused objects. By removing them you can reduce upgrade time and improve your server's performance. - [Upgrading your test server and database](/docs/7-2/deploy/-/knowledge_base/d/upgrading-your-test-server-and-database): First you'll optimize your database for the data upgrade. Taking time to do this can save upgrade time. Then you'll do an upgrade test run (or several test runs) on a the pruned database copy. After going through the upgrade process, resolving any issues, and testing the upgraded server successfully, you can confidently upgrade your production database. | **Tip:** These steps and | [preparing a new @product@ server](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade) | can be done in parallel to save time. Now prepare your test environment. ## Preparing a Test Server and Database Using a new separate server and database let's you safely test upgrading. ### Copy the Production Installation to a Test Server Prepare a test server to use a copy of your production installation. Your test server must use the same Liferay version you're using on production. Configure your server to use a new empty database for testing data upgrades. ### Copy the Production Backup to the Test Database Import data from your [production database backup](/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation) to the new empty database. | **Important:** Make sure to save the data import log---you'll examine it in | the next steps. Next you'll prune your database of unneeded data. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/03-test-ugrading-a-liferay-backup-copy/02-pruning-the-database.markdown ================================================ --- header-id: pruning-the-database --- # Pruning the Database [TOC levels=1-4] Accumulating unneeded site data is common. For example, you may have many unused versions of Web Content articles or Documents and Media files. If you're done revising them and don't need the intermediate revisions, you can remove them. This saves you space and upgrade time. Here you'll remove unneeded data and then test your server. ## Remove Duplicate Web Content Structure Field Names If you've used Web Content Management extensively, you might have structures without unique field names. Find and remove duplicate field names before upgrading. If you upgraded to Liferay Portal 6.2 previously and skipped doing this, you'll encounter this error: 19:29:35,298 ERROR [main][VerifyProcessTrackerOSGiCommands:221] com.liferay.portal.verify.VerifyException: com.liferay.dynamic.data.mapping.validator.DDMFormValidationException$MustNotDuplicateFieldName: The field name page cannot be defined more than once com.liferay.portal.verify.VerifyException: com.liferay.dynamic.data.mapping.validator.DDMFormValidationException$MustNotDuplicateFieldName: The field name page cannot be defined more than once If this is the case, roll back to your previous backup of Liferay Portal 6.2 and find and remove duplicate field names. ## Find and Remove Unused Objects In the UI or using database queries, identify unused objects. Then remove them via Liferay's UI or using Liferay's API through the [script console](/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console) or a portlet you create. | **Important**: You should only use Liferay's UI or API because they account | for relationships between @product@ objects. | | Never use SQL directly on your database to remove records. Your SQL might miss | object relationships, orphaning objects and causing performance problems. Here are some common places to check for unused objects. ### Objects From the Large/Populated Tables Table rows are mapped to @product@ objects. Large tables with many records might contain lots of unused objects. The greater the table size and the records per table, the longer upgrading takes. Finding and removing unused objects associated with such tables reduces upgrade times. Your data import log (from the previous step) can provide valuable table information. Database engines show this information in different ways. Your database import log might look like this: Processing object type SCHEMA\_EXPORT/TABLE/TABLE\_DATA imported "LIFERAY"."JOURNALARTICLE" 13.33 GB 126687 rows imported "LIFERAY"."RESOURCEPERMISSION" 160.9 MB 1907698 rows imported "LIFERAY"."PORTLETPREFERENCES" 78.13 MB 432285 rows imported "LIFERAY"."LAYOUT" 52.05 MB 124507 rows imported "LIFERAY"."ASSETENTRY" 29.11 MB 198809 rows imported "LIFERAY"."MBMESSAGE" 24.80 MB 126185 rows imported "LIFERAY"."PORTALPREFERENCES" 4.091 MB 62202 rows imported "LIFERAY"."USER\_" 17.32 MB 62214 rows ... Several items stand out in the example database import: - The `JOURNALARTICLE` table makes up 98% of the database size. - There are many `RESOURCEPERMISSION` records. - There are many `PORTLETPREFERENCES` records. Search for unused objects associated with the tables that stand out and use Liferay's API (e.g., the UI or [script console](/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console)) to delete the objects. ### Common Object Types Worth Checking Some object types should be checked for unused objects. Here are some reasons for checking them: - Removing them frees related unused objects for removal - They're version objects that aren't worth keeping Check these object types: - **Sites**: Remove sites you don't need. When you remove a site, remove its related objects: - Layouts - Portlet preferences - File entries (document library objects) - Asset Entries - Tags - Vocabularies and categories - Expando fields and their values - `ResourcePermission` objects - (and everything else) - **Instances**: Unused instances are rare, but since they are the highest object in the hierarchy, removing their objects can optimize upgrades considerably: - Sites (and all their related content) - Users - Roles - Organizations - Global `ResourcePermission` objects - **Intermediate web content versions:** @product@ generates a new web content version after any modification (including translations). Consider removing versions you don't need. Removing a Journal Article, for example, also removes related objects such as image files (`JournalArticleImage`) that are part of the content. Removing unneeded image files frees space in your database and file system. For more details, see [Example: Removing Intermediate Journal Article Versions](/docs/7-2/deploy/-/knowledge_base/d/example-removing-intermediate-journal-article-versions). - **Document versions**: As with Journal Articles, if you don't need intermediate document versions, delete them. This saves space both in the database and on the file system, space that no longer needs to be upgraded. - **Layouts:** Layouts are site pages, and they affect upgrade performance because they relate to other entities such as portlet preferences, permissions, assets, ratings, and more. Remove unneeded layouts. - **Roles**: Remove any Roles you don't need. Deleting them also deletes related `ResourceBlockPermission` and `ResourcePermission` objects. - **Users:** If you have Users that aren't active anymore, remove them. - **Vocabularies**: Remove any unused vocabularies. Note that removing a vocabulary also removes its categories. - **Orphaned data**: Check for unused objects that are not connected to anything. Here are some examples: - `DLFileEntries` with no file system data. - `ResourcePermission` objects associated to a Role, Layout, User, portlet instance, etc. that no longer exists. - `PortletPreference` objects associated with a portlet or layout that no longer exists. This is common in environments with many embedded portlets. These portlet instances have a different lifecycle and aren't deleted when the portlet is removed from a template. If you want to see an example of removing intermediate object versions, read [Example: Removing Intermediate Journal Article Versions](/docs/7-2/deploy/-/knowledge_base/d/example-removing-intermediate-journal-article-versions) and then return here. Next, you'll test @product@ with its pruned database. ## Test with the Pruned Database Copy Find and resolve any issues related to the objects you removed. You can always restart pruning a new copy of your production database if you can't resolve an issue. | **Warning:** the upgrade to @product@ 7.2 moves Web Content images to the | Document Library and then deletes their former table `JournalArticleImage`. | Make sure the images show in the upgraded Web Content articles. Once you've successfully tested @product@ with its pruned database copy, you can upgrade the database to @product-ver@. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/03-test-ugrading-a-liferay-backup-copy/03-example-removing-intermediate-journal-article-versions.markdown ================================================ --- header-id: example-removing-intermediate-journal-article-versions --- # Example: Removing Intermediate Journal Article Versions [TOC levels=1-4] These instructions and code samples demonstrate removing intermediate Journal Article versions. In the [script console](/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-console), you can remove unneeded object versions by executing Java or Groovy code. Here are example steps for removing intermediate Journal Article versions: 1. Decide how many of the latest versions to keep. You must keep the original version and the most recent version, but you may keep older recent versions too. For example, you may want to keep the two latest versions or just the latest. 2. Find a method for deleting the entity versions. @product@ [app APIs](@app-ref@/apps/) and [com.lifieray.portal.kernel API](@platform-ref@/7.2-latest/javadocs/portal-kernel/) are available at [@platform-ref@](@platform-ref@). If it's a [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) entity, examine the `delete*` methods in the entity's `*LocalServiceUtil` class. For example, this `deleteArticle` in [`JournalArticleLocalServiceUtil`](@app-ref@/web-experience/latest/javadocs/com/liferay/journal/service/JournalArticleLocalServiceUtil.html#deleteArticle-long-java.lang.String-double-java.lang.String-com.liferay.portal.kernel.service.ServiceContext-) deletes a Journal Article version: ```java deleteArticle(long groupId, java.lang.String articleId, double version, java.lang.String articleURL, com.liferay.portal.kernel.service.ServiceContext serviceContext) ``` 3. Aggregate the entity versions to delete and the information required to delete them. For example, get all the Journal Article versions in range that match your removal criteria and associate their entity IDs and group IDs with them---the `deleteArticle` method requires the entity ID and group ID. The entity object (e.g., `JournalArticle`) typically has a version field. `JournalArticleResource` has each Journal Article's article ID (the entity's ID) and group ID. `JournalArticleResource` is our key to getting each `JournalArticle`, which can have multiple versions. Here are steps for identifying the Journal Article versions to delete: 1. Get all the `JournalArticleResource` objects. ```java List journalArticleResources = JournalArticleLocalServiceUtil.getJournalArticleResources(start, end); ``` 2. Get each Journal Article version's workflow status via the `JournalArticle` object associated with each `JournalArticleResource`. Dynamic Query is an efficient way to get exactly the data you want (and nothing more) from each object. ```java for (JournalArticleResource journalArticeResource : journalArticleResources) { List journalArticlesVersionsToDelete = new ArrayList(); DynamicQuery dq = DynamicQueryFactoryUtil.forClass(JournalArticle.class) .setProjection(ProjectionFactoryUtil.projectionList() .add(ProjectionFactoryUtil.property("id")) .add(ProjectionFactoryUtil.property("version")) .add(ProjectionFactoryUtil.property("status"))) .add(PropertyFactoryUtil.forName("groupId") .eq(journalArticeResource.getGroupId())) .add(PropertyFactoryUtil.forName("articleId") .eq(journalArticeResource.getArticleId())) .addOrder(OrderFactoryUtil.asc("version")); List result = JournalArticleLocalServiceUtil.dynamicQuery(dq); // See the next step for the sample code that goes here } ``` 3. For each `JournalArticleResource` (there's one for each Journal Article entity), build a list of intermediate versions in range of the first or latest versions you want to keep and whose status qualifies them for deletion. For example, you may want to delete intermediate article versions that are approved or expired (i.e., [WorkflowConstants.STATUS_APPROVED or WorkflowConstants.STATUS_EXPIRED](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/workflow/WorkflowConstants.html)). The `MIN_NUMBER_FIRST_VERSIONS_KEPT` and `MIN_NUMBER_LATEST_VERSIONS_KEPT` variables here mark the minimum and maximum number of first (oldest) and latest (newest) versions to keep. ```java List journalArticlesVersionsToDelete = new ArrayList(); for (int i=0; i < result.size(); i++) { long id = (long) result.get(i)[0]; double version = (double) result.get(i)[1]; int status = (int) result.get(i)[2]; if ((status == WorkflowConstants.STATUS_APPROVED) || (status == WorkflowConstants.STATUS_EXPIRED) { if (i < MIN_NUMBER_FIRST_VERSIONS_KEPT) { continue; } if (i >= (result.size() - MIN_NUMBER_LATEST_VERSIONS_KEPT)) { continue; } journalArticlesVersionsToDelete.add(version); } } // See the next step for the sample code that goes here ``` 4. Lastly, delete each Journal Article matching the versions you aggregated. ```java for (double version : journalArticlesVersionsToDelete) { { JournalArticleLocalServiceUtil.deleteArticle(journalArticeResource.getGroupId(), journalArticeResource.getArticleId(), journalArticlesVersionsToDelete(i), null, null); } ``` You can write similar code to remove intermediate versions of other entities. | **Tip:** Print the version (and any other information of interest) of each | object you're removing. You can also comment out the object deletion call and | read the printout of versions to be removed as a test before committing to | deleting them. After you've pruned your database, test it with @product@. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/03-test-ugrading-a-liferay-backup-copy/04-upgrading-your-test-server-and-database.markdown ================================================ --- header-id: upgrading-your-test-server-and-database --- # Upgrading Your Test Server and Database [TOC levels=1-4] After you've [pruned your database and tested it successfully](/docs/7-2/deploy/-/knowledge_base/d/pruning-the-database), it's ready for upgrade. Here you'll install @product-ver@ and migrate your current installation files to it and upgrade them. Then you'll optimize your database for the upgrade and upgrade your data. Lastly, you'll test this upgraded test environment. You may run into issues that require you to start again with backup of your pruned database. After you're satisfied with the test upgrade, you can prepare for upgrading production. Start with preparing @product-ver@ on a test server. ## Install Liferay on a Test Server and Configure It to Use the Pruned Database [Prepare a new test server with @product-ver@](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade). Configure it to use the pruned database copy---keep the original backup in case you want to restart test upgrades on a copy of it. You'll use the new test server's Liferay upgrade tool next. ## Tune Your Database for the Upgrade [Tune your database for the upgrade](/docs/7-2/deploy/-/knowledge_base/d/tuning-for-the-data-upgrade). ## Upgrade the Database Upgrade the database to @product-ver@ (see [Upgrade the Database](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data)); then return here. If the upgrade took too long, search the upgrade log to identify more unused objects. Then retry these steps with a fresh copy of the production database. ## Test the Upgraded Portal and Resolve Any Issues Test this upgraded @product-ver@ instance and resolve any issues. If you can't resolve an issue, retry these steps with a fresh copy of the production database. ## Checkpoint: You've Pruned and Upgraded a Production Database Copy By removing unused objects from @product@ in your test environment, you've made upgrading feasible to do in production. You identified unused objects, documented/scripted removing them, and successfully upgraded the @product@ database copy. It's time to prepare your production environment for upgrading. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/04-preparing-to-upgrade-the-liferay-database.markdown ================================================ --- header-id: preparing-to-upgrade-the-product-database --- # Preparing to Upgrade the @product@ Database [TOC levels=1-4] After testing the upgrade on a copy of your production database, you can apply what you learned to your production database. | **Tip:** This step and | [preparing a new @product@ server](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade) | can be done in parallel to save time. ## Remove All Unused Objects You Identified Earlier Previously you identified and removed unused objects from a copy of your @product@ production database backup. In the same way (in the script console or UI) you removed the unused objects from the backup, remove them from your pre-upgrade production database. ## Test Using the Pruned Database Find and resolve any issues related to the objects you removed. By removing the objects from production and testing your changes before upgrading, you can more easily troubleshoot issues, knowing that they're not related to upgrade processes. ## Upgrade Your Marketplace Apps Upgrade each Marketplace app (Kaleo, Calendar, Notifications, etc.) that you're using to its latest version for your @product@ installation. Before proceeding with the upgrade, troubleshoot any issues regarding these apps. ## Publish all Staged Changes to Production If you have [local/remote staging enabled](/docs/7-2/user/-/knowledge_base/u/enabling-staging) and have content or data saved on the staged site, [publish](/docs/7-2/user/-/knowledge_base/u/publishing-staged-content-efficiently) it to the live site. If you skip this step, you must run a full publish (or manually publish changes) after the upgrade, since the system won't know what content changed since the last publishing date. ## Synchronize a Complete Backup [Completely back up your @product@ installation, pruned production database, and document repository](/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation). It's time to prepare a new @product@ server. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/05-preparing-a-new-liferay-server.markdown ================================================ --- header-id: preparing-a-new-product-server-for-data-upgrade --- # Preparing a New @product@ Server for Data Upgrade [TOC levels=1-4] To upgrade your @product@ database, prepare a new server for hosting @product-ver@. You'll use this server to run the database upgrade and run @product-ver@. Then you can run your production server while you're configuring a new server to host @product-ver@ exactly the way you want. | **Note:** these steps can be done in parallel with any of the upgrade | preparation steps: planning for deprecated apps, testing upgrades on a | @product@ backup copy, or preparing to upgrade the @product@ database. Get the latest fixes for @product-ver@ by requesting an upgrade patch. ## Request an Upgrade Patch from Liferay Support (Liferay DXP only) An *upgrade patch* contains the latest fix pack and hot fixes planned for the next service pack. Upgrade patches provide the latest fixes available for your data upgrade. ## Install Liferay [Install @product@ on your application server](/docs/7-2/deploy/-/knowledge_base/d/deploying-product) or [use @product@ bundled with your application server of choice](/docs/7-2/deploy/-/knowledge_base/d/installing-product). | **Important:** Do not start your application server. It's not ready to start | until after the @product@ database upgrade. ## Install the Latest Upgrade Patch or Fix Pack (Liferay DXP only) Install the upgrade patch (if you requested it from Liferay Support) or the [latest Fix Pack](https://help.liferay.com/hc/en-us/articles/360028810452-Patching-Liferay-DXP). ## Migrate Your OSGi Configurations (7.0+) Copy your [OSGi configuration files](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) (i.e., `.config` files) to your new server's `[Liferay Home]/osgi/configs` folder. ## Migrate Your Portal Properties It is likely that you have overridden portal properties to customize your installation. If so, you must update the properties files (e.g., `portal-setup-wizard.properties` and `portal-ext.properties`) to @product-ver@. For features that use OSGi Config Admin, you must convert your properties to OSGi configurations. As you do this, you must account for property changes in all versions of @product@ since your current version up to and including @product-ver@. Start with updating your portal properties. ### Update Your Portal Properties If you're coming from a version prior to Liferay Portal 6.2, start with these property-related updates: - If you're on Liferay Portal 6.1, [adapt your properties to the new defaults that Liferay Portal 6.2 introduced](/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay#review-the-liferay-6). - If you're on Liferay 6.0.12, [migrate the Image Gallery](/docs/6-2/deploy/-/knowledge_base/d/upgrading-liferay#migrate-your-image-gallery-images). - If you have a sharded environment, [configure your upgrade to generate a non-sharded environment](/docs/7-2/deploy/-/knowledge_base/d/upgrading-a-sharded-environment). - Liferay's image sprite framework is deprecated as of 7.2 and is disabled by default. The framework requires scanning plugins for image sprites. If you don't use the framework, there's no need for it to scan for images sprites. If you use the framework, enable it by overriding the default `sprite.enabled` portal property (new in 7.2) value with the following setting in a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file: ```properties sprite.enabled=true ``` | **Note:** You can build image sprites using any framework you like and deploy | them in your plugins. When a new version of @product@ is released, there are often changes to default settings, and this release is no different. If you rely on the defaults from your old version, you should review the changes and decide to keep the defaults from your old version or accept the defaults of the new. Because no existing properties changed from 7.1 to 7.2, here's a list of the 6.2 properties that have changed in 7.2: ```properties users.image.check.token=false organizations.types=regular-organization,location organizations.rootable[regular-organization]=true organizations.children.types[regular-organization]=regular-organization,location organizations.country.enabled[regular-organization]=false organizations.country.required[regular-organization]=false organizations.rootable[location]=false organizations.country.enabled[location]=true organizations.country.required[location]=true layout.set.prototype.propagate.logo=true editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=simple web.server.servlet.check.image.gallery=true blogs.trackback.enabled=true discussion.comments.format=bbcode discussion.max.comments=0 dl.file.entry.thumbnail.max.height=128 dl.file.entry.thumbnail.max.width=128 ``` This property was removed: ```properties organizations.children.types[location] ``` The latest [portal properties reference](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) provides property details and examples. Some properties are replaced by OSGi configurations. ### Convert Applicable Properties to OSGi Configurations Properties in modularized features have changed and must now be deployed separately in [OSGi configuration files](/docs/7-2/user/-/knowledge_base/u/system-settings#exporting-and-importing-configurations) (OSGi Config Admin). Use the [`blade upgradeProps`](/docs/7-2/reference/-/knowledge_base/r/blade-cli) command to scan your `portal-ext.properties` file to discover which properties are now set via OSGi Config Admin. You can also check the upgrade log from previous attempts for traces like these: ```properties 2019-03-09 17:05:17.678 ERROR [main][VerifyProperties:161] Portal property "layout.first.pageable[link_to_layout]" is obsolete 2019-03-09 17:05:17.679 ERROR [main][VerifyProperties:136] Portal property "journal.article.check.interval" was modularized to com.liferay.journal.web as "check.interval" ``` | **Tip:** The Control Panel's *Configuration → System Settings* screens | are the most accurate way to create `.config` files. Use them to | [export a screen's configuration](/docs/7-2/user/-/knowledge_base/u/system-settings#exporting-and-importing-configurations) | to a `.config` file. ## Update Your Database Driver Install the recommended database driver and update your database connection driver specified in your `portal-ext.properties`. See the [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). ## Configure Your Documents and Media File Store General document store configuration (e.g., `dl.store.impl=[File Store Impl Class]`) continues to be done using `portal-ext.properties`. But here's what's changed for document storage: - Store implementation class package names changed from `com.liferay.portlet.documentlibrary.store.*` in Liferay Portal 6.2 to `com.liferay.portal.store.*` in @product@ 7.0+. Make sure your `portal-ext.properties` file sets `dl.store.impl` in one of these ways: ```properties dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore dl.store.impl=com.liferay.portal.store.db.DBStore dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore dl.store.impl=com.liferay.portal.store.s3.S3Store ``` - JCR Store was deprecated in @product@ 7.0. The [Document Repository Configuration](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration) documentation describes other store options. [Migrate to a supported document store](/docs/7-2/user/-/knowledge_base/u/server-administration) before upgrading your data. - CMIS Store was deprecated since 7.0.10 Fix Pack 14 and was removed in @product@ 7.2. The [Document Repository Configuration](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration) documentation describes other store options. [Migrate to a supported document store](/docs/7-2/user/-/knowledge_base/u/server-administration) before upgrading your data. - Since @product@ 7.0, document store type-specific configuration (e.g., specific to Simple File Store, Advanced File Store, S3, etc.) is done in the Control Panel at *Configuration → System Settings → File Storage* or using OSGi configuration files (`.config` files). Type specific configuration is no longer done using `portal-ext.properties`. For example, these steps to create a `.config` file specifying a root file location for a Simple File Store or Advanced File Store: 1. Create a `.config` file named after your store implementation class. Simple File Store: `com.liferay.portal.store.file.system.configuration.FileSystemStoreConfiguration.config` Advanced File Store: `com.liferay.portal.store.file.system.configuration.AdvancedFileSystemStoreConfiguration.config` 2. Set the following `rootDir` property and replace `{document_library_path}` with your file store's path. ```properties rootDir="{document_library_path}" ``` 3. Copy the `.config` file to your `[Liferay Home]/osgi/configs` folder. The [Document Repository Configuration](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration) provides more document store configuration details. ## Configure Kerberos in place of NTLM If you're using NTLM to authenticate Microsoft Windows ™ accounts with @product@, switch to using [Kerberos](/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-kerberos). Security vulnerabilities persist with NTLM. NTLM has been deprecated and removed from the bundle, but you can still [build and deploy the module](https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/portal-security-sso-ntlm). ## Disable Indexing Before starting the upgrade process in your new installation, you must disable indexing to prevent upgrade process performance issues that arise when the indexer attempts to re-index content. To disable indexing, create a file called `com.liferay.portal.search.configuration.IndexStatusManagerConfiguration.config` in your `[Liferay Home]/osgi/configs` folder and add the following content: ```properties indexReadOnly="true" ``` After you complete the upgrade, re-enable indexing by removing the `.config` file or setting `indexReadOnly="false"`. Your new @product-ver@ server is ready for upgrading your database. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/06-upgrading-the-liferay-database/01-upgrading-the-liferay-database-intro.markdown ================================================ --- header-id: upgrading-the-product-data --- # Upgrading the @product@ Data [TOC levels=1-4] Now you're ready to upgrade the @product@ data. The upgrade processes update the database schema for the core and your installed modules. Verification processes test the upgrade. Configured verifications for the core and modules run afterwards, but can be run manually too. Here are the ways to upgrade: - **Upgrade everything in one shot**: Use the upgrade tool to upgrade the core and all the modules. - **Upgrade the core and the modules separately**: Use the upgrade tool (recommended) or [Gogo shell](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell) to upgrade the core. Then use Gogo shell to upgrade each module. If you are upgrading from Liferay Portal 6.2 or earlier, use the upgrade tool to upgrade everything. It's the easiest, most comprehensive way to upgrade from those versions. Since version 7.0, however, @product@'s modular framework lets you upgrade modules---even the core---individually. A helpful practice for large databases is to focus first on upgrading the core and your most important modules; then back up your database before continuing upgrades. Upgrading is a flexible process that adjusts to your preferences. | **Note:** Liferay enterprise subscribers can use the upgrade tool to execute | upgrades for fix packs. Since @product@ 7.1, a fix pack's micro upgrade | processes in the core (database schema micro version changes) are not | mandatory. This means you can install a fix pack (i.e., core code) without | having to execute the database schema micro version changes. You can execute | micro version changes when you want, even outside of major or minor version | upgrades. Before using the upgrade tool to execute a fix pack's micro upgrade | process, however, you must shut down the server, install the fix pack, and | [back up the @product@ database, installation, and Document Library store](/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation). | | Module micro database schema version changes in fix packs execute | automatically on server startup unless the | [`autoUpgrade` setting](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade) | is `false` (the default is `true`). ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/06-upgrading-the-liferay-database/02-tuning-for-the-data-upgrade.markdown ================================================ --- header-id: tuning-for-the-data-upgrade --- # Tuning for the Data Upgrade [TOC levels=1-4] Upgrading impacts the database differently from daily running in production. Because of this, you should tune your database for the upgrade process before you run it, and then re-apply your production settings after the upgrade completes. - Data upgrades execute many more update statements (`INSERT`, `UPDATE`, and `DELETE`) and less `SELECT` statements than production instances. When upgrading, tune your database for executing updates. - Data upgrades should be done in safe environments completely separate from production servers and should use database backup copies. If upgrade errors occur or you make mistakes, they don't impact production, and you can always restart using your database backup copy. The data upgrade tuning instructions given here are a starting point for tuning your @product@ data upgrade. They account for data upgrade activities and a safe data upgrade environment: - Deactivate data integrity measures that impact performance. Restore the backup if failures occur. - Make commit-related transaction I/O operations asynchronous. - Increase the interval to flush commits to disk. - Minimize transaction logging. | **Note:** These options worked well for us on specific versions of each | database. Please consult your database vendor's documentation for | information on how to optimize executing updates on your specific database | version. | **Important:** Test your database configuration to determine tuning that's | best for your system, and consult your DBA as appropriate. **Never** use | database upgrade configurations in production. Always restore your production | database settings before starting your @product@ server for production use | with the database. | **Warning:** Some database properties and configurations are global and affect | schemas in the same database. These configurations were optimal for upgrading data in a Liferay 6.2 EE installation that had these characteristics: - 3.2 GB database - 15 GB Document Library - Content translated in 3 languages - Record count for most populated entities: - 1,694,000 rating entries - 1,605,000 permissions (`ResourcePermission` objects) - 871,000 assets (`AssetEntry` objects) - 400,000 users - 400,000 sites (`Group` objects) - 402,000 images - 259,000 message forum threads and posts - 200,000 documents - 193,000 portlet preferences - 103,000 web content pieces (`JournalArticle` objects) - 50,600 pages - 3,276 journal article images - 3,100 document folders Start with configuring the database upgrade tool's Java process. ## Tuning the Database Upgrade Java Process Make sure to provide adequate memory for the database upgrade tool's Java process. 15GB was appropriate for the test scenario. Also make sure to set the file encoding to UTF-8 and the time zone to GMT. Here are the Java process settings: - Xmx 15 GB RAM - File encoding UTF-8 - User time zone GMT Here is the `db_upgrade.sh` command: ```bash db_upgrade.sh -j "-Xmx15000m -Dfile.encoding=UTF-8 -Duser.timezone=GMT" ``` It's time to tune your database transaction engine. ## Tuning the Database Transaction Engine for Executing Updates Many more update statements are executed during data upgrade than in production. Here's how to optimize each database's transaction engine for the updates. ### IBM DB2 Please consult IBM's official DB2 documentation. ### MariaDB In addition to the default database configuration, turn off InnoDB double-write. ### Microsoft SQL Server In addition to the default database configuration, set [transaction durability](https://docs.microsoft.com/en-us/sql/relational-databases/logs/control-transaction-durability) to `FORCED`. ### MySQL In addition to the default database configuration, turn off [InnoDB double-write](https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_doublewrite). ### Oracle Database The default configuration works well. It configures [asynchronous I/O to disk](https://docs.oracle.com/database/121/REFRN/GUID-FD8D1BD2-0F85-4844-ABE7-57B4F77D1608.htm#REFRN10048) automatically. ### PostgreSQL In addition to the default database configuration, turn off [synchronous commits](https://www.postgresql.org/docs/10/wal-async-commit.html). ## Tuning the Database Transaction Log In production, transaction logs mark safe states to roll back to. In data upgrades, however, the safe state is the original data backup. Since transaction logging is insignificant for data upgrades, it should be disabled or minimized. Here are log tuning instructions for each database. ### IBM DB2 Please consult IBM's official DB2 documentation. ### MariaDB In addition to the default database configuration, set the InnoDB flush log at transaction commit to `0`. ### Microsoft SQL Server Use the default database configuration. ### MySQL In addition to the default database configuration, set the [InnoDB flush log at transaction commit](https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit) to `0`. ### Oracle Database Use the default database configuration. ### PostgreSQL In addition to the default database configuration, Set the [write ahead log writer delay](https://www.postgresql.org/docs/10/wal-async-commit.html) to `1000` milliseconds. Congratulations! You have a starting point to plan your own @product@ data upgrade project. Remember, optimal tuning depends on your data, infrastructure conditions, and database vendor. Analyze your data, tune for upgrade, and time your test upgrades. Use this information to determine the best database and Java process configuration for your @product@ data upgrade. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/06-upgrading-the-liferay-database/03-configuring-the-data-upgrade.markdown ================================================ --- header-id: configuring-the-data-upgrade --- # Configuring the Data Upgrade [TOC levels=1-4] The upgrade tool provides the easiest way to upgrade the core and installed modules. You can use text files or the tool's command line interface to configure your upgrade. The upgrade tool can upgrade everything---the core and all the modules---together or separately. @product-ver@ bundles include the upgrade tool. If you installed @product-ver@ manually, you can download the upgrade tool separately. - *Liferay DXP 7.2*: Go to the [*Downloads* page](https://customer.liferay.com/group/customer/downloads) and select the *DXP 7.2* product and the *Product/Service Packs* file type. In the listing that appears, click *Download* for the *Liferay DXP Upgrade Client*. - *Liferay Portal CE 7.2*: Go to the [*Downloads* page](https://www.liferay.com/downloads-community) and select *Download* for *Liferay Portal Tools for 7.2*. Before starting the data upgrade process, configure the upgrade tool for the core upgrade and specify whether the upgrade tool should upgrade non-core module data automatically. ## Configuring the Core Upgrade The core upgrade requires configuration. You can configure it at runtime via the command line interface or pre-configure it in these files in `[Liferay Home]/tools/portal-tools-db-upgrade-client/`: - `app-server.properties`: Specifies the server's location and libraries. - `portal-upgrade-database.properties`: Configures the database connection. - `portal-upgrade-ext.properties`: Sets the rest of the portal properties that the upgrade requires. You might want to copy your current portal properties (except your database properties) into this file. Before copying your current properties, make sure you've [updated the portal properties for @product-ver@](/docs/7-2/deploy/-/knowledge_base/d/preparing-to-upgrade-the-product-database). Each file's properties are described next. ### Configuring app-server.properties Specify the following information to configure @product-ver@'s app server: `dir:` the absolute path of the application server folder. *(required)* `extra.lib.dirs:` a comma delimited list of extra directories containing any binaries or resources to add to the class path. Use all absolute paths OR all paths relative to `dir`. *(required)* `global.lib.dir:` the application server's global library directory. Use the absolute path or a path relative to `dir`. *(required)* `portal.dir:` the directory where portal is installed in your app server. Use the absolute path or a path relative to `dir`. *(required)* `server.detector.server.id:` ID of a supported application server. (*required*) Here are the IDs: - `jboss` - `jonas` - `resin` - `tomcat` - `weblogic` - `websphere` - `wildfly` Relative paths must use Unix style format. The following properties, for example, are for Windows and use relative paths: ```properties dir=D:\ extra.lib.dirs=Liferay/liferay-portal-master/tomcat-9.0.10/bin global.lib.dir=Liferay/liferay-portal-master/tomcat-9.0.10/lib portal.dir=Liferay/liferay-portal-master/tomcat-9.0.10/webapps/ROOT server.detector.server.id=tomcat ``` These properties, for example, are for Linux and use all absolute paths: ```properties dir=/ extra.lib.dirs=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/bin global.lib.dir=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/lib portal.dir=/home/user/liferay/liferay-portal-master/tomcat-9.0.10/webapps/ROOT server.detector.server.id=tomcat ``` ### Configuring portal-upgrade-database.properties Specify the following information to configure the database you're upgrading. Note that these properties correspond exactly to the [JDBC portal properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#JDBC) you'd use in a `portal-ext.properties` file. `jdbc.default.driverClassName` *(required)* `jdbc.default.url` *(required)* `jdbc.default.username` *(required)* `jdbc.default.password` *(required)* ### Configuring portal-upgrade-ext.properties Specify the following information to configure the upgrade: `liferay.home:` The [Liferay home folder](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) *(required)* `dl.store.impl:` The implementation for persisting documents to the document library store. This property is mandatory if you're using a `*FileSystemStore` implementation. If you [updated this property in your `portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/preparing-a-new-product-server-for-data-upgrade), copy it here. Otherwise, set the property one of these ways: ```properties dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore dl.store.impl=com.liferay.portal.store.db.DBStore dl.store.impl=com.liferay.portal.store.file.system.AdvancedFileSystemStore dl.store.impl=com.liferay.portal.store.s3.S3Store ``` `hibernate.jdbc.batch_size:` The JDBC batch size used to improve performance; set to *250* by default *(optional)* ### Example Upgrade Configuration Here's an example interaction with the upgrade tool's command line interface: Please enter your application server (tomcat): tomcat Please enter your application server directory (../../tomcat-8.0.32): Please enter your extra library directories (../../tomcat-8.0.32/bin): Please enter your global library directory (../../tomcat-8.0.32/lib): Please enter your portal directory (../../tomcat-8.0.32/webapps/ROOT): [ db2 mariadb mysql oracle postgresql sqlserver sybase ] Please enter your database (mysql): mariadb Please enter your database host (localhost): (etc.) The command line interface creates the configuration files based on your input. You can put this information into configuration files to configure the tool manually. Here are example upgrade configuration files that you can customize and copy into `[Liferay Home]/tools/portal-tools-db-upgrade-client/`: - `app-server.properties`: ```properties dir=../../tomcat-8.0.32 global.lib.dir=/lib portal.dir=/webapps/ROOT server.detector.server.id=tomcat extra.lib.dirs=/bin ``` - `portal-upgrade-database.properties`: ```properties jdbc.default.url=jdbc:mysql://lportal62?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.username=root jdbc.default.password= ``` - `portal-upgrade-ext.properties`: ```properties liferay.home=/home/user/servers/liferay7 module.framework.base.dir=/home/user/servers/liferay7/osgi dl.store.impl=com.liferay.portal.store.file.system.FileSystemStore ``` Next, decide if the upgrade tool should upgrade non-core modules automatically. ## Configuring Non-Core Module Data Upgrades You can configure the upgrade tool to upgrade all installed modules automatically or to open a Gogo shell (after core upgrade completes) for you to execute module upgrades manually. If the upgrade tool's `autoUpgrade` property is set to `true` (the default setting), upgrade processes for all installed modules are run too. If you set `autoUpgrade="false"` in a file called `com.liferay.portal.upgrade.internal.configuration.ReleaseManagerConfiguration.config` and copy the file into the `[Liferay Home]/osgi/configs` folder, the upgrade tool opens Gogo shell after the core upgrade. In the Gogo shell, you can [administer module upgrades](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell). It's time to run the upgrade tool. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/06-upgrading-the-liferay-database/04-upgrading-the-core-using-the-upgrade-tool.markdown ================================================ --- header-id: upgrading-the-core-using-the-upgrade-tool --- # Upgrading the Core Using the Upgrade Tool [TOC levels=1-4] The upgrade tool provides the easiest way to upgrade the core and installed modules. Here's how to use it. ## Upgrade Tool Usage The `db_upgrade.sh` script in the `[Liferay Home]/tools/portal-tools-db-upgrade-client` folder (`db_upgrade.bat` on Windows) invokes the upgrade tool. This command prints the upgrade tool usage: db_upgrade.sh --help This configuration prevents automatic module upgrade, but causes the upgrade tool to open a Gogo shell for [upgrading modules](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell) after finishing the core upgrade. Here are the tool's default Java parameters: -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.timezone=GMT -Xmx2048m The `-j` option overrides the JVM parameters. For example, these options set the JVM memory to 10GB, which is a good starting point for this process type: db_upgrade.sh -j "-Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.timezone=GMT -Xmx10240m" The `-l` option specifies the tool's log file name: db_upgrade.sh -l "output.log" Here are all the upgrade tool command line options: **--help** or **-h**: Prints the tool's help message. **--jvm-opts** or **-j** + **[arg]**: Sets any JVM options for the upgrade process. **--log-file** or **-l** + **[arg]**: Specifies the tool's log file name---the default name is `upgrade.log`. **--shell** or **-s**: Automatically connects you to the Gogo shell after finishing the upgrade process. | **Note:** Only execute the upgrade process on a server with ideal memory, CPU, | and database connection configurations. If executing an upgrade remotely using | `ssh`, make sure to guard against interruptions: | | - If you're executing the upgrade using `ssh`, ignore hangups (connection | loss) by using `nohup` or something similar. | - On the machine you're connecting from, disable settings that shutdown or | sleep that machine. | | The upgrade process continues on the server even if you lose connection to it. | If you lose connection, reconnect and monitor upgrade status via the log | (default log file is `upgrade.log`). If you're using an earlier version of | @product-ver@ and upgrade execution is interrupted, check your log file for | where execution stopped. | | - If execution stopped during an upgrade process for Core 7.1 or higher, or | any module upgrade process, restart the upgrade tool to continue the | upgrade from that point. You can also use Gogo shell to | [check module upgrade status](/docs/7-2/deploy/-/knowledge_base/d/upgrading-modules-using-gogo-shell#checking-upgrade-status) | and continue upgrading modules. | - If execution stopped during an upgrade process for Core 7.0 or lower, you | must | [restore the data from a backup](/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation) | and start the upgrade again. | **Warning:** To prevent the tool's expanded command from growing too large for | Windows, execute the upgrade tool script from the `[Liferay | Home]/tools/portal-tools-db-upgrade-client` folder. It's time to upgrade your core data using the upgrade tool. ## Running and Managing the Core Upgrade Start the upgrade tool, as the previous section explains. Here are the core upgrade stages: 1. Show the upgrade patch level 2. Execute the core upgrade processes 3. Execute the core verifiers Monitor the upgrade via the upgrade tool log file (default file is `upgrade.log`). If a core upgrade process fails, analyze the failure and resolve it. If a core upgrade step for @product@ 7.1 (or newer) fails, executing the upgrade tool again starts it from that step. If you configured the upgrade tool to upgrade non-core modules, the tool opens a Gogo shell and starts upgrading them. The Gogo shell lets you upgrade modules, check module upgrade status, verify upgrades, and restart module upgrades. Read on to learn how to use Gogo shell commands to complete @product@ upgrades. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/06-upgrading-the-liferay-database/05-upgrading-modules-using-gogo-shell.markdown ================================================ --- header-id: upgrading-modules-using-gogo-shell --- # Upgrading Modules Using Gogo Shell [TOC levels=1-4] Liferay's Gogo shell can upgrade and verify individual modules. It's a fine-grained approach to upgrading the core and non-core modules. If you haven't already upgraded your non-core modules using the upgrade tool or if there are modules you need to revisit upgrading, you can upgrade them using Gogo Shell. | **Note**: You must | [Configure the core upgrade](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade) | before using Gogo shell commands to upgrade the core. Below is a list of commands. ## Command Usage If you ran the upgrade tool and it opened Gogo shell, you're already connected. Otherwise, you can execute commands using the [Gogo Shell portlet](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell). Here are the commands: `exit` or `quit:` Exits the Gogo shell `upgrade:help:` Displays upgrade commands `upgrade:check:` Lists upgrades pending execution because they failed in the past or the module hasn't reached its final version `upgrade:execute {module_name}:` Executes upgrades for that module `upgrade:executeAll:` Executes all pending module upgrade processes `upgrade:list:` Lists all registered upgrades `upgrade:list {module_name}:` Lists the module's required upgrade steps `upgrade:list | grep Registered:` Lists registered upgrades and their versions `verify:help:` Displays verify commands `verify:check {module_name}:` Lists the latest execution result for the module's verify process `verify:checkAll:` Lists the latest execution results for all verify processes `verify:execute {module_name}:` Executes the module's verifier `verify:executeAll:` Executes all verifiers `verify:list:` Lists all registered verifiers There are many useful [Liferay commands and standard commands available in Gogo shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell). The following sections describe Liferay upgrade commands. ## Listing module upgrade processes Before upgrading modules, you should find which have unresolved dependencies, which are resolved and available to upgrade, and examine the module upgrade processes. Executing `upgrade:list` in the Gogo shell lists the modules whose upgrade dependencies are satisfied. These modules can be upgraded. If a module is active but not listed, its dependencies must be upgraded. The Gogo shell command `scr:info [upgrade_step_class_qualified_name]` shows the upgrade step class's unsatisfied dependencies. Here's an example `scr:info` command: scr:info com.liferay.journal.upgrade.JournalServiceUpgrade Invoking `upgrade:list [module_name]` lists the module's upgrade processes, in no particular order. For example, executing `upgrade:list com.liferay.bookmarks.service` (for the Bookmarks Service module), lists this: Registered upgrade processes for com.liferay.bookmarks.service 1.0.0 {fromSchemaVersionString=0.0.0, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.portal.spring.extender.internal.context.ModuleApplicationContextExtender$ModuleApplicationContextExtension$1@6e9691da} {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0-step-3, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradePortletId@5f41b7ee} {fromSchemaVersionString=1.0.0-step-1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradePortletSettings@53929b1d} {fromSchemaVersionString=1.0.0-step-2, toSchemaVersionString=1.0.0-step-1, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradeLastPublishDate@3e05b7c8} {fromSchemaVersionString=1.0.0-step-3, toSchemaVersionString=1.0.0-step-2, upgradeStep=com.liferay.bookmarks.upgrade.v1_0_0.UpgradeClassNames@6964cb47} An application's upgrade step class names typically reveal their intention. For example, the example's `com.liferay.bookmarks.upgrade.v1_0_0.UpgradePortletId` upgrade step class updates the app's portlet ID. The other example upgrade step classes update class names, the `LastPublishDate`, and `PortletSettings`. The example's step from `0.0.0` to `1.0.0` upgrades the module from an empty database. To examine a module's upgrade process better, you can sort the listed upgrade steps mentally or in a text editor. Here's the upgrade step order for a Bookmarks Service module to be upgraded from Liferay Portal 6.2 (the module's database exists) to schema version `1.0.0`: - `0.0.1` to `1.0.0-step-3` - `0.0.1-step-3` to `1.0.0-step-2` - `0.0.1-step-2` to `1.0.0-step-1` - `0.0.1-step-1` to `1.0.0` The overall module upgrade process starts at version `0.0.1` and finishes at version `1.0.0`. The first step starts on the initial version (`0.0.1`) and finishes on the target version's highest step (`step-3`). The last step starts on the target version's lowest step (`step-1`) and finishes on the target version (`1.0.0`). Once you understand the module's upgrade process, you can execute it with confidence. ## Executing module upgrades Executing `upgrade:execute [module_name]` upgrades the module. You might run into upgrade errors that you must resolve. Executing the command again starts the upgrade from the last successful step. You can check upgrade status by executing `upgrade:list [module_name]`. For example, entering `upgrade:list com.liferay.iframe.web` outputs this: Registered upgrade processes for com.liferay.iframe.web 0.0.1 {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.iframe.web.upgrade.IFrameWebUpgrade$1@1537752d} The first line lists the module's name and current version. The example module's current version is `0.0.1`. The `toSchemaVersionString` value is the target version. Executing `upgrade:list [module_name]` on the module after successfully upgrading it shows the module's name followed by the version you targeted. For example, if you successfully upgraded `com.liferay.iframe.web` to version `1.0.0`, executing `upgrade:list com.liferay.iframe.web` shows the module's version is `1.0.0`: Registered upgrade processes for com.liferay.iframe.web 1.0.0 {fromSchemaVersionString=0.0.1, toSchemaVersionString=1.0.0, upgradeStep=com.liferay.iframe.web.upgrade.IFrameWebUpgrade$1@1537752d} For module upgrades that don't complete, you can check their status and resolve their issues. ## Checking upgrade status The command `upgrade:check` lists modules that have impending upgrades. For example, if module `com.liferay.dynamic.data.mapping.service` failed in a step labeled `1.0.0-step-2`, executing `upgrade:check` shows this: Would upgrade com.liferay.dynamic.data.mapping.service from 1.0.0-step-2 to 1.0.0 and its dependent modules Modules often depend on other modules to complete upgrading. Executing `scr:info [upgrade_step_class_qualified_name]` shows the upgrade step class's dependencies. You must upgrade modules on which your module depends. To resolve and activate a module, its upgrade must complete. The [Apache Felix Dependency Manager](http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager/tutorials/leveraging-the-shell.html) Gogo shell command `dm wtf` reveals unresolved dependencies. If your module requires a certain data schema version (e.g., its `bnd.bnd` specifies `Liferay-Require-SchemaVersion: 1.0.2`) but the module hasn't completed upgrade to that version, `dm wtf` shows that the schema version is not registered. 1 missing dependencies found. ------------------------------------- The following service(s) are missing: * com.liferay.portal.kernel.model.Release (&(release.bundle.symbolic.name=com.liferay.journal.service)(release.schema.version=1.0.2)) is not found in the service registry The `dm wtf` command can also help detect errors in portlet definitions and custom portlet `schemaVersion` fields. Browsing the @product@ database `Release_` table can help you determine a module's upgrade status too. The core's `servletContextName` field value is `portal`. If the core's `schemaVersion` field matches your new @product@ version (e.g., `7.2.1` for Liferay Portal CE GA2) and the `verified` field is `1` (true), the core upgrade completed successfully. Each module has one `Release_` table record, and the value for its `schemaVersion` field must be `1.0.0` or greater (`1.0.0` is the initial version for @product-ver@ modules, except for those that were previously traditional plugins intended for Liferay Portal version 6.2 or earlier). ## Executing verify processes Some modules have verify processes. These make sure the upgrade executed successfully. Verify processes in the core are automatically executed after upgrading @product@. You can also execute them by configuring the [`verify.*` portal properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Verify) and restarting your server. To check for available verify processes, enter the Gogo shell command `verify:list`. To run a verify process, enter `verify:execute [verify_qualified_name]`. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/07-executing-post-upgrade-tasks.markdown ================================================ --- header-id: executing-post-upgrade-tasks --- # Executing Post-Upgrade Tasks [TOC levels=1-4] Since you optimized your system for upgrading, after the upgrade is complete you must re-optimize it for production. ## Tuning Your Database for Production Prior to upgrading your @product@ database, you tuned it for upgrade. Now that upgrade is complete, restore the production database tuning you used previously. ## Re-enabling Search Indexing and Re-indexing Search Indexes Make sure to re-enable search indexing by removing the `com.liferay.portal.search.configuration.IndexStatusManagerConfiguration.config` file from your `[Liferay Home]/osgi/configs` folder or setting this property in it: ```properties indexReadOnly="false" ``` Then re-index @product@'s search indexes. Don't just do this blindly, however. By default, @product@ ships with an embedded configuration for Elasticsearch. This configuration works great for demo purposes, but is not supported in production. Make sure to [install and configure a standalone Elasticsearch instance to run in production](/docs/7-2/deploy/-/knowledge_base/d/installing-elasticsearch). ## Enabling Web Content View Permissions Prior to @product@ 7.1, all users could view web content articles by default. Now view permissions are checked by default. Here are options for opening view permissions: Option 1: Edit view permissions per web content article per role. Option 2: Open view permissions for all web content articles by going to *System Settings → Web Experience → Web Content* and deselecting *Article view permissions check enabled*. Once you've configured search, re-indexed your search index, and set web content view permissions, your upgraded system is ready for action! Congratulations! ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/08-upgrading-a-sharded-environment.markdown ================================================ --- header-id: upgrading-a-sharded-environment --- # Upgrading a Sharded Environment [TOC levels=1-4] Since Liferay DXP 7.0, Liferay removed its own physical partitioning implementation (also known as sharding) in favor of the capabilities provided natively by database vendors. Upgrading a sharded installation to DXP 7.0 or higher requires migrating it to as many non-sharded Liferay DXP installations (servers) as you have shards. These steps guide you through configuring the new Liferay DXP servers to use your formerly sharded data. | **Note:** Liferay continues to support its logical partitioning capabilities | (also known as | [virtual instances](/docs/7-2/user/-/knowledge_base/u/setting-up-a-virtual-instance)) | for the foreseeable future. | For any further assistance with sharding contact your Liferay account manager | or Liferay Support. ## Add Configurations Before the Data Upgrade In addition to other configurations, you will need to set more properties to migrate your shards to virtual instances for your data upgrade. Here is how to configure the upgrade to migrate from sharding: 1. Copy all of the shard JDBC connection properties from `portal-ext.properties` to`portal-upgrade-database.properties`. For example, JDBC connections for a default shard and two non-default shards might look like this: ```properties jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to the default database shard] jdbc.default.username=[the user name] jdbc.default.password=[the password] jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] jdbc.two.driverClassName=[the database driver class name] jdbc.two.url=[the URL to database shard two] jdbc.two.username=[the user name] jdbc.two.password=[the password] ``` 1. Set the JDBC _default_ connection properties in each server's `portal-upgrade-database.properties` to specify the associated shard. * Add the original JDBC properties for the respective non-default shard database. For example, shard `one`'s original properties might start with `jdbc.one`: ```properties jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] ``` * Rename the properties to start with `jdbc.default`. For example: ```properties jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to database shard one] jdbc.default.username=[the user name] jdbc.default.password=[the password] ``` ## Upgrade and Update Properties When you perform the database upgrade, upgrade the default shard first, and then each of the non-default shards. See [Using the Database Upgrade Tool](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data) for more information on running the database upgrade. After the database upgrade has been completed, make the following configuration changes to your application servers: 1. In each server's `portal-ext.properties`, use the JDBC _default_ properties you specified in the `portal-upgrade-database.properties` (see the _default_ properties above). 1. Remove the non-default shard JDBC properties from the default shard server's `portal-ext.properties` file, leaving only the default shard database `jdbc.default` properties. For example: Old JDBC properties: ```properties jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to the default database shard] jdbc.default.username=[the user name] jdbc.default.password=[the password] jdbc.one.driverClassName=[the database driver class name] jdbc.one.url=[the URL to database shard one] jdbc.one.username=[the user name] jdbc.one.password=[the password] jdbc.two.driverClassName=[the database driver class name] jdbc.two.url=[the URL to database shard two] jdbc.two.username=[the user name] jdbc.two.password=v[the password] ``` New JDBC properties: ```properties jdbc.default.driverClassName=[the database driver class name] jdbc.default.url=[the URL to your database] jdbc.default.username=[the user name] jdbc.default.password=[the password] ``` Once you have completed all of these steps, you have migrated off of a sharded environment to virtual instances on separate Liferay DXP servers together with your DXP upgrade. Congratulations! You have migrated off of a sharded environment to virtual instances on separate @product@ servers. You have also upgraded to @product-ver@. Your virtual instances are ready for action. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/09-migrating-from-audience-targeting/01-migrating-from-audience-targeting-intro.markdown ================================================ --- header-id: migrating-from-audience-targeting-to-segmentation-and-personalization --- # Migrating From Audience Targeting to Segmentation and Personalization [TOC levels=1-4] @product-ver@ integrates all the Audience Targeting app's features into Liferay's core as Segmentation and Personalization. This enables better integration with other applications and provides developers with easier access to Segmentation and Personalization features. Audience Targeting users must migrate their user segments into the new Segments application. There are three steps to the migration process: 1. Upgrade to @product-ver@. 2. Migrate custom rules. 3. Migrate behavior-based features. First, to upgrade to the latest version of @product@, follow the [upgrade guide](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver). Most of your Audience Targeting configuration is automatically transferred into the new engine. Next, any custom rules that were created in Audience Targeting must be re-evaluated. Some custom rules may have an out-of-the-box equivalent now, while others must be migrated. If a rule must be re-implemented, follow the [Segmentation and Personalization development guide](/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization). You can check [the list of rules that are automatically migrated](/docs/7-2/deploy/-/knowledge_base/d/migrating-user-segments) to see how much additional work you have in store. You must also [migrate display widgets](/docs/7-2/deploy/-/knowledge_base/d/manually-migrating-from-audience-targeting) since the new Personalization features use different tools. Finally, you must migrate behavior-based features, but since Audience Targeting's analytics features are now part of Analytics Cloud, there isn't a direct path to upgrade. See the [Analytics Cloud documentation](https://help.liferay.com/hc/en-us/articles/360006947671-Creating-Segments). ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/09-migrating-from-audience-targeting/02-migrating-user-segments.markdown ================================================ --- header-id: migrating-user-segments --- # Migrating User Segments [TOC levels=1-4] In Audience Targeting, a user segment represents a subset of users. A user segment is defined by one or more rules that users must match to belong to that user segment. In @product-ver@, segments work in a similar way, but they are defined by criteria instead of rules. Segment criteria are sets of fields defined by different user actions or properties (profile information, organization information, session information) that can be combined through operations (like equals, not equals, contains, not contains, greater than, and less than) and conjunctions (AND, OR) to define complex filters. Due to the similarities between Audience Targeting user segments and @product-ver@ Segments, certain data can be migrated automatically as part of the upgrade process. ## Upgrade Process As a result of the upgrade process, - All Audience Targeting User Segments appear under the new Segments administration in 7.2, with the same name. - For every segment, those Audience Targeting rules with an equivalent in @product@ 7.2 have been migrated into the corresponding criteria fields (see Table below). - Audience Targeting tables have been removed from your @product@ Database. | Audience Targeting Rule | @product@ 7.2. Segment Criteria Field | Upgrade Path | | --- | --- | --- | | Browser | Browser | Automated. Use user agent field with `contains` operation as an alternative | | Custom Field | Custom Field | Automated | | Language | Language | Automated | | Last Login Date | Last Sign In Date | Automated | | Organization Member | Organization | Automated | | OS | User Agent | Automated | | Previous Visited Site | Not Available | Automated | | Regular Role | Role | Automated | | Site Member | Site | Automated | | User Group Member | User Group | Automated | | Age | Not Available | Suggested: custom field | | Facebook (various) | Not Available | Suggested: custom field | | Gender | Not Available | Suggested: custom field | | Score Points | Not Available | Suggested: cookie | | Visited Page/Content | Not Available | Suggested: cookie | Here's an example user segment as it would appear in Audience Targeting for @product@ 7.1: ![Figure 1: A @product@ 7.1 Audience Targeting Segment.](../../../images/migrating-audience-targeting-segment.png) And here is the same segment migrated to Liferay 7.2: ![Figure 2: A @product@ 7.2 Segment](../../../images/migrating-new-segment.png) For those Audience Targeting rules without a direct equivalent, a manual migration is required. If you have any these rules, you can learn about your next steps in [Manual Migration ](/docs/7-2/deploy/-/knowledge_base/d/manually-migrating-from-audience-targeting). ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/09-migrating-from-audience-targeting/03-manually-migrating-from-audience-targeting.markdown ================================================ --- header-id: manually-migrating-from-audience-targeting --- # Manually Migrating from Audience Targeting [TOC levels=1-4] As explained in the previous article, some Audience Targeting rules do not have a direct equivalent in @product@ 7.2 and, therefore, they cannot be migrated automatically. Here are the recommended solutions for each rule type. ## User Attribute Rules Some User Attributes, like Gender or Age, do not have a direct equivalent in @product-ver@. User Attributes retrieved from external sources like Facebook also do not have a replacement. To replace these, you must create a [custom user field](/docs/7-2/user/-/knowledge_base/u/creating-segments-with-custom-fields-and-session-data) and use that to define your new Segment. ## Session Rules For Session attributes that do not have a direct equivalent, the recommended solution is to use a URL field for the current URL or a previously visited URL on your site as criteria, or to use a Cookie for more advanced session tracking needs. ## Behavior Rules In @product-ver@ analytics is now managed through Analytics Cloud. You can learn more about creating behavior based rules in the [Analytics Cloud documentation](https://help.liferay.com/hc/en-us/articles/360006947671-Creating-Segments). ## Migrating Custom Rules Audience Targeting segmentation features could be extended using custom rules. As part of the upgrade planning process, the function of any such rules should be re-evaluated with the new Segmentation features of @product-ver@ in mind. First, check the [Segmentation reference](/docs/7-2/reference/-/knowledge_base/r/defining-segmentation-criteria) if any new criteria fields can replace their function. In particular, custom fields, URL fields, and cookies might help you migrate your custom rules with little to no additional development. If none of them cover your requirements, follow the development guide for instructions on [how to add new criteria fields and contributors](/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization). ## Migrating Display Portlets With Audience Targeting, you could display personalized content with the User Segment Display Content portlet or by using Asset Publisher with the [Segments filter enabled](https://help.liferay.com/hc/en-us/articles/360018174271-Using-the-Audience-Targeting-Widgets-). In @product-ver@, you must choose the most appropriate personalization option for your use cases. ### User Segment Content Display The User Segment Content Display portlet was used to display existing content based on segment membership rules. In @product-ver@, you can cover the same use case by defining manual content sets with variations for your different audiences and applying it to an asset publisher. See the documentation for [creating personalized Content Sets](/docs/7-2/user/-/knowledge_base/u/content-set-personalization). With this feature, you can assign any number of assets to the Content List for the given audience, and then use the Asset Publisher to define how content is displayed on the page. ### Asset Publisher Personalization Finally, if you want to display a dynamic list of content for your different audiences based on a filter in the same way you did with in Audience Targeting with the Segments filter in the Asset Publisher, you can create a dynamic content set with variations for your audiences and apply it to an asset publisher. In addition, the new [Experience-based Content Page personalization](/docs/7-2/user/-/knowledge_base/u/content-page-personalization) feature may fulfill a use case that you were previously solving with one of the methods previously available. ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/98-deprecated-apps-in-7-2-what-to-do.markdown ================================================ --- header-id: deprecated-apps-in-7-2-what-to-do --- # Deprecated Apps in 7.2: What to Do [TOC levels=1-4] During the development of any software product, it's sometimes necessary to stop development on or remove outdated or unpopular features. @product-ver@ is no different. In @product-ver@, Liferay has deprecated several apps and features. There are three types of deprecated apps: 1. Deprecated apps that remain in @product@, but will be removed in a future release. (Availability: *Bundled*) 2. Deprecated apps that have been removed from @product@, yet are still available for download via [Liferay Marketplace](https://web.liferay.com/marketplace) (Availability: *Marketplace*) 3. Deprecated apps that have been removed from @product@ and aren't available for download. (Availability: *Removed*) | **Note:** All apps deprecated by Liferay are no longer in active development. | You should therefore plan to stop using these apps. Such apps, however, may | still be available for download. | **Note:** For information on apps deprecated in @product@ 7.1, please see | [Deprecated Apps in 7.1: What to Do](/docs/7-1/deploy/-/knowledge_base/d/deprecated-apps-in-7-1-what-to-do) Here are the apps deprecated in @product-ver@. ## Foundation | App | Availability | Notes | | --- | ------------- | ------ | | AlloyUI | Bundled | Replaced by [MetalJS](https://metaljs.com/) (temporary) exposed as [ClayUI tag](/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs) equivalents. | | CMIS Store | Removed | Migrate to another [Document Repository Store option](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration). Before [upgrading to @product-ver@](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver), migrate your document store data using [Data Migration in Server Administration](/docs/7-2/user/-/knowledge_base/u/server-administration). | | JCRStore | Removed | Migrate to another [Document Repository Store option](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration). Before [upgrading to @product-ver@](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver), migrate your document store data using [Data Migration in Server Administration](/docs/7-2/user/-/knowledge_base/u/server-administration). | | Legacy Search Portlet | Bundled | Will be removed in a future release. Replaced by the [Search widgets](/docs/7-2/user/-/knowledge_base/u/search). | | Liferay Mobile Device Detection Enterprise | Removed | Contact 51Degrees for up-to-date definitions. | | Sprite framework | Bundled | Liferay's image sprite framework is deprecated and is disabled by default via the `sprite.enabled` [portal property](/docs/7-2/deploy/-/knowledge_base/d/portal-properties). You can still build image sprites using any framework you like and deploy them in your plugins. | ## Personalization | App | Availability | Notes | | --- | ------------- | ------ | | Audience Targeting | Removed | Replaced by [Personalization](/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization). | ## Web Experience | App | Availability | Notes | | --- | ------------- | ------ | | RSS Publisher | Bundled | See [the article](/docs/7-1/user/-/knowledge_base/u/the-rss-publisher-widget) on enabling and using this widget. | | User Group Pages (Copy Mode) | Bundled | See the [Legacy User Group Sites Beahavior](/docs/7-1/user/-/knowledge_base/u/user-group-sites#legacy-user-group-sites-behavior) instructions on how to enable it. | | Resources Importer | Bundled | Deprecated as of 7.1 with no direct replacement | ## Forms | App | Availability | Notes | | --- | ------------------ | ----------- | | Web Form | Removed | Final version released for 7.0. | ## Security | App | Availability | Notes | | --- | ------------------ | ----------- | | Central Authentication Service (CAS) | Bundled | Migrate to [SAML based authentication](https://help.liferay.com/hc/en-us/articles/360028711032-Introduction-to-Authenticating-Using-SAML). | | Google Login | Marketplace | Replaced by [OpenID Connect](/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-openid-connect). | | NTLM | Marketplace | Replaced by [Kerberos](/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-kerberos). | | OpenAM / OpenSSO | Bundled | Migrate to [SAML based authentication](https://help.liferay.com/hc/en-us/articles/360028711032-Introduction-to-Authenticating-Using-SAML). | | OpenID | Marketplace | Replaced by [OpenID Connect](/docs/7-2/deploy/-/knowledge_base/d/authenticating-with-openid-connect). | ## User and System Management | App | Availability | | --- | ------------------ | | Live Users | Enabled through the [`live.users.enabled`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) [portal property](/docs/7-2/deploy/-/knowledge_base/d/portal-properties). | ## Related Topics [Apps in Maintenance Mode](/docs/7-2/deploy/-/knowledge_base/d/apps-in-maintenance-mode) ================================================ FILE: en/deployment/articles/05-upgrading-to-liferay-7-2/99-apps-in-maintenance-mode.markdown ================================================ --- header-id: apps-in-maintenance-mode --- # Apps in Maintenance Mode [TOC levels=1-4] At a designated time, Liferay may cease enhancing a product or capability. This is called *maintenance mode*. During this mode, Liferay actively supports and provides bug fixes for the product or capability in accordance with the subscribers' subscription level and the end of service life policies of the compatible Liferay DXP version. Maintenance mode does not necessarily mean that deprecation in a future Liferay DXP version is planned for the product or capability; it only means that enhancements aren't being made for the current Liferay DXP development cycle. As of Liferay DXP 7.2, these products and capabilities have transitioned into maintenance mode: - Liferay Connected Services - Liferay Connector to OAuth 1.0a - Liferay Drools - Liferay Mobile Experience (Liferay Screens, Liferay Mobile SDK, Liferay Push) - Liferay Reports - Liferay Sync - Staging ## Related Topics [Deprecated Apps in 7.2: What to do?](/docs/7-2/deploy/-/knowledge_base/d/deprecated-apps-in-7-2-what-to-do) ================================================ FILE: en/deployment/articles/06-maintaining-liferay/01-maintaining-liferay-intro.markdown ================================================ --- header-id: maintaining-liferay --- # Maintaining @product@ [TOC levels=1-4] Once you have a @product@ installation, there are some things you must do to keep it running smoothly. Backing up your installation in case of a hardware failure protects your data and helps you get your system back in working order quickly. And if you're a DXP customer, patching your system regularly brings the latest bug fixes to your running instance. | Upgrading @product-ver@ to a new GA version can require | [data upgrade](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver). | Until you perform all required data upgrades (if any), @product@ startup fails | with messages like these: | | ```bash | 2019-03-06 17:22:35.025 INFO [main][StartupHelper:72] There are no patches installed | You must first upgrade to @product@ 7210 | 2019-03-06 17:22:35.098 ERROR [main][MainServlet:277] java.lang.RuntimeException: You must first upgrade to @product@ 7201 | ``` Read on to learn about how to keep your system running well. ================================================ FILE: en/deployment/articles/06-maintaining-liferay/02-backing-up-a-liferay-installation.markdown ================================================ --- header-id: backing-up-a-liferay-installation --- # Backing up a @product@ Installation [TOC levels=1-4] Once you have an installation of @product@ running, you should implement a comprehensive backup plan. In case some kind of catastrophic hardware failure occurs, you'll be thankful to have backups and procedures for restoring @product@ from one of them. @product@ isn't very different from other Java web applications that might be running on your application server. Nevertheless, there are some specific components you should include in your backup plan. The recommended backup plan includes backing up these things: - Source code - @product@'s file System - @product@'s database ## Backing up Source Code If you have extended @product@ or have written any plugins, they should be stored in a source code repository such as Git, Subversion, or CVS, unless you're Linus Torvalds, and then tarballs are okay too (that's a joke). You should back up your source code repository on a regular basis to preserve your ongoing work. This probably goes without saying in your organization since nobody wants to lose source code that's taken months to produce. Thus you should include source code in your @product@ backup plan. Next, let's examine the @product@ installation items you should back up. ## Backing up @product@'s File System The [Liferay Home folder](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) stores @product@'s properties configuration files, such as `portal-setup- wizard.properties` and `portal-ext.properties`. You should absolutely back them up. In fact, it's best to back up your entire application server and Liferay Home folder contents. @product@ stores configuration files, search indexes, and cache information in Liferay Home's `/data` folder. If you're using the File System store or the Advanced File System store, the documents and media repository is also stored here by default. It's always important to back up your `/data` folder. The files that comprise @product@'s OSGi runtime are stored in Liferay Home's `/osgi` folder. It contains all of the app and module JAR files deployed to @product@. The `/osgi` folder also contains other required JAR files, configuration files, and log files. It's also important to back up your `/osgi` folder. Liferay Home's `/logs` folder contains @product@'s log files. If a problem occurs on @product@, the @product@ log files often provide valuable information for determining what went wrong. The `/data`, `/osgi`, and `/logs` folders are all contained in the Liferay Home folder. Thus, if you're backing up both your application server folder and your Liferay Home folder, you're in good shape. Remember that if you've configured the document library to store files to a location other than the default location, you should also back up that location. That covers the @product@ file system locations you should back up. Next, let's discuss how to back up @product@'s database. ## Backing up @product@'s Database @product@'s database is the central repository for all of the portal's information. It's the most important component to back up. You can back up the database live (if your database allows this) or by exporting (dumping) the database into a file and then backing up the exported file. For example, MySQL ships with a `mysqldump` utility which lets you export the entire database and data into a large SQL file. This file can then be backed up. On restoring the database you can import this file into the database to recreate the database state to that of the time you exported the database. If you're storing @product@'s Documents and Media Library files to a Jackrabbit JSR-170 repository database, you should back it up. If you've placed your search index into a database (not recommended; see the [@product@ Clustering](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) article for information on using Cluster Link or Solr), you should back up that database too. If you wish to avoid re-indexing your content after restoring your database, back up your search indexes. This is easiest to do if you have a separate Elastic or Solr environment on which your index is stored. If you're in a clustered configuration and you're replicating indexes, you'll need to back up each index replica. Restoring your application server, your Liferay Home folder, the locations of any file system-based media repositories, and your database from a backup system should give you a functioning portal. Restoring search indexes should avoid the need to re-index when you bring your site back up after a catastrophic failure. Good, consistent backup procedures are key to recovering successfully from a hardware failure. ================================================ FILE: en/deployment/articles/07-monitoring-liferay/01-monitoring-liferay-intro.markdown ================================================ --- header-id: monitoring-product --- # Monitoring @product@ [TOC levels=1-4] These articles show you how to monitor @product@. Monitoring vital statistics such as Java memory heaps, garbage collection, database connection pools, and the application server helps you optimize performance. Better monitoring means better tuning and thus avoids dangerous runtime scenarios like out of memory errors and wasted heap space. You'll learn basic monitoring techniques, such as - Using the Visual VM tool and the JMX Console - Garbage Collection Read on to learn more about monitoring @product@! ================================================ FILE: en/deployment/articles/07-monitoring-liferay/02-monitoring-gc-and-the-jvm.markdown ================================================ --- header-id: monitoring-garbage-collection-and-the-jvm --- # Monitoring Garbage Collection and the JVM [TOC levels=1-4] Although the [tuning parameters](/docs/7-2/deploy/-/knowledge_base/d/tuning-guidelines) give you a good start to JVM tuning, you must monitor GC performance to ensure you have the best settings to meet your needs. There are several tools to help you monitor Oracle JVM performance. ## VisualVM [VisualVM](https://visualvm.github.io/) provides a centralized console for viewing Oracle JVM performance information and its Visual GC plugin shows garbage collector activities. ![Figure 1: VisualVM's Visual GC plugin shows the garbage collector in real-time.](../../images-dxp/visual-vm-gc.png) | **Note:** Oracle's JDK has VisualVM bundled (`$JAVA_HOME/bin/jvisualvm`). | However, always download and use the latest version from VisualVM's | [official website](https://visualvm.github.io/). ## JMX Console This tool helps display various statistics like @product@'s distributed cache performance, application server thread performance, JDBC connection pool usage, and more. | **Note:** The JMX Console is the preferred tool for monitoring application | server performance. To enable JMX connections, add these JVM arguments: ```bash -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false ``` If you're running JMX Console from a another machine, add these JVM arguments too: ```bash -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.rmi.port=5000 -Djava.rmi.server.hostname=[place IP address here] ``` ![Figure 2: VisualVM monitors the JVM using Java Management Extensions.](../../images-dxp/visual-vm-jmx.png) ## Garbage Collector Verbose Logging Add these JVM arguments to activate verbose logging for the JVM garbage collector. ```bash -verbose:gc -Xloggc:/tmp/liferaygc1.log -XX:+PrintGCDetails -XX:+PrintGCCause -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime ``` Examining these logs helps you tune the JVM properly. | **Note:** Adding these JVM arguments generates a heap dump if an | `OutOfMemoryError` occurs. The dump is written to the heap dump path | specified. Specify the path to use: | | `-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/heap/dump/path/` Garbage collector log files can grow huge. You can use additional arguments like the following ones to rotate the log to a new log file when the current log file reaches a maximum size: ```bash -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=50M ``` These arguments rotate the logs to up to `10` log files with a maximum size of `50M` each. Now you can monitor garbage collection in the JVM and tune it for top performance. ================================================ FILE: en/deployment/articles/100-reference/01-deployment-reference-intro.markdown ================================================ --- header-id: deployment-reference --- # Deployment Reference [TOC levels=1-4] Here you'll find definitions, default settings, templates, and more. Here are some of the topics: - [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home): From this location, @product@ launches applications, applies configurations, loads JAR files, and generates logs. - [Portal properties](/docs/7-2/deploy/-/knowledge_base/d/portal-properties): Use a `portal-ext.properties` file (or another qualified properties file) to configure @product@ and override features. - [System properties](/docs/7-2/deploy/-/knowledge_base/d/system-properties): Examine @product@ default system configuration. - [Database templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates): Use these portal properties templates to specify the @product@ database. - [Elasticsearch settings](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch-connector-settings-reference): Examine the configuration settings for Liferay's default Elasticsearch adapter. ================================================ FILE: en/deployment/articles/100-reference/02-liferay-home.markdown ================================================ --- header-id: liferay-home --- # Liferay Home [TOC levels=1-4] *Liferay Home* is the location from which @product@ launches applications, applies configurations, loads JAR files, and generates logs. - *In a @product@ bundle,* Liferay Home is the installation's top-level folder and it contains the application server. - *In a manual installation,* the Liferay Home folder varies by application server. If you're doing a manual installation, please refer to the article covering that app server (e.g., *Installing @product@ on [app server]*) for the Liferay Home location. Bundles contain this folder structure regardless of application server: - **[Liferay Home]** - **[Application Server]**: This folder is named after the application server where @product@ is installed. - `data` (if HSQL database is selected): Stores an embedded HSQL database, @product@'s file repository, and search indexes. The embedded HSQL database is configured by default, but it's intended for demonstration and trial purposes only. The [Portal property `jdbc.default.url`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#JDBC) sets the Hypersonic embedded HSQL database location. - `deploy`: To auto-deploy plugins, copy them to this folder. It supports application `.lpkg` files from Liferay Marketplace, plugin `.war` files, and plugin `.jar` files. The [Portal property `auto.deploy.deploy.dir`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Auto%20Deploy) sets the auto-deploy location. - `license`: @product@'s copyright and version files are here. - `logs`: Log files go here. Examine them as you diagnose problems. `portal-impl.jar`'s `portal-impl/src/META-INF/portal-log4j.xml` file sets the log file location. To override the log file location, you must [use an `ext-impl/src/META-INF/portal-log4j-ext.xml` file in an Ext plugin](/docs/7-0/tutorials/-/knowledge_base/t/advanced-customization-with-ext-plugins#using-advanced-configuration-files). - `osgi`: All the JAR files and a few configuration files for the OSGi runtime belong in this folder. The [Portal property `module.framework.base.dir`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Module%20Framework) sets the OSGi folder location. Here are its subfolders: - `configs`: Component configuration files. - `core`: @product@'s core modules. - `marketplace`: Marketplace applications and application suites. - `modules`: Modules you've deployed. - `portal`: @product@'s non-core modules. - `state`: Contains OSGi internal state files for such things as OSGi bundle installation, bundle storage, and more. - `target-platform`: Target platform index. - `test`: Modules that support test integration. - `war`: WAR plugins you've deployed. - `patching-tool`: (Liferay DXP only) This folder contains patches and a utility for installing the patches. - `tools`: For @product@ upgrade and target platform indexer. - `work`: Module Jasper work files. | **Note:** If @product@ cannot create resources in the | Liferay Home folder or if it finds itself running on certain application | servers, it creates a folder called `liferay` in the home folder of the | operating system user that is running @product@. In this case, that `liferay` | folder becomes Liferay Home. For example, if the operating system user's name | is jbloggs, the `liferay` folder path is `/home/jbloggs/liferay` or | `C:\Users\jbloggs\liferay`. ================================================ FILE: en/deployment/articles/100-reference/03-portal-properties.markdown ================================================ --- header-id: portal-properties --- # Portal Properties [TOC levels=1-4] You can set portal properties to configure and override @product@ features. Your installation's `portal-impl.jar` embeds the default properties file:

    portal.properties (Opens New Window)

    Overriding a portal property requires creating an *extension* portal properties file that specifies the properties you're overriding. | **Note:** In a portal properties extension file, specify only the properties | you're overriding. Here's an example of setting Portal's data source to a MySQL database by adding override properties in a `[Liferay Home]/portal-ext.properties` file: ```properties jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.username=jbloggs jdbc.default.password=pass123 ``` The [`include-and-override`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Properties Override) property specifies portal property files that override the defaults. It specifies the order the files are read---the last file read takes highest priority. Properties file prioritization (highest to lowest): 1. `[Liferay Home]/portal-setup-wizard.properties` 2. `[user home]/portal-setup-wizard.properties` 3. `[Liferay Home]/portal-ext.properties` 4. `[user home]/portal-ext.properties` 5. `[Liferay Home]/portal-bundle.properties` 6. `[user home]/portal-bundle.properties` 7. `[Liferay Home]/portal.properties` 8. `portal-impl.jar/portal.properties` `[Liferay Home]/portal-ext.properties` is the most commonly used file for overriding the defaults. ================================================ FILE: en/deployment/articles/100-reference/04-system-properties.markdown ================================================ --- header-id: system-properties --- # System Properties [TOC levels=1-4] System properties configure the @product@ system. Your installation's `portal-impl.jar` embeds the default properties file:

    system.properties (Opens New Window)

    ================================================ FILE: en/deployment/articles/100-reference/05-database-templates.markdown ================================================ --- header-id: database-templates --- # Database Templates [TOC levels=1-4] Below are templates (example [portal properties](/docs/7-2/deploy/-/knowledge_base/d/portal-properties)) for configuring various databases as a built-in data source for @product@. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. ## MariaDB ```properties jdbc.default.driverClassName=org.mariadb.jdbc.Driver jdbc.default.url=jdbc:mariadb://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.default.username= jdbc.default.password= ``` ## MySQL | **Note:** MySQL Connector/J 8.0 is highly recommended for use with MySQL | Server 8.0 and 5.7. ```properties jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&serverTimezone=GMT&useFastDateParsing=false&useUnicode=true jdbc.default.username= jdbc.default.password= ``` ## PostgreSQL ```properties jdbc.default.driverClassName=org.postgresql.Driver jdbc.default.url=jdbc:postgresql://localhost:5432/lportal jdbc.default.username=sa jdbc.default.password ``` See the [default portal properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#JDBC) for more database templates. ================================================ FILE: en/deployment/articles/99-troubleshooting-deployments/01-troubleshooting-deployments-intro.markdown ================================================ --- header-id: troubleshooting-deployments --- # Troubleshooting Deployments [TOC levels=1-4] 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 did the entity sort order change when I migrated to a new database type? 

    How can I use files to configure components? 

    The application server and database started, but @product@ failed to connect to the database. What happened and how can I fix this? 

    @product@ initialization can fail while attempting to connect to a database server that isn't ready. Configuring startup to retry JDBC connections facilitates connecting @product@ to databases.

    ================================================ FILE: en/deployment/articles/99-troubleshooting-deployments/database-not-ready.markdown ================================================ --- header-id: portal-failed-to-initialize-because-the-database-wasnt-ready --- # @product@ Failed to Initialize Because the Database Wasn't Ready [TOC levels=1-4] If you start your database server and application server at the same time, @product@ might try connecting to the data source before the database is ready. By default, @product@ doesn't retry connecting to the database; it just fails. But there is a way to avoid this situation: database connection retries. 1. Create a `portal-ext.properties` file in your [Liferay Home](/docs/7-1/deploy/-/knowledge_base/d/liferay-home) folder. 2. Set the property `retry.jdbc.on.startup.max.retries` equal to the number of times to retry connecting to the data source. 3. Set property `retry.jdbc.on.startup.delay` equal to the number of seconds to wait before retrying connection. If at first the connection doesn't succeed, @product@ uses the retry settings to try again. ## Related Topics [Connecting to JNDI Data Sources](/docs/7-2/appdev/-/knowledge_base/a/connecting-to-data-sources-using-jndi) ================================================ FILE: en/deployment/articles/99-troubleshooting-deployments/sort-order-changed-with-different-database.markdown ================================================ --- header-id: sort-order-changed-with-a-different-database --- # Sort Order Changed with a Different Database [TOC levels=1-4] If you've been using @product@, but are switching it to use a different database type, consult your database vendor documentation to understand your old and new database's default query result order. The default order is either case-sensitive or case-insensitive. This affects entity sort order in @product@. Here are some examples of ascending alphabetical sort order. Case-sensitive: 111 222 AAA BBB aaa bbb Case-insensitive: 111 222 AAA aaa BBB bbb Your new database's default query result order might differ from your current database's order. Consult your vendor's documentation to configure the order the way you want. ================================================ FILE: en/deployment/articles/99-troubleshooting-deployments/using-files-to-config-components.markdown ================================================ --- header-id: using-files-to-configure-product-modules --- # Using Files to Configure Module Components [TOC levels=1-4] @product@ uses [Felix File Install](http://felix.apache.org/documentation/subprojects/apache-felix-file-install.html) to monitor file system folders for new/updated configuration files, and the [Felix OSGi implementation](http://felix.apache.org/) of [Configuration Admin](http://felix.apache.org/documentation/subprojects/apache-felix-config-admin.html) to let you use files to configure module service components. To learn how to work with configuration files, first review [Understanding System Configuration Files](/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files). ## Configuration File Formats There are two different configuration file formats: - `.cfg`: An older, simple format that only supports `String` values as properties. - `.config`: A format that supports strings, type information, and other non-string values in its properties. Although @product@ supports both formats, use `.config` files for their flexibility and ability to use type information. Since `.cfg` files lack type information, if you want to store anything but a `String`, you must use properties utility classes to cast `String`s to intended types (and you must carefully document properties that aren't `String`s). `.config` files eliminate this need by allowing type information. The articles below explain the file formats: - [Understanding System Configuration Files](/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files) - [Configuration file (`.config`) syntax](https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#configuration-files-config) - [Properties file(`.cfg`) syntax](https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#property-files-cfg) ## Naming Configuration Files Before you [create a configuration file](/docs/7-1/user/-/knowledge_base/u/creating-configuration-files), follow these steps to determine whether multiple instances of the component can be created or if the component is intended to be a singleton: 1. Deploy the component's module if you haven't done so already. 2. In @product@'s UI, go to *Control Panel* → *Configuration* → *System Settings*. 3. Find the component's settings by searching or browsing for the component. 4. If the component's settings page has a section called *Configuration Entries*, you can create multiple instances of the component configured however you like. Otherwise, you should treat the component as a singleton. ![Figure 1: You can create multiple instances of components whose System Settings page has a *Configuration Entries* section.](../../images/system-settings-page-lists-configuration-entries.png) *All* configuration file names must start with the component's PID (PID stands for *persistent identity*) and end with `.config` or `.cfg`. For example, this class uses [Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) to define a component: ```java package com; @Component class Foo {} ``` The component's PID is `com.Foo`. All the component's configuration files must start with the PID `com.Foo`. For each non-singleton component instance you want to create or update with a configuration, you must use a uniquely named configuration file that starts with the component's PID and ends with `.config` or `.cfg`. Creating configurations for multiple component instances requires that the configuration files use different *subnames*. A subname is the part of a configuration file name after the PID and before the suffix `.config` or `.cfg`. Here's the configuration file name pattern for non-singleton components: - `[PID]-[subname1].config` - `[PID]-[subname2].config` - etc. For example, you could configure two different instances of the component `com.Foo` by using configuration files with these names: - `com.Foo-one.config` - `com.Foo-two.config` Each configuration file creates and/or updates an instance of the component that matches the PID. The subname is arbitrary---it doesn't have to match a specific component instance. This means you can use whatever subname you like. For example, these configuration files are just as valid as the two above: - `com.Foo-puppies.config` - `com.Foo-kitties.config` Using the subname `default`, however, is @product@'s convention for configuring a component's first instance. The file name pattern is therefore [PID]-default.config A singleton component's configuration file must also start with `[PID]` and end with `.config` or `.cfg`. Here's the common pattern used for singleton component configuration file names: [PID].config When you're done creating a configuration file, you can [deploy it](/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files#deploying-a-configuration-file). ## Resolving Configuration File Deployment Failures The following `IOException` hints that the configuration file has a syntax issue: Failed to install artifact: [path to .config or .cfg file] java.io.IOException: Unexpected token 78; expected: 61 (line=0, pos=107) To resolve this, fix the [configuration file's syntax](#configuration-file-formats). Great! Now you know how to configure module components using configuration files. ## Related Articles [Understanding System Configuration Files](/docs/7-1/user/-/knowledge_base/u/understanding-system-configuration-files) ================================================ FILE: en/deployment/articles-dxp/01-deploying-liferay/07-installing-liferay-portal-on-jboss-eap.markdown ================================================ --- header-id: installing-product-on-jboss-eap --- # Installing @product@ on JBoss EAP [TOC levels=1-4] Installing @product@ on JBoss EAP 7.1 takes three steps: - Installing dependencies to your application server - Configuring your application server for @product@ - Installing the @product@ WAR file to your application server | **Important:** Before installing @product@, familiarize yourself with | [preparing for install](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install). Now, [download the @product@ WAR and Dependency JARs](/docs/7-2/deploy/-/knowledge_base/d/obtaining-product#downloading-the-liferay-war-and-dependency-jars): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file Not that [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) is the folder containing your JBoss server folder. After installing and deploying @product@, the Liferay Home folder contains the JBoss server folder as well as `data`, `deploy`, `logs`, and `osgi` folders. `$JBOSS_HOME` refers to your JBoss server folder. This folder is usually named `jboss-eap-[version]`. ## Installing Dependencies @product@ depends on several Liferay-specific and third-party JARs. Download and install the required JARs as described below. 1. Create the folder `$JBOSS_HOME/modules/com/liferay/portal/main` if it doesn't exist and extract the JARs from the dependencies ZIP to it. 2. Download your database driver `.jar` file and copy it into the same folder. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. 3. Create the file `module.xml` in the `$JBOSS_HOME/modules/com/liferay/portal/main` folder and insert this configuration: ```xml ``` Replace `[place your database vendor's jar here]` with the driver JAR for your database. 4. Create an `osgi` folder in your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder. Extract the OSGi Dependencies ZIP file that you downloaded into the `[Liferay Home]/osgi` folder. The `osgi` folder provides the necessary modules for @product@'s OSGi runtime. **Checkpoint:** 1. The dependencies files have been unzipped into the `$JBOSS_HOME/modules/com/liferay/portal/main` folder and a database jar. 1. The `module.xml` contains all JARs in the `` elements. 1. The `osgi` dependencies have been unzipped into the `osgi` folder. ## Running @product@ on JBoss EAP in Standalone Mode vs. Domain Mode JBoss EAP can be launched in either *standalone* mode or *domain* mode. Domain mode allows multiple application server instances to be managed from a single control point. A collection of such application servers is known as a *domain*. For more information on standalone mode vs. domain mode, please refer to the section on this topic in the [JBoss EAP Product Documentation](https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.1/html/introduction_to_jboss_eap/overview_of_jboss_eap#operating_modes). @product@ supports JBoss EAP when it runs in standalone mode but not when it runs in domain mode. @product@'s hot-deploy does not work with a managed deployment, since JBoss manages the content of a managed deployment by copying files (exploded or non-exploded). This prevents JSP hooks and Ext plugins from working as intended. For example, JSP hooks don't work on JBoss EAP running in managed domain mode, since @product@'s JSP override mechanism relies on the application server. Since JSP hooks and Ext plugins are deprecated, however, you may not be using them. The command line interface is recommended for domain mode deployments. | **Note:** This does not prevent @product@ from running in a clustered | environment on multiple JBoss servers. You can set up a cluster of @product@ | instances running on JBoss EAP servers running in standalone mode. Please | refer to the [@product@ clustering articles](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) | for more information. ## Configuring JBoss Configuring JBoss to run @product@ includes these things: - Setting environment variables - Setting properties and descriptors - Removing unnecessary configurations Optionally, you can configure JBoss to manage @product@'s data source and mail session. Start with configuring JBoss to run @product@. Make the following modifications to `$JBOSS_HOME/standalone/configuration/standalone.xml`: 1. Locate the closing `` tag. Directly beneath that tag, insert these system properties: ```xml ``` 2. Add the following `` tag within the `` tag, directly below the `` tag: ```xml ``` 3. Add a timeout for the deployment scanner by setting `deployment-timeout="600"` as seen in the excerpt below. ```xml ``` 4. Add the following JAAS security domain to the security subsystem `` defined in element ``. ```xml ``` 5. Remove the welcome content code snippets: ```xml ``` and ```xml ``` 6. Find the `` tag and set the `development`, `source-vm`, and `target-vm` attributes in the tag. Once finished, the tag should look like this: ```xml ``` **Checkpoint:** Before continuing, verify the following properties have been set in the `standalone.xml` file: 1. The new `` is added. 2. The new `` is added. 3. The `` is set to `360`. 4. The new `` is created. 5. Welcome content is removed. 6. The `` tag contains its new attributes. Now you should configure your JVM and startup scripts. In the `$WILDFLY_HOME/bin/` folder, modify your standalone domain's configuration script file `standalone.conf` (`standalone.conf.bat` on Windows): - Set the file encoding to `UTF-8` - Set the user time zone to `GMT` - Set the preferred protocol stack - Increase the default amount of memory available. | **Important:** For @product@ to work properly, the application server JVM must | use the `GMT` time zone and `UTF-8` file encoding. Make the following edits as applicable to your operating system: **Windows** 1. Comment out the initial `JAVA_OPTS` assignment as demonstrated in the following line: ```bash rem set "JAVA_OPTS=-Xms1G -Xmx1G -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m" ``` 2. Add the following `JAVA_OPTS` assignment one line above the `:JAVA_OPTS_SET` line found at end of the file: ```bash set "JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=768m" ``` **Unix** 1. Below the `if [ "x$JAVA_OPTS" = "x" ];` statement, replace this `JAVA_OPTS` statement: ```bash JAVA_OPTS="-Xms1303m -Xmx1303m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=2560m -Djava.net.preferIPv4Stack=true" ``` with this: ```bash JAVA_OPTS="-Djava.net.preferIPv4Stack=true" ``` 2. Add the following statement to the bottom of the file: ```bash JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Djava.net.preferIPv4Stack=true -Djboss.as.management.blocking.timeout=480 -Duser.timezone=GMT -Xms2560m -Xmx2560m -XX:MaxMetaspaceSize=512m" ``` On JDK 11, it's recommended to add this JVM argument to display four-digit years. ```bash -Djava.locale.providers=JRE,COMPAT,CLDR ``` | **Note:** If you plan on using the IBM JDK with your JBoss server, you must | complete some additional steps. First, navigate to the | `$JBOSS_HOME/modules/com/liferay/portal/main/module.xml` file and insert the | following dependency within the `` element: | | | | Then navigate to the | `$JBOSS_HOME/modules/system/layers/base/sun/jdk/main/module.xml` file and | insert the following path names inside the `...` element: | | | | | | | | The added paths resolve issues with portal deployment exceptions and image | uploading problems. **Checkpoint:** At this point, you've finished configuring the application server's JVM settings. 1. The file encoding, user time-zone, preferred protocol stack have been set in the `JAVA_OPTS` in the `standalone.conf.bat` file. 2. The default amount of memory available has been increased. The prescribed script modifications are now complete for your @product@ installation on JBoss. Next you'll configure the database and mail. ## Database Configuration The easiest way to handle your database configuration is to let @product@ manage your data source. The [Basic Configuration](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#using-the-built-in-data-source) page lets you configure @product@'s built-in data source. If you want to use the built-in data source, skip this section. This section demonstrates configuring a MySQL database. If you're using a different database, modify the data source and driver snippets as necessary. If using JBoss to manage the data source, follow these steps: 1. Add the data source inside the `$JBOSS_HOME/standalone/configuration/standalone.xml` file's the `` element. ```xml [place the URL to your database here] [place the driver name here] [place your user name here] [place your password here] ``` Make sure to replace the database URL, user name, and password with the appropriate values. | **Note:** If the data source `jndi-name` must be changed, edit the `datasource` element in the `` tag. 2. Add your driver to the `standalone.xml` file's `` element also found within the `` element. ```xml [place your JDBC driver class here] ``` A final data sources subsystem that uses MySQL should look like this: ```xml jdbc:mysql://localhost/lportal mysql root root ``` 3. In a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file in your Liferay Home, specify your data source: ```properties jdbc.default.jndi.name=java:jboss/datasources/ExampleDS ``` Now that you've configured your data source, the mail session is next. ## Mail Configuration As with database configuration, the easiest way to configure mail is to let @product@ handle your mail session. If you want to use @product@'s built-in mail session, skip this section and [configure the mail session](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail) in the Control Panel. If you want to manage your mail session with JBoss, follow these steps: 1. Specify your mail subsystem in the `$JBOSS_HOME/standalone/configuration/standalone.xml` file like this: ```xml ... ... ``` 2. In your `portal-ext.properties` file in Liferay Home, reference your mail session: ```properties mail.session.jndi.name=java:jboss/mail/MailSession ``` You've got mail! Next, you'll deploy @product@ to your JBoss app server. ## Deploy Liferay Now you're ready to deploy @product@ using the @product@ WAR file. 1. If the folder `$JBOSS_HOME/standalone/deployments/ROOT.war` already exists in your JBoss installation, delete all of its subfolders and files. Otherwise, create a new folder called `$JBOSS_HOME/standalone/deployments/ROOT.war`. 2. Unzip the @product@ `.war` file into the `ROOT.war` folder. 3. To trigger deployment of `ROOT.war`, create an empty file named `ROOT.war.dodeploy` in your `$JBOSS_HOME/standalone/deployments/` folder. On startup, JBoss detects this file and deploys it as a web application. 4. Start the JBoss application server by navigating to `$JBOSS_HOME/bin` and running `standalone.bat` or `standalone.sh`. Congratulations; you've now deployed @product@ on JBoss! | After deploying @product@, you may see excessive warnings and log messages, | such as the ones below, involving `PhaseOptimizer`. These are benign and can | be ignored. Make sure to adjust your app server's logging level or log | filters to avoid excessive benign log messages. | | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass gatherExternProperties | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass checkControlFlow | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] | current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] ================================================ FILE: en/deployment/articles-dxp/01-deploying-liferay/08-installing-liferay-on-weblogic.markdown ================================================ --- header-id: installing-liferay-dxp-on-weblogic-12c-r2 --- # Installing @product@ on WebLogic 12c R2 [TOC levels=1-4] Although you can install @product@ in a WebLogic Admin Server, this isn't recommended. It's a best practice to install web apps, including @product@, in a WebLogic Managed server. Deploying to a Managed Server lets you start or shut down @product@ more quickly and facilitates transitioning into a cluster configuration. This article therefore focuses on installing @product@ in a Managed Server. Before getting started, create your Admin and Managed Servers. See [WebLogic's documentation](http://www.oracle.com/technetwork/middleware/weblogic/documentation/index.html) for instructions on setting up and configuring Admin and Managed Servers. Also familiarize yourself with [preparing for install](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install). Now, [download the @product@ WAR and Dependency JARs](/docs/7-2/deploy/-/knowledge_base/d/obtaining-product#downloading-the-liferay-war-and-dependency-jars): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file ## Configuring WebLogic's Node Manager WebLogic's Node Manager starts and stops managed servers. If you're running WebLogic on a UNIX system other than Solaris or Linux, use the Java Node Manager, instead of the native version of the Node Manager, by configuring these Node Manager properties in the `domains/your_domain_name/nodemanager/nodemanager.properties` file: ```properties NativeVersionEnabled=false StartScriptEnabled=true ``` | **Note:** By default, SSL is used with Node Manager. If you want to disable SSL during development, for example, set `SecureListener=false` in your `nodemanager.properties` file. See Oracle's [Configuring Java Node Manager](https://docs.oracle.com/middleware/1212/wls/NODEM/java_nodemgr.htm#NODEM173) documentation for details. ## Configuring WebLogic Next, you must set some variables in two WebLogic startup scripts. These variables and scripts are as follows. Be sure to use `set` instead of `export` if you're on Windows. 1. `your-domain/startWebLogic.[cmd|sh]`: This is the Admin Server's startup script. 2. `your-domain/bin/startWebLogic.[cmd|sh]`: This is the startup script for Managed Servers. Add the following variables to both `startWebLogic.[cmd|sh]` scripts: ```bash export DERBY_FLAG="false" export JAVA_OPTIONS="${JAVA_OPTIONS} -Dfile.encoding=UTF-8 -Duser.timezone=GMT -da:org.apache.lucene... -da:org.aspectj..." export MW_HOME="/your/weblogic/directory" export USER_MEM_ARGS="-Xmx2560m -Xms2560m" ``` | **Important:** For @product@ to work properly, the application server JVM | must use the `GMT` time zone and `UTF-8` file encoding. The `DERBY_FLAG` setting disables the Derby server built in to WebLogic, as @product@ doesn't require this server. The remaining settings support @product@'s memory requirements, UTF-8 requirement, Lucene usage, and Aspect Oriented Programming via AspectJ. Also make sure to set `MW_HOME` to the directory containing your WebLogic server on your machine. For example: ```bash export MW_HOME="/Users/ray/Oracle/wls12210" ``` 3. Some of the settings are also found in the `your-domain/bin/SetDomainEnv.[cmd|sh]` . Add the following variables (Windows): ```bash set WLS_MEM_ARGS_64BIT=-Xms2560m -Xmx2560m set WLS_MEM_ARGS_32BIT=-Xms2560m -Xmx2560m ``` or on Mac or Linux: ```bash WLS_MEM_ARGS_64BIT="-Xms2560m -Xmx2560m" export WLS_MEM_ARGS_64BIT WLS_MEM_ARGS_32BIT="-Xms2560m -Xmx2560m" export WLS_MEM_ARGS_32BIT ``` 4. Set the Java file encoding to UTF-8 in `your-domain/bin/SetDomainEnv.[cmd|sh]` by appending `-Dfile.encoding=UTF-8` ahead of your other Java properties: ```bash JAVA_PROPERTIES="-Dfile.encoding=UTF-8 ${JAVA_PROPERTIES} ${CLUSTER_PROPERTIES}" ``` 5. You must also ensure that the Node Manager sets @product@'s memory requirements when starting the Managed Server. In the Admin Server's console UI, navigate to the Managed Server you want to deploy @product@ to and select the *Server Start* tab. Enter the following parameters into the *Arguments* field: ```bash -Xmx2560m -Xms2560m -XX:MaxMetaspaceSize=512m ``` Click *Save* when you're finished. Next, you'll set some @product@-specific properties for your @product@ installation. ## Setting @product@ Properties Before installing @product@, you must set the [*Liferay Home*](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder's location via the `liferay.home` property in a [`portal-ext.properties`](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) file. You can also use this file to override [other @product@ properties](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) that you may need. First, decide which folder you want to serve as Liferay Home. In WebLogic, your domain's folder is generally Liferay Home, but you can choose any folder on your machine. Then create your `portal-ext.properties` file and add the `liferay.home` property: ```properties liferay.home=/full/path/to/your/liferay/home/folder ``` Remember to change this file path to the location on your machine that you want to serve as Liferay Home. Now that you've created your `portal-ext.properties` file, you must put it inside the @product@ WAR file. Expand the @product@ WAR file and place `portal-ext.properties` in the `WEB-INF/classes` folder. Later, you can deploy the expanded archive to WebLogic. Alternatively, you can re-WAR the expanded archive for later deployment. In either case, @product@ reads your property settings once it starts up. If you need to make any changes to `portal-ext.properties` after @product@ deploys, you can find it in your domain's `autodeploy/ROOT/WEB-INF/classes` folder. Note that the `autodeploy/ROOT` folder contains the @product@ deployment. Next, you'll install @product@'s dependencies. ## Installing @product@ Dependencies You must now install @product@'s dependencies. Recall that earlier you downloaded two ZIP files containing these dependencies. Install their contents now: 1. Unzip the Dependencies ZIP file and place its contents in your WebLogic domain's `lib` folder. 2. Unzip the OSGi Dependencies ZIP file and place its contents in the `Liferay_Home/osgi` folder (create this folder if it doesn't exist). You must also add your database's driver JAR file to your domain's `lib` folder. | **Note:** Although Hypersonic is fine for testing purposes, **do not** | use it for production @product@ instances. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. Next, you'll configure your database. ## Database Configuration Use the following procedure if you want WebLogic to manage your [database](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#using-the-built-in-data-source) for @product@. You can skip this section if you want to use @product@'s built-in Hypersonic database. 1. Log in to your AdminServer console. 2. In the *Domain Structure* tree, find your domain and navigate to *Services* → *JDBC* → *Data Sources*. 3. To create a new data source, click *New*. Fill in the *Name* field with `Liferay Data Source` and the *JNDI Name* field with `jdbc/LiferayPool`. Select your database type and driver. For example, MySQL is *MySQL's Driver (Type 4) Versions:using com.mysql.cj.jdbc.Driver*. Click *Next* to continue. 4. Accept the default settings on this page and click *Next* to move on. 5. Fill in your database information for your MySQL database. 6. If using MySQL, add the text `?useUnicode=true&characterEncoding=UTF-8&\useFastDateParsing=false` to the URL line and test the connection. | **Tip:** For more example URLs, see the `jdbc.default.url` values in [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). If the connection works, click *Next*. 7. Select the target for the data source and click *Finish*. 8. You must now tell @product@ about the JDBC data source. Create a `portal-ext.propreties` file in your Liferay Home directory, and add the line: ```propreties jdbc.default.jndi.name=jdbc/LiferayPool ``` Alternatively, you can make the above configuration strictly via properties in the `portal-ext.properties` file. Please see the [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates) for example properties. Next, you'll configure your mail session. ## Mail Configuration If you want WebLogic to manage your [mail session](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail), use the following procedure. If you want to use Liferay's built-in mail session (recommended), skip this section. 1. Start WebLogic and log in to your Admin Server's console. 2. Select *Services* → *Mail Sessions* from the *Domain Structure* box on the left hand side of your Admin Server's console UI. 3. Click *New* to begin creating a new mail session. 4. Name the session *LiferayMail* and give it the JNDI name `mail/MailSession`. Then fill out the *Session Username*, *Session Password*, *Confirm Session Password*, and *JavaMail Properties* fields as necessary for your mail server. See the [WebLogic documentation](http://docs.oracle.com/middleware/1221/wls/FMWCH/pagehelp/Mailcreatemailsessiontitle.html) for more information on these fields. Click *Next* when you're done. 5. Choose the Managed Server that you'll install @product@ on, and click *Finish*. Then shut down your Managed and Admin Servers. 6. With your Managed and Admin servers shut down, add the following property to your `portal-ext.properties` file in Liferay Home: ```propreties mail.session.jndi.name=mail/MailSession ``` @product@ references your WebLogic mail session via this property setting. If you've already deployed @product@, you can find your `portal-ext.properties` file in your domain's `autodeploy/ROOT/WEB-INF/classes` folder. Your changes take effect upon restarting your Managed and Admin servers. ## Deploying @product@ As mentioned earlier, although you can deploy @product@ to a WebLogic Admin Server, you should instead deploy it to a WebLogic Managed Server. Dedicating the Admin Server to managing other servers that run your apps is a best practice. Follow these steps to deploy @product@ to a Managed Server: 1. Make sure the Managed Server you want to deploy @product@ to is shut down. 2. In your Admin Server's console UI, select *Deployments* from the *Domain Structure* box on the left hand side. Then click *Install* to start a new deployment. 3. Select the @product@ WAR file or its expanded contents on your file system. Alternatively, you can upload the WAR file by clicking the *Upload your file(s)* link. Click *Next*. 4. Select *Install this deployment as an application* and click *Next*. 5. Select the Managed Server you want to deploy @product@ to and click *Next*. 6. If the default name is appropriate for your installation, keep it. Otherwise, give it a name of your choosing and click *Next*. 7. Click *Finish*. After the deployment finishes, click *Save* if you want to save the configuration. 8. Start the Managed Server where you deployed @product@. @product@ precompiles all the JSPs and then launches. Nice work! Now you're running @product@ on WebLogic. | After deploying @product@, you may see excessive warnings and log messages, | such as the ones below, involving `PhaseOptimizer`. These are benign and can | be ignored. Make sure to adjust your app server's logging level or log filters | to avoid excessive benign log messages. | | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass gatherExternProperties | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass checkControlFlow | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] | current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] ================================================ FILE: en/deployment/articles-dxp/01-deploying-liferay/09-installing-liferay-on-websphere.markdown ================================================ --- header-id: installing-liferay-dxp-on-websphere --- # Installing @product@ on WebSphere [TOC levels=1-4] IBM ® WebSphere ® is a trademark of International Business Machines Corporation, registered in many jurisdictions worldwide. | **Tip:** Throughout this installation and configuration process, WebSphere | prompts you to click *Save* to apply changes to the Master Configuration. Do | so intermittently to save your changes. For @product@ to work correctly, WebSphere 9 (Fix Pack 11 is the latest) must be installed. You can find more information about this fix pack [here](http://www-01.ibm.com/support/docview.wss?uid=swg24043005). Please also note that @product@ doesn't support the WebSphere Application Liberty Profile. | **Important:** Before installing @product@, familiarize yourself with | [preparing for install](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install). Now, [download the @product@ WAR and Dependency JARs](/docs/7-2/deploy/-/knowledge_base/d/obtaining-product#downloading-the-liferay-war-and-dependency-jars): - @product@ WAR file - Dependencies ZIP file - OSGi Dependencies ZIP file Note that the [*Liferay Home* folder](docs/7-2/deploy/-/knowledge_base/d/liferay-home) is important to the operation of @product@. In Liferay Home, @product@ creates certain files and folders that it needs to run. On WebSphere, Liferay Home is typically `[Install Location]/WebSphere/AppServer/profiles/your-profile/liferay`. Without any further ado, get ready to install @product@ in WebSphere! ## Preparing WebSphere for @product@ When the application server binaries have been installed, start the *Profile Management Tool* to create a profile appropriate for @product@. 1. Click *Create...*, choose *Application Server*, and then click *Next*. 2. Click the *Advanced* profile creation option and then click *Next*. You need the advanced profile to specify your own values for settings such as the location of the profile and names of the profile, node and host, to assign your own ports, or to optionally choose whether to deploy the administrative console and sample application and also add web-server definitions for IBM HTTP Server. See the WebSphere documentation for more information about these options. ![Figure 1: Choose the Advanced profile option to specify your own settings.](../../images-dxp/websphere-01-profile.png) 3. Check the box *Deploy the administrative console*. This gives you a web-based UI for working with your application server. Skip the default applications. You'd only install these on a development machine. Click *Next*. 4. Set the profile name and location. Ensure you specify a performance tuning setting other than *Development*, since you're installing a production server. See the WebSphere documentation for more information about performance tuning settings. Click *Next*. 5. Choose node, server, and host names for your server. These are specific to your environment. Click *Next*. 6. Administrative security in WebSphere is a way to restrict who has access to the administrative tools. You may want to have it enabled in your environment so that a user name and password are required to administer the WebSphere server. See WebSphere's documentation for more information. Click *Next*. 7. Each profile needs a security certificate, which comes next in the wizard. If you don't have certificates already, choose the option to generate a personal certificate and a signing certificate and click *Next*. 8. Once the certificates are generated, set a password for your keystore. Click *Next*. 9. Now you can customize the ports this server profile uses. Be sure to choose ports that are open on your machine. When choosing ports, the wizard detects existing WebSphere installations and if it finds activity, it increments ports by one. 10. Choose whether to start this profile when the machine starts. Click *Next*. 11. WebSphere ships with IBM HTTP Server, which is a re-branded version of Apache. Choose whether you want a web server definition, so that this JVM receives requests forwarded from the HTTP server. See WebSphere's documentation for details on this. When finished, click *Next*. 12. The wizard then shows you a summary of what you selected, enabling you to keep your choices or go back and change something. When you're satisfied, click *Next*. WebSphere then creates your profile and finishes with a message telling you the profile was created successfully. Awesome! Your profile is complete. Now there are a few things you need to configure in your application server. ![Figure 2: Example of the settings before creating the profile.](../../images-dxp/websphere-02-profile.png) ### Configuring the WebSphere Application Server In this version of WebSphere, servlet filters are not initialized on web application startup, but rather, on first access. This can cause problems when deploying certain apps to @product@. To configure servlet filters to initialize on application startup (i.e., deployment), set the following `webcontainer` properties in your WebSphere application server: ```properties com.ibm.ws.webcontainer.initFilterBeforeInitServlet = true com.ibm.ws.webcontainer.invokeFilterInitAtStartup = true ``` To set `webcontainer` properties in the WebSphere application server, follow the instructions [here in WebSphere's documentation](http://www-01.ibm.com/support/docview.wss?rss=180&uid=swg21284395). ### Setting up JVM Parameters for Liferay DXP Next, in the WebSphere profile you created for @product@, you must set an argument that supports @product@'s Java memory requirements. You'll modify this file: ``` [Install Location]/WebSphere/AppServer/profiles/your-profile/config/cells/your-cell/nodes/your-node/servers/your-server/server.xml ``` Add `maximumHeapSize="2560"` inside the `jvmEntries` tag. For example: ```xml ``` | **Note:** The JVM parameters used here are defaults intended for initial | deployment of production systems. Administrators should change the settings to | values that best address their specific environments. These must be tuned | depending on need. Administrators can set the UTF-8 properties in the `` attribute in `server.xml`. This is required or else special characters will not be parsed correctly. Set the maximum and minimum heap sizes to `2560m` there too. Add the following inside the `jvmEntries` tag: ```xml ``` | **Important:** For @product@ to work properly, the application server JVM must | use the `GMT` time zone and `UTF-8` file encoding. Alternately, you can set the UTF-8 properties from the WebSphere Admin Console. (See below.) ### Removing the secureSessionCookie Tag In the same profile, you should delete a problematic `secureSessionCookie` tag that can cause @product@ startup errors. Note that this is just a default setting; once @product@ is installed, you should tune it appropriately based on your usage. In `[Install Location]/WebSphere/AppServer/profiles/your-profile/config/cells/your-cell/cell.xml`, Delete the `secureSessionCookie` tag containing `xmi:id="SecureSessionCookie_1"`. If this tag is not removed, an error similar to this may occur: ``` WSVR0501E: Error creating component com.ibm.ws.runtime.component.CompositionUnitMgrImpl@d74fa901 com.ibm.ws.exception.RuntimeWarning: com.ibm.ws.webcontainer.exception.WebAppNotLoadedException: Failed to load webapp: Failed to load webapp: SRVE8111E: The application, LiferayEAR, is trying to modify a cookie which matches a pattern in the restricted programmatic session cookies list [domain=*, name=JSESSIONID, path=/]. ``` ## Installing @product@'s Dependencies You must now install @product@'s dependencies. Recall that earlier you downloaded two ZIP files containing these dependencies. Install their contents now: 1. Unzip the Dependencies ZIP file and place its contents in your WebSphere application server's `[Install Location]/WebSphere/AppServer/lib/ext` folder. If you have a JDBC database driver `JAR`, copy it to this location as well. | **Note:** The [Liferay DXP Compatibility Matrix](https://web.liferay.com/documents/14/21598941/Liferay+DXP+7.2+Compatibility+Matrix/b6e0f064-db31-49b4-8317-a29d1d76abf7?) specifies supported databases and environments. 2. From the same archive, copy `portlet.jar`into `[Install Location]/WebSphere/AppServer/javaext` for WebSphere 9.0.0.x. WebSphere already contains an older version of `portlet.jar` which must be overridden at the highest class loader level. The new `portlet.jar` (version 3) is backwards-compatible. 3. Unzip the OSGi Dependencies ZIP file and place its contents in the `[Liferay Home]/osgi` folder (create this folder if it doesn't exist). This is typically `[Install Location]/WebSphere/AppServer/profiles/your-profile/liferay/osgi`. ### Ensuring that @product@'s portlet.jar is loaded first In addition to placing the `portlet.jar` in the correct folder, you must configure the `config.ini` file so that it is loaded first. Navigate to `/IBM/WebSphere/AppServer/configuration/config.ini`. 1. Find the property `com.ibm.CORBA,com.ibm` 2. Insert the property `javax.portlet,javax.portlet.filter,javax.portlet.annotations` after `com.ibm.CORBA` and before `com.ibm`. 3. Save the file. Once you've installed these dependencies and configured the `config.ini` file, start the server profile you created for @product@. Once it starts, you're ready to configure your database. ## Database Configuration If you want WebSphere to manage the database connections, follow the instructions below. Note this is not necessary if you plan to use @product@'s standard database configuration; in that case, skip this section. See the [Using the Built-in Data Sources](/docs/7-2/deploy/-/knowledge_base/d/preparing-for-install#using-the-built-in-data-source) section for more article. You'll set your database information in @product@'s setup wizard after the install. | **Note:** Although @product@'s embedded database is fine for testing purposes, | you **should not** use it for production @product@ instances. ![Figure 3: WebSphere JDBC providers](../../images-dxp/websphere-jdbc-providers.png) 1. Start WebSphere. 2. Open the Administrative Console and log in. 3. Click *Resources → JDBC Providers*. 4. Select a scope and then click *New*. 5. Select your database type, provider type, and implementation type. If you select a predefined database, the wizard fills in the name and description fields for you. If the database you want to use isn't listed, select *User-defined* from the *Database type* field and then fill in the *Implementation Class Name*. For example, if you use MySQL, select *Database type* → *User-defined*, and then enter `com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource` in *Implementation Class Name*. Click *Next* when you are finished. 6. Clear any text in the class path settings. You already copied the necessary JARs to a location on the server's class path. Click *Next*. 7. Review your settings and click *Finish*. The final configuration should look like this: ![Figure 4: Completed JDBC provider configurations.](../../images-dxp/websphere-03.png) 8. Click your new provider configuration when it appears in the table, and then click *Data Sources* under *Additional Properties*. Click *New*. 9. Enter *liferaydatabasesource* in the *Data source name* field and `jdbc/LiferayPool` in the *JNDI name* field. Click *Next*. 10. Click *Next* in the remaining screens of the wizard to accept the default values. Then review your changes and click *Finish*. 11. Click the data source when it appears in the table and then click *Custom Properties*. Now click the *Show Filter Function* button. This is the second from last of the small icons under the *New* and *Delete* buttons. 12. Type *user* into the search terms and click *Go*. ![Figure 5: Modifying data source properties in WebSphere](../../images-dxp/websphere-database-properties.png) 13. Select the *user* property and give it the value of the user name to your database. Click *OK* and save to master configuration. 14. Do another filter search for the *url* property. Give this property a value that points to your database. For example, a MySQL URL would look like this: ```properties jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT&useFastDateParsing=false ``` | **Tip:** For more example URLs, see the `jdbc.default.url` values in | [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates). Click *OK* and save to master configuration. 15. Do another filter search for the *password* property. Enter the password for the user ID you added earlier as the value for this property. Click *OK* and save to master configuration. 16. Go back to the data source page by clicking it in the breadcrumb trail. Click the *Test Connection* button. It should connect successfully. Once you've set up your database, you can set up your mail session. ## Mail Configuration If you want WebSphere to manage your mail sessions, use the following procedure. If you want to use @product@'s built-in mail sessions, you can skip this section. See the [Configuring Mail](/docs/7-2/deploy/-/knowledge_base/d/configuring-mail) article on how to use @product@'s built-in mail sessions. ### Creating a WebSphere-Managed Mail Session (Optional) 1. Click *Resources → Mail → Mail Providers*. 2. Click the Built-In Mail Provider for your node and server. 3. Click *Mail Sessions* and then click the *New* button. 4. Give your mail session a name of *liferaymail* and a JNDI name of `mail/MailSession`. Fill in the correct information for your mail server in the sections *Outgoing Mail Properties* and *Incoming Mail Properties*. Click *OK* and then save to the master configuration. 5. Click your mail session when it appears in the table and select *Custom Properties* under the *Additional Properties* section. Set any other JavaMail properties required by your mail server, such as the protocol, ports, whether to use SSL, and so on. 6. Click *Security → Global Security* and de-select *Use Java 2 security to restrict application access to local resources* if it is selected. Click *Apply*. Note that you may also need to retrieve a SSL certificate from your mail server and add it to WebSphere's trust store. See WebSphere's documentation for instructions on this. ### Verifying WebSphere Mail Provider To validate that the mail session has been configured correctly, there are a number of ways to test this once the WAR has been deployed, the server has started, and the user has signed in as the system administrator. One quick way to validate is to create a new user with a valid email account. The newly created user should receive an email notification. The logs should display that the SMTP server has been pinged with the correct port number listed. ## Enable Cookies for HTTP Sessions WebSphere restricts cookies to HTTPS sessions by default. If you're using HTTP instead, this prevents users from signing in to @product@ and displays the following error in the console: ``` 20:07:14,021 WARN [WebContainer : 1][SecurityPortletContainerWrapper:341] User 0 is not allowed to access URL http://localhost:9081/web/guest/home and portlet com_liferay_login_web_portlet_LoginPortlet ``` This occurs because @product@ can't use the HTTPS cookie when you use HTTP. The end result is that new sessions are created on each page refresh. Follow these steps to resolve this issue in WebSphere: 1. Click *Application Servers* → *server1* → *Session Management* → Enable Cookies 2. De-select *Restrict cookies to HTTPS sessions* 3. Click *Apply* 4. Click *Save* ## Enable UTF-8 If you did not add the `-Dfile.encoding=UTF-8` property in the `server.xml`, you can do so in the Administrative Console. 1. Click *Application Servers* → *server1* → *Process definition*. 2. Click *Java Virtual Machine* under *Additional Properties*. 3. Enter `-Dfile.encoding=UTF-8` in the *Generic JVM arguments* field. 4. Click *Apply* and then *Save* to master configuration. Once the changes have been saved, @product@ can parse special characters if there is localized content. ## Deploy @product@ Now you're ready to deploy @product@! 1. In WebSphere's administrative console, click *Applications* → *New Application* → *New Enterprise Application*. 2. Browse to the @product@ `.war` file, select it, and click *Next*. 3. Leave *Fast Path* selected and click *Next*. Ensure that *Distribute Application* has been checked and click *Next* again. 4. Choose the WebSphere runtimes and/or clusters where you want @product@ deployed. Click *Next*. 5. Select the virtual host to deploy @product@ on and click *Next*. 6. Map @product@ to the root context (`/`) and click *Next*. 7. Select the *metadata-complete attribute* setting that you want to use and click *Next*. 8. Ensure that you have made all the correct choices and click *Finish*. When @product@ has installed, click *Save to Master Configuration*. ![Figure 6: Review your deployment options before deploying.](../../images-dxp/websphere-deploy-dxp.png) You've now installed @product@! ## Setting the JDK Version for Compiling JSPs @product@ requires that its JSPs are compiled to the Java 8 bytecode format. To ensure that WebSphere does this, shut down WebSphere after you've deployed the @product@ `.war` file. Navigate to the `WEB_INF` folder and add the following setting to the `ibm-web-ext.xml` or in most cases the `ibm-web-ext.xmi` file: ```xml ``` The exact path to the `ibm-web-ext.xmi` file depends on your WebSphere installation location and @product@ version, but here's an example: ```bash /opt/IBM/WebSphere/AppServer/profiles/AppSrv01/config/cells/localhostNode01Cell/applications/liferayXX.ear/deployments/liferayXX/liferayXX.war/WEB-INF/ibm-web-ext.xmi ``` Note that the @product@ `.war` comes pre-packaged with the `ibm-web-ext.xmi` file; this format is functionally the same as `.xml` and WebSphere recognizes both formats. For more general information on how WebSphere compiles JSPs see IBM's official documentation for [WebSphere Application Server 9.0.0.x](https://www.ibm.com/support/knowledgecenter/en/SSEQTP_9.0.0/com.ibm.websphere.base.doc/ae/rweb_jspengine.html). ## Start @product@ 1. If you plan to use @product@'s [setup wizard](/docs/7-2/deploy/-/knowledge_base/d/installing-product#using-the-setup-wizard), skip to the next step. If you wish to use WebSphere's data source and mail session, create a file called `portal-ext.properties` in your Liferay Home folder. Place the following configuration in the file: ```properties jdbc.default.jndi.name=jdbc/LiferayPool mail.session.jndi.name=mail/MailSession setup.wizard.enabled=false ``` 2. In the WebSphere administrative console, navigate to *Enterprise Applications*, select the @product@ application, and click *Start*. While @product@ is starting, WebSphere displays a spinning graphic. 3. In @product@'s setup wizard, select and configure your database type. Click *Finish* when you're done. @product@ then creates the tables it needs in the database. Congratulations! You've installed @product@ on WebSphere! | After deploying @product@, you may see excessive warnings and log messages, such | as the ones below, involving `PhaseOptimizer`. These are benign and can be | ignored. Make sure to adjust your app server's logging level or log filters to | avoid excessive benign log messages. | | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass gatherExternProperties | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | WARNING: Skipping pass checkControlFlow | May 02, 2018 9:12:27 PM com.google.javascript.jscomp.PhaseOptimizer$NamedPass process | INFO: pass supports: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, modules, exponent operator (**), async function, trailing comma in param list] | current AST contains: [ES3 keywords as identifiers, getters, reserved words as properties, setters, string continuation, trailing comma, array pattern rest, arrow function, binary literal, block-scoped function declaration, class, computed property, const declaration, default parameter, destructuring, extended object literal, for-of loop, generator, let declaration, member declaration, new.target, octal literal, RegExp flag 'u', RegExp flag 'y', rest parameter, spread expression, super, template literal, exponent operator (**), async function, trailing comma in param list, object literals with spread, object pattern rest] ================================================ FILE: en/deployment/articles-dxp/01-deploying-liferay/11-activating-liferay-dxp.markdown ================================================ --- header-id: activating-liferay-dxp --- # Activating Liferay DXP [TOC levels=1-4] There are two ways to activate your Liferay DXP instance: - With an XML activation key that you request and receive from Liferay Support. - Online activation through Liferay Connected Services (LCS). Liferay DXP 7.0 introduced LCS as a way to activate Liferay DXP instances. LCS can also install fix packs, monitor each instance's performance, and help administrators automatically manage Liferay DXP subscriptions. See the [LCS documentation](/docs/7-1/deploy/-/knowledge_base/d/managing-liferay-dxp-with-liferay-connected-services) for instructions on activating your instances with LCS. | **Note:** You must use LCS for activation of Elastic subscriptions. Otherwise, | you don't have to use LCS for activation. You can instead request an XML | activation key from Liferay Support. ================================================ FILE: en/deployment/articles-dxp/01-deploying-liferay/13-trial-plugin-installation.markdown ================================================ --- header-id: trial-plugin-installation --- # Trial Plugin Installation [TOC levels=1-4] Although setting names may differ, these concepts apply to most application servers. To keep things simple, Tomcat is used as the example. For other application servers, consult the provider's documentation for specific settings. Here are the tuning topics: - Database Connection Pool - Deactivating Development Settings in the JSP Engine - Thread Pool ## Database Connection Pool The database connection pool should be roughly 30-40% of the thread pool size. It provides a connection whenever @product@ needs to retrieve data from the database (e.g., user login). If the pool size is too small, requests queue in the server waiting for database connections. If the size is too large, however, idle database connections waste resources. As with thread pools, monitor these settings and adjust them based on your performance tests. In Tomcat, the connection pools are configured in the Resource elements in `$CATALINA_HOME/conf/Catalina/localhost/ROOT.xml`. Liferay Engineering tests with this configuration: This configuration starts with 10 threads and increments by 5 as needed to a maximum of 75 connections in the pool. There are a variety of database connection pool providers, including DBCP, C3P0, HikariCP, and Tomcat. You may also configure the Liferay JDBC settings in your [`portal-ext.properties`](https://docs.liferay.com/ce/portal/7.2-latest/propertiesdoc/portal.properties.html) file. For example JDBC connection values, please see [Database Templates](/docs/7-2/deploy/-/knowledge_base/d/database-templates) ## Deactivating Development Settings in the JSP Engine Many application servers' JSP Engines are in development mode by default. Deactivate these settings prior to entering production: **Development mode:** This makes the JSP container poll the file system for changes to JSP files. Since you won't change JSPs on the fly like this in production, turn off this mode. **Mapped File:** Generates static content with one print statement versus one statement per line of JSP text. To disable these in Tomcat, for example, update the `$CATALINA_HOME/conf/web.xml` file's JSP servlet configuration to this: jsp org.apache.jasper.servlet.JspServlet development false mappedFile false 3 Development mode and mapped files are disabled. ## Thread Pool Each request to the application server consumes a worker thread for the duration of the request. When no threads are available to process requests, the request is queued to wait for the next available worker thread. In a finely tuned system, the number of threads in the thread pool are balanced with the total number of concurrent requests. There should not be a significant amount of threads left idle to service requests. Use an initial thread pool setting of 50 threads and then monitor it within your application server's monitoring consoles. You may wish to use a higher number (e.g., 250) if your average page times are in the 2-3 second range. Too few threads in the thread pool might queue excessive requests; too many threads can cause excessive context switching. In Tomcat, the thread pools are configured in the `$CATALINA_HOME/conf/server.xml` file's `Connector` element. The [Apache Tomcat documentation](https://tomcat.apache.org/tomcat-9.0-doc/config/http.html) provides more details. Liferay Engineering tests with this configuration: Additional tuning parameters around Connectors are available, including the connector types, the connection timeouts, and TCP queue. Consult your application server's documentation for further details. ================================================ FILE: en/deployment/articles-dxp/02-configuring-liferay/08-dxp-configuration-and-tuning/02-jvm-tuning.markdown ================================================ --- header-id: java-virtual-machine-tuning --- # Java Virtual Machine Tuning [TOC levels=1-4] Java Virtual Machine (JVM) tuning primarily focuses on adjusting the garbage collector and the Java memory heap. We used Oracle's 1.8 JVM for the reference architecture. You may choose other supported JVM versions and implementations. Please consult the [Liferay DXP Compatibility Matrix](https://web.liferay.com/group/customer/dxp/support/compatibility-matrix) for additional compatible JVMs. Here are the JVM tuning topics: - [Garbage Collector](#garbage-collector) - [Code Cache](#code-cache) - [Java Heap](#java-heap) - [JVM Advanced Options](#jvm-advanced-options) Garbage collection is first. ## Garbage Collector Choosing the appropriate garbage collector (GC) helps improve the responsiveness of your @product@ deployment. Use the concurrent low pause collectors: -XX:+UseParNewGC -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSCompactWhenClearAllSoftRefs -XX:CMSInitiatingOccupancyFraction=85 -XX:+CMSScavengeBeforeRemark You may choose from other available GC algorithms, including parallel throughput collectors and G1 collectors. Start tuning using parallel collectors in the new generation and concurrent mark sweep (CMS) in the old generation. **Note:** the `ParallelGCThreads` value (e.g., `ParallelGCThreads=16`) varies based on the type of CPUs available. Set the value according to CPU specification. On Linux machines, report the number of available CPUs by running `cat /proc/cpuinfo`. **Note:** There are additional "new" algorithms like G1, but Liferay Engineering's tests for G1 indicated that it does not improve performance. Since your application performance may vary, you should add G1 to your testing and tuning plans. ## Code Cache Java's just-in-time (JIT) compiler generates native code to improve performance. The default size is `48m`. This may not be sufficient for larger applications. Too small a code cache reduces performance, as the JIT isn't able to optimize high frequency methods. For @product@, start with `64m` for the initial code cache size. -XX:InitialCodeCacheSize=64m -XX:ReservedCodeCacheSize=96m Examine the efficacy of the parameter changes by adding the following logging parameters: -XX:+PrintCodeCache -XX:+PrintCodeCacheOnCompilation ## Java Heap When most people think about tuning the Java memory heap, they think of setting the maximum and minimum memory of the heap. Unfortunately, most deployments require far more sophisticated heap tuning to obtain optimal performance, including tuning the young generation size, tenuring durations, survivor spaces, and many other JVM internals. For most systems, it's best to start with at least the following memory settings: -server -XX:NewSize=700m -XX:MaxNewSize=700m -Xms2048m -Xmx2048m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=6 -XX:TargetSurvivorRatio=9 -XX:MaxTenuringThreshold=15 On systems that require large heap sizes (e.g., above 4GB), it may be beneficial to use large page sizes. You can activate large page sizes like this: -XX:+UseLargePages -XX:LargePageSizeInBytes=256m You may choose to specify different page sizes based on your application profile. **Note:** To use large pages in the JVM, you must configure your underlying operating system to activate them. In Linux, run `cat /proc/meminfo` and look at "huge page" items. | **Caution:** Avoid allocating more than 32GB to your JVM heap. Your heap size | should be commensurate with the speed and quantity of available CPU resources. ## JVM Advanced Options The following advanced JVM options were also applied in the Liferay benchmark environment: -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseCompressedOops -XX:+DisableExplicitGC -XX:-UseBiasedLocking -XX:+BindGCTaskThreadsToCPUs -XX:UseFastAccessorMethods Please consult your JVM documentation for additional details on advanced JVM options. Combining the above recommendations together, makes this configuration: -server -XX:NewSize=1024m -XX:MaxNewSize=1024m -Xms4096m -Xmx4096m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:SurvivorRatio=12 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15 -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseParNewGC -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSCompactWhenClearAllSoftRefs -XX:CMSInitiatingOccupancyFraction=85 -XX:+CMSScavengeBeforeRemark -XX:+UseLargePages -XX:LargePageSizeInBytes=256m -XX:+UseCompressedOops -XX:+DisableExplicitGC -XX:-UseBiasedLocking -XX:+BindGCTaskThreadsToCPUs -XX:+UseFastAccessorMethods -XX:InitialCodeCacheSize=32m -XX:ReservedCodeCacheSize=96m | **Caution:** The above JVM settings should formulate a starting point for your | performance tuning. Every system's final parameters vary due to many factors, | including number of current users and transaction speed. Monitor the garbage collector statistics to ensure your environment has sufficient allocations for metaspace and also for the survivor spaces. Using the configuration above in the wrong environment could result in dangerous runtime scenarios like out of memory failures. Improperly tuned survivor spaces also lead to wasted heap space. ================================================ FILE: en/deployment/articles-dxp/03-installing-a-search-engine/03-enterprise-search/01-les-intro.markdown ================================================ --- header-id: installing-liferay-enterprise-search --- # Installing Liferay Enterprise Search [TOC levels=1-4] A Liferay Enterprise Search (LES) subscription gets you additional features beyond what's available out of the box with your @product@ subscription. It includes - Liferay Enterprise Search Security\* - Liferay Enterprise Search Monitoring - Liferay Enterprise Search Learning to Rank \* A LES subscription is not necessary if using Elasticsearch 7 via the _Liferay Connector to Elasticsearch 7_as X-Pack's security features are bundled. See the [LES compatibility matrix](https://help.liferay.com/hc/en-us/articles/360016511651#Liferay-Enterprise-Search) for more information. X-Pack is an [Elasticsearch extension](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-xpack.html) for securing and monitoring Elasticsearch clusters. If you use Elasticsearch, you should secure it with X-Pack. The security features of X-Pack include authenticating access to the Elasticsearch cluster's data and encrypting Elasticsearch's internal and external communications. These are necessary security features for most production systems. A LES subscription gets you access to two connectors if you're using Elasticsearch 6: monitoring and security. Elasticsearch 7 bundles these security features, and Liferay has followed suit. Therefore, security is bundled with the Liferay Connector to Elasticsearch 7, and no LES subscription is necessary. Because of this, the documentation for [installing Liferay Enterprise Search Security](/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-security) on @product@ has been moved from the LES documentation section (this section) to the [Elasticsearch](/docs/7-2/deploy/-/knowledge_base/d/elasticsearch) installation and configuration guide. Contact [Liferay's Sales department for more information](https://www.liferay.com/contact-us#contact-sales). Here's an overview of using the LES applications with @product@: 1. Get an [Enterprise Search subscription](https://help.liferay.com/hc/en-us/articles/360014400932). 2. You'll receive a license for X-Pack monitoring. Install it on your Elasticsearch servers. **Note:** If using Elasticsearch 6, you'll also need a LES subscription for X-Pack security. 3. Download and install the Liferay Enterprise Search apps you purchased. Find them in the [Help Center Downloads page](https://customer.liferay.com/en/downloads), choosing Enterprise Search from the Product drop-down menu. 4. Configure the connectors with the proper credentials, encryption information, and settings. 5. Restart Elasticsearch. These steps require a full cluster restart. More detailed installation instructions are available in the article for each LES feature. Elastic's documentation explains additional configuration options, features, and the architecture of [X-Pack](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/configuring-security.html). | **Note:** Out of the box, X-Pack comes with a [30-day | trial](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/start-trial.html). | This can be useful if there's a delay between your subscription and receipt of | your production X-Pack license. Now configure security, monitoring, and/or Learning to Rank, depending on your needs. ================================================ FILE: en/deployment/articles-dxp/03-installing-a-search-engine/03-enterprise-search/02-monitoring/01-monitoring-intro.markdown ================================================ --- header-id: installing-liferay-enterprise-search-monitoring --- # Installing Liferay Enterprise Search Monitoring [TOC levels=1-4] First configure security if you're using X-Pack's security features. Then come back here for instructions on installing and configuring Kibana (the monitoring server) with X-Pack so that Elasticsearch (secured with X-Pack), Kibana (secured with X-Pack), and @product@ can communicate effortlessly and securely. A Liferay Enterprise Search (LES) subscription is necessary for this integration. Contact [Liferay's Sales department for more information](https://www.liferay.com/contact-us#contact-sales). | **The same monitoring connector is used for Elasticsearch 6 and 7**: When first | created, the Liferay Enterprise Search Monitoring app was | intended to be used only with Elasticsearch 6. However, testing revealed that | the same connector works with Elasticsearch 7, so a new connector is not | necessary if you're using Elasticsearch 7. To install X-Pack monitoring, 1. Tell Elasticsearch to enable data collection. 2. Download and install Kibana. 3. Configure Kibana with the proper security settings. 4. Install the Liferay Enterprise Search Monitoring app. 5. Configure the connector to communicate with Elasticsearch. This document assumes you're enabling security (with authentication and encrypted communication) *and* monitoring for Elasticsearch 7, but differences in the process for Elasticsearch 6 are noted where necessary. ## Enable Encrypting Communication (TLS/SSL) in Elasticsearch and in @product@ Start by following the steps in this [article](/docs/7-2/deployment/-/knowledge_base/u/installing-liferay-enterprise-search-security) to enable TLS/SSL in your Elasticsearch and @product@ installation. Then continue by enabling data collection in Elasticsearch. ## Enable Data Collection Monitoring is enabled on Elasticsearch by default, but data collection isn't. Enable data collection by adding this line to `elasticsearch.yml`. ```yaml xpack.monitoring.collection.enabled: true ``` Now install Kibana. ## Install Kibana Make sure to install the correct version of Kibana. Check the [Liferay Enterprise Search compatibility matrix](https://help.liferay.com/hc/en-us/articles/360016511651#Liferay-Enterprise-Search) for details. 1. [Download Kibana](https://www.elastic.co/downloads/kibana) and extract it. The root folder is referred to as *Kibana Home*. 2. Tell Kibana where to send monitoring data by setting Elasticsearch's URL in `kibana.yml`: ```yaml elasticsearch.hosts: [ "https://localhost:9200" ] ``` On 6.5 and below, use ```yaml elasticsearch.url: "https://localhost:9200" ``` If TLS/SSL is not enabled on Elasticsearch, this is an `http` URL, otherwise use `https`. 3. If not using X-Pack security, start Kibana by opening a command prompt to Kibana Home and entering this command: ```bash ./bin/kibana ``` If you're using X-Pack's security features on the Elasticsearch server, there's additional configuration required before starting Kibana. ### Configure Kibana with Authentication If X-Pack requires authentication to access the Elasticsearch cluster, follow these steps or refer to [Elastic's documentation](https://www.elastic.co/guide/en/kibana/7.x/monitoring-xpack-kibana.html). 1. Set the password for the built-in `kibana` user in `[Kibana Home]/config/kibana.yml`: ```yaml elasticsearch.username: "kibana" elasticsearch.password: "liferay" ``` Use your `kibana` user password from your X-Pack setup. Once Kibana is installed, you can change the built-in user passwords from the *Management* user interface. 2. If you're not encrypting communication with the Elasticsearch cluster, start Kibana from Kibana home. ```bash ./bin/kibana ``` 3. Go to `http://localhost:5601` and make sure you can sign in as a [user](https://www.elastic.co/guide/en/elasticsearch/reference/current/realms.html) who has the `kibana_user` [role](https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-roles.html) or a superuser (like the `elastic` user). ### Configuring Kibana with Encryption (TLS/SSL) Follow these steps to configure Kibana if X-Pack encrypts communication with the Elasticsearch cluster. Consult [Elastic's guide](https://www.elastic.co/guide/en/kibana/7.x/using-kibana-with-security.html#using-kibana-with-security) for more information. 1. Copy the `[Elasticsearch Home]/config/certs` folder into the `[Kibana Home]/config/` folder. This example reuses the certificate files [created for Elasticsearch itself](/docs/7-2/deployment/-/knowledge_base/u/installing-liferay-enterprise-search-security). If you wish to generate a separate certificate for your Kibana instance, make sure it is signed by the same CA as the Elasticsearch node certificates. 2. Add these settings to `kibana.yml`: ```yaml xpack.security.encryptionKey: "xsomethingxatxleastx32xcharactersx" xpack.security.sessionTimeout: 600000 elasticsearch.hosts: [ "https://localhost:9200" ] elasticsearch.ssl.verificationMode: certificate elasticsearch.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] elasticsearch.ssl.certificate: config/certs/localhost.crt elasticsearch.ssl.key: config/certs/localhost.key server.ssl.enabled: true server.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] server.ssl.certificate: config/certs/localhost.crt server.ssl.key: config/certs/localhost.key ``` Elasticsearch/Kibana 6.5 and below use a different property for specifying the host URL. Replace the `elasticsearch.hosts` property with ```yaml elasticsearch.url: "https://localhost:9200" ``` For more information about monitoring and security best practices in a clustered environment, refer to [Elastic's documentation](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/setup-xpack.html). After this step you can access Kibana at `https://localhost:5601` and sign in with a Kibana user. The last step is to connect Kibana to @product@. ## Configuring the Liferay Enterprise Search Monitoring app If you have a LES subscription, download the Liferay Enterprise Search Monitoring app . Install the LPKG file by copying it into the `Liferay Home/deploy` folder. 1. Once the connector is installed and Kibana and Elasticsearch are securely configured, create a [configuration file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) named com.liferay.portal.search.elasticsearch6.xpack.monitoring.web.internal.configuration.XPackMonitoringConfiguration.config 2. Place these settings in the `.config` file: kibanaPassword="liferay" kibanaUserName="elastic" kibanaURL="https://localhost:5601" The values depend on your Kibana configuration. For example, use a URL such as `kibanaURL="http://localhost:5601"` if you are not using X-Pack Security TLS/SSL features. Alternatively, configure the monitoring adapter from [System Settings](/docs/7-2/user/-/knowledge_base/u/system-settings). Navigate to *Control Panel* → *Configuration* → *System Settings* and find the X-Pack Monitoring entry in the Search category. All the configuration options for the monitoring connector appear there. 3. Deploy this configuration file to `Liferay Home/osgi/configs`, and your running instance applies the settings. There's no need to restart the server. 4. There are two more settings to add to Kibana itself. The first forbids Kibana from rewriting requests prefixed with `server.basePath`. The second sets Kibana's base path for the Monitoring portlet to act as a proxy for Kibana's monitoring UI. Add this to `kibana.yml`: ```yaml server.rewriteBasePath: false server.basePath: "/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy" ``` Note that once you set the `server.basePath`, you cannot access the Kibana UI through Kibana's URL (e.g., `https://localhost:5601`). All access to the Kibana UI is through the Monitoring portlet, which is only accessible to signed in @product@ users. Navigate directly to the portlet using this URL: [http://localhost:8080/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy/app/monitoring](http://localhost:8080/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy/app/monitoring) 5. Because you're using the Monitoring portlet in @product@ as a proxy to Kibana's UI, if you are using X-Pack Security with TLS/SSL, you must configure the application server's startup JVM parameters to recognize a valid *truststore* and *password*. First, navigate to Elasticsearch Home and generate a PKSC#12 certificate from the CA you created when setting up X-Pack security: ```bash ./bin/elasticsearch-certutil cert --ca-cert /path/to/ca.crt --ca-key /path/to/ca.key --ip 127.0.0.1 --dns localhost --name localhost --out /path/to/Elasticsearch_Home/config/localhost.p12 ``` Next use the `keytool` command to generate a truststore: ```bash keytool -importkeystore -deststorepass liferay -destkeystore /path/to/truststore.jks -srckeystore /path/to/Elasticsearch_Home/config/localhost.p12 -srcstoretype PKCS12 -srcstorepass liferay ``` Add the trustore path and password to your application server's startup JVM parameters. Here are example truststore and path parameters for appending to a Tomcat server's `CATALINA_OPTS`: ```properties -Djavax.net.ssl.trustStore=/path/to/truststore.jks -Djavax.net.ssl.trustStorePassword=liferay ``` Restart @product@ and Kibana. ## Monitoring in @product@ Once Kibana and X-Pack are successfully installed and configured and all the servers are running, add the X-Pack Monitoring portlet to a page: 1. Open the *Add* menu on a page and choose *Widgets* 2. Search for *monitoring* and drag the *X-Pack Monitoring* widget from the Search category onto the page. See the Elastic documentation for information on [monitoring Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/es-monitoring.html). ## Example Kibana Configuration Here are the `kibana.yml` properties demonstrated in this article, for convenient copy/pasting: ```yaml # X-Pack Security enabled (Basic Auth) elasticsearch.username: "kibana" elasticsearch.password: "liferay" # When TLS/SSL is enabled in X-Pack Security xpack.security.encryptionKey: "xsomethingxatxleastx32xcharactersx" xpack.security.sessionTimeout: 600000 # If on Elasticsearch 6.5 or below, replace the next property with: # elasticsearch.url: "http://localhost:9200" elasticsearch.hosts: [ "https://localhost:9200" ] # Enable TLS/SSL for out-bound traffic: from Kibana to Elasticsearch elasticsearch.ssl.verificationMode: certificate elasticsearch.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] elasticsearch.ssl.certificate: config/certs/localhost.crt elasticsearch.ssl.key: config/certs/localhost.key # Enable TLS/SSL for in-bound traffic: from browser or # DXP (X-Pack Monitoring widget, proxy) to Kibana server.ssl.enabled: true server.ssl.certificateAuthorities: [ "config/certs/ca.crt" ] server.ssl.certificate: config/certs/localhost.crt server.ssl.key: config/certs/localhost.key # To use Kibana inside the X-Pack Monitoring widget server.rewriteBasePath: false server.basePath: "/o/portal-search-elasticsearch-xpack-monitoring/xpack-monitoring-proxy" ``` ================================================ FILE: en/deployment/articles-dxp/03-installing-a-search-engine/03-enterprise-search/03-ltr/01-learning-to-rank-intro.markdown ================================================ --- header-id: liferay-enterprise-search-learning-to-rank --- # Liferay Enterprise Search: Learning to Rank [TOC levels=1-4] Search engines like Elasticsearch have well-tuned relevance algorithms, good for general search purposes. Sometimes, this "generally good" relevance scoring just isn't good enough. You can attain more perfect search results by employing machine learning. Learning to Rank harnesses machine learning to improve search result rankings. It combines the expertise of data scientists with machine learning to produce a smarter scoring function that's applied to search queries. @product-ver@, Service Pack 1/Fix Pack 2 and later, supports Learning to Rank through its support of Elasticsearch versions 6.x and 7.4.x. It requires a [LES](https://help.liferay.com/hc/en-us/articles/360014400932) subscription. It's important to understand that the [Elasticsearch Learning to Rank plugin](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/index.html) is not produced by Elastic, and there is not a pre-built plugin for all of @product@'s supported Elasticsearch versions. ## Disabling Learning to Rank on a Search Page Learning to Rank does not work with the [Sort widget](/docs/7-2/user/-/knowledge_base/u/sorting-search-results-with-the-sort-widget). If you must use Learning to Rank in your @product@ instance, but want to disable it on a particular Search page (perhaps to use the Sort widget), you can: 1. Add a [Low Level Search Options](/docs/7-2/user/-/knowledge_base/u/low-level-search-options-searching-additional-or-alternate-indexes) widget to the Search page. 2. Open the widget's Configuration screen by clicking _Configure additional low level search options in this page._ 3. In the _Contributors to Exclude_ field, enter `com.liferay.portal.search.learning.to.rank` Now the Learning to Rank re-scoring process is skipped for queries entered into the page's Search Bar, and results are sortable in the Sort widget and returned using the default relevance algorithm. ## Prerequisites There are some prerequisites for using Learning to Rank to re-score Liferay queries sent to Elasticsearch: - If using Elasticsearch 7, @product-ver@ Service Pack 1/Fix Pack 2 or later is required, with the appropriate Elasticsearch Connector version installed. - If using Elasticsearch 6, @product-ver@ Fix Pack 3 or later is required, with the appropriate Elasticsearch Connector version installed. - A [Liferay Enterprise Search](https://help.liferay.com/hc/en-us/articles/360014400932) (LES) subscription is required for Learning to Rank. Once you have a subscription, [download the Liferay Enterprise Search Learning to Rank](https://customer.liferay.com/downloads) LPKG file and [install it to your @product@ server.](/docs/7-2/user/-/knowledge_base/u/installing-apps-manually#installing-apps-manually) - A remote Elasticsearch server, with your data indexed into it. - The corresponding version of the [Elasticsearch Learning to Rank](https://github.com/o19s/elasticsearch-learning-to-rank) plugin installed into Elasticsearch. - A [trained model](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/training-models.html) uploaded into the Learning to Rank plugin. To understand more about the compatibility requirements for LES, see its [compatibility matrix](https://help.liferay.com/hc/en-us/articles/360016511651#Liferay-Enterprise-Search). How does Learning to Rank work? ### Technical Overview In a normal search, the User sends a query to the search engine via Liferay DXP's [Search Bar](/docs/7-2/user/-/knowledge_base/u/searching-for-assets#search-bar). The order of returned results is dictated by the search engine's [relevance scoring algorithm](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/index-modules-similarity.html#bm25). Here's where Learning to Rank intervenes and makes that process different: 1. User enters a query into the search bar. 2. Liferay sends the query to Elasticsearch, and retrieves the first 1000 results as usual, using the search engine's relevance algorithm. 3. The top 1000 results are not returned as search hits, but are used by Elasticsearch for [re-scoring](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/search-request-body.html#request-body-search-rescore) via the [rescore functionality](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/searching-with-your-model.html#rescore-top-n-with-sltr). 4. The results are re-scored by the [SLTR query](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/searching-with-your-model.html), which includes the keywords and the trained model to use for re-scoring. 5. Once the trained model re-ranks the results, they're returned in Liferay's [Search Results](/docs/7-2/user/-/knowledge_base/u/search-results) in their new order. Though it's just a sub-bullet point in the ordered list above, much of the work in this paradigm is in creating and honing the trained model. That's beyond the scope of Liferay's role, but we'll help you get all the pieces in place to orchestrate the magic of machine learning on your Liferay queries. Here's a brief overview of what constitutes _model training_. ### Model Training A useful trained model is produced when a good judgment list and a good feature set are fed to a Learning to Rank algorithm (this is the machine learning part of the puzzle). Therefore, it's incumbent on you to assemble - The Learning to Rank algorithm you wish to use for creating a training model. This demonstration uses [RankLib](https://sourceforge.net/p/lemur/wiki/RankLib/). - A _judgment list_, containing a graded list of search results. The algorithm is designed to produce a model that honors the ordering of the judgment list. - A feature set, containing all the _features_ you're handing to the Learning to Rank algorithm, which it uses in conjunction with the judgment list to produce a reliable model. An example feature set for @product@ data is shown in the next article. [Judgment lists](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html#judgments-expression-of-the-ideal-ordering) are lists of graded search results. [Features](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html#features-the-raw-material-of-relevance) are the variables that the algorithm uses to create a function that can score results in a smarter way. If you don't give enough, or the correct, relevant features, your model will not be "smart" enough to provide improved results. In the next article you'll see the steps required to configure Learning to Rank with @product@. ================================================ FILE: en/deployment/articles-dxp/03-installing-a-search-engine/03-enterprise-search/03-ltr/02-configuring-learning-to-rank.markdown ================================================ --- header-id: configuring-learning-to-rank --- # Configuring Learning to Rank [TOC levels=1-4] Before beginning, you must have a remote [Elasticsearch 6 or 7](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-elasticsearch-7) cluster communicating with @product-ver@. See the [compatibility matrix for more information](https://help.liferay.com/hc/en-us/articles/360016511651#Liferay-Enterprise-Search) | **Helpful hint:** Use | [Suggestions](/docs/7-2/user/-/knowledge_base/u/searching-for-assets#search-suggestions) | to discover the most common queries (this can be one way to decide which queries | to create Learning to Rank models for). ## Step 1: Install the Learning to Rank Plugin on Elasticsearch See [the Elasticsearch Learning to Rank plugin documentation](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/#installing) to learn about installing the Learning to Rank plugin. You'll be running a command like this one, depending on the plugin version you're installing: ```sh ./bin/elasticsearch-plugin install http://es-learn-to-rank.labs.o19s.com/ltr-1.1.0-es6.5.4.zip ``` If using X-Pack security in your Elasticsearch cluster, there [may be additional steps.](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/x-pack.html) ## Step 2: Training and Uploading a Model Detailed instructions on training models is outside the scope of this guide. This requires the intervention of data scientists, who can recommend appropriate tools and models. Use what works for you. In doing so, you'll almost certainly be compiling [Judgment lists](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/core-concepts.html#judgments-expression-of-the-ideal-ordering) and [feature sets](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/building-features.html) that can be used by the training tool you select to generate a model that produces good search results. This can be a long journey, but once you get to the end of it, you'll want to upload the model to the Learning to Rank plugin. ### Upload the Model to the Learning to Rank Plugin You'll upload the model using a `POST` request, but first you need to make sure you have a `_ltr` index and a feature set uploaded to the Learning to Rank plugin. Use Kibana (or even better, the [Monitoring widget](/docs/7-2/deploy/-/knowledge_base/d/installing-liferay-enterprise-search-monitoring)), to make these tasks easier. 1. If you don't already have an `_ltr` index, create one: ```http PUT _ltr ``` 2. Add a feature set to the `_ltr` index. In this example the set is called `liferay`: ```json POST _ltr/_featureset/liferay { "featureset": { "name": "liferay", "features": [ { "name": "title", "params": [ "keywords" ], "template": { "match": { "title_en_US": "{{keywords}}" } } }, { "name": "content", "params": [ "keywords" ], "template": { "match": { "content_en_US": "{{keywords}}" } } }, { "name": "asset tags", "params": [ "keywords" ], "template": { "match": { "assetTagNames": "{{keywords}}" } } } ] } } ``` Take note of the syntax used here, since it's required. 3. Add the trained model to the feature set: ```json POST _ltr/_featureset/liferay/_createmodel { "model": { "name": "linearregression", "model": { "type": "model/ranklib", "definition": """ ## Linear Regression ## Lambda = 1.0E-10 0:-0.717621803830712 1:-0.717621803830712 2:-2.235841905601106 3:19.546816765721594 """ } } } ``` This is a very high level set of instructions, because there's not much to do in @product@. To learn in more detail about what's required, see the [Learning to Rank plugin's documentation](https://elasticsearch-learning-to-rank.readthedocs.io/en/latest/index.html). Keep reworking those judgment lists! ## Step 3: Enable Learning to Rank Enable Learning to Rank from Control Panel → Configuration → System Settings → Search → Learning to Rank. There's a simple on/off configuration and a text field where you must enter the name of the trained model to apply to search queries. The model in the previous step was named `linearregression`, so that's what you'd enter. ![Figure 1: Enable Learning to Rank in @product@ from the System Settings entry.](../../../../images-dxp/search-learning-to-rank.png) That's all the configuration required to get the Elasticsearch Learning to Rank plugin ingesting a trained model, a feature set, and search queries from @product@. ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/01-intro.markdown ================================================ --- header-id: authenticating-using-saml --- # Authenticating Using SAML [TOC levels=1-4] The SAML (Security Assertion Markup Language) adapter provides Single Sign On (SSO) and Single Log Off (SLO) in your deployment. Each @product@ instance serves as either the Service Provider (SP) or the Identity Provider (IdP). An identity provider is a trusted provider that provides single sign-on for users to access other websites. A service provider is a website that hosts applications and grants access only to identified users with proper credentials. | **Note:** A single @product@ instance is *either* the SP or the IdP in your SSO | setup; it can't be both. You can, however, use separate instances for both | purposes (for example, one instance is the SP and another is the IdP). Below is background on how SAML works. To jump right to its configuration, see the articles on [Setting Up SAML as an Identity Provider](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider) or [Setting Up SAML as a Service Provider](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-service-provider) for instructions on using the [SAML adapter](https://web.liferay.com/marketplace/-/mp/application/15188711). Use the instructions to make the conceptual magic from this article come to life! ## What's new in Liferay Connector to SAML 2.0 The `5.0.0` version of the application for @product@ brings some long-awaited improvements: * @product@ acting as a Service Provider (SP) can now connect to multiple Identity Providers (IdP). * Developers have an extension point for customizing which Identity Providers to users can use to sign in. * Support for other Signature Algorithms (like `SHA-256`) * Signature method algorithm URL's can now be blacklisted from the metadata (for example, disabling `SHA-1`: `http://www.w3.org/2000/09/xmldsig#rsa-sha1`) | **Note:** If you're migrating from a Liferay SAML adapter prior to version | 3.1.0, your portal properties are automatically migrated to System Settings configurations. Please see the | [Configuring SAML](/docs/7-2/deploy/-/knowledge_base/d/configuring-saml) | article for details on settings. ## Important SAML Paths For reference, here are a few important SAML paths. This URL is the default location of the metadata XML file: [host]:[port]/c/portal/saml/metadata Note that when configuring SAML, no importing of SAML certificates is required. @product@ reads certificates from the SAML metadata XML file. If you want a third-party application like Salesforce to read a Liferay SAML certificate, you can export the @product@ certificate from the keystore. The default keystore file is ```bash [Liferay Home]/data/keystore.jks ``` You can change this path in System Settings → SSO → SAML Configuration → Key Store Path. ## Single Sign On Both the IdP and the SP can initiate the Single Sign On process, and the SSO flow is different depending on each one. Regardless of how it's initiated, SSO is configured for HTTPS between the SP and IdP, so all transport-level communication is encrypted. SAML requests are signed using certificates configured in @product@, using the SAML Web Browser SSO profile as defined in the [SAML 2.0 specification](http://saml.xml.org/saml-specifications). In all cases, responses are sent using HTTP-POST or HTTP-Redirect. HTTP-POST is preferred because it reduces the risk that the URL is too long for a browser to handle. Consider IdP initiated SSO first. ### Identity Provider Initiated SSO Sometimes a user enters the SSO cycle by sending a request directly from the browser to the IdP. ![Figure 1: Identity Provider Initiated SSO](../../../images-dxp/saml-idp-initiated-sso.png) #### The SSO Request to the IdP If @product@ is the IdP, the IdP initiated SSO URL - Must specify the path as `/c/portal/saml/sso`. - Must include the `entityId` parameter which is the identifier to a previously configured Service Provider Connection (SPC). - May include a `RelayState` parameter which contains a URL encoded value where the user is redirected upon successful authentication. This URL should point to a location on the desired SPC (according to the [SAML 2.0 standards section 3.4.3](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf), this value *must not* exceed 80 bytes in length). It is useful to specify a landing page after SSO has been executed. For non-@product@ IdPs (Siteminder, ADFS, etc.), consult the vendor's documentation on constructing IdP initiated SSO URLs. If the IdP determines that the user isn't authenticated, it prompts the user with the appropriate login screen. #### The SSO Response from the IdP Upon successful authentication, the IdP constructs a SAML Response. It includes attribute statements configured in the designated Service Provider Connection (SPC; see the [next article](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider) on setting up the SPC in @product@'s SAML adapter). The IdP sends the response to the Assertion Consumer Service URL. The request contains two parameters: `SAMLResponse` and `RelayState`. | **Note:** The method for sending the SAML response (for example, HTTP-POST) and | the Assertion Consumer Service URL are usually imported as part of the SAML | metadata XML provided by the SP. In @product@, you import the SP's metadata in the | SAML Adapter's Service Provider Connections tab. #### The SP Processes the SSO Response The SP validates and processes the SAML Response. @product@'s SAML solution requires signed `SAMLResponse` messages. This signature process ensures proper identification for the IdP and prevents potential SAML Response spoofing. - If one @product@ instance is the IdP and another is the SP, make sure the SAML metadata XML file imported into the SP contains the IdP's certificate. - If @product@ is the IdP and another application is the SP, export the certificate from the IdP and import it into the SP's certificate store. If a `RelayState` is included in the SAML Response, the user is redirected to it. Otherwise the home page of the SP is served. ### Service Provider Initiated SSO Most of the time, authentication requests come from the Service Provider. ![Figure 2: Service Provider Initiated SSO](../../../images-dxp/saml-sp-initiated-sso.png) #### The SSO Request to the SP When the user's browser requests a protected resource or login URL on the SP, it triggers the SP initiated SSO process. When @product@ is the SAML SP, SSO is initiated either by requesting `/c/portal/login` URL or a protected resource that requires authentication (for example, a document not viewable by the Guest Role). If the user requests a protected resource, its URL is recorded in the `RelayState` parameter. If the user requested `/c/portal/login`, the `RelayState` can be set by providing the `redirect` parameter. Otherwise, if the [portal property](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html) `auth.forward.by.last.path` is set to `true`, the last accessed path is set as the `RelayState`. For non-@product@ SPs, consult the vendor documentation on initiating SSO. #### The AuthnRequest to the IdP The SP looks up the IdP's Single Sign On service URL and sends an `AuthnRequest`. When @product@ is the SP it looks up the configured SAML Identity Provider Connection and sends a SAML `AuthnRequest` to the IdP's Single Sign On service URL as defined in the SAML metadata XML document. @product@ supports sending and receiving the `AuthnRequest` using HTTP-POST or HTTP-Redirect binding. HTTP-POST is preferred. If the user doesn't have an active session or if `ForceAuthn` was requested by the SP, the user must authenticate by providing credentials. When @product@ is the IdP, authentication occurs in the Login Portlet. @product@ decodes and verifies the `AuthnRequest` before requesting the user to authenticate. #### The SSO Response from the IdP After authentication, a SAML Response is constructed, sent to the Assertion Consumer Service URL of the SP, and verified. The IdP automatically makes this choice based on the SP metadata. When @product@ is configured as the IdP, any attributes configured on the Service Provider Connection are included in the response as attribute statements. The Assertion Consumer Service URL is looked up from the SAML metadata XML of the SP. When @product@ is configured as the SP, response and assertion signatures are verified. @product@ requires the sender to be authenticated. This is done via whole message signature from the issuing IdP. Responses missing the signature are considered unauthenticated and the response is rejected. For non-@product@ SP or IdP vendors, consult their documentation. The user is redirected to the requested resource or to the URL contained in the `RelayState` parameter (for example, the last page the user accessed before initiating SSO). ## Single Log Off The Single Log Off request is sent from the user's browser to the IdP or an SP, and the SLO flow differs in each case. First consider IdP initiated SLO. ### Identity Provider Initiated SLO ![Figure 3: Identity Provider Initiated SLO](../../../images-dxp/saml-idp-initiated-slo.png) #### The SLO Request to the IdP An IdP initiated SLO request is sent directly to the IdP by the user's browser. When @product@ is the IdP, the IdP initiated SSO URL must specify the URL path as `/c/portal/logout` If the user is signed on to any configured SP, the SAML plugin takes over the logout process, displaying all the signed on services. The single logout screen displays the authentication status of each SP and whether any SPs can't be logged out of (for example, if the SP is down or doesn't support SLO). For non-@product@ IdPs (Siteminder, ADFS, etc.) consult the vendor's documentation on constructing IdP initiated SLO URLs. The IdP sends a SAML `LogoutRequest` to the SP. - When @product@ is configured as the IdP, the `LogoutRequest` is sent using either HTTP-POST, HTTP-Redirect, or SOAP binding. HTTP-Post binding is preferred but in its absence, the first available SLO endpoint with supported binding is selected. - When @product@ is configured as the SP, supported bindings for `LogoutRequest` are HTTP-Post, HTTP-Redirect, or SOAP. - For other IdPs or SPs, please consult the vendor's documentation. #### The SLO Response from the SP The SP delivers a `LogoutResponse` to the IdP. The IdP sends a SAML `LogoutRequest` to the second SP. The second SP then delivers the `LogoutResponse` to the IdP. The process is repeated for all SPs the user is logged into. When @product@ is the IdP, @product@ logs the user out after the last SP has delivered its `LogoutResponse` or has timed out. ### Service Provider Initiated SLO ![Figure 4: Service Provider Initiated SLO](../../../images-dxp/saml-sp-initiated-slo.png) #### The SLO Request to the SP In SP initiated SLO, the user's browser sends a logout request directly to the SP. When @product@ is configured as the SP, the SLO is initiated by requesting this logout URL: /c/portal/logout For other SPs, consult the vendor's documentation on initiating SLO. A SAML `LogoutRequest` is sent to the Single Log Out service URL of the IdP. - If @product@ serves as the SP, the `LogoutRequest` is sent to the IdP configured by the IdP Connections tab of the SAML provider (see the [next article](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider) to set up the IdP Connection) and the SLO service URL defined in the SAML metadata. - When @product@ is the IdP, if the user has logged on to other SPs, the user is presented with a single logout screen with the status of each SP logout, flagging any that can't be looged out of (some SPs might not support SLO or are currently down). If there are no other SPs to log out of, the SAML session terminates and the IdP destroys its session. #### The SLO Response from the SP If the user is logged in to additional SPs (beyond just the initiating SP), the IdP sends the SAML `LogoutRequest` to each one. When @product@ is the IdP, the `LogoutResponse` is sent using either HTTP-Post, HTTP-Redirect, or SOAP binding. Each SP delivers its `LogoutResponse` to the IdP. When @product@ is the SP, the `LogoutResponse` is sent using either HTTP-Post, HTTP-Redirect or direct response to SOAP request. After all additional SPs deliver their `LogoutResponse`s to the IdP, the IdP destroys its SSO session. When @product@ is the IdP, once the last SP has delivered its `LogoutResponse` or has timed out, the IdP destroys the @product@ session, logging out the user. Finally, the IdP sends a `LogoutResponse` to the SP that initiated SLO. The initiating SP terminates its SAML session and logs the user out. ## Related Topics - [Setting Up SAML as an Identity Provider](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-identity-provider) - [Setting Up SAML as a Service Provider](/docs/7-2/deploy/-/knowledge_base/d/setting-up-liferay-as-a-saml-service-provider) - [Token-Based SSO Authentication](/docs/7-2/deploy/-/knowledge_base/d/token-based-single-sign-on-authentication) ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/02-setting-up-identity-provider.markdown ================================================ --- header-id: setting-up-liferay-as-a-saml-identity-provider --- # Setting up @product@ as a SAML Identity Provider [TOC levels=1-4] An identity provider is a trusted provider that provides single sign-on for users to access other websites. A service provider is a website that hosts applications and grants access only to identified users with proper credentials. Liferay Portal 6.1 EE and later versions support SAML 2.0 integration via the [Liferay Connector to SAML 2.0](https://web.liferay.com/marketplace/-/mp/application/15188711) application. It is provided from Liferay Marketplace and allows @product@ to act as a SAML 2.0 identity provider or as a service provider. | **Important:** You can set @product@ up as an Identity Provider or as a Service | Provider. Each single @product@ instance can serve as an identity provider or as | a service provider, but **not both**. Both configurations are covered in this | article. ## Storing Your Keystore Your first step is to determine where to store your keystore. You have two options: - In the file system - In the Documents and Media library The file system keystore manager is used by default and the default location is the `[Liferay Home]/data` directory (you can change the location in System Settings → SSO → SAML Configuration → Key Store Path). To use Documents and Media library storage for your keystore instead of file system storage, go to *Control Panel* → *System Settings* → *Security* → *SSO* → *SAML KeyStoreManager Implementation Configuration*. Select from the two options: *Filesystem Keystore Manager* or *Document Library Keystore Manager*. If you use Document Library storage, you can use any number of [back-end file stores](/docs/7-2/deploy/-/knowledge_base/d/document-repository-configuration). These are protected not only by the system where the key is stored, but also by @product@'s permissions system. ## Configuring @product@ as a SAML Identity Provider 1. To access the SAML Admin interface, click on *Control Panel* → *Configuration* and then on *SAML Admin*. 2. To begin configuring @product@ to use SAML, select a SAML role for @product@ and choose an entity ID. ![Figure 1: Select a SAML role for Liferay and enter an entity ID.](../../../images-dxp/saml-initial-config.png) Select the *Identity Provider* SAML role. Enter your own entity ID. Then click *Save*. A new Certificate and Private Key section appears. 3. The Certificate and Private Key section lets you create a keystore for SAML. Click on *Create Certificate* and enter the following information: - Your common name (your first and last name) - The name of your organization - The name of your organizational unit - The name of your city or locality - The name of your state or province - The name of your country - The length in days that your keystore will remain valid (how long before the keystore expires) - The key algorithm (RSA is the default) - The key length in bits (2048 is the default) - The key password Click *Save*. When you create the certificate and private key, you also create a keystore if one doesn't already exist. As described above, this keystore has two storage options: file system storage (the default) and Documents and Media storage. By default, the certificate uses the `SHA256` algorithm for encryption and is fingerprinted and self-signed via RSA and `SHA256`. 4. After you click *Save*, you can click *Replace Certificate* at any time to replace the current certificate with a new one if your old one has expired or if you want to change the key's password. ![Figure 2: The General tab of the SAML Admin portlet displays information about the current certificate and private key and allows administrators to download the certificate or replace the certificate.](../../../images-dxp/saml-keystore-info.png) Three more tabs now appear: **General:** For enabling or disabling a SAML IdP and managing the required keystore. **Identity Provider:** Contains IdP options, such as whether to enable SSL. If SSL has been enabled, then SAML requests are not approved unless they are also encrypted. **Service Provider Connections:** This tab manages any Service Providers connected to this @product@ instance. See below for more information on the Identity Provider and Service Provider Connections tabs. 5. After you save your certificate and private key information, check the *Enabled* box at the top of the General tab and click *Save*. You successfully set @product@ up as a SAML Identity Provider! ## Changing the Identity Provider Settings To configure @product@'s SAML Identity Provider Settings, navigate to the *Identity Provider* tab of the SAML Admin Control Panel entry. The *Identity Provider* tab includes these options: **Sign Metadata?:** Check this box to ensure the metadata XML file that's produced is signed. **SSL Required:** Check this box to reject any SAML messages that are *not* sent over SSL. This affects URLs in the generated metadata. **Require Authn Request Signature?:** When this box is checked, each Authn Request must be signed by the sending Service Provider. In most cases, this should be enabled. **Session Maximum Age:** Specify the maximum duration of the SAML SSO session in seconds. If this property is not set or is set to `0`, the SSO session has an unlimited duration. The SSO session maximum duration can be longer than the portal session maximum duration. If the portal session expires before the SSO session expires, the user is logged back in to @product@ automatically. SSO session expiration does not trigger a single logout from all service providers. You can use the session maximum age, for example, to force users to sign in again after a certain period of time. **Session Timeout:** Specify the maximum idle time of the SAML SSO session. Even if the session maximum age is unlimited, the SSO session expires whenever the user's idle time reaches the limit set by the session timeout property. ## Checkpoint Before adding a Service Provider (SP), verify you've completed these tasks: 1. A SAML keystore has been generated. It can be stored in one of two locations: the `data` folder or in the Documents and Media library. 2. On the *Identity Provider* tab, the following settings have been set: a. **Sign Metadata** has been checked. b. **SSL Required** - checked if SSL is active elsewhere. SSL is disabled by default. c. **Authn Request Signature Required:** has been checked. d. **Session Maximum Age:** has been set. If set to `0`, then the SSO has an unlimited duration. e. **Session Timeout:** Specify the maximum idle time of the SAML SSO session. 3. Once the *Enabled* checkbox has been checked, the IdP is live, and you can generate the required metadata. This URL is the default location of @product@'s metadata XML file: [host]:[port]/c/portal/saml/metadata If this URL does not display correctly, then the SAML instance has not been enabled. Use the URL or click *Save* in the browser to generate an actual `XML` file. Your Identity Provider is now set up. Next, you must register a Service Provider. ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/03-registering-a-service-provider.markdown ================================================ --- header-id: registering-a-saml-service-provider --- # Registering a SAML Service Provider [TOC levels=1-4] Setting up @product@ as a SAML Identity Provider is only useful if you can connect to one or more SAML Service Providers. Navigate to the Service Provider Connections tab of the SAML Admin Control Panel entry and click the *Add Service Provider* button to add a SAML Service Provider. The New Service Provider page includes these options: **Name:** The name of the Service Provider with which to connect. The name can be anything; it's purely cosmetic. **Entity ID:** The Service Provider's entity ID. This value must match the entity ID declared in the Service Provider metadata. **Enabled:** Check this box to activate the Service Provider connection. **Assertion Lifetime:** Defines the number of seconds after which the SAML assertion issued by the Identity Provider should be considered expired. **Force Encryption:** If the SP does not provide a public key for encrypting the assertions, abort the single sign-on. **Metadata:** Provide a URL to the Service Provider metadata XML file or manually upload the Service Provider metadata XML file. If you provide a URL, the XML file is retrieved and periodically polled for updates. The update interval can be configured in System Settings with the Runtime Metadata Refresh Interval (`saml.metadata.refresh.interval` if using a `config` file) property which specifies a number of seconds. If fetching the metadata XML file by URL fails, you can't enable the Service Provider connection. If the Identity Provider server cannot access the metadata via URL, you can upload the XML file manually. In this case, the metadata XML file is not updated automatically. **Name Identifier Format:** Choose the Name Identifier Format used in the SAML Response. This should be set according to what the Service Provider expects to receive. For Liferay Service Providers, any selection other than email address indicates that the Name Identifier refers to screen name. The formats don't have any special meaning to Liferay Identity Providers. The `NameID` value is defined by the Name Identifier attribute. See the next option. **Name Identifier Attribute Name:** This specifies which attribute of the @product@ `User` object to use as the `NameID` value. Possible values include `emailAddress`, `screenName` and `uuid`. Additionally, you can prefix the name with `static:` or `expando:`. If you use the prefix `static`, the value is whatever comes after `static:`. If you use the prefix `expando`, the value is whatever custom field is specified after `expando:`. For example, `expando:SSN` would look up the `User` custom field with the name `SSN`. **Attributes Enabled:** Include and resolve assertion attributes. **Attributes Namespace Enabled:** When this box is checked, the attribute names are namespaced like this: urn:liferay:user:expando: urn:liferay:user: urn:liferay:groups: urn:liferay:organizationRole: urn:liferay:organization: urn:liferay:roles: urn:liferay:siteRole: urn:liferay:userGroupRole: urn:liferay:userGroups: **Attributes:** Enter a list of attributes to include in the assertion, one per line. Each line is an expression that gets parsed. Examples: organizations organizationRoles roles siteRoles userGroups static:[attributeName]=[attributeValue] expando:[userCustomFieldName] Note that the full namespace depends on the attribute name. Attribute namespaces can be useful. Use them when attribute names from different namespaces might conflict. For example, `expando:user` vs `urn:liferay:roles:user`. **Keep Alive URL:** If users are logged into several @product@ SP instances via a @product@ IdP, their sessions can be kept alive as long as they keep a browser window open to one of them. Configure this only if the SP is @product@. The URL is `https://[SP host name]/c/portal/saml/keep_alive`. ## Checkpoint Verify your settings are correct by connecting the @product@-based IdP to its first SP. SPs connect to only one IdP, so if the first one doesn't work, the rest won't either. 1. Provide a general name for the SP. 2. The `Entity ID` name must be identical to the one declared in the Service Provider metadata. 3. Check the *Enabled* checkbox. 4. Set a value for the *Assertion Lifetime*. 5. Choose whether encryption should be required (recommended). 6. Make sure the SP's metadata has been provided either as a URL or an XML file has been uploaded. 7. Make sure *Name Identifier Format* and *Name Identifier Attribute Name* have been set. 8. Make sure *Attributes Namespace Enabled* has been set. If you don't have a Service Provider to add right now, that's fine. In the next section, you'll learn how to set @product@ up as a SAML Service Provider. The same instance can't be both, but after you set up another @product@ instance as a Service Provider, come back to this one and add the Service Provider according to the instructions above. ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/04-setting-up-service-provider.markdown ================================================ --- header-id: setting-up-liferay-as-a-saml-service-provider --- # Setting up @product@ as a SAML Service Provider [TOC levels=1-4] Many of these steps are similar to configuring @product@ as a SAML Identity Provider. As a reminder, a single @product@ installation can be configured as a SAML Identify Provider *or* as a SAML Service Provider but not as both. If your @product@ installation is already a SAML Identity Provider, use a *different* @product@ installation as a SAML Service Provider. | **Note:** If you have a third party IdP with @product@ as the SP, all | messages coming from the IdP must be signed. If they're not, an error message | appears and communication between the IdP and @product@ fails. 1. Install the Liferay Connector to SAML 2.0 app. To confirm successful deployment, look for the *SAML Admin* entry in the Configuration section of the Control Panel. 2. To begin configuring @product@ to use SAML, you must select a SAML role for @product@ and choose an entity ID. Select the *Service Provider* SAML role. Choose your own entity ID. Then click *Save* and a new section entitled Certificate and Private Key appears. 3. The Certificate and Private Key section is for creating a keystore for SAML. Click *Create Certificate* and enter the following information: - Your common name (your first and last name) - The name of your organization - The name of your organizational unit - The name of your city or locality - The name of your state or province - The name of your country - The length in days that your keystore will remain valid (how long before the keystore expires) - The key algorithm (RSA is the default) - The key length in bits (2048 is the default) - The key password Remember that the keystore has two storage options: file system storage (the default) and Documents and Media storage.By default, the certificate uses the SHA256 algorithm for encryption and is fingerprinted and self-signed via MD5 and SHA1. When you enter all the required information, click *Save*. 4. After you clicked *Save*, check that you can view information about your certificate or download your certificate. If you can, you successfully created a keystore. After you create a keystore, additional options appear. There are three tabs: **General**: Enables or disables SAML SP and manages the required keystore. **Service Provider**: This tab manages basic and advanced configurations for the SP. **Identity Provider Connection**: This tab manages connections to the IdP. There can be multiple IdP connections. 5. You can also generate an encryption certificate. This is a separate key for encrypting assertions. If you want assertions encrypted, you must generate a key for this. The procedure is exactly the same as generating your certificate in step 3 above. 5. Next, you must configure an Identity Provider connection. Click on the *Identity Provider Connections* tab. Enter a name for the Identity Provider, enter its entity ID, and enter its metadata URL. If you have already followed the previous instructions and configured a separate @product@ installation as an Identify provider, you'd enter the following information: - Name: *Liferay IdP* - Entity ID: [ID of IdP] - Clock Skew: Set the tolerance in milliseconds between SP and IdP. - Force Authn: Whether the IdP should force reauthentication regardless of context. - Metadata URL: http://localhost:8080/c/portal/saml/metadata (test this URL first) - Name Identifier Format: See settings. - Attribute Mapping: See settings. - Keep Alive URL: See settings. **Important**: The Liferay Connector to SAML 2.0 app supports using *either* a URL to a SAML IdP metadata file *or* an actual (uploaded) SAML metadata XML file. The value entered in the *Metadata URL* field is persisted to the database only when there a metadata URL and there is no specified metadata XML file. Otherwise, @product@ keeps the original metadata URL in the database. This behavior ensures that once a metadata URL has been specified, there is always a metadata URL saved in the database. This way, if a portal administrator forgets the previously entered metadata URL or its format, he or she can look at the displayed metadata URL and choose to modify the displayed metadata URL or overwrite the previously saved metadata URL by specifying a metadata XML file. 6. Finally, after you save your certificate and private key information and configure an Identity Provider connection, check the *Enabled* box at the top of the General tab and click *Save*. @product@ is now a SAML Service Provider! Note that the SAML Service Provider session is tied to the normal session on the application server. Session expiration on the application server terminates the session on the Service Provider but does not initiate single logout. You can add multiple IdP connections. To add another Identity Provider, click *Add Identity Provider* again and enter the details for the other provider. When users log in, they are asked to choose an identity provider, so be sure to name the providers so users can recognize them. ## Checkpoint 1. A SAML keystore has been generated. 2. Verify the connection to the IdP. a. *Name*: generic name for the IdP. b. *Entity ID*: the same name of the IdP. If the IdP is another @product@ instance, then it is the same name as the above example. c. *Metadata URL*: The IdP's metadata as a URL or as an XML file. d. If the IdP is another @product instance, ensure its corresponding Service Provider Connection for this SP is enabled. 3. On the *General* tab, the *Enabled* checkbox has been checked. 4. Once *Enabled* checkbox has been checked, the service provider's metadata becomes available: [host]:[port]/c/portal/saml/metadata ## Setting Up @product@ as a SAML Service Provider in a Clustered Environment You can use the Liferay Connector to SAML 2.0 app as an SSO solution for a clustered @product@ environment. If your multi-node cluster is behind a load balancer, you must enable all the nodes as SPs, and they must share the same keystore manager. If using the Filesystem Keystore Manager (the default): 1. Configure each node of your [@product@ cluster](/docs/7-2/deploy/-/knowledge_base/d/liferay-clustering) as a SAML service provider as above. 2. Copy the keystore file (`[Liferay Home]/data/keystore.jks`, by default) from the first node to the remaining nodes. This file is the Java keystore that's created by the SAML Provider app. The keystore contains the valid or self-signed certificate managed by the SAML connector app. 3. Verify that the service provider metadata has been generated to be used either as a URL or an XML file. The metadata is the same for all nodes because of the same database back-end. The IdP's request goes through the load balancer. 4. At this point, all nodes have the same SAML SP configuration and each of them can respond to web requests and handle the SAML protocol. To test your SSO solution, sign into @product@ via your load balancer, navigate to a few pages of a few different sites, and then log out. If using the Document Library Keystore Manager, skip step 2 because the keystore file is stored in the database shared by all the nodes. Now you know how to configure @product@ either as a SAML identity provider or a service provider. You also know how to configure SAML in a clustered environment. ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/05-configuring-sp-and-idp-connections.markdown ================================================ --- header-id: changing-the-settings-for-service-provider-and-identity-provider-connection --- # Changing the Settings for Service Provider and Identity Provider Connections [TOC levels=1-4] To change the SAML Service Provider Settings, navigate to the Service Provider tab. The Service Provider tab includes these options: **Require Assertion Signature?:** Check this box to require SAML assertions to be individually signed in addition to the entire SAML message. | **Note:** Individual assertions need not be signed as long as the SAML response | itself is signed. The SP and IdP should always communicate over `https` to have | encryption at the transport level. | | If you believe man-in-the-middle attacks are possible, the SAML response can be | signed. The only reason to sign the assertions is if the SAML response is not | signed. In this case, assertions should not only be signed but also encrypted. **Clock Skew:** Clock skew is a tolerance in milliseconds used by the Service Provider for mitigating time differences between the clocks of the Identity Provider and the Service Provider. This usually only matters when assertions have been made to expire very quickly. **LDAP Import Enabled:** Check this box to import user information from the configured LDAP connection based on the resolved `NameID`. LDAP connections can be configured from Instance Settings. **Sign Authn Requests:** Check this box to sign the `AuthnRequest` even if the Identity Provider metadata indicates that it's not required. **Sign Metadata:** Check this box to sign the metadata XML file. **SSL Required:** Check this box to reject SAML messages that are not sent over HTTPS. This does not affect how URLs are generated. ## Changing the SAML Identity Provider Connection Settings To configure @product@'s SAML Identity Provider Settings, navigate to the Identity Provider Connection tab of the SAML Admin portlet and click the *Edit* action button on the IdP you want to configure. **Name:** The name of the Identity Provider with which to connect. **Entity ID:** The Identity Provider's entity ID. This value must match the entity ID declared in the Identity Provider metadata. **Enabled:** Check the box to enable this IdP. **Clock Skew:** Clock skew is a tolerance in milliseconds used by the Service Provider for mitigating time differences between the clocks of the Identity Provider and the Service Provider. This usually only matters when assertions have been made to expire very quickly. **Force Authn:** Check this box to have the Service Provider ask the Identity Provider to re-authenticate the user before verifying the user. **Metadata:** You can provide a URL to the Identity Provider metadata XML file or you can manually upload it. If you provide a URL, the XML file is automatically retrieved and periodically polled for updates. You can change the update interval in System Settings by modifying the Runtime Metadata Refresh Interval property which specifies a number of seconds. If fetching the metadata XML file by URL fails, you can't enable the Identity Provider connection. If the metadata is inaccessible via URL, you can upload the XML file manually. In this case, the metadata XML file is not updated automatically. **Name Identifier Format:** Choose the Name Identifier Format used in the SAML Response. Set this according to what the Service Provider expects to receive. For Liferay Service Providers, selections other than email address indicate that the Name Identifier refers to screen name. The formats don't have any special meaning to Liferay Identity Providers. The Name Identifier attribute defines the `NameID` value. **Attribute Mapping:** Attribute mapping is done from the attribute name or friendly name in the SAML Response to the @product@ attribute name. For example, if you want to map a response attribute named `mail` to the @product@ attribute `emailAddress`, enter the following mapping: mail=emailAddress Available @product@ attributes are: `emailAddress`, `screenName`, `firstName`, `lastName`, `modifiedDate`, and `uuid`. **Keep Alive URL:** If users are logged into several @product@ SP instances via a @product@ IdP, their sessions can be kept alive as long as they keep a browser window open to one of them. Configure this only if the IdP is @product@. The URL is `https://[IdP host name]/c/portal/saml/keep_alive`. On the @product@ IdP, configure this URL the same way, but point back to this SP. Save your changes when you are finished configuring the @product@ instance as a service provider. There is no need to restart the server: the changes are applied immediately. Make the above configurations through the SAML Control Panel interface and not via properties. Some features of the Liferay Connector to SAML 2.0 app are not available as properties. | **Limitation:** The Liferay SAML app can only be used with a single virtual | host. Technically, this means that in the SAML metadata for @product@, only one | binding can be added in this form: | | ```xml | | ... | | ... | | ... | | ================================================ FILE: en/deployment/articles-dxp/04-securing-liferay/10-saml/06-configuring-saml.markdown ================================================ --- header-id: configuring-saml --- # Configuring SAML [TOC levels=1-4] There are some ways of configuring the SAML plugin outside the UI. This is done via OSGi configuration files and by uploading metadata XML to configure how connections are negotiated. ## OSGi Configuration Properties As noted in the previous tutorials, anything related to configuring SP connections must be done through the SAML Admin UI where configurations are saved to Liferay's database. SP connections can no longer be made via properties files as they were in versions prior to 3.1.0. | **Note:** Don't use OSGi `.config` files or @product@'s System Settings Control | Panel application to configure SAML providers (IdP or SP). The System Settings | UI is auto-generated, and is for advanced administrators. It does not perform the | enhanced validation on the fields that the SAML Admin UI performs, so it could | allow administrators to create invalid configurations. This is a portal instance scoped configuration which can be managed via OSGi Configuration Admin. The affected properties are those in the `SAMLProviderConfiguration` metatype: - `keyStoreCredentialPassword()` - `keyStoreEncryptionCredentialPassword()` - `assertionSignatureRequired()` - `authnRequestSignatureRequired()` - `clockSkew()` - `defaultAssertionLifetime()` - `entityId()` - `enabled()` - `ldapImportEnabled` - `role()` - `sessionMaximumAge` - `sessionTimeout()` - `signAuthnRequest()` - `signMetadata()` - `sslRequired()` - `allowShowingTheLoginPortlet()` The SAML Admin UI remains the place for creating the portal instance scoped configuration instances. Note that there is also a system wide configuration, represented by the `SamlConfiguration` metatype. If you used Liferay 6.2, please note that the following system wide properties were removed: `saml.metadata.paths` (served no purpose after removal of SP connection defaults) `saml.runtime.metadata.max.refresh.delay` `saml.runtime.metadata.min.refresh.delay` The latter two properties were replaced with the single property `com.liferay.saml.runtime.configuration.SamlConfiguration.getMetadataRefreshInterval()`. Note also the introduction of the *SAML KeyStoreManager Implementation Configuration* in *Control Panel* → *System Settings* → Security → SSO. The options for this configuration are explained above in the Setting up @product@ as a SAML Identity Provider section. In the latest version of the plugin, the `SHA256` algorithm is the default encryption algorithm used in the configuration and to generate keys. The default configuration tries `SHA256`, then `SHA384`, then `SHA512` before falling back to `SHA1`. Because `SHA1` is potentially vulnerable, you can blacklist it using this property: ```properties blacklisted.algorithms=["blacklisted_algorithm_url", "another_blacklisted_algorithm_url"] ``` To blacklist `SHA1`, therefore, you'd have this configuration: ```properties blacklisted.algorithms=["http://www.w3.org/2000/09/xmldsig#sha1"] ``` Place these in a config file with this name: ```bash com.liferay.saml.opensaml.integration.internal.bootstrap.SecurityConfigurationBootstrap.config ``` There's a lot more granularity in how connections are negotiated if you configure the metadata XML. ## Configuring Negotiation Via metadata.xml If the default negotiation configuration doesn't work for you, you can craft your own configuration and upload it. Before doing this, visit your host's metadata URL and save a copy of the configuration in case you need it later: http://[hostname]/c/portal/saml/metadata For example, if you're stuck connecting to a legacy IdP that only supports `SHA1`, you can upload a configuration that disables the other algorithms: ```xml ... omitted ... ``` Notice that in the configuration above, the `` block has only one signing algorithm: `SHA1`. | **Note:** Since the default configuration falls back to `SHA1`, you shouldn't | need to do this unless your legacy system can't negotiate via the fallback | mechanism. Also note that if you blacklisted `SHA1`, this won't work. Due to | [vulnerabilities in `SHA1`](https://en.wikipedia.org/wiki/SHA-1), it's best to | avoid using it altogether if possible. If you've changed your metadata configuration, you can go back to the default configuration if you saved it before making the change. If you didn't, you can provide a URL instead of an uploaded XML file to one of your peers' metadata configurations. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/01-patching-liferay-dxp-intro.markdown ================================================ --- header-id: patching-liferay --- # Patching @product@ [TOC levels=1-4] While we strive for perfection with every @product@ release, the reality of the human condition dictates that releases may not be as perfect as originally intended. But we've planned for that. Included with every @product@ bundle is a Patching Tool that handles installing two types of patches: fix packs and hotfixes. | **Important:** Make sure to | [back up your @product@ installation and database](/docs/7-2/deploy/-/knowledge_base/d/backing-up-a-liferay-installation) | regularly, especially before patching. The patching tool installs code changes | and some of these make data changes (if necessary) automatically on startup. | | Certain fix packs (service packs) can include data/schema | micro changes---they're optional and revertible. Module upgrades and any | micro changes they include are applied at server startup by default, or can be | applied manually by | [disabling the `autoUpgrade` property](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-data-upgrade#configuring-non-core-module-data-upgrades). | Server startup skips all Core micro changes. Instead, you | can apply them using the [upgrade | tool](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver) | before server startup. | **Important:** Installing the latest service pack on top of @product@ 7.2 | GA1/FP1 requires running the | [data upgrade tool](/docs/7-2/deploy/-/knowledge_base/d/upgrading-the-product-data). | Examine the | [@product@ upgrade instructions](/docs/7-2/deploy/-/knowledge_base/d/upgrading-to-product-ver) | to determine preparations, testing, and post upgrade steps that are | appropriate for you. To eliminate system down time during upgrade, consider | using the | [Blue-green deployment technique](/docs/7-2/deploy/-/knowledge_base/d/other-cluster-update-techniques). | | @product@ 7.2 FP2/SP1's data schema change adds version columns to several | tables. | [Hibernate's optimistic locking system](https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html#d0e2225) | uses the columns to preserve data integrity during concurrent data | modifications. | **Note:** [Patching a cluster](/docs/7-2/deploy/-/knowledge_base/d/updating-a-cluster) | requires additional considerations. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/02-patching-basics/01-patching-basics-intro.markdown ================================================ --- header-id: patching-basics --- # Patching Basics [TOC levels=1-4] Liferay ships @product-ver@ fixes through three different channels: - Fix packs - Hotfixes - Service Packs ## Fix Packs The latest fixes that patch the core are bundled together weekly into fix packs that are provided to all of Liferay's customers. Fix packs include fixes for both the core and the applications and modules that ship with @product@. The fixes address regressions or obvious bugs and don't require you to make additional changes. Each fix pack contains all previous fix packs since the last service pack. [Security Fix Packs](https://help.liferay.com/hc/en-us/articles/360035038331) are a special fix pack type for deploying critical security fixes quickly without changing fix pack levels. Fixes that don't fit these requirements are considered for service packs or hot fixes. ## Hotfixes A hotfix is provided to customers when they contact Liferay about an emergency situation, and Liferay's support team---working with the customer---determines that the problem is a product issue that must be fixed very quickly. Support fixes the bug and provides a hotfix to the customer immediately. This is a short-term fix. Hotfixes can patch the core, the applications, and modules. ## Service Packs Service packs are built on top of the original @product@ release and repackaged with the latest fix pack, Patching Tool, and modules. Since a service pack is a fix pack, it contains all previous fix packs since the last service pack. Each one includes the most recent patches and updates. Service packs can also include changes that have these characteristics: - Require more extensive testing. - Require some of your attention, such as updating your documentation. - Improve the product. Rather than updating existing @product@ systems with service packs, you should 1. Keep systems up-to-date with fix packs (according to your own deployment schedule). 2. Install the latest Marketplace updates frequently. 3. Update the Patching Tool when necessary. This method updates the installation to the service pack levels, while allowing scheduled deployments and avoiding full environment rebuilds. ## How Patches are Tested Liferay extensively tests service packs, fix packs, and hotfixes to ensure high quality. Fixes in fix packs go through both automated regression testing and manual testing. Hotfixes receive similar automated testing, and the support engineer who fixes a reported issue tests it. Before releasing a service pack, Liferay runs test suites on the packaged service pack. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/03-using-the-patching-tool/01-using-the-patching-tool-intro.markdown ================================================ --- header-id: patching-tool --- # Using the Patching Tool [TOC levels=1-4] The Patching Tool installs, removes, compares, and prepares @product@ patches. It is pre-installed in @product@ bundles, easy to install into @product@ manual installations, and easy to update. The Patching Tool's executable scripts facilitate patching. Here are the essentials to get started using the Patching Tool: - [Installing the Patching Tool](#installing-the-patching-tool) (for manual installations only) - [Executables](#executables) ## Installing the Patching Tool @product@ bundles come with the Patching Tool pre-installed (in `[Liferay Home]/patching-tool`) and pre-configured with the default settings. Skip this section if you're using a bundle. If you installed @product@ manually, however, you must also install the Patching Tool manually. 1. Download the Patching Tool from the [Customer Portal](https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066). 2. Unzip the Patching Tool to your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder (recommended) or to another folder. After installing the Patching Tool, you must [configure it to use your @product@ installation](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-patching-tool). The `patching-tool` folder you extracted from the Patching Tool ZIP file contains the Patching Tool, including its executable scripts. ## Executables The Unix shell and Windows batch scripts distributed with the Patching Tool make it easier to use. On Unix systems, run ```bash ./patching-tool.sh parameters ``` On Windows, run ```bash patching-tool parameters ``` The Windows command `patching-tool` is used in the examples that follow. On Unix, replace the name of the executable before running the commands. Installing patches is next! ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/03-using-the-patching-tool/02-installing-patches.markdown ================================================ --- header-id: installing-patches --- # Installing Patches [TOC levels=1-4] Before installing any patches, you must shut down your server. On Windows operating systems, files in use are locked by the OS, and can't be patched. On Unix-style systems, you can usually replace files that are running, but the old ones reside in memory. For these reasons, it is best to shut down @product@ before installing patches. Liferay distributes all patches (fix packs and hotfixes) as ZIP files. When you download a patch, either from a [Help Center](https://help.liferay.com/hc) ticket (hotfix) or from the [Customer Portal](https://customer.liferay.com/downloads) (fix pack), place it in the Patching Tool's `patches` folder (e.g., `[Liferay Home]/patching-tool/patches`) without unzipping it. To list your installed patches and available local patches, execute this command: ```bash patching-tool info ``` This displays a list of patches you've already installed, along with a list of patches that *can* be installed from what's in the `patches` folder. To install the available patches, use the following steps. First, issue the following command: ```bash patching-tool install ``` To make sure the all changed OSGi bundles replace the existing ones, delete the `osgi/state` folder from the [Liferay Home folder](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). | **Note**: The `osgi/state` folder contains OSGi bundle state information. If an | OSGi bundle's changes in a hot fix or fix pack are internal only, they are | invisible to the OSGi framework, that OSGi bundle stays installed, and its state | information stays unchanged. Hot fixes, for example, may contain in-place | changes that do not use the API. The framework cannot detect such changes. | A fix pack's changes may also be transparent to the framework. For these | reasons, deleting the `osgi/state` folder after applying fix packs and hot fixes | is recommended. | **Important**: The `osgi/state` folder should ONLY be deleted when working in a | development environment or when applying a fix pack or hot fix. If there are new database indexes created by the patch, the Patching Tool tells you to update them. To get the list, run this command: ```bash patching-tool index-info ``` Since there's no database connection at patching time, the indexes must be created at portal startup. If the server has permissions to modify the database indexes, instruct @product@ to create the indexes automatically at startup by adding this setting to your `portal-ext.properties` file: ```properties database.indexes.update.on.startup=true ``` Otherwise, you must create the indexes manually. Check the `patching-tool index-info` command output for more details. After installing patches, you can execute the `patching-tool info` command to verify them. | **Note:** If there are any issues with the installed patches, verify that there | aren't any remaining files from the previous patch installation of a fix pack or | hotfix within the application server cache. During the installation, `patching-backup-deps.zip` and `patching-backup.zip` files are created and stored in the web application's `WEB-INF` folder. These files are required to restore the @product@'s original state; removing them disables patching. | **Note:** When installing patches, @product@'s `web.xml` is always overwritten | by the one contained in the patch. If you've customized `web.xml`, you must | re-implement your customizations after installing a patch. The `patching-backup.zip` file is necessary for installing future patches, because the Patching Tool reverts the installed fix pack before installing a new one. To revert the installed fix pack, it examines the contents of the `patching-backup.zip` to determine the changes that it needs to revert. ## Handling Hotfixes and Patches As stated previously, hotfixes are short term fixes provided as quickly as possible, and fix packs are larger bundles of hotfixes provided to all customers at regular intervals. If you already have a hotfix installed and the fix pack that contains that hotfix is released, the Patching Tool can manage this for you. Fix packs always supersede hotfixes; so when you install your fix pack, the hotfix it contains is uninstalled and the fix pack version is installed in its place. The Patching Tool applies fixes to fix packs automatically. If a new (fixed) version of a fix pack is released, install it with the Patching Tool. The Patching Tool uninstalls the old fix pack and installs the new version in its place. ## Fix Pack Dependencies Some hotfixes depend on fix packs. If you attempt to install a hotfix that depends on a fix pack, the Patching Tool notifies you. Go to the [Customer Portal](hhttps://customer.liferay.com/downloads) and obtain the hotfix dependency. Once all the necessary patches are available in the `patches` folder, the Patching Tool installs them. ## Updating the Patching Tool When a patch you're trying to install requires a Patching Tool update, the Patching Tool tells you. To update the Patching Tool, download the latest one from the [Customer Portal](https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066). Overwrite the existing Patching Tool by unzipping the new one to the `patching-tool` folder's parent folder. ## Cleaning Up After you've performed your patching procedure (whether you've installed or [removed patches](/docs/7-2/deploy/-/knowledge_base/d/working-with-patches#uninstalling-patches)), it's important to clean up @product@'s cache of deployed code. This ensures that you're using the revision you've just installed the patches for when you start the server. This is really easy to do. To clear out the cached code, remove the contents of the `[Liferay Home]/work` folder. Now you're ready to start your server. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/03-using-the-patching-tool/03-working-with-patches.markdown ================================================ --- header-id: working-with-patches --- # Working with Patches [TOC levels=1-4] Here are some things you might need to do with patches: - Report Patch Levels to Liferay Support - Uninstall Patches - Show collisions between patches and deployed plugins - Separate Patches from your Installation Start with reporting patch levels to Liferay Support. ## Including support-info in Support Tickets Providing your environment's information (e.g., hardware architecture) and patch level to Liferay Support is critical for reproducing your issues. Write your support information (including your patch level) to a file by executing this command: ```bash patching-tool support-info ``` The support information is written to file `patching-tool-support-info-actual-timestamp.txt` in your `patching-tool` folder. Please upload this file to the [Help Center](https://help.liferay.com/hc) ticket. ## Uninstalling Patches Have you noticed that the Patching Tool only seems to have an `install` command? This is because patches are managed not by the command, but by what appears in the `patches` folder. You manage the patches you have installed by adding or removing patches from this folder. Here's how to uninstall (remove) a patch: 1. Remove the patch from your `patches` folder. 2. Run the `patching-tool install` command. To revert ALL patches, run this command: ```bash patching-tool revert ``` Now you know how to remove and revert patches. You can also compare patch levels. See the [reference guide](/docs/7-2/deploy/-/knowledge_base/d/comparing-patch-levels) for a list of the available commands. ## Showing collisions between patches and deployed plugins Some patches update files you might have customized via a plugin. The `patching-tool list-collisions` command lists differences (collisions) between installed patch files and your plugin's version of them. Here's the command: ```bash patching-tool list-collisions ``` It is an alias for the following diff command: ```bash patching-tool diff collisions files _base ``` `_base` is the literal patch level name. Collisions are only listed for installed patches that contain source code files. | **Note:** As of Patching Tool 2.0.9, `patching-tool list-collisions` lists only | JSP file collisions in fragment bundles. ## Separating Patches from the Installation The Patching Tool's `separate` command helps reduce the patched @product@ installation size. If the installation has been patched, you can make it smaller by moving the restore files out of it. Patched installations are large because the restore files are stored inside the web application's `WEB-INF` folder by default. These files are required for patching the installation again. If these files are removed, subsequent patching processes fail. Because of this, Liferay added an option to separate the patching files from the installation while still preserving and restoring them safely when new patches arrive. To do this, use this command: ```bash patching-tool separate [separation_name] ``` This command produces a `liferay-patching-files-[separation-name].zip` file in the Patching Tool's `patches` folder. It contains the necessary files and metadata for patching, verification, and validation. Once you create this file, the patch files are removed from their default location and are now only available in this file. You can store this file elsewhere to reduce your installation's size. **WARNING:** If the product is separated from its patches in this way, you cannot run most of the Patching Tool commands until the patches are restored. After the separation process only the following commands can be used: - `auto-discovery` - `info` - `setup` Any other command returns this: ```bash This installation does not include data for patching. Please copy the liferay-patching-files-[separation-name].zip file into the 'patches' directory and run patching-tool setup. ``` This is how you restore the patch files to your system. Details below. ## Restoring the Separated Patch Files When you need to patch @product@ again, you must restore the separated patch artifact. To do this, copy the `liferay-patching-files-[separation-name].zip` back to the Patching Tool's `patches` folder and run `patching-tool setup` command. The command finds the necessary patching artifact and restores the patch files to the installation. After that, the Patching Tool works like it did prior to separating the patches. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/04-configuring-the-patching-tool/01-configuring-the-patching-tool-intro.markdown ================================================ --- header-id: configuring-the-patching-tool --- # Configuring the Patching Tool [TOC levels=1-4] The Patching Tool installs @product@ patches. It ships with prepackaged @product@ bundles. If any of the following scenarios describes your @product@ installation, however, you must configure the Patching Tool manually: - Installed @product@ manually on an existing application server - Customized your @product@ folder structure - Running in a cluster If none of the above scenarios describe your installation, you can skip this section. If you installed @product@ manually, you must also install the Patching Tool manually. Download it from the [Customer Portal](https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066). Unzipping it to your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder is the easiest way to use it. Read on to configure the Patching Tool for your environment. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/04-configuring-the-patching-tool/02-patching-tool-basic-configuration.markdown ================================================ --- header-id: patching-tool-basic-configuration --- # Patching Tool Basic configuration [TOC levels=1-4] There are two ways to configure the Patching Tool: 1. Automatically by executing the `auto-discovery` command 2. Manually by editing the configuration file (see [Patching Tool Advanced Configuration](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration)) Automatic configuration generates the configuration files by looking for @product@ files in the local file system. By default the Patching Tool looks for them in its parent folder. To start the process, run this command in your Patching Tool folder (`patching-tool`): ```bash patching-tool auto-discovery ``` If @product@ is not installed in the parent folder, specify its location: ```bash patching-tool auto-discovery /opt/liferay-dxp ``` If you specified the wrong location of @product@ or it is not in the parent folder, the Patching Tool can't find the [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) and reports an error like this: ```bash The .liferay-home has not been detected in the given directory tree. Configuration: patching.mode=binary war.path=../tomcat-9.0.17/webapps/ROOT/ global.lib.path=../tomcat-9.0.17/lib/ext/ liferay.home=**[please enter manually]** The configuration hasn't been saved. Please save this to the default.properties file. ``` Here are ways to resolve the Liferay Home issue: - Specify the Liferay Home path in the `default.properties` file. - If the Liferay Home is in the Patching Tool's tree, create a `.liferay-home` file in the Liferay Home folder and re-run the auto-discovery process. When the Patching Tool is configured, running `patching-tool info` reports product version information. That's it! Now that you've installed and configured the Patching Tool, you're ready to download and install patches. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/04-configuring-the-patching-tool/03-patching-tool-advanced-configuration.markdown ================================================ --- header-id: patching-tool-advanced-configuration --- # Patching Tool Advanced Configuration [TOC levels=1-4] By default, the Patching Tool's configuration file called `default.properties` is in the tool's folder. A Patching Tool configuration file typically looks like this: ```properties patching.mode=binary war.path=../tomcat-9.0.17/webapps/ROOT/ global.lib.path=../tomcat-9.0.17/lib/ext/ liferay.home=../ ``` The properties above (described fully in [Patching Tool Configuration Properties](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-configuration-properties)) define the location of [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home), the patching mode (binary or source), the path to where WAR files are deployed in the app server, and the global library path. The tool's auto-discovery bases the OSGi module framework paths on the Liferay Home. If, however, you changed the OSGi module framework paths to something different than those under the default folder `[Liferay Home]/osgi`, you must manually specify the following properties: ```properties module.framework.core.path=path_to_modules_core_dir module.framework.marketplace.path=path_to_modules_marketplace_dir module.framework.modules.path=path_to_modules_modules_dir module.framework.portal.path=path_to_modules_portal_dir module.framework.static.path=path_to_modules_static_dir ``` Using auto-discovery and working with the default profile `default.properties` is the easiest way to use the Patching Tool, and is great for smaller, single server installations. But many @product@ installations serve millions of pages per day, and the Patching Tool has been designed for this as well. So if you're running a small, medium, or large cluster of @product@ machines, you can use the Patching Tool profiles to manage patching for all of them. ## Using Profiles with the Patching Tool You can create profiles for multiple runtimes by running auto-discovery or creating them manually. To auto-discover other runtimes, run the Patching Tool with parameters like this: ```bash ./patching-tool.sh [name of profile] auto-discovery [path/to/Liferay Home] ``` This runs the same discovery process, but on the path you specify. It writes the profile information to a file called `[name of profile].properties`. Alternatively, you can manually create profile property files in your `patching-tool` folder. See [Patching Tool configuration properties](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-configuration-properties) (profile properties) for a complete list of the available configuration properties. You can have as many profiles as you want and use the same Patching Tool to patch all of them. This helps to keep all your installations in sync. ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/04-configuring-the-patching-tool/04-installing-patches-on-the-liferay-war.markdown ================================================ --- header-id: installing-patches-on-the-liferay-de-war --- # Installing patches on the @product-ver@ WAR [TOC levels=1-4] If you [installed @product@ manually](/docs/7-1/deploy/-/knowledge_base/d/installing-product-on-tomcat) as a WAR file on a supported application server, you must apply patches to the WAR file and supporting files and re-deploy them. This article shows you how to do that. ## Prerequisites Download the necessary artifacts from the [Customer Portal:](https://customer.liferay.com/downloads) - @product@ WAR file (`liferay-dxp-[version].war`) - Dependencies ZIP file (`liferay-dxp-dependencies-[version].zip`) - OSGi JARs ZIP file (`liferay-dxp-osgi-[version].zip`) - [Latest Patching Tool](https://customer.liferay.com/downloads?p_p_id=com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_productAssetCategoryId=118191019&_com_liferay_osb_customer_downloads_display_web_DownloadsDisplayPortlet_fileTypeAssetCategoryId=118191066) ## Install the patch on the @product@ WAR and artifacts 1. Create an arbitrary folder. Unzip the dependency artifacts and the Patching Tool into it. The folder contents should look like this: - `[patching-home]/` - `liferay-dxp-dependencies-[version]/` ← Unzipped Dependencies - `osgi/` ← Unzipped OSGi JARs - `patching-tool/` ← Unzipped Patching Tool - `liferay-dxp-[version].war/` ← @product@ WAR File 2. Create the default profile configuration file in the Patching Tool folder: `patching-home/patching-tool/default.properties`. The contents should look like this: ```properties patching.mode=binary war.path=../../patching-home/liferay-dxp-[version].war global.lib.path=../../patching-home/liferay-dxp-dependencies-[version] liferay.home=../../patching-home ``` If you're using a different OSGi folder structure, you can specify it as the [Patching Tool Advanced Configuration](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration) documentation describes: ```properties module.framework.core.path=/osgi-home/osgi/core module.framework.marketplace.path=/osgi-home/osgi/marketplace module.framework.modules.path=/osgi-home/osgi/modules module.framework.portal.path=/osgi-home/osgi/portal module.framework.static.path=/osgi-home/osgi/static ``` 3. Download the patch (fix pack or hotfix) to install and put it in a folder called `patches` in your Patching Tool folder (i.e. `[patching-home]/patching-tool/patches`). 4. Execute the Patching Tool's `info` command: ```bash /patching-home/patching-tool> patching-tool info Loading product and patch information... Product information: * installation type: binary * build number: 7210 * service pack version: - available SP version: Not available - installable SP version: Not available * patching-tool version: 2.0.12 * time: 2019-06-03 18:30Z * host: 91WRQ72 (8 cores) * plugins: no plugins detected Currently installed patches: - ... ``` 5. Install the patch. ```bash /patching-home/patching-tool> patching-tool.sh install One patch is ready to be installed. Applying dxp... Cleaning up: [1%..10%..20%..30%..40%..50%..60%..70%..80%..90%..100%] Installing patches: [1%..10%..20%..30%..40%..50%..60%..70%..80%..90%...100%] The installation was successful. One patch is installed on the system. ``` Great! You have successfully patched the artifacts, and they are ready to be deployed on any supported Application Server. ## Related Topics [Patching Tool Advanced Configuration](/docs/7-2/deploy/-/knowledge_base/d/patching-tool-advanced-configuration) [Deploying @product@](/docs/7-2/deploy/-/knowledge_base/d/deploying-product) ================================================ FILE: en/deployment/articles-dxp/06-maintaining-liferay/01-patching-liferay-dxp/05-keeping-up-with-fix-packs/01-keeping-up-with-fix-packs-intro.markdown ================================================ --- header-id: keeping-up-with-fix-packs-and-service-packs --- # Keeping up with Fix packs and Service Packs [TOC levels=1-4] The *Announcements* section on [Liferay's Help Center page](https://help.liferay.com/hc) lists all fix pack updates, security alerts, product releases, and system updates. The approximate frequency of fix pack and service pack releases is explained [here](/docs/7-2/deploy/-/knowledge_base/d/patching-basics). The *Receive Notifications* sidebar lets you subscribe to the latest updates on products, patches, and system improvements. Click *Downloads* on the Liferay Digital Experience Platform page to access: - Latest Release - Fix Packs - Service Packs Archive - Security Advisories - Patching Tool Click *Support Information* to access the compatibility matrix, support FAQs, and more. ================================================ FILE: en/deployment/articles-dxp/08-lcs/01-intro.markdown ================================================ --- header-id: managing-liferay-dxp-with-liferay-connected-services --- # Managing Liferay DXP with Liferay Connected Services [TOC levels=1-4] Liferay Connected Services (LCS) is a set of tools and services for managing and monitoring your @product@ instances. LCS can help you install fix packs, monitor your instances' performance, activate your instances, and help you manage your subscriptions. In other words, LCS is like a butler for the mansion that is @product@. It's like having a single butler that can serve several mansions at once! | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Before going any further, you should take note of a few key terms used throughout this guide: **Project:** Represents a group of users belonging to a company or organization. For example, a project can consist of all the users from a project team or business unit, or it can include the entire company. **Environment**: Represents a physical cluster of servers or a virtual or logical aggregation of servers. **Server**: Describes a concrete @product@ instance. It can be a standalone server or a cluster node. As you go through this guide, you'll cover the following topics: - [Getting Started](/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs) - [LCS Preconfiguration](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration) - [Registering Your @product@ Server with LCS](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs) - [Using LCS](/docs/7-2/deploy/-/knowledge_base/d/using-lcs) - [Troubleshooting Your LCS Connection](/docs/7-2/deploy/-/knowledge_base/d/troubleshooting-your-lcs-connection) You'll get started with the configuration steps required to use LCS with @product@. ================================================ FILE: en/deployment/articles-dxp/08-lcs/02-getting-started.markdown ================================================ --- header-id: getting-started-with-lcs --- # Getting Started with LCS [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). To use LCS, you must register a server in an LCS environment. An LCS environment represents a physical cluster of servers or a virtual or logical aggregation of servers. Each environment is part of an LCS project. An LCS project represents a group of users belonging to a company or organization. For example, a project can consist of all the users from a project team or business unit, or it can include the entire company. LCS projects don't initially contain any environments. You must therefore create one before you can register any servers in LCS. The first time you log in to [lcs.liferay.com](https://lcs.liferay.com), LCS presents you with a wizard that walks you through the environment creation process. Click *Get Started* to begin. ![Figure 1: Click *Get Started* to begin the wizard.](../../images-dxp/lcs-onboarding-00.png) Each of these steps corresponds to a step in the wizard: 1. Select the LCS project for your new environment. You can select any of your available LCS projects. Note that each project lists its available subscriptions and whether it supports elastic subscriptions. ![Figure 2: Select the LCS project you want to create the environment in, and click *Next*.](../../images-dxp/lcs-onboarding-01.png) 2. Name and describe the environment. The name is mandatory, but the description is optional. Although you can enter anything you wish in these fields, it's best to choose a name and description that accurately identify the environment (e.g., Development, Production, Test, etc.). Note that you can change these values after creating the environment. ![Figure 3: Name and describe the environment, then click *Next*.](../../images-dxp/lcs-onboarding-02.png) 3. Select the environment's subscription type from the project's available subscriptions. Even if you won't use LCS to activate the servers defined for this environment, you must still select a subscription type. Also note that you can't change this selection after creating the environment. ![Figure 4: Select the environment's subscription type, then click *Next*.](../../images-dxp/lcs-onboarding-03.png) 4. Select whether servers that connect to this environment are part of a cluster. LCS provides additional tools in clustered environments that help you manage the cluster. For example, clustered environments show cluster-specific metrics, and fix packs apply to all cluster nodes. There are a few things to keep in mind if you set the environment as clustered: - You can't change this setting after creating the environment. - Each clustered environment can only support nodes that belong to a single cluster. To connect a different cluster's nodes, you must create a separate clustered environment exclusively for those nodes. - You must set the portal property `cluster.link.enabled` to `true` in any servers that connect to a clustered environment. ![Figure 5: Select whether this is a clustered environment, then click *Next*.](../../images-dxp/lcs-onboarding-04.png) 5. Select whether the environment allows elastic subscriptions. Elastic subscriptions let you register an unlimited amount of servers. This is critical for auto-scaling situations in which servers are created and destroyed automatically in response to demand. Elastic environments are also useful for bringing additional servers online on a temporary basis for any other purpose, such as business continuity planning. For more information, see [the documentation on elastic subscriptions](/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions#elastic-subscriptions). Also note that you can't change this selection after creating the environment. ![Figure 6: Select whether this is an elastic environment, then click *Next*.](../../images-dxp/lcs-onboarding-05.png) 6. Enable the LCS service you want to use with servers that connect to this environment. The following service is available: **Liferay Instance Activation:** Enabling this lets LCS activate any @product@ instances that connect to the environment. If you disable this service, you must activate via an XML file from Liferay support, and such instances must run version 5.0.0 or newer of the LCS client app. Note that you **must** use LCS for activation of Elastic subscriptions. Otherwise, you don't have to use LCS for activation. Portal Analytics, Fix Pack Management and Portal Properties Analysis have been removed from the list of available services. For more information about this change, please read [this article](https://help.liferay.com/hc/en-us/articles/360037317691-Liferay-Connected-Services-Feature-Deprecation-Update-March-2020) ![Figure 7: Enable or disable the LCS services you want to use for servers that connect to the environment, then click *Next*.](../../images-dxp/lcs-onboarding-06.png) 7. A completed form presents your selections. Review them and make any changes that you want. When you're finished, click *Create Environment*. ![Figure 8: This form contains each of your selections from the previous steps. Make any changes you want, then click *Create Environment*.](../../images-dxp/lcs-onboarding-07.png) After creating your environment, the wizard shows a screen that lets you download the LCS client app, download the environment's token file, and go to your project's dashboard in LCS. Before registering a server in your new environment, however, you must complete the [preconfiguration steps](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration) for that server. ================================================ FILE: en/deployment/articles-dxp/08-lcs/03-lcs-preconfiguration.markdown ================================================ --- header-id: lcs-preconfiguration --- # LCS Preconfiguration [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Before registering your server with LCS, there are a few things you must configure. The sections in this guide walk you through these steps: 1. [Downloading the LCS Client App](#downloading-the-lcs-client-app) 2. [Preconfiguring LCS to Connect Through a Proxy](#preconfiguring-lcs-to-connect-through-a-proxy) 3. [Ensuring Access to LCS](#ensuring-access-to-lcs) 4. [NTP Server Synchronization](#ntp-server-synchronization) 5. [Configuring WebSphere](#configuring-websphere): This is only necessary if you're running @product@ on the WebSphere application server. 6. [Installing the LCS Client App](#installing-the-lcs-client-app) [The last section](#upgrading-the-lcs-client-app) in this guide shows you how to upgrade the LCS client app once your server is registered with LCS. We highly recommend that you upgrade the app whenever Liferay releases a new version of it. | **Note:** You must use LCS for activation of Elastic subscriptions. Otherwise, | you don't have to use LCS for activation. You can instead request an XML | activation key from Liferay Support. ## Downloading the LCS Client App The LCS client app is included in each @product@ bundle and autodeploys when the bundle starts. The included version of the app, however, may be outdated. To get the latest version of the LCS client app, you must first download it via Liferay Marketplace. | **Note:** Even though Liferay Marketplace and this guide use the term | *purchase*, the LCS client app is free of charge. The purchase process for a | free app in Liferay Marketplace adds the app to your Liferay project, much | like downloading a free app in a mobile app store adds the app to your | account. Use these steps to purchase and download the app (if you've already purchased the app, you can skip to step 3 to download it): 1. Navigate to [the LCS client app in Liferay Marketplace](https://web.liferay.com/marketplace/-/mp/application/71774947). Sign in to Marketplace, then click the LCS client app's *Free* button. ![Figure 1: Click the app's *Free* button to begin the purchase process.](../../images-dxp/lcs-client-app-marketplace.png) 2. Select your project, accept the license agreement, and then click the *Purchase* button. Marketplace then displays your receipt. ![Figure 2: Liferay Marketplace displays your receipt for the LCS client app.](../../images-dxp/lcs-client-app-receipt.png) 3. On the receipt, click *See Purchased*. This shows where you can download the LCS client app. To download the app, click the *App* button next to the latest version of the app. | **Note:** If you must download the LCS client app later, such as when | [upgrading it](#upgrading-the-lcs-client-app), | select *Purchased Apps* from the User menu at the top-right of Liferay | Marketplace. On the Purchased Apps screen, select the project you | associated with the LCS client app and then select the app. This takes you | to the same downloads page shown in the screenshot. ![Figure 3: Click the *App* button next to the version of the app you want to download.](../../images-dxp/lcs-client-download-page.png) Great! You've successfully downloaded the LCS client app. Before installing it, however, there are a few additional pre-configuration steps you should complete. These appear next; then you'll learn how to install the app. | **Note:** If your server connects to the Internet through a proxy, you must | configure your server or the LCS client app **before** deploying the app. The | following section contains instructions on this. If your server doesn't | connect through a proxy, skip this section. ## Preconfiguring LCS to Connect Through a Proxy If your server connects to the Internet through a proxy, you must set some properties **before** deploying the LCS client app. There are two ways to do so---chose only one: 1. [As JVM app server arguments](#jvm-app-server-arguments). 2. [As LCS client app properties](#lcs-client-app-properties). | **Note:** Use only one of these methods to configure your server to connect | through a proxy. ### JVM App Server Arguments To set the proxy properties in your server, set them as JVM app server arguments. Set these properties to the appropriate values for your proxy: ```properties -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttp.proxyUser= -Dhttp.proxyPassword= -Dhttps.proxyHost= -Dhttps.proxyPort= ``` Note that the `user`, `password`, and `https` properties are only needed if your proxy requires authentication. ### LCS Client App Properties To set the proxy properties via the LCS client app, you must create and deploy a config file containing the properties. Follow these steps to do so: 1. Create the config file `com.liferay.lcs.client.configuration.LCSConfiguration.config`. In the steps that follow, you'll set the proxy properties in this file. 2. Set these `proxy*` properties to the appropriate values for your proxy: ```properties proxyHostName="" proxyHostPort="" ``` 3. If your proxy requires authentication, pass the credentials via these properties: ```properties proxyHostLogin="" proxyHostPassword="" ``` 4. If your proxy requires NTLM authentication, you must also populate these properties: ```properties proxyAuthType="ntlm" proxyDomain="" proxyWorkstation="" ``` Be sure to set `proxyDomain` and `proxyWorkstation` to the appropriate values for your proxy. Note that you can leave `proxyWorkstation` blank if you don't need it. 5. Deploy the config file to `osgi/configs`. ## Ensuring Access to LCS For the LCS client app to work, it must be able to access the following DNS names. If your server is behind a proxy and/or a firewall, then you must open access to these: - `lcs.liferay.com` - `lcs-gateway.liferay.com` As an added security measure, you can also restrict traffic to HTTPS. The next section discusses NTP server synchronization. ## NTP Server Synchronization For LCS to work properly, the application server running @product@ should be synchronized with a time server. If it's not, you may get log errors similar to these: ERROR [pool-6-thread-3][HandshakeTask:68] java.lang.RuntimeException: Handshake expired. Check that the server is synchronized with an NTP server. WARN [liferay/hot_deploy-1][LCSHotDeployMessageListener:186] LCS portlet is not connected java.lang.RuntimeException: com.liferay.jsonwebserviceclient.JSONWebServiceInvocationException: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'oauth_problem': was expecting ('true', 'false' or 'null')_ at [Source: oauth_problem=timestamp_refused&oauth_acceptable_timestamps=1477311475-1477312075; line: 1, column: 14] [Sanitized] For information on how to synchronize your application server with a time server, see your application server's documentation. ## Configuring WebSphere IBM ® WebSphere ® is a trademark of International Business Machines Corporation, registered in many jurisdictions worldwide. If you're running the WebSphere application server, then there are some additional configuration steps you must take before deploying the LCS client app: 1. Shut down the application server. 2. Add these properties in a `portal-ext.properties` file: ```properties module.framework.properties.org.osgi.framework.bootdelegation=\ __redirected,\ com.sun.ccpp,\ com.sun.ccpp.*,\ com.liferay.aspectj,\ com.liferay.aspectj.*,\ com.liferay.portal.servlet.delegate,\ com.liferay.portal.servlet.delegate*,\ com.sun.crypto.*,\ com.sun.image.*,\ com.sun.jmx.*,\ com.sun.jna,\ com.sun.jndi.*,\ com.sun.mail.*,\ com.sun.management.*,\ com.sun.media.*,\ com.sun.msv.*,\ com.sun.org.*,\ com.sun.syndication,\ com.sun.tools.*,\ com.sun.xml.*,\ com.yourkit.*,\ com.ibm.crypto.*,\ sun.*,\ javax.validation,\ javax.validation.*,\ jdk.*,\ weblogic.jndi,\ weblogic.jndi.*\ ``` 3. In your @product@ installation, delete the `osgi/state` folder. 4. Start the application server. 5. Navigate to the WebSphere console in a browser. 6. Select your server and navigate to *Java and Process Management* → *Process Definition* → *Additional Properties*. 7. Select *Java Virtual Machine* → *Custom Properties*. 8. Click *New*, and enter the following: - Name: `com.ibm.crypto.provider.DoRSATypeChecking` - Value: `false` 9. Click *Save*, then *OK* to apply changes to the master configuration. Note that for LCS client app versions prior to 5.0.0, you must also change the value of the `digital.signature.algorithm.provider` property in the app's `portlet.properties` file to `IBMJCE`: ```properties digital.signature.algorithm.provider=IBMJCE ``` ## Installing the LCS Client App Once you've addressed the above pre-configuration steps, you're ready to install the LCS client app. Follow these steps to install the app: 1. In your [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder (usually the parent folder of the application server's folder), delete this file: osgi/marketplace/Liferay Connected Services Client.lpkg 2. Place the new `Liferay Connected Services Client.lpkg` in `osgi/marketplace`. Great! Now you're all set to [register your server with LCS](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs). The next section shows you how to upgrade the LCS client app. We highly recommend that you do this whenever Liferay releases a new version of the app. ## Upgrading the LCS Client App Your server should always be running the latest version of the LCS client app. There are two ways to upgrade the app, depending on the exact LCS pre-configuration steps you followed: 1. Via Liferay Marketplace *inside* @product@. Use this method if you don't need to configure the LCS client app (e.g., to connect through a proxy) before it deploys. | **Note:** If you choose this method and have a clustered environment, you | must perform the upgrade separately on each node in your cluster. | Therefore, you may prefer to upgrade manually as detailed in the next step | to ensure that all your nodes are running the exact same version of the | LCS client app. To perform the upgrade, first navigate to *Control Panel* → *Apps* → *Purchased*. Apps needing an update are listed first. Click *Update* next to the LCS client app. Note that you may need to restart your server for the upgrade to complete. 2. Manually, after downloading the LCS client app's LPKG file to your machine. Use this method if you must pre-configure the LCS client app to connect through a proxy. | **Note:** If you used JVM app server arguments to configure your server to | connect through a proxy, then you don't need to pre-configure the LCS | client app to connect through the same proxy. To update the LCS client app manually, follow the previous sections in this guide for downloading and pre-configuring the app. Then deploy it to `[Liferay Home]/deploy` as you would any other app. Contact Liferay Support if you need additional assistance with the upgrade process. ================================================ FILE: en/deployment/articles-dxp/08-lcs/04-registration.markdown ================================================ --- header-id: activating-your-liferay-dxp-server-with-lcs --- # Registering Your Liferay DXP Server with LCS [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Follow these steps to register your @product@ server with LCS: 1. Ensure that you've completed the [LCS preconfiguration steps](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration). 2. Log in to [lcs.liferay.com](https://lcs.liferay.com). This takes you to your company's LCS project. If your company has multiple projects, from the menu to the right of the Dashboard tab select the project that's getting a new server. ![Figure 1: Select your LCS project from the menu highlighted by the red box in this screenshot.](../../images-dxp/lcs-select-project.png) 3. Select or create the environment in which to register this server. If you're using LCS for activation, upon connection to LCS your server consumes an activation key from the subscription type assigned to the environment. Note that a subscription type can only be assigned to an environment when creating the environment. If you have sufficient permissions in your company's project, you can [create a new environment](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments#creating-environments) by selecting *Add Environment*. ![Figure 2: You must register your server in an LCS environment. The red box in this screenshot highlights environments.](../../images-dxp/lcs-registration-select-environment.png) 4. Select the environment's *Registration* tab. This is where you manage and download the [environment's token file](/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens), that registers servers in the environment. In the Registration tab's *Services* section, change the Liferay Instance Activation setting, if needed. Note that if you change this option and there are servers already registered in the environment, you must regenerate the token file and use it to reconnect those servers to LCS. You'll regenerate and/or download the token in the next step. Additionally, If you disable this service, you must activate via an XML file from Liferay support, and such instances must run version 5.0.0 or newer of the LCS client app. Liferay Instance Activation is either enabled or disabled for all servers that connect to this environment. If Portal Property Analysis is selected, you can prevent LCS environment. ![Figure 3: An environment's Registration tab lets you manage the token file used to register your server in the environment.](../../images-dxp/lcs-registration.png) 5. What you do now depends on what you did in the previous step: **Changes to Liferay Instance Activation:** Regenerate and download the token. Regenerating a token causes all servers using the old token to disconnect from LCS. You must reconnect them using the new token. **No changes to LCS service selections:** Download the token. 6. Place the token file in your server's `[Liferay Home]/data` folder. Note that [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) is usually the parent folder of the application server's folder. If your server is running, it should connect to LCS in about one minute. If your server isn't running, it connects to LCS on startup. 7. Celebrate! Your @product@ server is registered in LCS. If for some reason it isn't, see the [LCS troubleshooting article](/docs/7-2/deploy/-/knowledge_base/d/troubleshooting-your-lcs-connection). | **Note:** You may be wondering what happens if LCS goes offline. Don't worry, | this doesn't cause a rift in the space-time continuum. LCS is deployed on | a global cloud infrastructure set up for automatic failure recovery. The | potential for non-availability is very low. In the event of an outage, | however, registered servers maintain a local copy of their uptime information | to transmit to LCS when it comes back online. If you use LCS for activation, | active subscriptions have a 30-day grace period to re-establish connectivity | and remain valid. This is ample time for LCS to come back online. ## Determining Your Server's LCS Connection Status In @product@, you can view your LCS connection status in the LCS client app. Access the client by clicking *Control Panel* → *Configuration* → *Liferay Connected Services*. Here's a full description of what a connected LCS client app displays: **Connection Uptime:** The duration of the client's connection with LCS. **Last Message Received:** The time the LCS client received the latest connection message from LCS. These messages occur only upon connection/reconnection and are unrelated to server metrics. It's therefore common for a long period of time to pass before the client receives another such message for a reconnection event. **Services:** The LCS services enabled for this server. Note that all servers in an environment use the same set of LCS services. LCS services can't be controlled on a server-by-server basis. Note: Portal Analytics, Fix Pack Management and Portal Properties Analysis have been removed from the list of available services. For more information about this change, please read [this article](https://help.liferay.com/hc/en-us/articles/360037317691-Liferay-Connected-Services-Feature-Deprecation-Update-March-2020) **Project Home:** A link to this server's LCS project. **Environment:** A link to this server's LCS environment. **Server Dashboard:** A link to the server on LCS. ![Figure 4: The server is connected to LCS.](../../images-dxp/lcs-server-connected.png) ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/01-intro.markdown ================================================ --- header-id: using-lcs --- # Using LCS [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Once your @product@ server is connected to LCS, you can get down to the business that LCS is designed for---managing your servers. If you're not already there, log in with your account on [lcs.liferay.com](https://lcs.liferay.com). This is where you'll manage environments, register servers and more. This articles in this section each detail one or more of LCS's features: - [**What LCS Stores About Your Liferay DXP Servers:**](/docs/7-2/deploy/-/knowledge_base/d/what-lcs-stores-about-your-liferay-dxp-servers) For LCS to work, the LCS servers must store certain information about your servers. Sensitive data, however, isn't stored on the LCS servers. This article describes the data that LCS does and doesn't store. - [**Managing LCS Users in Your Project:**](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-users-in-your-project) Learn how to manage your LCS project's users by assigning them roles. - [**Using the Dashboard:**](/docs/7-2/deploy/-/knowledge_base/d/using-the-dashboard) Learn how to manage your LCS projects and access your environments and servers in LCS. - [**Managing LCS Environments:**](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments) Learn how to create and manage your LCS project's environments. This includes instructions on generating tokens for an environment's servers. - [**Managing LCS Servers:**](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-servers) Learn how to manage your servers in LCS. This includes viewing server status and editing server settings. - [**Managing Your LCS Account:**](/docs/7-2/deploy/-/knowledge_base/d/managing-your-lcs-account) Learn how to manage your LCS account. This includes setting general account preferences, managing LCS web notifications, and configuring LCS to send you notification emails when specific events occur in your LCS projects. - [**Managing Liferay DXP Subscriptions:**](/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions) Learn how to view and manage your @product@ subscriptions for the servers in your LCS project. - [**Understanding Environment Tokens:**](/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens) Learn about the environment tokens that you use to connect your servers to LCS. ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/02-lcs-property-storage.markdown ================================================ --- header-id: what-lcs-stores-about-your-liferay-dxp-servers --- # What LCS Stores About Your Liferay DXP Servers [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). To protect your users' privacy, LCS only stores system-specific data. LCS doesn't gather or store data on your users. By default, LCS stores the following information about your server: - Portal build number and edition - Patching Tool Version - LCS Client Build Number - Application Server Name - Database Name - File Encoding - OS Name and Version - Timezone - IP Address - Java Version and Java Options - Number of Processor Cores - File System Usage - Memory Usage The other data LCS stores depends on the services you enabled in your environment token, and whether your server was connected before certain services were removed. For more information on this, see [Registering Servers with LCS](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs). If you enabled the following services, LCS gathered and stored the data listed for each: - **Portal analytics:** - Portal and portlet metrics - JVM metrics - Cache and server metrics - **Fix pack management:** - Patches installed on the server - **Portal properties analysis:** - `portal.properties` (except sensitive data) Sensitive data is any key-value pair that contains user names or passwords. For example, LCS did not store the following properties because they contain sensitive data: omniadmin.users ldap.security.credentials.0, ldap.security.credentials.1, ldap.security.credentials.2 ... facebook.connect.app.secret auth.token.shared.secret auth.mac.shared.key captcha.engine.recaptcha.key.private amazon.secret.access.key tunneling.servlet.shared.secret microsoft.translator.client.secret dl.store.s3.secret.key auto.deploy.glassfish.jee.dm.passwd LCS also did not store properties that end in `.password`, besides the following non-sensitive properties: portal.jaas.plain.password portal.jaas.strict.password login.create.account.allow.custom.password LCS also allowed you to prevent it from analyzing specific properties of your choosing, by defining blacklisted properties. LCS is no longer gathering or storing the data listed above, that was associated with enabled services. ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/03-managing-lcs-users.markdown ================================================ --- header-id: managing-lcs-users-in-your-project --- # Managing LCS Users in Your Project [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). The Users section of LCS is where you manage the LCS users that are part of your project. It's here that you can grant or revoke LCS Roles. To manage users, first click the *Users* tab just below the Dashboard tab on the upper-left of your screen. | **Note:** You can't add users to your project via the LCS UI or the LCS client | app. To add users to your project, you must contact Liferay support. ![Figure 1: The Users tab lets you manage the LCS users in your project.](../../../images-dxp/lcs-users.png) The *Users* tab displays a list of the users in your project. This list includes each user's name, email, image, LCS Roles, and a *Manage Roles* button. Each LCS user must have an assigned Role. The following Roles are available: **LCS Administrator:** All LCS functionality is available to administrators. This is the only Role that can manage other users' Roles. **LCS Environment Manager:** All LCS functionality is available in the scope of an environment, with the exception of managing other users. **LCS Environment Viewer:** Has read-only access in the scope of an environment. You should note that each of these LCS Roles assume users already have the LCS User Role in their Liferay.com accounts. The LCS User Role is granted automatically the first time a user logs into LCS. The actions that can be performed by each of the LCS Roles are detailed in the below permissions matrix. **LCS Permissions Matrix** Action |  LCS Administrator |  LCS Environment Manager |  LCS Environment Viewer | ------ | ----------------------- | ----------------------------- | ---------------------------- | Access LCS | true | true | true | Access Any Environment | true | false | false | Access a Particular Environment | true | true | true | Manage Users | true | false | false | Create and Delete Environments | true | false | false | Edit Any Environment | true | false | false | Edit a Particular Environment | true | true | false | Server Registration in Any Environment | true | false | false | Server Registration in a Particular Environment | true | true | false | Install Fix Packs in Any Environment | true | false | false | Install Fix Packs in a Particular Environment | true | true | false | Now that you know what Roles are available in an LCS project and what they do, you're ready to learn how to manage them. ## Managing LCS Roles Follow these steps to manage a user's LCS Roles: 1. Click the user's *Manage Roles* button. 2. To revoke a Role, click *Revoke Role* for that Role. 3. To assign a Role, choose the Role (and environment, if applicable) and click *Assign*. | **Note:** A user can't have an environment Role (e.g., LCS Environment | Manager, LCS Environment Viewer) and the LCS Administrator Role at the same | time. ![Figure 2: You can assign or revoke a user's LCS Roles.](../../../images-dxp/lcs-user-roles.png) ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/04-lcs-dashboard.markdown ================================================ --- header-id: using-the-dashboard --- # Using the Dashboard [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). The LCS Dashboard shows a project's environments and servers. If you're not already at the Dashboard, click it near the upper left-hand corner of your LCS site. Clicking *Dashboard* takes you to the project view. From there, you can get to the environment view and the server view. Each of these views gives you a different look into certain aspects of your LCS project. You'll start with the project view. ## Using the Project View You can get to the project view at any time by clicking the *Dashboard* tab near the upper left-hand corner of your LCS site. The project appears to the right of this tab, with a drop-down arrow for switching between projects if you have more than one. You can also switch between projects from the user menu at the top right of the Dockbar. The project view contains a Status table that lists status messages for each server in your project. For example, a status message appears for a server when the server is offline. Status messages also appear for servers when fix packs are available, monitoring is unavailable, the patching tool is unavailable, or other events occur that relate to LCS. ![Figure 1: The LCS project view shows an overview of your LCS project.](../../../images-dxp/lcs-project-view.png) LCS lists the environments in your project on the left side of the screen. You can also create new environments here by clicking the *Add Environment* tab (more on this shortly). To view an environment's settings, click the environment's gear icon. Clicking an environment shows more information about it. This takes you to the environment view. Also note that each environment's icon indicates the environment's type and status: **Red icon:** There's a problem with one or more of the environment's servers. **Green icon:** The environment's servers are operating properly. **Icon with a circle:** The environment's servers are clustered. ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/05-lcs-environments.markdown ================================================ --- header-id: managing-lcs-environments --- # Managing LCS Environments [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Environments are the key components of your LCS project. When you register a server in LCS, you do so in an environment. An environment is therefore the gateway to managing and monitoring your servers in LCS. ## Creating Environments The first time you log in to LCS, a wizard walks you through each step required to create your project's first environment. The [getting started article](/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs) explains this in detail. You can create additional environments via the same wizard or a simple form. To create an environment, click the *Add Environment* button from the Dashboard. This opens the New Environment form. Each section in this form corresponds to a step in the wizard. If you want to use the wizard instead, click the *Open Wizard* link at the top of the form. See the [getting started article](/docs/7-2/deploy/-/knowledge_base/d/getting-started-with-lcs) for a description of each setting in the form and wizard. | **Note:** When creating an environment, make your selections carefully for the | *Subscription Type*, *Cluster*, and *Elastic* fields. You can't change them | after creating the environment. ![Figure 1: The New Environment form lets you create environments.](../../../images-dxp/lcs-new-environment.png) ## Working with Environments Clicking an environment on the left-hand side of the Dashboard takes you to the environment view, which lets you manage an environment in your LCS project. The UI is segmented into three tabs: 1. **Fix Packs:** View and apply fix packs for the environment's servers. This tab only appears if a server is registered in the environment. A table displays the following information for each fix pack: - **Name:** The fix pack's name. - **Status:** The fix pack's status. - **Server:** The server the fix pack can be applied to. - **Size:** The fix pack's size. This only appears if the server is running. - **Download:** A button to download the fix pack to the server. This only appears if the server is running. Once a fix pack downloads, LCS prompts you to restart your server, which installs any downloaded fix packs. Note that you must start your server with the privileges required to write to the disk location where patches are stored and processed (the `patching-tool` folder). To use LCS to install fix packs across a cluster, follow the same procedure. LCS downloads and installs fix packs simultaneously across all nodes---you don't have to handle each separately. 2. **Registration:** Generate and download [*environment tokens*](/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens) that connect your servers to LCS. 3. **Environment Settings:** Change the environment's name, location, and description. You can also see if the environment allows clustered servers and view the environment's subscription type. Click the *Save* button to save any changes you make in the Environment Settings tab. You can also delete the environment by clicking *Delete Environment*. ![Figure 2: The LCS environment view shows an overview of an LCS environment.](../../../images-dxp/lcs-environment-view.png) ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/06-lcs-servers.markdown ================================================ --- header-id: managing-lcs-servers --- # Managing LCS Servers [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). Clicking a server in the Dashboard or environment view takes you to the server view. Server view provides detailed information about a server. To protect your users' privacy, LCS doesn't gather, store, or analyze user data. Server view is segmented into six tabs: **Page Analytics:** This service has been disabled, if you enabled it earlier you can see here the past history for metrics on page views and load times. **Snapshot Metrics:** This service has been disabled, if you enabled it earlier you can see here the past history for application, JVM, and server metrics. **Fix Packs:** This service has been disabled, if you enabled it earlier you can see here the past history for the server's available and installed fix packs. **Portal Properties:** This service has been disabled, if you enabled it earlier you can see here the past history for your portal's properties and their settings. **Details:** Displays general information about your @product@ installation, Java version, and hardware. **Server Settings:** View or change your server's name, location, and description. You can also unregister the server from LCS. | **Note:** LCS only supported Snapshot Metrics for servers running on Tomcat or | WebLogic. On other application servers you may see a console message | indicating that LCS doesn't support server metrics for your application | server. You may also see a benign `NullPointerException` for the LCS | `TaskSchedulerServiceImpl` and `ScheduleTasksCommand`. ## Details The *Details* tab shows general information about your server. There are three tabs under Details: *Software*, *Java*, and *Hardware*. Each shows information, respectively, about your @product@ installation, Java installation, and hardware. This information is useful to the Liferay Support team in the event you need their assistance. ![Figure 1: The Details tab shows information about your server.](../../../images-dxp/lcs-server-details.png) ## Server Settings Finally, the *Server Settings* tab lets you view and edit your server's name, location, and description. You can also unregister your server from LCS. ![Figure 2: You can use the Server Settings tab to give your server a fun name.](../../../images-dxp/lcs-server-settings.png) ## Page Analytics Page Analytics appears by default when you enter server view. Page Analytics shows page views and load times for the selected site and time period. By default, all sites are selected. You can select a specific site from the *Site* selector menu. You can also select a different time period in the *Period* and *Ending At* fields. The arrows next to the *Ending At* field move the selected time period up or down, respectively, by one period. For example, if you select *One Hour* in the *Period* field, pressing the right arrow next to *Ending At* moves the selected time period up by one hour. Note that at the beginning of the current time period, it can take up to 15 minutes for data to become available. Also note that data is available for three months from the time LCS collected it. By default, load times and page views for all pages are plotted against time in separate graphs. Below these graphs, a table displays summary statistics of data over the same time period, for each page. If you click a page in the table, the graphs plot the data for just that page. If you can't find the page you're looking for, you can search for it in the *Search* box at the top of the table. To plot data for all pages again, click the *All Pages* row at the bottom of the table. Load times are also color coded to indicate speed. The *Load Times* graph's background is red for values above 3,000 ms, orange for values from 2,000 to 3,000 ms, and green for values less than 2,000 ms. Likewise, the table displays all load times greater than 3,000 ms in red text. ![Figure 3: The Page Analytics interface in the LCS Server view.](../../../images-dxp/lcs-page-analytics-01.png) ## Snapshot Metrics To view other metrics and statistics of your server's performance, click the *Snapshot Metrics* tab near the top of the page. These metrics are broken down into three main categories: *Application*, *JVM*, and *Server*. Application is selected by default when you click the Snapshot Metrics button. The Application category also has three other categories: *Pages*, *Portlets*, and *Cache*. Pages lists the frequency that specific pages load, along with their average load times. Portlets lists the same statistics, but for specific portlets in your server. The Cache category lists Liferay Single VM metrics and Hibernate metrics. The following screenshot shows the statistics in the Portlets category. ![Figure 4: The LCS application metrics show portlet performance statistics, like frequency of use and average load time.](../../../images-dxp/lcs-server-metrics-application-portlets.png) The JVM category, as its name indicates, shows statistics about the JVM running on your server. This includes data on the garbage collector and memory. The number of runs, total time, and average time are listed for each garbage collector item. The memory metrics are presented in a bar chart that shows the usage of the PS Eden Space, Code Cache, Compressed Class Space, PS Old Gen, PS Survivor Space, and Metaspace. ![Figure 5: The LCS JVM metrics show performance data for memory and the garbage collector.](../../../images-dxp/lcs-server-metrics-jvm.png) Server is the third category in Snapshot Metrics. The Server category shows additional information about how your server is running. For example, horizontal bar graphs show the number of current threads running on your server, as well as the JDBC connection pools. ![Figure 6: The LCS server metrics show current threads and JDBC connection pools.](../../../images-dxp/lcs-metrics-server.png) Note that in Snapshot Metrics, the application and garbage collector metrics are based on data collected by LCS from server registration to the present. Memory and server metrics, however, show only the current state. ## Fix Packs To view your server's fix packs, click the Fix Packs tab near the top of the page. The available and installed fix packs appear in separate tables. The available fix packs table functions exactly like the Fix Packs table in environment view for downloading and installing fix packs. ![Figure 7: The Fix Packs tab displays your server's available and installed fix packs.](../../../images-dxp/lcs-server-fix-packs.png) ## Portal Properties The *Portal Properties* tab lets you view your portal's property values in a searchable table. This gives you a convenient display for your portal property settings. The properties in this table are organized into the following categories: **Default Values:** The default values for your portal's properties. **Custom Values:** Any custom values you've set for your portal's properties. This includes any property values you change via a `portal-ext.properties` file. **Dynamic Properties:** Any property values set at runtime. For example, the [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder's location depends on your configuration. To specify this folder when setting any properties that require it, use `${liferay.home}` instead of an absolute directory path. You can display any combination of these categories by selecting the corresponding checkboxes from the gear icon next to the search box at the top-right of the table. For example, by checking the *Show Default Values* and *Show Custom Values* checkboxes, the table shows your portal's default and custom property values. To show only the custom values, select only *Show Custom Values*. ![Figure 8: Click the gear icon to select the type of portal properties to show in the table.](../../../images-dxp/lcs-server-portal-properties.png) ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/07-lcs-account.markdown ================================================ --- header-id: managing-your-lcs-account --- # Managing Your LCS Account [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). To manage your LCS account, select *My Account* from the user menu in the Dockbar. This takes you to a UI with four tabs: **Projects:** Displays your LCS projects in a searchable table that lists the administrator's email address for each project. **Email Notifications:** Configure LCS to send you emails when specific events occur in your LCS projects by adding rules that define what events trigger a notification. There are no rules by default. Click the *Add Rule* button to define one. First specify the project, environment, and server for the notification. Note that you have the option of selecting all environments and servers in a project. Then check the checkbox for each event that you want to trigger an email notification. For example, if you create a notification rule with *The server unexpectedly shuts down* selected for all servers and environments in your project, then LCS sends you an email if any server in your project goes offline without a normal shut down event. Click *Save* when you're done defining the notification rule. It then appears in a table along with other existing rules. Each has Edit and Delete Action buttons. ![Figure 1: You can add rules to determine the events that trigger notifications.](../../../images-dxp/lcs-add-notification-rule.png) **Notification History:** Displays your web notification history in a searchable table. You can also select the date range from which to display notifications. **Preferences:** Manage your LCS account's preferences. You can change your account's language, time zone, and default LCS project. Your default LCS project is the one shown each time you log in to LCS. ![Figure 2: You can change your LCS account's general preferences.](../../../images-dxp/lcs-account-preferences.png) ## Using Web Notifications LCS also displays web notifications under the bell icon in the Dockbar. A red badge on this icon shows your unread notification count. LCS and Liferay Support send these notifications. For example, LCS generates notifications when a server shuts down or some other event requiring your attention occurs. To mark a notification as read, click the small *x* icon next to it. To mark all notifications as read, click the *Mark All as Read* button. To mark notifications as unread again, click the *Undo* button that appears. To see your notification history, click the *Notifications History* button. You can also access your notification history by selecting *My Account* from the user menu in the Dockbar. ![Figure 3: Web notifications let you know what's happening in your LCS projects.](../../../images-dxp/lcs-user-web-notifications.png) ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/08-lcs-subscriptions.markdown ================================================ --- header-id: managing-liferay-dxp-subscriptions --- # Managing Liferay DXP Subscriptions [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). LCS lets you use and view your @product@ subscriptions. Recall that when you [create an environment](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments#creating-environments), you assign its subscription type and choose whether LCS activates servers that connect to that environment. If you use LCS for activation, registering a server in that environment consumes an activation key from the environment's subscription type. You can also view your project's available activation keys and see how they're being used. Depending on your subscription agreement, LCS also lets you register servers via *elastic subscriptions*. Elastic subscriptions let you register an unlimited number servers. This is invaluable in auto-scaling environments, where servers are automatically created and destroyed in response to load. Note that to use elastic subscriptions, you must set the environment as elastic when you create it. Also note that LCS only uses elastic subscriptions for servers that exceed the number that the environment's subscription type allows. In other words, LCS uses the environment's regular subscriptions before any elastic subscriptions. You can access these features from the *Subscriptions* tab on the upper-left of the LCS site. This tab contains two other tabs: *Details* and *Elastic Subscriptions*. ![Figure 1: LCS lets you view and manage your subscriptions.](../../../images-dxp/lcs-subscriptions.png) There are four tables in the *Details* tab: 1. **Subscriptions:** Shows a list of the available subscriptions in your LCS project. For each subscription, this table shows the following information: - Subscription Type - Start Date - Expiration Date - Support End Date - Platform - Product - Processor Cores Allowed - Activation Keys - Used Activation Keys Note that *Processor Cores Allowed* shows the number of processor cores that the subscription allows for each server. 2. **Subscriptions Summary:** Shows how your subscriptions are currently used in your project. For each subscription type, this table shows the number of activation keys allowed, used, and available. 3. **Project Environments:** Shows your project's environments and their assigned subscription types. Each environment must have a subscription type. 4. **Project Servers:** Shows the environment and subscription type for each server in your LCS project. If any of the information in these tables is missing or incorrect, contact Liferay Support. | **Note:** If you don't use LCS for activating your servers, then you can | register as many servers as you want in LCS. | **Note:** If you try to activate a server that exceeds the number of processor | cores that your subscription allows per server, the activation fails and the | server is locked down. A console error also indicates the server's core count. | You can compare this with your subscription's processor cores allowed in LCS's | Subscriptions table. To activate the server, you can either reduce the number | of cores it uses (e.g., by deploying to different server hardware, or reducing | the number of virtual processors in a VM or container), or contact Liferay | Sales to increase the number of processor cores that your subscription allows | per server. ## Decommissioning Servers To decommission a server and free its activation key for reuse, select the server's environment on the left and then select the server. In the server's *Server Settings* tab, select *Unregister*. Also note that when you shut down a server normally, its activation key is immediately freed for reuse. If the server crashes or its shutdown is forced (e.g., kill), its activation key is freed for reuse within six minutes. ## Elastic Subscriptions Elastic subscriptions let you register an unlimited number of servers. This is crucial for auto-scaling environments where servers are created and destroyed automatically. You can view data on your elastic servers from the *Subscriptions* tab's *Elastic Subscriptions* tab. | **Note:** To register elastic servers in an environment, that environment must | be set as elastic when it's created. For more information, see the | [documentation on creating environments](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-environments#creating-environments). ![Figure 2: The *Elastic Subscriptions* tab shows details about your project's elastic servers.](../../../images-dxp/lcs-elastic-subscriptions.png) The *Elastic Subscriptions* tab displays the number of elastic servers online and the uptime details for each. A graph shows the number of elastic servers online each day, while a table lists each elastic server's start time, end time, and duration. The total duration for servers is below the table. To download a report of the table's data, click *Download Report*. Also, you can use the *Environment* and *Month* selectors above the graph to select the environment and month to show data from, respectively. The data in both the graph and the table reflect your selections here. ================================================ FILE: en/deployment/articles-dxp/08-lcs/05-using-lcs/09-lcs-tokens.markdown ================================================ --- header-id: understanding-environment-tokens --- # Understanding Environment Tokens [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). To register a server in an environment, you must use that environment's token file. LCS Administrators and Environment Managers can generate and distribute this file. It contains all the information the LCS client app needs to register the server in the environment. When the server starts up, it uses the token to connect to LCS. If you use LCS for activation, the server automatically consumes an activation key from the environment's subscription upon connection. This makes it possible to activate servers automatically on startup with no interaction required. | **Note:** For instructions on using and managing your environment tokens, see | the instructions on | [registering your server with LCS](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs). There are a few things to keep in mind when using environment tokens: - Each environment can have only one token file. If you regenerate the token, servers using the old file are disconnected from LCS and can't reconnect until receiving the new file. If the server disconnects due to token regeneration and is running version 4.0.2 or later of the LCS client app, the server enters a 30-day grace period during which it functions normally. This gives the administrator time to use the new token file to reconnect to LCS. Servers running earlier versions of the LCS client app present users with an error page until the administrator reconnects with the new token. - Use caution when distributing the token file, as anyone can use it to connect to your environment (and consume an activation key in your subscription if you're using LCS for activation). - Minimal information (server name, location, etc...) is used to register a server with LCS. You can change this information from [the server view in LCS](/docs/7-2/deploy/-/knowledge_base/d/managing-lcs-servers) at any time. - Environment tokens connect using OAuth. Using an environment token overrides the OAuth authorization cycle. If LCS Administrators or Environment Managers have never registered servers in LCS, the first time they do so an OAuth authorization entry is created in LCS. If they've previously registered servers in LCS, their existing credentials are used when they create a token file. - If the credentials of the LCS user who generated the token become invalid, you must generate a new token and use it to reconnect to LCS. An LCS user's credentials become invalid if the user leaves the LCS project or becomes an LCS Environment Manager or LCS Environment Viewer in a different environment. So why bother with environment tokens at all? Besides simplifying the LCS connection process, environment tokens are valuable in auto-scaling environments where algorithms create and destroy servers automatically. In this situation, having clients that activate and configure themselves is crucial. | **Note**: If your auto-scaling environment creates new server nodes from a | server in a system image, that server can't require human interaction during | setup. When creating such an image, you must change any portal property | settings that prevent automatic setup. By default, @product@'s setup wizard | requires human interaction. You must therefore set the `setup.wizard.enabled` | property to `false` if you want your auto-scaling environment to create new | nodes from the server. ================================================ FILE: en/deployment/articles-dxp/08-lcs/06-lcs-troubleshooting.markdown ================================================ --- header-id: troubleshooting-your-lcs-connection --- # Troubleshooting Your LCS Connection [TOC levels=1-4] | **Note:** LCS is deprecated and will be shut down on December 31, 2021. | Customers who activate LCS are advised to replace it with our latest activation | key type which is suitable for virtualized environments. | | For further information, please see [Changes to Liferay Product Activation](https://help.liferay.com/hc/en-us/articles/4402347960845-Changes-to-Liferay-Product-Activation). If you use LCS to activate @product@, your server must maintain its connection to LCS at all times. If this connection is interrupted, your server enters a grace period to allow for reconnection. Lengthy interruptions, however, can affect your server's uptime. | **Note:** You must use LCS for activation of Elastic subscriptions. Otherwise, | you don't have to use LCS for activation. You can instead request an XML | activation key from Liferay Support. The following sections in this document provide some background information and help you troubleshoot problems with your server's LCS connection: [**LCS Grace Periods:**](#lcs-grace-periods) Describes how grace periods work in LCS. You should read this section before attempting any troubleshooting steps. [**Troubleshooting:**](#troubleshooting) Presents troubleshooting steps for specific problems. [**Increasing Log Levels:**](#increasing-log-levels) If you contact Liferay Support, you'll be asked to increase your server's log levels and then provide your log files. This section shows you how to do this. | **Note:** The odds of LCS being unavailable are low. LCS is deployed on a | global cloud infrastructure set up for automatic failure recovery. | Notifications also let the LCS team react quickly to any downtime. During LCS | updates and new version releases, however, LCS is unavailable for a few | minutes while changes are applied. ## LCS Grace Periods There are 2 grace period types in LCS: 1. **Connection Grace Period:** Occurs when your activated LCS connection is interrupted. This gives you time to re-establish the connection. 2. **Subscription Grace Period:** Occurs when your subscription is about to expire. This gives you time to renew the subscription. | **Note:** These grace periods only apply to servers previously connected and | activated in LCS. If the subscription check or connection fails when a server | attempts to connect to LCS for the first time, that server doesn't enter a | grace period. It's therefore important to verify that an active subscription | is available before connecting a new server to LCS. To do this, check the | [Subscriptions tab](/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions) | in LCS. ### Connection Grace Period If your server's LCS connection is interrupted, the server continues to run and enters a grace period that lasts for up to 30 days to allow for reconnection. During this grace period, @product@ displays a warning message to administrators. Upon seeing this message, administrators should contact Liferay Support and follow the troubleshooting steps below. LCS automatically restores your server's activation upon reconnection (you shouldn't need to restart the server). If for some reason the connection can't be restored, Liferay Support will provide an alternative way to activate your server. ![Figure 1: A warning message is displayed to administrators if the server can't connect to LCS to validate the subscription.](../../images-dxp/lcs-grace-period.png) While disconnected from LCS, the LCS client app continually attempts to reconnect. If reconnection continues to fail, ensure that your server can access `lcs.liferay.com` and `lcs-gateway.liferay.com`. If the LCS client app stops attempting to reconnect, there will be no activity in the logs. In this case, you can force reconnection by redeploying the app. Follow these steps to do so: 1. In your server's [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder (usually the parent folder of the application server's folder), remove this file: osgi/marketplace/Liferay Connected Services Client.lpkg 2. Place `Liferay Connected Services Client.lpkg` in `[Liferay Home]/deploy`. If you [connect to LCS through a proxy](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration#preconfiguring-lcs-to-connect-through-a-proxy), and configured this inside the LCS client app, make sure the app you deploy is also configured to do so. You should also ensure that you've enabled email notifications in LCS for server disconnection events. To do this, you must create a notification rule that sends an email whenever the server shuts down unexpectedly. The documentation on [managing your LCS account](/docs/7-2/deploy/-/knowledge_base/d/managing-your-lcs-account) explains how to do this. ### Subscription Grace Period At least 90 days before the subscription expires, Liferay will reach out to begin the renewal process. 30 days before expiration, Liferay Support sends warning messages through the Help Center, [the LCS site](https://lcs.liferay.com), and [the Customer Portal](https://www.liferay.com/group/customer). After the expiration date, your servers may be placed in an additional grace period, which is communicated through the same support channels. If the renewal isn't completed during this grace period, then the subscription becomes inactive and the @product@ instance enters the 30-day grace period. As soon as the renewal is processed, the instance activates and any error or warning messages disappear within 24 hours. Note that by using XML activation keys (provided by Liferay Support upon request), you can continue to use your @product@ instances even after a subscription has expired. ![Figure 2: LCS sends you a notification prior to the expiration of your subscription.](../../images-dxp/lcs-support-expiration.png) ## Troubleshooting If you encounter issues with LCS, the Liferay Support team is here to help. If you need support, open a [Help Center](https://help.liferay.com/hc) ticket. You can begin troubleshooting the following scenarios, which the Liferay Support team can also assist you with. | **Note:** Before troubleshooting specific issues or contacting Liferay | Support, make sure that you've followed the LCS | [preconfiguration](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration) | and | [registration](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs) | steps correctly. ### Server Can't Reach LCS If your server can't reach LCS, verify that you can access the public sites required by LCS: - [`lcs.liferay.com`](https://lcs.liferay.com/) should be viewable in a browser. - `lcs-gateway.liferay.com` should respond on port 443: curl -vk -I "https://lcs-gateway.liferay.com" telnet lcs-gateway.liferay.com 443 ### Subscription Issues For issues related to your subscription, first review the documentation on [managing your subscription](/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions). Subscription errors usually involve one of these problems: - Your server can reach LCS, but can't locate a subscription. - Your server can reach LCS and locate a subscription, but activating your server would exceed the subscription's number of activation keys or cores. In either case, you must verify that a subscription is available and that you're not exceeding its number of activation keys or cores. You can find this information on the LCS site's Subscriptions page, as described in [the documentation on managing subscriptions](/docs/7-2/deploy/-/knowledge_base/d/managing-liferay-dxp-subscriptions). If the environment in which you're trying to activate a server isn't assigned the subscription you want to use, then you must create a new environment and assign it the correct subscription. Once assigned, you can't change an environment's subscription. Follow [the initial registration steps](/docs/7-2/deploy/-/knowledge_base/d/activating-your-liferay-dxp-server-with-lcs) for instructions on creating a new environment and activating a new server. | **Note:** When shutting down servers, you must ensure that the LCS site | receives the server shutdown commands. Otherwise, LCS may not release that | server's activation key for reuse and attempts to activate additional servers | may exceed the subscription's number of activation keys. There's a higher | likelihood of this happening in rolling deployments and/or when using | containers. For more information, see the | [KB article on properly unregistering subscriptions](https://help.liferay.com/hc/en-us/articles/360018261011). ### Invalid Token If the token is invalid, first review the documentation on [environment tokens](/docs/7-2/deploy/-/knowledge_base/d/understanding-environment-tokens). The following table lists causes and solutions for invalid tokens. |  Cause |  Solution | | ----------- | -------------- | | The LCS user who generated the token no longer has permissions. This happens when the user leaves the LCS project or becomes an LCS Environment Manager or LCS Environment Viewer in a different environment. | Regenerate the token. | | The token's file name is changed after download. | Download the token again from LCS. | | The token is regenerated. | Use the regenerated token. | ## Increasing Log Levels If you contact Liferay Support, you're asked to increase your server's log levels and then provide your log files. You can find these log files in `[Liferay Home]/logs` ([Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) is usually the parent folder of the application server's folder). There are 2 types of log files in this folder: 1. **Liferay log files:** The files `liferay.[date].log` and `liferay.[date].xml` are the logs for your @product@ installation. Note that LOG and XML files for the same date contain the same information--the only difference is the file format. 2. **LCS log files:** The `lcs-portlet-[date].log` files are the LCS client app's logs. Note that if there's only a single LCS log file, it may appear without a date as `lcs-portlet.log`. When you increase the log levels as described in the following sections, the additional log messages are written to these LCS log files. There are 2 ways to increase the log levels: 1. **In your @product@ instance's Control Panel:** This is a temporary configuration that resets upon shutting down the server. Note that if the server isn't activated, you can't access the Control Panel. In that case, Liferay Support can provide an XML activation key. 2. **In a Log4j configuration:** This is a permanent configuration that persists through server shutdown and restart. The following sections cover both options. ### Control Panel Follow these steps to increase the log levels via the Control Panel: 1. Navigate to *Control Panel* → *Configuration* → *Server Administration*. 2. Click the *Log Levels* tab. 3. Search for "lcs". 4. Change the log level for each matching entry to DEBUG. 5. While in the Control Panel, you should also navigate to *Configuration* → *Liferay Connected Services* and take a screenshot of what you see there. This is useful to Liferay Support. ### Log4j Follow these steps to increase the log levels via Log4j: 1. Download the latest LCS client as instructed in the [LCS preconfiguration article](/docs/7-2/deploy/-/knowledge_base/d/lcs-preconfiguration). The app downloads as `Liferay Connected Services Client.lpkg`. If you don't want to download the latest client, you can use the one already installed in your server: it's in `[Liferay Home]/osgi/marketplace` (just make sure to shut down your server before following the rest of the steps in this section). Recall that the [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder is usually the parent folder of the application server's folder. 2. Expand the LPKG file, then expand the `lcs-portlet-[version].war` file inside it. 3. Inside the `WAR` file, replace the contents of `WEB-INF\classes\META-INF\portal-log4j.xml` with the following configuration: 4. Save the file and repackage the WAR and LPKG (make sure not to change the names of these files). 5. Make sure your server is shut down. 6. In your installation's [Liferay Home](/docs/7-2/deploy/-/knowledge_base/d/liferay-home) folder, delete the existing LCS client app: osgi/marketplace/Liferay Connected Services Client.lpkg 7. Place the `Liferay Connected Services Client.lpkg` that you repackaged in step 4 in `osgi/marketplace`. 8. Start your server. If you need assistance with the issues in this guide, or any other issues with LCS, contact Liferay Support. ================================================ FILE: en/deployment/articles-dxp/100-reference/06-comparing-patch-levels.markdown ================================================ --- header-id: comparing-patch-levels --- # Comparing Patch Levels [TOC levels=1-4] If you're a developer, the Patching Tool can show you what changed between different @product@ patches and versions. These commands show you information about the different patch levels: `patching-tool diff`: Prints the differences between two patch levels. At least one stored patch level must be available. This command accepts options for filtering the output: - `source`: Shows the source differences between the two patch levels. - `files`: Shows a list of the modified files. - `fixed-issues`: Shows a list of LPS/LPE issues from our issue tracking system. - `html`: Specify this along with one of the filtering options (`source`, `files`, or `fixed-issues`) and after the patch levels, to write the differences to an HTML file (`--[type]-diff.html`) in the `diffs` folder. Additions are colored green and deletions are colored red. - `collisions`: Shows a list of modified files which collide with deployed plugins. For detailed usage information, run `patching-tool help diff`. `patching-tool store`: Manages patching level information for the `diff` command. Your patches must contain source code to store the patch level and to prepare usable information for the `diff` command. Here are the `store` command options: - `info`: Prints the list of patches which make up the stored patch level. - `add`: Stores the patch level that can be found in the patches directory. - `update`: Adds or updates patch level information. - `rm`: Removes previously stored patch level information. For detailed usage information, run `patching-tool help store`. ================================================ FILE: en/deployment/articles-dxp/100-reference/07-patching-tool-configuration-properties.markdown ================================================ --- header-id: patching-tool-configuration-properties --- # Patching Tool Configuration Properties [TOC levels=1-4] Here are the Patching Tool configuration properties. See [Configuring the Patching Tool](/docs/7-2/deploy/-/knowledge_base/d/configuring-the-patching-tool) for more information on configuring the Patching Tool. **patching.mode:** This can be `binary` (the default) or `source` if you're patching a source tree. Patches contain both binary and source patches. If your development team extends @product@, have them patch their source tree. **patches.folder:** Specify where to store patches. The default location is `./patches`. **war.path:** Specify the location of the @product@ installation inside your application server. Alternatively, you can specify a `.war` file here, and you can patch a @product@ `.war` for installation to your application server. **global.lib.path:** Specify the location for storing `.jar` files on the global classpath. If you're not sure, search for `portal-kernel.jar`; it's on the global classpath. This property is only valid if your `patching.mode` is `binary`. **liferay.home:** Specify the default location for the `data`, `osgi`, and `tools` folders. **source.path:** Specify the location of your @product@ source tree. This property is only valid if your `patching.mode` is `source`. Service Pack detection is available behind a proxy server. To configure your proxy, use the following settings, making sure to replace `[PROXY_IP_ADDRESS]` with your proxy server's IP address and replace the port numbers with yours: ```properties ### Proxy settings # HTTP Proxy #proxy.http.host=[PROXY_IP_ADDRESS] #proxy.http.port=80 #proxy.http.user=user #proxy.http.password=password # HTTPS Proxy proxy.https.host=[PROXY_IP_ADDRESS] proxy.https.port=80 proxy.https.user=user proxy.https.password=password # SOCKS Proxy #proxy.socks.host=[PROXY_IP_ADDRESS] #proxy.socks.port=1080 #proxy.socks.user=user #proxy.socks.password=password ``` ================================================ FILE: en/deployment/build.xml ================================================ ================================================ FILE: en/deployment/drawings/README.markdown ================================================ Use this directory to hold any miscellaneous files, such as graphical drawings, used in making the articles or images. ================================================ FILE: en/developer/appdev/articles/01-application-development/01-application-development-intro.markdown ================================================ --- header-id: application-development --- # Application Development [TOC levels=1-4] Writing applications on Liferay's standards-based platform makes your life easier. Whether you create headless services for clients to access, full-blown web applications with beautiful UIs, or anything in between, @product@ streamlines the process to help you get your job done faster. Liferay's framework embraces your existing tools and build environments like [Maven](https://maven.apache.org) and [Gradle](https://gradle.org). You can work with the standard technologies you know and leverage Liferay's APIs for Documents, Permissions, Search, or Content when you need them. Here's a high level view of what you can do: - **Deployment of existing standards-based apps:** If you have an existing app built outside of @product@, you can deploy it on @product@. The Liferay Bundler Generator and Liferay npm Bundler provide the project scaffolding and packaging to deploy [Angular](https://angular.io/), [React](https://reactjs.org/), and [Vue](https://vuejs.org/) web front-ends as Widgets. Spring Portlet MVC app conversion to [PortletMVC4Spring](https://github.com/liferay/portletmvc4spring) requires only a few steps. JSF applications work almost as-is. Portlet 3.0 or 2.0 compliant portlets deploy on @product@. - **Back-end Java services, web services, and REST services:** Service Builder is an object-relational mapper where you describe your data model in a single `xml` file. From this, you can generate the tables, a Java API for accessing your data model, and web services. On top of these, REST Builder generates OpenAPI-based REST services your client applications can call. - **Authentication and single-sign on (SSO):** OAuth 2.0, OpenID Connect, and SAML are built-in and ready to go. - **Front-end web development using Java EE and/or JavaScript:** Use Java EE standard Portlet technology (JSR 168, JSR 286, JSR 362) with CDI and/or JSF. Prefer Spring? [PortletMVC4Spring](https://github.com/liferay/portletmvc4spring) brings the Spring MVC Framework to Liferay. Rather have a client-side app? Write it in [Angular](https://angular.io/), [React](https://reactjs.org/), or [Vue](https://vuejs.org/). Been using @product@ for a while? Liferay MVC Portlet is better than ever. - **Frameworks and APIs for every need:** Be more productive by using Liferay's built-in and well-tested APIs that cover often-used features like file management(upload/download), permissions, comments, out-of-process messaging, or UI elements such as data tables and item selectors. @product@ offers many APIs for every need, from an entire workflow framework to a streamlined way of getting request parameters. - **Tool freedom:** Liferay provides Maven archetypes, [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace), [Gradle](/docs/7-2/reference/-/knowledge_base/r/gradle-plugins) and [Maven](/docs/7-2/reference/-/knowledge_base/r/maven-plugins) plugins, a [Yeoman](http://yeoman.io/)-based [theme generator](/docs/7-2/reference/-/knowledge_base/r/theme-generator), and [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) to integrate with any development workflow. On top of that, you can use our [IntelliJ plugin](/docs/7-2/reference/-/knowledge_base/r/intellij) or the Eclipse-based [Liferay Developer Studio](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) if you need a full-blown development environment. - **Developer community:** The [@product@ community](https://liferay.dev) is helpful and active. ## Getting Started with Liferay Development Want to see what it's like to develop an app on @product@? Here's a quick tour. ### Create Your Object Model and Database in One Shot You don't need a database to work with Liferay, but if your app uses one, you can design it and your object model at the same time with Liferay's object-relational mapper, [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder). You define your object model in a single `xml` file: ```xml liferay GB ``` Service Builder generates your object model, database, SOAP, and JSON web services automatically. Java classes are ready for you to implement your business logic around generated CRUD operations. The web services are mapped to your business logic. If you want a REST interface, you can create one. ### Create a REST Interface [REST Builder](/docs/7-2/appdev/-/knowledge_base/a/rest-builder) helps you define REST interfaces for your APIs, using [OpenAPI/Swagger](https://swagger.io/docs/specification/about/). Create your [YAML definition](https://swagger.io/docs/specification/basic-structure/) file for your REST interface along with a configuration file defining where Java classes, a client, and tests should be generated, and you have REST endpoints ready to call your API. Next, you need a client. You can use @product@ in headless mode and write your web and mobile clients any way you want. Or you can create your web clients on Liferay's platform and take advantage of its many tools and APIs that speed up development. ### Create a Web Client @product@ is an ideal platform upon which to build a web client. Its Java EE-based technology means you can pick from the best it has to offer: Spring MVC using [PortletMVC4Spring](https://github.com/liferay/portletmvc4spring), the new backwards-compatible Portlet 3, JSF using [Liferay Faces](https://liferayfaces.org), or the venerable OSGi-based [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet). If you're a front-end developer, deploy your Angular, React, or Vue-based front-end applications to run as widgets next to the rest of @product@'s installed applications. ### Use Liferay's Frameworks Your apps need features. Liferay has implemented tons of common functionality you can use in your applications. The [Liferay-UI](@platform-ref@/7.2-latest/taglibs/util-taglib/liferay-ui/tld-summary.html) tag library has tons of web components like Search Container (a sortable data table), panels, buttons, and more. Liferay's [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) can publish data from your application in context wherever users need it---as a notification, a related asset, as tagged or categorized data, or as relevant data based on a [user segment](/docs/7-2/user/-/knowledge_base/u/creating-user-segments). Need to provide file upload/download? Use the [Documents API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Need a robust permissions system? Use [Liferay permissions](/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions). Want users to submit comments? Use Liferay's [comments](/docs/7-2/frameworks/-/knowledge_base/f/adding-comments-to-your-app). Need to process data outside the request/response? Use the Message Bus. Should users select items from a list? Use the [Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/item-selector). ## Next Steps So what's next? [Download](/download) @product@ and [create your first project](/docs/7-2/reference/-/knowledge_base/r/creating-a-project)! Have a look at our [back-end](/docs/7-2/appdev/-/knowledge_base/a/service-builder), [REST Builder](/docs/7-2/appdev/-/knowledge_base/a/rest-builder), and [front-end](/docs/7-2/appdev/-/knowledge_base/a/web-front-ends) docs, examine what Liferay's [frameworks](/docs/7-2/frameworks/-/knowledge_base/f/frameworks) have to offer, and then go create the beautiful things that only you can make. ================================================ FILE: en/developer/appdev/articles/02-web-front-ends/01-web-front-ends-intro.markdown ================================================ --- header-id: web-front-ends --- # Developing Web Front-Ends [TOC levels=1-4] Liferay's open development framework removes barriers so developers can write applications faster. If you already have an application, you can deploy it on @product@: - Java-based standards (CDI, JSF, Portlets, Spring) - Front-end standards (Angular, React, Vue) If you plan to write a new application and deploy it on @product@, you can use the frameworks you know along with the build tools (Gradle, Maven) you know. Liferay also offers its own development framework called MVC Portlet that it uses to develop applications. When you want to integrate with [Liferay services](/docs/7-2/appdev/-/knowledge_base/a/service-builder) and frameworks such as permissions, assets, and indexers, you'll find that these easily and seamlessly blend with your application to provide a great user experience. Regardless of your development strategy for applications, you'll find @product@ to be a flexible platform that supports anything you need to write. ## Using Popular Frameworks Liferay gives you a head start on developing and deploying apps that use these popular Java and JavaScript-based technologies: - [Angular Widget](/docs/7-2/appdev/-/knowledge_base/a/developing-an-angular-application) - [React Widget](/docs/7-2/appdev/-/knowledge_base/a/developing-a-react-application) - [Vue Widget](/docs/7-2/appdev/-/knowledge_base/a/developing-a-vue-application) - [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) - [PortletMVC4Spring Portlet](/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring) - [JSF Portlet](/docs/7-2/appdev/-/knowledge_base/a/jsf-portlet) | **Note:** The Reference section describes | [sample projects](/docs/7-2/reference/-/knowledge_base/r/sample-projects) and | [project templates](/docs/7-2/reference/-/knowledge_base/r/project-templates) | for creating UIs using other technologies. Angular, React, and Vue applications are written the same as you would outside of @product@---using [npm](https://www.npmjs.com/) and the webpack dev server. The Liferay JS Generator creates a portlet bundle (project) for developing and deploying each type of app. The bundle project comes with npm commands for building, testing, and deploying the app. It packages the app's dependencies (including JavaScript packages), deploys the bundle as a JAR, and installs the bundle to @product@'s run time environment, making your app available as a widget. You can also develop web front-ends using Java EE standards. @product@ supports the [JSR 362](https://jcp.org/en/jsr/detail?id=362) Portlet 3.0 standard which is backwards-compatible with the [JSR 286](http://jcp.org/en/jsr/detail?id=286) Portlet 2.0 standard from the Java Community Process (JCP). Each portlet framework has benefits you may wish to consider. Bean Portlet is the only framework containing all of the Portlet 3 features: - Contexts and Dependency Injection (CDI) - Extended method annotations - Explicit render state - Action, render, and resource parameters - Asynchronous support If you're a JavaServer Faces (JSF) developer, the [Liferay Faces Bridge](/docs/7-1/reference/-/knowledge_base/r/understanding-liferay-faces-bridge) supports deploying 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. If Spring is your thing, Spring Portlet MVC portlets are easy to configure and deploy on @product@. You can continue using Spring features, including Spring beans and Spring dependency injection. Last but not least, Liferay MVC Portlet continues to be a favorite with experienced Liferay developers, and makes portlet development easy for Liferay newcomers. It leverages OSGi Declarative Services (DS) for injecting dependencies and defining configurable extension points. Since @product@ core and Liferay-written apps use DS, gaining experience with DS helps you develop @product@ extensions and customizations. Liferay MVC Portlet works seamlessly with many Liferay frameworks, such as MVC commands, Service Builder, and more. No matter which development framework you choose, you'll be able to get an app up and running fast. ## Getting Started If you have an existing app that uses one the frameworks described above, your first step is to deploy it to @product@. Most deployments involve configuration steps that you can complete in an hour or less. You can also build apps from scratch using the tools you like or leveraging Liferay's tool offering. Liferay provides templates for creating all kinds of apps and samples that you can examine and modify to fit your needs. Once your app is functional, you can improve your app by integrating it with Liferay frameworks: - Localization - Permissions - Search and indexing - Asset publishing - Workflow - Staging - Data export and import Liferay provides frameworks that integrate these features fast. As you develop apps on @product@, you'll enjoy using what you know, discover frameworks and tools that boost your productivity, and have fun creating rich, full-featured applications. If you're experienced with developing one of the listed app types, feel free to jump ahead to it. Otherwise, Angular Widgets is next. ================================================ FILE: en/developer/appdev/articles/02-web-front-ends/02-angular-widget/01-developing-an-angular-application-intro.markdown ================================================ --- header-id: developing-an-angular-application --- # Developing an Angular Application [TOC levels=1-4] Running an existing Angular app on @product@ makes the app available as a widget for using on site pages. You can [adapt your existing Angular app](/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product), but this doesn't give you access to the bundler and its various loaders to develop your project further in @product@. To have access to all of @product@'s features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, adapt your routes and CSS, and deploy your bundle. ![Figure 1: Apps like this Guestbook app are easy to migrate to @product@.](../../../images/appdev-angular-app-migrated.png) Follow these steps: 1. Using [npm](https://www.npmjs.com), install the Liferay JS Generator: npm install -g yo generator-liferay-js 2. Generate an Angular-based portlet bundle project for deploying your app to your [@product@ installation](/deployment/docs/installing-product). yo liferay-js Select `Angular based portlet` and opt for generating sample code. Here's the bundle's structure: - `[my-angular-portlet-bundle]` - `assets/` → CSS, HTML templates, and resources - `css/` → CSS files - `styles.css` → Default CSS file - `app/` → HTML templates - `app.component.html` → Root component template - `features/` → @product@ bundle features - `localization/` → Resource bundles - `Language.properties` → Default language keys - `src/` → JavaScript an TypeScript files - `app/` → Application modules and Components - `app.component.ts` → Main component - `app.module.ts` → Root module - `dynamic.loader.ts` → Loads an Angular component dynamically for the portlet to attach to - `types/` - `LiferayParams.ts` → Parameters passed by @product@ to the JavaScript module - `index.ts` → Main module invoked by the "bootstrap" module to initialize the portlet - `polyfills.ts` → Fills in browser JavaScript implementation gaps - `package.json` → npm bundle configuration - `README.md` - `.npmbuildrc` → Build configuration - `.npmbundlerrc` → Bundler configuration - `tsconfig.json` → TypeScript configuration 3. Copy your app files, matching the types listed below, into your new project. | File type | Destination | Comments | | --------- | ----------- | -------- | | HTML | `assets/app/` | Merge your main component with the existing `app.component.html`. | | CSS | `assets/css/` | Overwrite `styles.css`. | | TypeScript and JavaScript | `src/app/` | Merge with all files **except** `app.module.ts`---the root module merge is explained in a later step. | 4. Update your component class `templateUrl`s to use the `web-context` value declared in your project's `.npmbundlerrc` file. Here's the format: templateUrl: `/o/[web-context]/app/[template]` Here's an example: templateUrl: '/o/my-angular-guestbook/app/add-entry/add-entry.component.html' 5. Update your bundle to use portlet-level styling. - Import all component CSS files through the CSS file (default is `styles.css`) your bundle's `package.json` file sets for your portlet. Here's the default setting: ```json "portlet": { "com.liferay.portlet.header-portlet-css": "/css/styles.css", ... } ``` - Remove `selector` and `styleUrls` properties from your component classes. 6. In your routing module's `@NgModule` decorator, configure the router option `useHash: true`. This tells Angular to use client-side routing in the form of `.../#/[route]`, which prevents client-side parameters (i.e., anything after `#`) from being sent back to @product@. For example, your routing module class `@NgModule` decorator might look like this: ```javascript @NgModule({ imports: [RouterModule.forRoot(routes, {useHash: true})], exports: [RouterModule] }) export class AppRoutingModule { } ``` 7. Also in your routing module, export your view components for your root module (discussed next) to use. For example, ```javascript export const routingComponents = [ViewComponent1, ViewComponent2] ``` 8. Merge your root module with `src/app/app.module.ts`, configuring it to dynamically load components. | **Note:** Components must be loaded dynamically to attach to the portlet's | DOM. The DOM is determined at run time when the portlet's page is | rendered. - Import the `routingComponents` constant and the app routing module class from your app routing module. For example, ```javascript import { AppRoutingModule, routingComponents } from './app-routing.module'; ``` - Specify the base href for the router to use in the navigation URLs. ```javascript import { APP_BASE_HREF } from '@angular/common'; ... @NgModule({ ... providers: [{provide: APP_BASE_HREF, useValue: '/'}] }) ``` - Declare the `routingComponents` constant in your `@NgModule` decorator. ```javascript @NgModule({ declarations: [ routingComponents, ... ], ... }) ``` - Make sure your `@NgModule` `bootstrap` property has no components. All components are loaded dynamically using the `entryComponents` array property. The empty `ngDoBootstrap()` method nullifies the default bootstrap implementation. ```javascript @NgModule({ ... entryComponents: [AppComponent], bootstrap: [], ... }) export class AppModule { ngDoBootstrap() {} ... } ``` Your root module `app.module.ts` should look like this: ```javascript import { APP_BASE_HREF } from '@angular/common'; import { AppRoutingModule, routingComponents } from './app-routing.module'; // more imports ... @NgModule({ declarations: [ AppComponent, routingComponents, // more declarations ... ], imports: [ AppRoutingModule, // more imports ... ], entryComponents: [AppComponent], providers: [{provide: APP_BASE_HREF, useValue: '/'}], bootstrap: [], // more properties ... }) export class AppModule { ngDoBootstrap() {} // ... } ``` 9. Merge your app `package.json` file's `dependencies` and `devDependencies` into the bundle's `package.json`. | **Note:** To work around build errors caused by the `rxjs` dependency, set | the dependency to version `"6.0.0"`. See | [LPS-92848](https://issues.liferay.com/browse/LPS-92848) | for details. 10. Finally, deploy your bundle: npm run deploy Congratulations! Your Angular app is deployed and now available as a widget that you can add to site pages. The Liferay npm Bundler confirms the deployment: Report written to liferay-npm-bundler-report.html Deployed my-angular-guestbook-1.0.0.jar to c:\git\bundles The @product@ console confirms your bundle started: 2019-03-22 20:17:53.181 INFO [fileinstall-C:/git/bundles/osgi/modules][BundleStartStopLogger:39] STARTED my-angular-guestbook_1.0.0 [1695] To find your widget, select the *Add* icon (![Add](../../../images/icon-add-app.png)), navigate to *Widgets* and then the category you specified to the Liferay Bundle Generator (*Sample* is the default category). ## Related Topics [Web Services](/docs/7-2/frameworks/-/knowledge_base/f/web-services) [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) [Localization](/docs/7-2/frameworks/-/knowledge_base/f/localization) ================================================ FILE: en/developer/appdev/articles/02-web-front-ends/03-react-widget/01-developing-react-application-intro.markdown ================================================ --- header-id: developing-a-react-application --- # Developing a React Application [TOC levels=1-4] Running an existing React app on @product@ makes the app available as a widget for using on site pages. You can [adapt your existing React app](/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product), but this doesn't give you access to the bundler and its various loaders to develop your project further in @product@. To have access to all of @product@'s features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, update your static resource paths, and deploy your bundle. ![Figure 1: Apps like this Guestbook app are easy to migrate to @product@.](../../../images/appdev-react-app-migrated.png) Follow these steps: 1. Using [npm](https://www.npmjs.com/), install the Liferay JS Generator: npm install -g yo generator-liferay-js 2. Generate a React based portlet bundle project for deploying your app to your [@product@ installation](/deployment/docs/installing-product). yo liferay-js Select `React based portlet` and opt for generating sample code. Here's the bundle's structure: - `my-react-portlet-bundle` - `assets/` → CSS and resources - `css/` → CSS files - `styles.css` → Default CSS file - `features/` → @product@ bundle features - `localization` → Resource bundles - `Language.properties` → Default language keys - `src/` → JavaScript and React component files - `AppComponent.js` → Sample React component that you can remove - `index.js` → Main module used to initialize the portlet - `.babelrc` → Babel configuration - `.npmbuildrc` → Build configuration - `.npmbundlerrc` → Bundler configuration - `package.json` → npm bundle configuration - `README.md` 3. Copy your app files, matching the types listed below, into your new project. | File type | Destination | Comments | | --------- | ----------- | -------- | | CSS | `assets/css/` | Overwrite `styles.css`. | | JavaScript | `src/` | Merge with all files **except** `index.js`---the main module merge is explained in a later step. | | Static resources | `assets/` | Include resources such as image files here | 4. Update your bundle to use portlet-level styling. - Import all component CSS files through the CSS file (default is `styles.css`) your bundle's `package.json` file sets for your portlet. Here's the default setting: ```json "portlet": { "com.liferay.portlet.header-portlet-css": "/css/styles.css", ... } ``` - Remove any CSS imports you have in your JS files 5. Update any static resource references to use the `web-context` value declared in your project's `.npmbundlerrc` file, and remove any imports for the resource. For example, if you have an image file called `logo.png` in your `assets` folder, you would use the format below. Note that the `assets` folder is not included in the path. Here is the format: ```html /o/[web-context]/[resource] ``` Here's an example image resource: ```html React logo ``` 6. Merge your entry module with `src/index.js`, configuring it to dynamically load components. | **Note:** Components must be loaded dynamically to attach to the portlet's | DOM. The DOM is determined at run time when the portlet's page is | rendered. - Use the `HashRouter` for routing between component views, as @product@ requires hash routing for proper portal navigation: ```javascript import { HashRouter as Router } from 'react-router-dom'; ``` - Place your code inside the `main()` function. - Render your app inside the `portletElementId` element that is passed in the `main()` function. This is required to render the React app inside the portlet. Your entry module `index.js` should look like this. ```javascript import React from 'react'; import ReactDOM from 'react-dom'; //import './index.css';//removed for Portal Migration import App from './App'; import { HashRouter as Router } from 'react-router-dom'; export default function main({portletNamespace, contextPath, portletElementId}) { ReactDOM.render(( ), document.getElementById(portletElementId)); } ``` 7. Merge your app `package.json` file's `dependencies` and `devDependencies` into the bundle's `package.json`. 8. Finally, deploy your bundle: npm run deploy Congratulations! Your React app is deployed and now available as a widget that you can add to site pages. The Liferay npm Bundler confirms the deployment: Report written to liferay-npm-bundler-report.html Deployed my-react-guestbook-1.0.0.jar to c:\git\bundles The @product@ console confirms your bundle started: 2019-03-22 20:17:53.181 INFO [fileinstall-C:/git/bundles/osgi/modules][BundleStartStopLogger:39] STARTED my-react-guestbook_1.0.0 [1695] To Find your widget, click the *Add* icon (![Add](../../../images/icon-add-app.png)), navigate to *Widgets* and then the category you specified to the Liferay Bundle Generator (*Sample* is the default category). ## Related Topics [Web Services](/docs/7-2/frameworks/-/knowledge_base/f/web-services) [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) [Localization](/docs/7-2/frameworks/-/knowledge_base/f/localization) ================================================ FILE: en/developer/appdev/articles/02-web-front-ends/04-vue-widget/01-developing-a-vue-application-intro.markdown ================================================ --- header-id: developing-a-vue-application --- # Developing a Vue Application [TOC levels=1-4] Running an existing Vue app on @product@ makes the app available as a widget for using on site pages. You can [adapt your existing Vue app](/docs/7-2/reference/-/knowledge_base/r/adapting-existing-apps-to-run-on-product), but this doesn't give you access to the bundler and its various loaders to develop your project further in @product@. To have access to all of @product@'s features, you must use the Liferay JS Generator and Liferay npm Bundler to merge your files into a portlet bundle, update your static resource paths, and deploy your bundle. The steps below demonstrate how to prepare a Vue app that uses single file components (`.vue` files) with multiple views. ![Figure 1: Vue Apps like this Guestbook App are easy to deploy, and they look great in @product@.](../../../images/appdev-vue-migrated.png) | **Note:** if you have a tree of components expressed as `.vue` templates, only | the root one will be available as a true AMD module. Follow these steps: 1. Using [npm](https://www.npmjs.com/), install the Liferay JS Generator: npm install -g yo generator-liferay-js 2. Generate a Vue based portlet bundle project: yo liferay-js Select `Vue based portlet` and opt for generating sample code. Here's the bundle's structure: - `my-vue-portlet-bundle` - `assets/` → CSS and resources - `css/` → CSS not included in `.vue` files. - `features/` → @product@ bundle features - `localization/` → Resource bundles - `Language.properties` → Default language keys - `settings.json` → Placeholder System Settings - `src/` → JavaScript and Vue files - `index.js` → Main module used to initialize the portlet - `.babelrc` → Babel configuration - `.npmbuildrc` → Build configuration - `.npmbundlerrc` → Bundler configuration - `package.json` → npm bundle configuration - `README.md` 3. Copy your app files, matching the types listed below, into your new project. | File type | Destination | Comments | | --------- | ----------- | -------- | | CSS | `assets/css/` | Overwrite `styles.css`. | | Static resources | `assets` | Include resources such as image files here | | VUE and JS| `src` | Merge your main component with the existing `index.js`. More info on that below. | 4. Update your bundle to use portlet-level styling. - If you have internal CSS included with ` ================================================ FILE: en/developer/appdev/code/vue-guestbook/after/vue-guestbook-migrated/src/components/AddEntry.vue ================================================ ================================================ FILE: en/developer/appdev/code/vue-guestbook/after/vue-guestbook-migrated/src/components/ViewGuestbook.vue ================================================ ================================================ FILE: en/developer/appdev/code/vue-guestbook/after/vue-guestbook-migrated/src/index.js ================================================ import Vue from 'vue/dist/vue.common'; // use Vue's runtime + compiler module import App from './App.vue' import VueRouter from 'vue-router' import AddEntry from './components/AddEntry.vue' import ViewGuestbook from './components/ViewGuestbook.vue' export default function main({portletNamespace, contextPath, portletElementId}) { Vue.config.productionTip = false Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: "/", redirect: { name: "ViewGuestbook" } }, { path: '/view-guestbook', name: 'ViewGuestbook', component: ViewGuestbook }, { path: '/add-entry', name: 'AddEntry', component: AddEntry } ] }) new Vue({ el: `#${portletElementId}`, render: h => h(App), router }) } ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/.gitignore ================================================ .DS_Store node_modules /dist # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw* ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/README.md ================================================ # vue-guestbook ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Run your tests ``` npm run test ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/babel.config.js ================================================ module.exports = { presets: [ '@vue/app' ] } ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/package.json ================================================ { "name": "vue-guestbook", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "vue": "^2.6.6", "vue-router": "^3.0.2" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.5.0", "@vue/cli-plugin-eslint": "^3.5.0", "@vue/cli-service": "^3.5.0", "babel-eslint": "^10.0.1", "eslint": "^5.8.0", "eslint-plugin-vue": "^5.0.0", "vue-template-compiler": "^2.5.21" }, "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "rules": {}, "parserOptions": { "parser": "babel-eslint" } }, "postcss": { "plugins": { "autoprefixer": {} } }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] } ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/public/index.html ================================================ vue-guestbook
    ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/src/Api.js ================================================ // import axios from 'axios'; export default class Api { static getEntries() { // This is a mock web service call // return axios.get(`https://localhost:8080/o/api/p/gb/entry`); // This returns mock data return [ { id: 1, name: 'Joe Bloggs', message: 'Had an awesome Time!' }, { id: 2, name: 'Jane Bloggs', message: 'Great event!' }, { id: 3, name: 'Bill Bloggs', message: 'Had a good time.' }, { id: 4, name: 'Bob Nosester', message: 'Great atmosphere!' }, { id: 5, name: 'Martha Nosester', message: 'Lovely aromas.' } ] // Here's a service to try too // return axios.get(`https://jsonplaceholder.typicode.com/users`); } static addEntry(entry) { return fetch('http://localhost:8080/o/api/p/gb/entry', { method: 'POST', body: JSON.stringify(entry), headers: { "Content-type": "application/json; charset=UTF-8" } }) .then(response => response.json()) .then(json => this.console.log(json)) } } ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/src/App.vue ================================================ ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/src/components/AddEntry.vue ================================================ ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/src/components/ViewGuestbook.vue ================================================ ================================================ FILE: en/developer/appdev/code/vue-guestbook/before/vue-guestbook/src/main.js ================================================ import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' import AddEntry from './components/AddEntry.vue' import ViewGuestbook from './components/ViewGuestbook.vue' Vue.config.productionTip = false Vue.use(VueRouter) const router = new VueRouter({ routes: [ { path: "/", redirect: { name: "ViewGuestbook" } }, { path: '/view-guestbook', name: 'ViewGuestbook', component: ViewGuestbook }, { path: '/add-entry', name: 'AddEntry', component: AddEntry } ] }) new Vue({ render: h => h(App), router }).$mount('#app') ================================================ FILE: en/developer/build.xml ================================================ ================================================ FILE: en/developer/customization/articles/01-liferay-customization-intro.markdown ================================================ --- header-id: liferay-customization --- # Liferay Customization [TOC levels=1-4] @product@ is highly customizable. Its modular architecture contains components you can extend and override dynamically. This section explains @product@'s architecture and customization fundamentals and demonstrates overriding and extending @product@ components and applications using APIs. - [Fundamentals](/docs/7-2/customization/-/knowledge_base/c/fundamentals) include understanding and configuring dependencies, packaging, and deployment. Here you'll work with module JARs, plugin WARs, components, and Java packages in @product@. - [Architecture](/docs/7-2/customization/-/knowledge_base/c/architecture) dives deep into how @product@ 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. - Built-in customization features, including [Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates) and [Web Experience Management](/docs/7-2/user/-/knowledge_base/u/web-experience-management) help you customize content and pages faster. All this is done from within the @product@ UI. - Application customization articles (listed after the Architecture articles) demonstrate modifying Liferay applications via their APIs and extension points. Start with [Fundamentals](/docs/7-2/customization/-/knowledge_base/c/fundamentals). ================================================ FILE: en/developer/customization/articles/02-fundamentals/01-fundamentals-intro.markdown ================================================ --- header-id: fundamentals --- # Fundamentals [TOC levels=1-4] The fundamentals of developing on @product@ and customizing it are perhaps best learned in the context of projects. It's in projects that you configure access to @product@'s API, extend and override @product@ 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: - **WARs Versus OSGi JAR** explains fundamental differences between the WAR and OSGi JAR structures and how they're deployed in @product@. - **Configuring Dependencies** demonstrates how to identify and configure Liferay artifacts and third-party artifacts to use their Java packages in your projects. - **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. - **Semantic Versioning** shows how @product@ uses a standard for ascribing meaning to major, minor, and micro versions of modules and Java packages. - **Deploying WARs (WAB Generator)** explains how Liferay's WAB Generator deploys WAR applications as OSGi Web Application Bundles (WABs). - **Gogo Shell** enables you to examine components, debug issues, and manage deployments. Start with understanding how WAR and OSGi JAR project structures are used in development. ================================================ FILE: en/developer/customization/articles/02-fundamentals/02-configuring-dependencies/01-configuring-dependencies-intro.markdown ================================================ --- header-id: configuring-dependencies --- # Configuring Dependencies [TOC levels=1-4] @product@'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. - [Finding Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) explains how to use the Application Manager, Gogo Shell, and @product@ reference documentation to find artifacts deployed on @product@ and available in repositories. - [Specifying Dependencies](/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies) demonstrates specifying artifacts to Maven and Gradle build frameworks. It shows you how to determine whether @product@ already exports packages from an artifact and how to configure such artifacts as compile-time dependencies. - [Resolving Third-Party Library Package Dependencies](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module) 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. Your first step is to find the artifacts you need. ================================================ FILE: en/developer/customization/articles/02-fundamentals/02-configuring-dependencies/02-finding-artifacts.markdown ================================================ --- header-id: finding-artifacts --- # Finding Artifacts [TOC levels=1-4] 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 [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/)). 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: - *Group ID*: Authoring organization - *Artifact ID*: Name/identifier - *Version*: Release number Here you'll learn how to find artifact attributes to specify artifact dependencies. ## Finding Core Artifact Attributes Each Liferay artifact is a JAR file whose `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: Bundle-SymbolicName: [artifact ID] Bundle-Version: [version] | **Important:** Artifacts in @product@ fix packs override @product@ | installation artifacts. Subfolders of a fix pack ZIP file's `binaries` folder | hold the artifacts. If an installed fix pack provides an artifact you depend | on, specify the version of that fix pack artifact in your dependency. This table lists each core @product@ artifact's group ID and artifact ID and where to find the artifact's manifest, which lists the artifact version: *Core @product@ Artifacts*: | File | Group ID | Artifact ID | Version | Origin | | :------------ | :--------------- | :-------- | :--------- | :------ | | `portal-kernel.jar` | `com.liferay.portal` | `com.liferay.portal.kernel` | (see JAR's `MANIFEST.MF`) | fix pack ZIP, @product@ installation, or @product@ dependencies ZIP | | `portal-impl.jar` | `com.liferay.portal` | `com.liferay.portal.impl` | (see JAR's `MANIFEST.MF`) | fix pack ZIP or @product@ `.war` | | `util-bridges.jar` | `com.liferay.portal` | `com.liferay.util.bridges` | (see JAR's `MANIFEST.MF`) | fix pack ZIP or @product@ `.war` | | `util-java.jar` | `com.liferay.portal` | `com.liferay.util.java` | (see JAR's `MANIFEST.MF`) | fix pack ZIP or @product@ `.war` | | `util-slf4j.jar` | `com.liferay.portal` | `com.liferay.util.slf4j` | (see JAR's `MANIFEST.MF`) | fix pack ZIP or @product@ `.war` | | `util-taglibs.jar` | `com.liferay.portal` | `com.liferay.util.taglib` | (see JAR's `MANIFEST.MF`) | fix pack ZIP or @product@ `.war` | | `com.liferay.*` JAR files | `com.liferay` | (see JAR's `MANIFEST.MF`) | (see JAR's `MANIFEST.MF`) | fix pack ZIP, @product@ installation, @product@ dependencies ZIP, or the OSGi ZIP | Next, you'll learn how to find @product@ app and independent module artifact attributes. ## Finding Liferay App and Independent Artifacts Independent modules and @product@ app modules aren't part of the @product@ core. You must still, however, find their artifact attributes if you depend on them. The resources below provide the artifact details for @product@'s apps and independent modules: | Resource | Artifact Type | | :-------- | :-------------- | | [App Manager](#app-manager) | Deployed modules | | [Reference Docs](#reference-docs) | @product@ modules (per release) | | [Maven Central](#maven-central) | All artifact types: @product@ and third party, module and non-module | | **Important**: `com.liferay` is the group ID for all of Liferay's apps and | independent modules. The App Manager is the best source for information on deployed modules. You'll learn about it next. ### App Manager [The App Manager](/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps#using-the-app-manager) knows what's deployed on your Liferay instance. Use it to find deployed module attributes. 1. In @product@, navigate to *Control Panel* → *Apps* → *App Manager*. 2. 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. ![Figure 1: You can inspect deployed module artifact IDs and version numbers.](../../../images/configuring-dependencies-search-app-manager-for-module.png) ![Figure 2: The App Manager aggregates Liferay and independent modules.](../../../images/configuring-dependencies-indep-modules-in-app-manager.png) If you don't know a deployed module's group ID, use the [Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) to find it: 1. Navigate to the Gogo Shell portlet in the Control Panel → *Configuration* → *Gogo Shell*. Enter commands in the Felix Gogo Shell command prompt. 2. Search for the module by its display name (e.g., `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. ![Figure 3: Results from this Gogo command show that the module's number is `1173`.](../../../images/configuring-deps-gogo-grep-for-module.png) 3. List the module's manifest headers by passing the module number to the `headers` command. In the results, note the `Bundle-Vendor` value: you'll match it with an artifact group in a later step: ![Figure 4: Results from running the `headers` command show the module's bundle vendor and bundle version.](../../../images/configuring-deps-gogo-module-info.png) 5. On [Maven Central](https://search.maven.org/) or [MVNRepository](https://mvnrepository.com), search for the module by its artifact ID. 6. Determine the group ID by matching the `Bundle-Vendor` value from step 3 with a group listed that provides the artifact. Next, @product@'s reference documentation provides @product@ app artifact attributes. ### Reference Docs @product@'s app Javadoc lists each app module's artifact ID, version number, and display name. This is the best place to look up @product@ app modules that aren't yet deployed to your @product@ instance. | **Note:** To find artifact information on a Core @product@ artifact, refer to | the previous section *Finding Core @product@ Artifact Attributes*. Follow these steps to find a @product@ app module's attributes in the Javadoc: 1. Navigate to Javadoc for an app module class. If you don't have a link to the class's Javadoc, find it by browsing [@app-ref@](@app-ref@). 2. Copy the class's package name. 3. Navigate to the *Overview* page. 4. On the *Overview* page, search for the package name you copied in step 2. 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 `com.liferay`. ![Figure 5: @product@ 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.](../../../images/intro-configuring-dependencies-module-info-in-javadoc-overview.png) | **Note**: Module version numbers aren't currently included in any tag library | reference docs. Next, you'll learn how to look up artifacts on MVNRepository and Maven Central. ### Maven Central Most artifacts, regardless of type or origin, are on [MVNRepository](https://mvnrepository.com/) and [Maven Central](https://search.maven.org/). 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 `org.osgi.service.component.annotations.Component`, search for the package name `org.osgi.service.component.annotations` on one of the Maven sites. | **Note:** Make sure to follow the instructions listed earlier to determine the | version of Liferay artifacts you need. Now that you know the artifact's attributes, you can configure a dependency on it. ## Related Topics [Specifying Dependencies](/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies) [Importing Packages](/docs/7-2/customization/-/knowledge_base/c/importing-packages) [Exporting Packages](/docs/7-2/customization/-/knowledge_base/c/exporting-packages) [Resolving Third Party Library Package Dependencies](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module) [Deploying WARs \(WAB Generator\)](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) ================================================ FILE: en/developer/customization/articles/02-fundamentals/02-configuring-dependencies/03-specifying-dependencies.markdown ================================================ --- header-id: specifying-dependencies --- # Specifying Dependencies [TOC levels=1-4] Compiling your project and deploying it to @product@ requires satisfying its dependencies on external artifacts. After [finding the attributes of an artifact](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts), set a dependency for it in your build file. Here's how: 1. Determine whether @product@ provides the Java packages you use from the artifact. These files list the packages @product@ exports: - `modules/core/portal-bootstrap/system.packages.extra.bnd` file in the [GitHub repository](https://github.com/liferay/liferay-portal/blob/7.2.x/modules/core/portal-bootstrap/system.packages.extra.bnd). It lists exported packages on separate lines, making them easy to read. - `META-INF/system.packages.extra.mf` file in `[LIFERAY_HOME]/osgi/core/com.liferay.portal.bootstrap.jar`. The file is available in @product@ bundles. It lists exported packages in a paragraph wrapped at 70 columns--they're harder to read here than in the `system.packages.extra.bnd` file. 2. If @product@ 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: **Gradle:** Add the `compileOnly` directive to the dependency **Maven:** Add the `provided` element to the dependency. 3. Add a dependency entry for the artifact. Here's the artifact terminology for the Gradle and Maven build frameworks: *Artifact Terminology* | Framework | Group ID | Artifact ID | Version | | :-------- | :-------- | :----------- | :-------- | | Gradle | `group` | `name` | `version` | | Maven | `groupId` | `artifactId` | `version` | Here is an example dependency on Liferay's Journal API module for Gradle, and Maven: *Gradle (`build.gradle` entry):* ```groovy dependencies { compileOnly group: "com.liferay", name: "com.liferay.journal.api", version: "1.0.1" ... } ``` *Maven (`pom.xml` entry):* ```xml com.liferay com.liferay.journal.api 1.0.1 provided ``` | **Important:** | [@product@ exports many third-party packages](/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports). | Deploy your module to check if @product@ 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: `provided` | | Gradle: `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 Party Library Dependencies](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module). | | If you're developing a WAR that requires a different version of a third-party | package that | [@product@ or another module exports](/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports), | specify that package in your | [`Import-Package:` list](/docs/7-2/customization/-/knowledge_base/c/importing-packages). | If the package provider is an OSGi module, publish its exported packages by | deploying that module. Otherwise, follow the instructions for | [adding a third-party library (not an OSGi module)](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module). Nice! You know how to specify artifact dependencies. Now that's a skill you can depend on! ## Related Topics [Finding Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) [Importing Packages](/docs/7-2/customization/-/knowledge_base/c/importing-packages) [Exporting Packages](/docs/7-2/customization/-/knowledge_base/c/exporting-packages) [Resolving Third Party Library Package Dependencies](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module) [Deploying WARs \(WAB Generator\)](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) ================================================ FILE: en/developer/customization/articles/02-fundamentals/02-configuring-dependencies/04-resolving-third-party-library-package-dependencies.markdown ================================================ --- header-id: adding-third-party-libraries-to-a-module --- # Resolving Third Party Library Package Dependencies [TOC levels=1-4] 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: **Option 1 - Find an OSGi module of the library**: Projects, such as [Eclipse Orbit](https://www.eclipse.org/orbit/) and [ServiceMix Bundles](https://servicemix.apache.org/developers/source/bundles-source.html), convert hundreds of traditional Java libraries to OSGi modules. Their artifacts are available at these locations: - [Eclipse Orbit downloads \(select a build\)](https://download.eclipse.org/tools/orbit/downloads/) - [ServiceMix Bundles](https://mvnrepository.com/artifact/org.apache.servicemix.bundles) 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, [deploy](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project) it. Then [add a compile-only dependency](/docs/7-2/customization/-/knowledge_base/c/specifying-dependencies) 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. | **Tip:** Refrain from embedding library JARs that provide the same | [packages that @product@ or existing modules provide already](/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports). | **Note:** If you're developing a WAR that requires a different version of a | third-party package that | [@product@ or another module exports](/docs/7-2/reference/-/knowledge_base/r/third-party-packages-portal-exports), | specify that package in your | [`Import-Package:` list](/docs/7-2/customization/-/knowledge_base/c/importing-packages). | 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 | [JAR that the WAB generator excludes](/docs/7-2/customization/-/knowledge_base/c/understanding-excluded-jars) | and embed the JAR in your project. **Option 2 - Resolve the Java packages privately in your project**: Copy *required packages* only from libraries into your project, if you can or embed *libraries* wholesale, if you must. The rest of this article shows you how to do these things. | **Note:** Features for manipulating library packages are only available to | module projects that use bnd and the `com.liferay.plugin` plugin, such as | [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace) | modules. WAR projects must embed libraries wholesale into their classpath. | **Note**: Liferay's Gradle plugin `com.liferay.plugin` automates several third | party library configuration steps. The plugin is automatically applied to | [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace) | Gradle module projects created using | [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) | or | [Liferay Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli). | | To leverage the `com.liferay.plugin` plugin outside of Liferay Workspace, add | code like the listing below to your Gradle project and update the version of | the `com.liferay.gradle.plugins` artifact to the latest version found in the | repository: | | 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" | | If you use Gradle without the `com.liferay.plugin` plugin, you must | [embed the third party libraries wholesale](#embedding-libraries-using-gradle). The recommended package resolution workflow is next. ## 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: 1. Add the library as a compile-only dependency (e.g., `compileOnly` in Gradle, `provided` in Maven). 2. Copy only the library packages you need by specifying them in a conditional package instruction (`Conditional-Package`) in your `bnd.bnd` file. Here are some examples: `Conditional-Package: foo.common*` adds packages your module uses such as `foo.common`, `foo.common-messages`, `foo.common-web` to your module's class path. `Conditional-Package: foo.bar.*` adds packages your module uses such as `foo.bar` and all its sub-packages (e.g., `foo.bar.baz`, `foo.bar.biz`, etc.) to your module's class path. [Deploy your project](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project). If a class your module needs or class its dependencies need isn't found, go back to main workflow **Step 1 - Find an OSGi module version of the library** to resolve it. **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. 3. If a library package you depend on requires non-class files (e.g., DLLs, descriptors) from the library, then you might need to [embed the library wholesale in your module](#embedding-libraries-in-a-project). This adds the entire library to your module's classpath. Next you'll learn how to embed libraries in your module project. ## Embedding Libraries in a Project You can use Gradle or Maven to embed libraries in your project. Below are examples for adding [Apache Shiro](https://shiro.apache.org) using both build utilities. ### Embedding Libraries Using Gradle Open your module's `build.gradle` file and add the library as a dependency in the `compileInclude` configuration: ```groovy dependencies { compileInclude group: 'org.apache.shiro', name: 'shiro-core', version: '1.1.0' } ``` The `com.liferay.plugin` plugin's `compileInclude` configuration is transitive. The `compileInclude` configuration embeds the artifact and all its dependencies in a `lib` folder in the module's JAR. Also, it adds the artifact JARs to the module's `Bundle-ClassPath` manifest header. **Note**: The `compileInclude` configuration does not download transitive [optional dependencies](https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html). If your module requires such artifacts, add them as you would another third party library. **Note:** If the library you've added as a dependency in your `build.gradle` file has transitive dependencies, you can reference them by name in an `-includeresource:` instruction without having to add them explicitly to the dependency list. See how it's used in the Maven section next. ### Embedding a Library Using Maven Follow these steps: 1. Open your project's `pom.xml` file and add the library as a dependency in the `provided` scope: ```xml org.apache.shiro shiro-core 1.1.0 provided ``` 2. Open your module's `bnd.bnd` file and add the library to an `-includeresource` instruction: -includeresource: META-INF/lib/shiro-core.jar=shiro-core-[0-9]*.jar;lib:=true This instruction adds the `shiro-core-[version].jar` file as an included resource in the module's `META-INF/lib` folder. The `META-INF/lib/shiro-core.jar` is your module's embedded library. The expression `[0-9]*` helps the build tool match the library version to make available on the module's class path. The `lib:=true` directive adds the embedded JAR to the module's class path via the `Bundle-Classpath` manifest header. Lastly, if after embedding a library you get unresolved imports when trying to deploy to Liferay, you might need to blacklist some imports: ``` Import-Package:\ !foo.bar.baz,\ * ``` The `*` 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. ## Related Topics [Importing Packages](/docs/7-2/customization/-/knowledge_base/c/importing-packages) [Exporting Packages](/docs/7-2/customization/-/knowledge_base/c/exporting-packages) [Creating a Project](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) ================================================ FILE: en/developer/customization/articles/02-fundamentals/02-configuring-dependencies/05-understanding-excluded-jars.markdown ================================================ --- header-id: understanding-excluded-jars --- # Understanding Excluded JARs [TOC levels=1-4] [Portal property `module.framework.web.generator.excluded.paths`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Module%20Framework) declares JARs that are stripped from all @product@ [generated WABs](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator). These JARs are excluded from web application bundles (WABs) because @product@ provides them already. All JARs listed for this property are excluded from a WAB, even if the WAB lists the JAR in a `portal-dependency-jars` property in its [`liferay-plugin-package.properties`](@platform-ref@/7.2-latest/propertiesdoc/liferay-plugin-package_7_2_0.properties.html) file. If your WAR requires different versions of the packages @product@ exports, you must include them in JARs named differently from the ones `module.framework.web.generator.excluded.paths` excludes. For example, @product@'s [`system.packages.extra`](https://github.com/liferay/liferay-portal/blob/7.2.x/modules/core/portal-bootstrap/system.packages.extra.bnd) module exports Spring Framework version 4.1.9 packages: ``` Export-Package:\ ... org.springframework.*;version='4.1.9',\ ... ``` @product@ uses the `module.framework.web.generator.excluded.paths` portal property to exclude their JARs. ```properties 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,\ ... ``` To use a different Spring Framework version in your WAR, you must name the corresponding Spring Framework JARs differently from the glob-patterned JARs `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 `WEB-INF/lib` but name it something other than `spring-aop.jar`. Adding the version to the JAR name (i.e., `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). ## Related Topics [Configuring Dependencies](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) [Deploying WARs \(WAB Generator\)](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) ================================================ FILE: en/developer/customization/articles/02-fundamentals/03-felix-gogo-shell/01-felix-gogo-shell-intro.markdown ================================================ --- header-id: using-the-felix-gogo-shell --- # Using the Felix Gogo Shell [TOC levels=1-4] The Gogo shell provides a way to interact with @product@'s module framework. You can - dynamically install/uninstall bundles - examine package dependencies - examine extension points - list service references - etc. 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 @product@ 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 *Configuration* → *Gogo Shell*. You can also interact with @product@'s module framework via a local telnet session. This is only recommended when you're developing your @product@ instance. This is not recommended for production environments. To open the Gogo shell via telnet, execute the following command: ```bash telnet localhost 11311 ``` Running this command requires a local running instance of @product@ and your machine's telnet command line utilities enabled. You must also have [Developer Mode enabled](/docs/7-2/frameworks/-/knowledge_base/f/using-developer-mode-with-themes#enabling-developer-mode-manually). To disconnect the session, execute the `disconnect` command. Avoid using the following commands, which stop the OSGi framework: - `close` - `exit` - `shutdown` If you have [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) installed and the telnet capability enabled, you can run the Gogo shell via Blade command too: ```bash blade sh ``` Here are some useful Gogo shell commands: `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 `diag [BUNDLE_ID]`: lists information about why the specified bundle is not working (e.g., unresolved dependencies, etc.) `headers [BUNDLE_ID]`: lists metadata about the bundle from the bundle's `MANIFEST.MF` file `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 `help` command is `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 `felix:refresh` command to be distinguished from the `equinox:refresh` command. `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. `inspect capability service [BUNDLE_ID]`: lists services exposed by a bundle `install [PATH_TO_JAR_FILE]`: installs the specified bundle into Liferay's module framework `lb`: lists all of the bundles installed in Liferay's module framework. Use the `-s` flag to list the bundles using the bundles' symbolic names. `packages [PACKAGE_NAME]`: lists all of the named package's dependencies `scr:list`: lists all of the components registered in the module framework (*scr* stands for service component runtime) `scr:info [COMPONENT_NAME]`: lists information about a specific component including the component's description, services, properties, configuration, references, and more. `services`: lists all of the services that have been registered in Liferay's module framework `start [BUNDLE_ID]`: starts the specified bundle `stop [BUNDLE_ID]`: stops the specified bundle `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 `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 `LIFERAY_HOME/osgi` folder. For more information on the `uninstall` command, see OSGi's [uninstall](https://osgi.org/javadoc/r6/core/org/osgi/framework/Bundle.html#uninstall\(\)) documentation. For more information about the Gogo shell, visit [Apache's official documentation](http://felix.apache.org/documentation/subprojects/apache-felix-gogo.html). ================================================ FILE: en/developer/customization/articles/02-fundamentals/04-importing-packages/01-importing-packages-intro.markdown ================================================ --- header-id: importing-packages --- # Importing Packages [TOC levels=1-4] 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 [export](/docs/7-2/customization/-/knowledge_base/c/exporting-packages) 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 `META-INF/MANIFEST.MF` file must specify the [`Import-Package`](https://bnd.bndtools.org/heads/import_package.html) OSGi manifest header with a comma-separated list of the Java packages it needs. For example, if a bundle needs classes from the `javax.portlet` and `com.liferay.portal.kernel.util` packages, it must specify them like so: ``` Import-Package: javax.portlet,com.liferay.portal.kernel.util,* ``` The `*` 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, @product@ [project templates](/docs/7-2/reference/-/knowledge_base/r/project-templates) and [tools](/docs/7-2/reference/-/knowledge_base/r/tooling) 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: - [Automatic Package Import Generation](#automatic-package-import-generation) - [Manually Adding Package Imports](#manually-adding-package-imports) Let's explore how package imports are specified in these scenarios. ## Automatic Package Import Generation [Gradle and Maven module projects](/docs/7-2/reference/-/knowledge_base/r/project-templates) created using [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli), [Liferay's Maven archetypes](/docs/7-2/reference/-/knowledge_base/r/maven), or [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) use [bnd](http://bnd.bndtools.org/). On building such a project's module JAR, bnd detects the packages the module uses and generates a `META-INF/MANIFEST.MF` file whose `Import-Package` header specifies the packages. | **Note:** Liferay's Maven module archetypes use the `bnd-maven-plugin`. | Liferay's Gradle module project templates use | [a third-party Gradle plugin](https://github.com/TomDmitriev/gradle-bundle-plugin) | to invoke bnd. For example, suppose you're developing a Liferay module using Maven or Gradle. In most cases, you specify your module's dependencies in your `pom.xml` or `build.gradle` file. At build time, the Maven or Gradle module plugin reads your `pom.xml` or `build.gradle` file and bnd adds the required `Import-Package` headers to your module JAR's `META-INF/MANIFEST.MF`. Here's an example dependencies section from a module's `build.gradle` file: ```groovy 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" } ``` And here's the `Import-Package` header that's generated in the module JAR's `META-INF/MANIFEST.MF` file: 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 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 [third party library JARs](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module). Regarding classes used by a plugin WAR, [Liferay's WAB Generator](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) detects their use in the WAR's JSPs, descriptor files, and classes (in `WEB-INF/classes` and embedded JARs). The WAB Generator searches the `web.xml`, `liferay-web.xml`, `portlet.xml`, `liferay-portlet.xml`, and `liferay-hook.xml` descriptor files. It adds package imports for classes that are neither found in the plugin's `WEB-INF/classes` folder nor in its embedded JARs. | **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 *not* | provide the exact contract, your bundle does not resolve. Resolving the | missing package is better than handling an incompatibility failure during | execution. | | - **Blade CLI and Liferay @ide@ module projects** specify Portable Java | Contracts automatically! For example, if your Blade CLI or Liferay @ide@ | 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. | | - **Module projects that use bnd but are not created using Blade CLI or | Liferay @ide@** must specify contracts in their `bnd.bnd` file. For | example, here are contract instructions for Java Portlet and Java Servlet | APIs: | | -contract: JavaPortlet,JavaServlet | | 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 *removes* version range information from | `Import-Package` entries for corresponding API packages---the package | version information isn't needed. | | - **Projects that don't use bnd** must specify contracts in their OSGi | bundle manifest. For example, here's the specified contract for | `JavaPortlet` 2.0, which goes in your `META-INF/MANIFEST.MF` file: | | Import-Package: javax.portlet | Require-Capability: osgi.contract;filter:=(&(osgi.contract=JavaPortlet)(version=2.0)) | | For Portable Java Contract details, see | [Portable Java Contract Definitions](https://www.osgi.org/portable-java-contract-definitions/). ## Manually Adding Package Imports The WAB Generator and bnd don't add package imports for classes referenced in these places: - Unrecognized descriptor file - Custom or unrecognized descriptor element or attribute - Reflection code - Class loader code In such cases, you must manually determine these packages and specify an `Import-Package` OSGi header that includes these packages and the packages that Bnd detects automatically. The `Import-Package` header belongs in the location appropriate to your project type: | Project type | `Import-Package` header location | | :----------- | :------------------------------- | | Module (uses bnd) | `[project]/bnd.bnd` | | Module (doesn't use bnd) | `[module JAR]/META-INF/MANIFEST.MF` | | Traditional Liferay plugin WAR | `WEB-INF/liferay-plugin-package.properties` | Here's an example of adding a package called `com.liferay.docs.foo` to the list of referenced packages that Bnd detects automatically: ``` Import-Package:\ com.liferay.docs.foo,\ * ``` | **Note:** The | [WAB Generator](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) | refrains from adding WAR project embedded | third-party JARs to a WAB if | [@product@ already exports the JAR's packages](/docs/7-2/customization/-/knowledge_base/c/understanding-excluded-jars). | | If your WAR requires a different version of a third-party package that | @product@ exports, specify that package in your `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 | [adding third-party libraries](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module). Please see the [`Import-Package`](https://bnd.bndtools.org/heads/import_package.html) header documentation for more information. Congratulations! Now you can import all kinds of packages for your modules and plugins to use. ## Related Topics [Configuring Dependencies](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) [Deploying WARs \(WAB Generator\)](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) [Project Templates](/docs/7-2/reference/-/knowledge_base/r/project-templates) [Liferay's Maven Archetypes](/docs/7-2/reference/-/knowledge_base/r/maven) [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) ================================================ FILE: en/developer/customization/articles/02-fundamentals/05-exporting-packages/01-exporting-packages-intro.markdown ================================================ --- header-id: exporting-packages --- # Exporting Packages [TOC levels=1-4] 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 [import](/docs/7-2/customization/-/knowledge_base/c/importing-packages). To export a package, add it to your module's or plugin's `Export-Package` OSGi header. A header exporting `com.liferay.petra.io` and `com.liferay.petra.io.unsync` would look like this: Export-Package:\ com.liferay.petra.io,\ com.liferay.petra.io.unsync The correct location for the header depends on your project's type: | Project Type | `Export-Package` header location | | :----------- | :------------------------------- | | Module JAR (uses bnd) | `[project]/bnd.bnd` | | Module JAR (doesn't use bnd) | `[module JAR]/META-INF/MANIFEST.MF` | | Plugin WAR | `WEB-INF/liferay-plugin-package.properties` | Module projects created using [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli), [Liferay's Maven archetypes](/docs/7-2/reference/-/knowledge_base/r/maven), or [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) use [bnd](http://bnd.bndtools.org/). On building such a project's module JAR, bnd propagates the OSGi headers from the project's `bnd.bnd` file to the JAR's `META-INF/MANIFEST.MF`. In module projects that don't use bnd, you must manually add package exports to an `Export-Package` header in the module JAR's `META-INF/MANIFEST.MF`. In plugin WAR projects, you must add package exports to an `Export-Package` header in the project's `WEB-INF/liferay-plugin-package.properties`. On copying the WAR into the `[Liferay Home]/deploy` folder, the [WAB Generator](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) propagates the OSGi headers from the WAR's `liferay-plugin-package.properties` file to the `META-INF/MANIFEST.MF` file in the generated Web Application Bundle (WAB). | **Note:** bnd makes a module's exported packages *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. | [Peter Kriens' blog post](http://blog.osgi.org/2007/04/importance-of-exporting-nd-importing.html) | provides more details on how substitutable exports works. | **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. Now you can share your module's or plugin's terrific [EDITOR: or terrible!] packages with other OSGi bundles! ## Related Topics [Configuring Dependencies](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) [Deploying WARs \(WAB Generator\)](/docs/7-2/customization/-/knowledge_base/c/deploying-wars-wab-generator) [Project Templates](/docs/7-2/reference/-/knowledge_base/r/project-templates) [Liferay's Maven Archetypes](/docs/7-2/reference/-/knowledge_base/r/maven) [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) [Semantic Versioning](/docs/7-2/customization/-/knowledge_base/c/semantic-versioning) ================================================ FILE: en/developer/customization/articles/02-fundamentals/06-semantic-versioning/01-semantic-versioning-intro.markdown ================================================ --- header-id: semantic-versioning --- # Semantic Versioning [TOC levels=1-4] [Semantic Versioning](https://semver.org) 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, [bnd](http://bnd.bndtools.org) (used when building [Liferay generated module projects](/docs/7-2/reference/-/knowledge_base/r/creating-a-project)) fails that project's build immediately. The semantic version format looks like this: MAJOR.MINOR.MICRO Certain events force each tier to increment: - *MAJOR:* an incompatible, API-breaking change is made - *MINOR:* a change that affects only providers of the API, or new backwards- compatible functionality is added - *MICRO:* a backwards-compatible bug fix is made For more details on semantic versioning, see the official [Semantic Versioning](https://semver.org/) site and [OSGi Alliance's Semantic Versioning](http://www.osgi.org/wp-content/uploads/SemanticVersioning1.pdf) technical whitepaper. All of @product@'s modules use Semantic Versioning. Following Semantic Versioning is especially important because @product@ 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 [Liferay tooling](/docs/7-2/reference/-/knowledge_base/r/tooling), managing your module project's versions is easy. ## 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 *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 *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 *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: ./gradlew baseline See the [Baseline Gradle Plugin](/docs/7-2/reference/-/knowledge_base/r/baseline-gradle-plugin) article for configuration details. This plugin is not provided in [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace) by default. When you run the `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. ## Managing Artifact and Dependency Versions There are two ways to track your project's artifact and dependency versions with Semantic Versioning: - Range of versions - Exact version (one-to-one) You should track a range of versions if you intend to build your project for multiple versions of @product@ 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 [OSGi Specifications](https://osgi.org/specification/osgi.core/7.0.0/framework.module.html#i3189032). A version range for imported packages in an OSGi bundle's `bnd.bnd` looks like this: Import-Package: com.liferay.docs.test; version="[1.0.0,2.0.0)" Popular build tools also follow this syntax. In Gradle, a version range for a dependency looks like this: compile group: "com.liferay.portal", name: "com.liferay.portal.test", version: "[1.0.0,2.0.0)" In Maven, it looks like this: ```xml com.liferay.portal com.liferay.portal.test [1.0.0,2.0.0) ``` 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 `version: "latest.release"`. This can be done in Maven 2.x with the usage of the version marker `RELEASE`. This is not possible if you're using Maven 3.x. See [Gradle](https://gradle.org/docs) and [Maven](http://maven.apache.org/guides/)'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 @product@. 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. | **Note:** When specifying package versions in your `bnd.bnd` file, exact | versions are typically specified like this: `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: `[1.1.2]`. See the | [Version Range](https://osgi.org/specification/osgi.core/7.0.0/framework.module.html#i3189032) | section in the OSGi specifications for more info. | | Gradle and Maven use exact versions when only one version is specified. You now know the pros and cons for tracking dependencies as a range and as an exact match. ## Related Topics [Importing Packages](/docs/7-2/customization/-/knowledge_base/c/importing-packages) [Exporting Packages](/docs/7-2/customization/-/knowledge_base/c/exporting-packages) [Configuring Dependencies](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) ================================================ FILE: en/developer/customization/articles/02-fundamentals/07-deploying-wars-wab-generator/01-deploying-wars-wab-generator-intro.markdown ================================================ --- header-id: deploying-wars-wab-generator --- # Deploying WARs (WAB Generator) [TOC levels=1-4] You can create applications for @product@ 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 `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 *WABs*. @product@ 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 `META-INF/MANIFEST.MF` file with the `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 `.jar` or `.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 `WEB-INF/classes` and embedded JARs). The descriptor files include `web.xml`, `liferay-web.xml`, `portlet.xml`, `liferay-portlet.xml`, and `liferay-hook.xml`. The WAB Generator verifies whether the detected packages are in the plugin's `WEB-INF/classes` folder or in an embedded JAR in the `WEB-INF/lib` folder. Packages that aren't found in either location are added to an `Import-Package` OSGi header in the WAB's `META-INF/MANIFEST.MF` file. To import a package that is only referenced in the following types of locations, you must add an `Import-Package` OSGi header to the plugin's `WEB-INF/liferay-plugin-package.properties` file and add the package to that header's list of values. - Unrecognized descriptor file - Custom or unrecognized descriptor element or attribute - Reflection code - Class loader code ## WAR versus WAB Structure The WAB folder structure and WAR folder structure differ. Consider the following folder structure of a WAR-style portlet. **WAR** - `my-war-portlet` - `src` - `main` - `java` - `webapp` - `WEB-INF` - `classes` - `lib` - `resources` - `views` - `liferay-display.xml` - `liferay-plugin-package.properties` - `liferay-portlet.xml` - `portlet.xml` - `web.xml` When a WAR-style portlet is deployed to @product@ and processed by the WAB Generator, the portlet's folder structure is transformed. **WAB** - `my-war-portlet-that-is-now-a-wab` - `META-INF` - `MANIFEST.MF` - `WEB-INF` - `classes` - `lib` - `resources` - `views` - `liferay-display.xml` - `liferay-plugin-package.properties` - `liferay-portlet.xml` - `portlet.xml` - `web.xml` The major difference is the addition of the `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 `liferay-plugin-package.properties` file. | **Note:** Adding a `bnd.bnd` file or a build-time plugin (e.g., | `bnd-maven-plugin`) to your WAR plugin is pointless, because the generated WAB | cannot use them. ## Deploying a WAR To deploy a WAB based on your WAR plugin, copy your WAR plugin to your @product@ instance's `deploy/` folder in your [`[Liferay Home]`](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). ## 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 `[Liferay Home]/portal-ext.properties` file. Then restart @product@: ```properties module.framework.web.generator.generated.wabs.store=true module.framework.web.generator.generated.wabs.store.dir=${module.framework.base.dir}/wabs ``` These properties instruct the WAB generator to store generated WABs in your Liferay instance's `osgi/wabs/` folder. The generated WABs have the same structure as the example WAB structure listed above. The [Module Framework Web Application Bundles](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Module%20Framework%20Web%20Application%20Bundles) 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! ## Related Topics [Creating a Project](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) [Deploying a Project](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project) [Developing Web Front-Ends](/docs/7-2/appdev/-/knowledge_base/a/web-front-ends) ================================================ FILE: en/developer/customization/articles/03-architecture/01-architecture-intro.markdown ================================================ --- header-id: architecture --- # Architecture [TOC levels=1-4] @product@ architecture comprises these parts: **Core:** Bootstraps @product@ and its frameworks. The Core provides a runtime environment for managing services, UI components, and customizations. **Services:** Liferay and custom functionality is exposed via Java APIs and web APIs. **UI:** The optional web application UI for adding portals, sites, pages, widgets, and content. You can use the @product@ UI and services together or focus solely on using services via [REST web APIs](/docs/7-2/frameworks/-/knowledge_base/f/headless-rest-apis). ![Figure 1: @product@ portals and Sites contain content and widgets. @product@ can also be used "headless"---without the UI.](../../images/architecture-options.png) The architecture satisfies these requirements: - Supports using common development technologies - Leverages development standards - Facilitates swapping components - Starts fast and performs well - Its runtime is easy to configure and inspect The Core supports UI and service deployments and orchestrates wiring them together. ## Core @product@ is a web application that runs on your application server. The Core bootstraps the application and [Liferay's built-in frameworks](/docs/7-2/frameworks/-/knowledge_base/f/frameworks). There are frameworks for these things and more: - [Adaptive Media](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media) - [Application Configuration](/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications) - [Application Security](/docs/7-2/frameworks/-/knowledge_base/f/application-security) - [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) - [File Management](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api) - [Localization](/docs/7-2/frameworks/-/knowledge_base/f/localization) - [Search](/docs/7-2/frameworks/-/knowledge_base/f/search) - [Segmentation and Personalization](/docs/7-2/frameworks/-/knowledge_base/f/segmentation-personalization) - [Upgrade Processes](/docs/7-2/frameworks/-/knowledge_base/f/upgrade-processes) - [Web Fragments](/docs/7-2/frameworks/-/knowledge_base/f/page-fragments) - [Workflow](/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework) The Core provides the component runtime environment for the frameworks, services, and UI. Here are some component examples: - [Services](/docs/7-2/appdev/-/knowledge_base/a/service-builder) - [Service customizations](/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers) - [Portlets](/docs/7-2/frameworks/-/knowledge_base/f/portlets) (templates, controllers, and resources) - [JavaScript applications](/docs/7-2/appdev/-/knowledge_base/a/web-front-ends) (templates, routers, and resources) - [JSP customization via Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) - [Theme]((/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction)) - [Shared Language Keys](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-language-module) - [Navigation components](/docs/7-2/frameworks/-/knowledge_base/f/screen-navigation-framework) The following figure shows these component types in the runtime environment. ![Figure 2: The Core provides a runtime environment for components, such as the ones here. New component implementations can extend or replace existing implementations dynamically.](../../images/component-runtime-environment.png) The runtime environment supports adding, replacing, and customizing components on-the-fly. This makes the following scenarios possible: **Replacement:** If the `ServiceC Impl 2` component has a higher ranking than existing component `ServiceC Impl 1`, `ServiceC Impl 2` is used in its place. **Customization:** The `PortletA Filter` intercepts and modifies requests to and responses from `PortletA`, affecting the content `PortletA` displays. Component WAR and module JAR projects install as [OSGi bundles](https://www.osgi.org/) (modules). @product@'s OSGi framework defines the module lifecycle, enforces dependencies, defines the class loading structure, and provides an API and CLI ([Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell)) for managing modules and components. The Core is configured via [portal properties files](/docs/7-2/deploy/-/knowledge_base/d/portal-properties) and [Server Administration panels](/docs/7-2/user/-/knowledge_base/u/server-administration). The service components provide business functionality. ## 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 @product@ via [dependency injection](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services). Front-end applications invoke the services to do work. You can deploy Java-based applications that call services directly using the [Java APIs](/docs/7-2/reference/-/knowledge_base/r/java-apis), and any web-based (Java and non-Java) application, whether deployed on @product@ or not, can use the web APIs, which include [headless REST APIs](/docs/7-2/appdev/-/knowledge_base/a/generating-apis-with-rest-builder) that conform to the [OpenAPI](https://swagger.io/docs/specification/about/) standard and include [plain web/REST services](/docs/7-2/frameworks/-/knowledge_base/f/web-services). The following figure shows @product@ applications and external clients invoking Liferay services. ![Figure 3: Remote and @product@ applications can invoke services via REST web APIs. @product@ Java-based portlets can also invoke services via Java APIs.](../../images/apps-invoking-services.png) Liferay services are built using [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) and made REST-ful using [REST Builder](/docs/7-2/appdev/-/knowledge_base/a/rest-builder). The services are easy to [override and extend](/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services) too. @product@ also provides a web-based UI, which makes content and service functionality available in browsers. ## UI [@product@'s UI](/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction) helps people do work, [collaborate](/docs/7-2/user/-/knowledge_base/u/collaboration), and [enjoy content](/docs/7-2/user/-/knowledge_base/u/web-experience-management). The UI consists of - [@product@ application](/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction): The web application for managing Portals, Sites, Users, Pages, Widgets, and more. - [Applications](/docs/7-2/appdev/-/knowledge_base/a/application-development): Widgets that provide a user interface for services already deployed. - [Themes](/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction): Plugins for styling Sites with a unique look and feel. The UI concepts article digs deeper into developing and customizing UI components. As you can see, the @product@ 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 @product@ architecture! ================================================ FILE: en/developer/customization/articles/03-architecture/02-classloader-hierarchy.markdown ================================================ --- header-id: liferay-portal-classloader-hierarchy --- # Liferay Portal Classloader Hierarchy [TOC levels=1-4] All @product@ 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: - Web application, such as Liferay Portal, deployed on the app server - OSGi bundle deployed in the Module Framework The following diagram shows @product@'s classloader hierarchy. ![Figure 1.0: Here is Liferay's classloader hierarchy.](../../images/portal-classloader-hierarchy.png) Here are the classloader descriptions: - **Bootstrap**: The JRE's classes (from packages `java.*`) and Java extension classes (from `$JAVA_HOME/lib/ext`). No matter the context, loading all `java.*` classes is delegated to the Bootstrap classloader. - **System**: Classes configured on the `CLASSPATH` and or passed in via the application server's Java classpath (`-cp` or `-classpath`) parameter. - **Common**: Classes accessible globally to web applications on the application server. - **Web Application**: Classes in the application's `WEB-INF/classes` folder and `WEB-INF/lib/*.jar`. - **Module Framework**: Liferay's OSGi module framework classloader which is used to provide controlled isolation for the module framework bundles. - **bundle**: Classes from a bundle's packages or from packages other bundles export. - **JSP**: A classloader that aggregates the following bundle and classloaders: - Bundle that contains the JSPs' classloader - JSP servlet bundle's classloader - Javax Expression Language (EL) implementation bundle's classloader - Javax JSTL implementation bundle's classloader - **Service Builder**: Service Builder classes 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 `java.*` packages. Classloading from a web application perspective is up next. ## Web Application Classloading Perspective Application servers dictate where and in what order web applications, such as @product@, search for classes and resources. Application servers such as [Apache Tomcat](https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html) enforce the following default search order: 1. Bootstrap classes 2. Web app's `WEB-INF/classes` 3. web app's `WEB-INF/lib/*.jar` 4. System classloader 5. Common classloader 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 [Oracle WebLogic](https://docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html) 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. ## Other Classloading Perspectives [Bundle Classloading Flow](/docs/7-2/customization/-/knowledge_base/c/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 @product@'s classloading hierarchy, understand it in context of web applications, and have references to information on other classloading perspectives. ## Related Topics [Bundle Classloading Flow](/docs/7-2/customization/-/knowledge_base/c/bundle-classloading-flow) ================================================ FILE: en/developer/customization/articles/03-architecture/03-portal-startup-phases.markdown ================================================ --- header-id: liferay-startup-phases --- # @product@ Startup Phases [TOC levels=1-4] 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 [implement actions for phase events](#acting-on-events). Startup consists of these main phases: 1. **Portal Context Initialization Phase:** focuses on low level tasks without a web context. 2. **Main Servlet Initialization Phase:** focuses on the portlet container and the @product@ web application's UI features such as Struts, Themes, and more. The Portal Context Initialization Phase sets the stage for the Main Servlet Initialization Phase. ### Portal Context Initialization Phase The Portal Context Initialization phase runs first with these tasks: 1. Set up low level utilities such as logging and those in [`PortalUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/PortalUtil.html) and [`InitUtil`](@platform-ref@/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/InitUtil.html). 2. OSGi framework is initialized. 3. Spring Phase 1: INFRASTRUCTURE beans specified by the Spring context files listed in Portal property [`spring.infrastructure.configs`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Spring) are loaded. 4. INFRASTRUCTURE beans are published as [OSGi services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services). 5. OSGi framework starts. 1. Static bundles are installed and started. 2. Dynamic bundles are started. 6. OSGi framework starts the runtime. 7. Spring Phase 2: MAIN 1. Load Spring beans specified by the Spring context files listed in Portal property [`spring.configs`](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Spring). 2. A [`ModuleServiceLifecycle` event service](#moduleservicelifecycle-events) with a service property `module.service.lifecycle` value `spring.initialized` (i.e., [`SPRING_INITIALIZED`](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.SPRING_INITIALIZED)) registers. 8. MAIN Spring beans are published as [OSGi services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services). ### Main Servlet Initialization Phase Here's the phase's activity sequence: 1. The [`ModuleServiceLifecycle` event service](#moduleservicelifecycle-events) is updated with the service property `module.service.lifecycle` value `database.initialized` (i.e., [`DATABASE_INITIALIZED`](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.DATABASE_INITIALIZED)). 2. The [Global Startup event](#portal-startup-events) fires. 3. For each portal instance, the [Application Startup events](#portal-startup-events) fire. 4. The [`ModuleServiceLifecycle` event service](#moduleservicelifecycle-events) is updated with the service property `module.service.lifecycle` value `portal.initialized` (i.e., [`PORTAL_INITIALIZED`](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.PORTAL_INITIALIZED)). Now that you're acquainted with the startup phases, you can concentrate on the events they fire. ## Acting on Events The ways to act on events depends on the event type. These subsections describe the event types. ### ModuleServiceLifecycle Events [You can wait for and act on `ModuleServiceLifecycle` event services.](/docs/7-2/customization/-/knowledge_base/c/waiting-on-lifecycle-events) ### Portal Startup Events In your `liferay-portal-ext.properties` file, you can override the following properties and add your own [`LifecycleAction`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/events/LifecycleAction.html) classes to the list of action classes to invoke on the events. **Global Startup Event** runs once when @product@ initializes. The [`global.startup.events` property](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Startup%20Events) defines the event's default actions. **Application Startup Events** runs once for each Site instance @product@ initializes. The [`application.startup.events` property](@platform-ref@/7.2-latest/propertiesdoc/portal.properties.html#Startup%20Events) defines the event's default actions. ## Related Topics [Waiting on Lifecycle Events](/docs/7-2/customization/-/knowledge_base/c/waiting-on-lifecycle-events) [OSGi Services and Dependency Injection with Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) ================================================ FILE: en/developer/customization/articles/03-architecture/04-benefits-of-modularity.markdown ================================================ --- header-id: the-benefits-of-modularity --- # The Benefits of Modularity [TOC levels=1-4] Dictionary.com defines [modularity](http://www.dictionary.com/browse/modularity) as *the use of individually distinct functional units, as in assembling an electronic or mechanical system.* The distinct functional units are called *modules*. NASA's Apollo spacecraft, for example, comprised three modules, each with a distinct function: - *Lunar Module*: Carried astronauts from the Apollo spacecraft to the moon's surface and back. - *Service Module*: Provided fuel for propulsion, air conditioning, and water. - *Command Module*: Housed the astronauts and communication and navigation controls. ![Figure 1: The Apollo spacecraft's modules collectively took astronauts to the moon's surface and back to Earth.](../../images/modularity_apollo_spacecraft_diagram.png) The spacecraft and its modules exemplified these modularity characteristics: - **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. - **Dependencies**: Modules can require capabilities other modules satisfy. The Apollo modules had these dependencies: - Lunar Module depended on the Service Module to get near the moon. - Command Module depended on the Service Module for power and oxygen. - Service Module depended on the Command Module for instruction. - **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. - **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. 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: - [Modularity benefits for software](#modularity-benefits-for-software) - [Example: How to design a modular application](#example-designing-a-modular-application) ## 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. ### 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. ### 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. ### 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. ### 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. ## 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 [deploy it with your app](/docs/7-2/customization/-/knowledge_base/c/adding-third-party-libraries-to-a-module). 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: *Function*: interface with users to translate their speech into text for the computer to understand. *Required capabilities*: - Translates user words to text - Uses a selected computer voice to speak to users. - Interacts with users based on a script of instructions that include questions, commands, requests, and confirmations. You could create modules to provide the required capabilities: - *Speech to text*: Translates spoken words to text the computer understands. - *Voice UI*: Interacts with users based on stored questions, commands, and confirmations. - *Instruction manager*: Stores and provides the application's questions, commands, and confirmations. - *Computer voice*: Stores and provides computer voices for users to choose from. The following diagram contrasts a monolithic design for the speech recognition application with a modular design. ![Figure 2: The speech recognition application can be implemented in a single monolithic code base or in modules, each focused on a particular function.](../../images/modularity-benefits-application-design-example.png) 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 *reused* in other scenarios too. For example, the *Instruction manager* and *Computer voice* modules can be *reused* by a navigation app. ![Figure 3: The *Instruction manager* and *Computer voice* modules designed for the speech recognition app can be used (or *reused*) by a navigation app.](../../images/modularity-benefits-module-reuse.png) Here are the benefits of designing the speech recognition app as modules: - Each module represents a capability that contributes to the app's overall function. - The app depends on modules, that are easy to develop, test, and maintain. - The modules can be reused in different applications. 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. ================================================ FILE: en/developer/customization/articles/03-architecture/05-osgi-and-modularity.markdown ================================================ --- header-id: osgi-and-modularity --- # OSGi and Modularity [TOC levels=1-4] Modularity makes writing software, especially as a team, fun! Here are some benefits to modular development on DXP: - @product@'s runtime framework is lightweight, fast, and secure. - 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. - 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. - Modules' dependencies are managed automatically by the container, dynamically (no restart required). - The container manages module life cycles dynamically. Modules can be installed, started, updated, stopped, and uninstalled while Liferay is running, making deployment a snap. - Only a module's classes whose packages are explicitly exported are publicly visible; OSGi hides all other classes by default. - 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. - Team members can develop, test, and improve modules in parallel. - You can use your existing developer tools and environment to develop modules. 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. ## Modules It's time to see what module projects look like and see @product@'s modular development features in action. To keep things simple, only project code and structure are shown: you can [create modules](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) 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. ### API The API module is first. It defines the contract that a provider implements and a consumer uses. Here is its structure: - `greeting-api` - `src` - `main` - `java` - `com/liferay/docs/greeting/api` - `Greeting.java` - `bnd.bnd` - `build.gradle` 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 `bnd.bnd`. The `bnd.bnd` file describes and configures the module: ```properties Bundle-Name: Greeting API Bundle-SymbolicName: com.liferay.docs.greeting.api Bundle-Version: 1.0.0 Export-Package: com.liferay.docs.greeting.api ``` The module's name is *Greeting API*. Its symbolic name--a name that ensures uniqueness--is `com.liferay.docs.greeting.api`. Its semantic version is declared next, and its package is *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: ```java package com.liferay.docs.greeting.api; import aQute.bnd.annotation.ProviderType; @ProviderType public interface Greeting { public void greet(String name); } ``` The interface's `@ProviderType` annotation tells the service registry that anything implementing the interface is a provider. The interface's one method asks for a `String` and doesn't return anything. That's it! As you can see, creating modules is not very different from creating other Java projects. ### 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: - `greeting-impl` - `src` - `main` - `java` - `com/liferay/docs/greeting/impl` - `GreetingImpl.java` - `bnd.bnd` - `build.gradle` It has the same structure as the API module: a build script, a `bnd.bnd` configuration file, and an implementation class. The only differences are the file contents. The `bnd.bnd` file is a little different: ```properties Bundle-Name: Greeting Impl Bundle-SymbolicName: com.liferay.docs.greeting.impl Bundle-Version: 1.0.0 ``` The bundle name, symbolic name, and version are all set similarly to the API. Finally, there's no `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: ```java 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 + "!"); } } ``` The implementation is simple. It uses the `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 `@Component` annotation specifies a higher service ranking property (e.g., `"service.ranking:Integer=100"`). This `@Component` annotation defines three options: `immediate = true`, an empty property list, and the service class that it implements. The `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 `@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 `greetings-api` project to your `dependencies { ... }` block. In a [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace) module, the dependency looks like this: ```groovy compileOnly project (':modules:greeting-api') ``` That's all there is to a provider module. ### 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. [Portlets](/docs/7-2/frameworks/-/knowledge_base/f/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: - `greeting-command` - `src` - `main` - `java` - `com/liferay/docs/greeting/command` - `GreetingCommand.java` - `bnd.bnd` - `build.gradle` Again, you have a build script, a `bnd.bnd` file, and a Java class. This module's `bnd.bnd` file is almost the same as the provider's: ```properties Bundle-Name: Greeting Command Bundle-SymbolicName: com.liferay.docs.greeting.command Bundle-Version: 1.0.0 ``` 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: ```java 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; } ``` The `@Component` annotation declares the same attributes, but specifies different properties and a different service. As in Java, where every class is a subclass of `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 `java.lang.Object`, so you must specify that class as the service. While Java doesn't require you to specify `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 `copy` or `delete`. These properties specify you're creating a command called `greet` in a scope called `greet`. While you get no points for imagination, this sufficiently defines the command. Since you specified `osgi.command.function=greet` in the `@Component` annotation, your class must have a method named `greet`, and you do. But how does this `greet` method work? It obtains an instance of the `Greeting` OSGi service and invokes its `greet` method, passing in the `name` parameter. How is an instance of the `Greeting` OSGi service obtained? The `GreetingCommand` class declares a private service bean, `_greeting` of type `Greeting`. This is the OSGi service type that the provider module registers. The `@Reference` annotation tells the OSGi runtime to instantiate the service bean with a service from the service registry. The runtime binds the `Greeting` object of type `GreetingImpl` to the private field `_greeting`. The `greet` method uses the `_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 [deploy these modules to a DXP instance](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project), you'd be able to attach to the [Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) and execute a command like this: ```bash greet:greet "Captain\ Kirk" ``` The shell would then return your greeting: ```bash Hello Captain Kirk! ``` 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. ## A Typical Liferay Application If you look at a typical application from Liferay's source, you'll generally find at least four modules: - An API module - A Service (provider) module - A Test module - A Web (consumer) module This is exactly what you'll find for some smaller applications, like the Mentions application that lets users mention other users with the `@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. ## Related Topics [Liferay Dev Studio](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio) [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace) [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) [Maven](/docs/7-2/reference/-/knowledge_base/r/maven) [Upgrading Code to 7.2](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver) ================================================ FILE: en/developer/customization/articles/03-architecture/06-module-lifecycle.markdown ================================================ --- header-id: module-lifecycle --- # Module Lifecycle [TOC levels=1-4] In OSGi, all components, Java classes, resources, and descriptors are deployed via modules. The `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: 1. *Installation*: Copying the module JAR into @product@'s `[Liferay Home]/deploy` folder installs the module to the OSGi framework, marking the module `INSTALLED`. 2. *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 `RESOLVED`. 3. *Activation*: Modules are activated *eagerly* by default. That is, they're started in the framework and marked `ACTIVE` on resolution. An active module's components are enabled. If a module specifies a `lazy` activation policy, as shown in the manifest header below, it's activated only after another module requests one of its classes. ```properties Bundle-ActivationPolicy: lazy ``` The figure below illustrates the module lifecycle. ![Figure 1: This state diagram illustrates the module lifecycle.](../../images/module-state-diagram.png) The [Apache Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-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 [Liferay @ide@](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio), [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace), and [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) offer similar shell commands that use the OSGi Admin API. On activating a module, its components are enabled. But only *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 *delayed* (default) or *immediate* activation policies. To specify immediate activation, the developer adds the attribute `immediate=true` to the `@Component` annotation. ```java @Component( immediate = true, ... ) ``` 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 [Service Component Runtime commands](http://felix.apache.org/documentation/subprojects/apache-felix-service-component-runtime.html#shell-command) let you manage components: - `scr:list [bundleID]`: Lists the module's (bundle's) components. - `scr:info [componentID|fullClassName]`: Describes the component, including its status and the services it provides. - `scr:enable [componentID|fullClassName]`: Enables the component. - `scr:disable [componentID|fullClassName]`: Disables the component. It's disabled on the server (or current server node in a cluster) until the server is restarted. 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 *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 `@Reference` annotation that uses a greedy policy: ```java @Reference(policyOption = ReferencePolicyOption.GREEDY) ``` Declarative Services annotations let you specify component activation and service policies. Gogo Shell commands let you control modules and components. ## Related Topics [Creating a Project](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) [Upgrading Code to 7.2](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-code-to-product-ver) ================================================ FILE: en/developer/customization/articles/03-architecture/06-ui-concepts/01-ui-architecture-intro.markdown ================================================ --- header-id: ui-architecture --- # UI Architecture [TOC levels=1-4] [@product@'s UI](/docs/7-2/user/-/knowledge_base/u/the-liferay-distinction) is a portal for adding sites, pages, widgets, and content. It helps people do work, [collaborate](/docs/7-2/user/-/knowledge_base/u/collaboration), and [share content](/docs/7-2/user/-/knowledge_base/u/web-experience-management). The UI comprises the following parts: - Content: images, videos, and text. - [Applications](/docs/7-2/appdev/-/knowledge_base/a/application-development): Widgets and portlets that expose functionality for accomplishing tasks. - [Themes](/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction): Plugins that use CSS, FreeMarker templates, HTML, and JavaScript to provide a site's overall look and feel. - Product navigation sidebars and panels: Use these for administering sites. ## Content @product@'s built-in applications help you publish images, video, forms, markup text, and more to site pages. [Documents and Media](/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media) stores images, videos, and documents to use throughout your site. The [Web Experience Management](/docs/7-2/user/-/knowledge_base/u/web-experience-management) suite helps you create, maintain, and organize content. [Liferay Forms](/docs/7-2/user/-/knowledge_base/u/forms) gives you robust form building capability. [Message Boards](/docs/7-2/user/-/knowledge_base/u/creating-forums-with-message-boards) facilitate lively discussions and [Blogs](/docs/7-2/user/-/knowledge_base/u/publishing-blogs) let users express themselves with markup text and images. These are just a few of the built-in applications for adding site content. ## Applications @product@ applications provide content and help users accomplish tasks. They're [developed the same way](/7-2/appdev/-/knowledge_base/a/web-front-ends) as other web applications, and @product@ can combine multiple applications on one page. @product@ supports developing JavaScript-based applications using popular front-end frameworks: - [Angular](/docs/7-2/appdev/-/knowledge_base/a/developing-an-angular-application) - [React](/docs/7-2/appdev/-/knowledge_base/a/developing-a-react-application) - [Vue](/docs/7-2/appdev/-/knowledge_base/a/developing-a-vue-application) Java-based portlet applications use the latest portlet standards and frameworks, including ones familiar to experienced Liferay portlet developers: - [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) - [PortletMVC4Spring](/docs/7-2/appdev/-/knowledge_base/a/portletmvc4spring) - [JSF Portlet](/docs/7-2/appdev/-/knowledge_base/a/jsf-portlet) 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. ![Figure 1: 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.](../../../images/architecture-ui-widgets.png) ## Themes A [theme](/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction) 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 @product@ offers [Bootstrap](https://getbootstrap.com/)-based components and [theme tooling](/docs/7-2/frameworks/-/knowledge_base/f/developing-themes) to create and deploy themes in no time. ![Figure 2: You can select an attractive theme and apply it to your site.](../../../images/architecture-ui-themes.png) Here's a quick demonstration of developing a theme: 1. Create a theme using the [Theme Generator](/docs/7-2/reference/-/knowledge_base/r/theme-generator). The theme extends the base theme you specified to the Theme Generator---Liferay's [Styled theme](https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/frontend-theme/frontend-theme-styled) is the default. 2. Run [`gulp build`](https://portal.liferay.dev/docs/7-2/frameworks/-/knowledge_base/f/building-your-themes-files) to generate the base theme files to the `build` folder subfolders: - `templates`: FreeMarker templates specify site page markup. `portal_normal.ftl` is the central file; it includes templates that define the page parts (e.g., header, navigation, footer). The `init.ftl` file defines default variables available to the templates. - `css`: SCCS files that provide styling. - `font`: Font Awesome and Glyphicons fonts. - `js`: JavaScript files; `main.js` is the Styled theme's JavaScript. - `images`: Image files. 3. Override aspects of the base theme by copying relevant files from the `build` subfolders to folders of the same name in your `src` folder. The [Theme Anatomy Guide](/docs/7-2/reference/-/knowledge_base/r/theme-reference-guide) describes all the files. Here's an example of a customized `portal_normal.ftl`: ```markup
    <#include "${full_templates_path}/navigation.ftl" />
    ${portlets}
    <#include "${full_templates_path}/footer.ftl" /> ``` 4. Add custom styling using your theme's `_custom.scss` file (i.e., `src/css/_custom.scss`). @product@ supports [Bootstrap](https://getbootstrap.com/), as well as [Sass](https://sass-lang.com/), so you can use Bootstrap utilities in your markup and Sass nesting, variables, and more in your CSS files. This snippet styles the logo: ```sass .logo { margin-left: 15px; img { height: auto; } @include media-breakpoint-down(md) { text-align: center; width: 100%; } } ``` ![Figure 3: You can provide custom styling using the theme's `_custom.sccs` file.](../../../images/architecture-ui-portal-dev-logo.png) 5. Deploy your theme by executing `gulp deploy`. The theme is available to [apply](/docs/7-2/frameworks/-/knowledge_base/f/deploying-and-applying-themes) to your site. For details, [Theme Components](/docs/7-2/customization/-/knowledge_base/c/theme-components) breaks down a theme's parts, and the [Themes section](/docs/7-2/frameworks/-/knowledge_base/f/themes-introduction) provides theme development details. ## Product Navigation Sidebars 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. ![Figure 4: Liferay facilitates integrating custom administrative functionality through navigation menus and administrative applications.](../../../images/architecture-ui-menus-and-panel-app.png) As you can see, @product@'s UI is highly flexible and customizable. Here's where to learn more: - [Theme Components](/docs/7-2/customization/-/knowledge_base/c/theme-components): Explains available mechanisms and extensions for customizing and theming pages, content, and applications. - [Understanding the Page Structure](/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure): Describes how the page's UI is organized and introduces tools for populating and developing each section. ================================================ FILE: en/developer/customization/articles/03-architecture/06-ui-concepts/02-theme-components.markdown ================================================ --- header-id: theme-components --- # Theme Components [TOC levels=1-4] This guide provides an overview of the following theme development and customization topics: - [Theme Templates](#theme-templates) - [CSS Frameworks and Extensions](#css-frameworks-and-extensions) - [Theme Customizations and Extensions](#theme-customizations-and-extensions) - [Portlet Customizations and Extensions](#portlet-customizations-and-extensions) ## Theme Templates and Utilities The default FreeMarker templates provide helpful utilities and handle key pieces of page layout (page) functionality: - `portal_normal.ftl`: Similar to a static site's `index.html`, this file is the hub for all the theme templates and provides the overall markup for the page. - `init.ftl`: Contains variables commonly used throughout the theme templates. Refer to it to look up theme objects. For convenience, the [FreeMarker Variable Reference Guide](/docs/7-2/reference/-/knowledge_base/r/freemarker-variable-reference-guide) lists the objects. **DO NOT override this file**. - `init_custom.ftl`: Used to override FreeMarker variables in `init.ftl` and to define new variables, such as [theme settings](/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings). - `portlet.ftl`: Controls the theme's portlets. If your theme uses [Portlet Decorators](/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets#portlet-decorators), modify this file to create application decorator-specific theme settings. - `navigation.ftl`: Contains the navigation markup. To customize pages in the navigation, you must use the `liferay.navigation_menu` macro. Then you can leverage [widget templates](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) for the navigation menu. Note that `navigation.ftl` also defines the hamburger icon and `navbar-collapse` class that provides the simplified navigation toggle for mobile viewports, as shown in the snippet below for the Classic theme: ```markup <#if has_navigation && is_setup_complete> ``` ![Figure 1: The collapsed navbar provides simplified user-friendly navigation for mobile devices.](../../../images/portal-layout-mobile-nav.png) - `portal_pop_up.ftl`: Controls pop up dialogs for the theme's portlets. Similar to `portal_normal.ftl`, `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 `init.ftl` and `init_custom.ftl`. ![Figure 2: Each theme template provides a portion of the page's markup and functionality.](../../../images/portal-layout-theme-templates.png) - [`FTL_Liferay.ftl`](https://github.com/liferay/liferay-portal/blob/7.2.x/modules/apps/portal-template/portal-template-freemarker/src/main/resources/FTL_liferay.ftl): Provides [macros](/docs/7-2/reference/-/knowledge_base/r/product-freemarker-macros) for commonly used portlets and theme resources. - `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 [FreeMarker Taglib Mappings reference guide](/docs/7-2/reference/-/knowledge_base/r/freemarker-taglib-macros). See the [Taglib reference](/docs/7-2/reference/-/knowledge_base/r/front-end-taglibs) for more information on using each taglib in your theme templates. ## CSS Frameworks and Extensions Themes are integrated with [SASS](https://sass-lang.com/), so you can take full advantage of Sass mixins, nesting, partials, and variables in your CSS. Also important to note is [Clay CSS](https://clayui.com/), the web implementation of Liferay's [Lexicon design language](https://lexicondesign.io/). An extension of Bootstrap, Clay CSS fills the gaps between Bootstrap and the needs of @product@, 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 @product@'s Classic theme. See [Customizing Atlas and Clay Base Themes](/docs/7-2/frameworks/-/knowledge_base/f/customizing-atlas-and-clay-base-themes) for more information. ## 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: - **Color Schemes:** Specifies configurable color scheme settings Administrators can configure via the Look and Feel menu. See the [color scheme tutorial](/docs/7-2/frameworks/-/knowledge_base/f/creating-color-schemes-for-your-theme) for more information. - **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 [Configurable Theme Settings tutorial](/docs/7-2/frameworks/-/knowledge_base/f/making-configurable-theme-settings) for more information. - **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 [Context Contributors tutorial](/docs/7-2/frameworks/-/knowledge_base/f/injecting-additional-context-variables-and-functionality-into-your-theme-templates) or more information. - **Theme Contributor:** A package containing UI resources, not attached to a theme, that you want to include on every page. See the [Theme Contributors tutorial](/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site) for more information. - **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 [Generating Themelets](/docs/7-2/reference/-/knowledge_base/r/creating-themelets-with-the-themes-generator) for more information. ## Portlet Customizations and Extensions You can customize portlets with these mechanisms and extensions: - **Portlet FTL Customizations:** Customize the base template markup for all portlets. See the [Theming Portlets](/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets) for more information. - **Widget Templates:** Provides an alternate display style for a portlet. Note that not all portlets support widget templates. See the [Widget Templates User Guide](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates) for more information. - **Portlet Decorator:** Customizes the exterior decoration for a portlet. See [Portlet Decorators](/docs/7-2/frameworks/-/knowledge_base/f/theming-portlets#portlet-decorators) for more information. - **Web Content Template:** Defines how structures are displayed for web content. See the [Web Content Templates User Guide articles](/docs/7-2/user/-/knowledge_base/u/designing-web-content-with-templates) for more information. ![Figure 3: There are several extension points for customizing portlets](../../../images/portal-layout-portlet-customizations.png) ## Related Topics - [Understanding the Page Structure](/docs/7-2/customization/-/knowledge_base/c/understanding-the-page-structure) - [Installing the Theme Generator and Creating a Theme](/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme) - [Developing Themes](/docs/7-2/frameworks/-/knowledge_base/f/developing-themes) ================================================ FILE: en/developer/customization/articles/03-architecture/06-ui-concepts/03-understanding-the-page-layout.markdown ================================================ --- header-id: understanding-the-page-structure --- # Understanding the Page Structure [TOC levels=1-4] 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 `portal_normal.ftl` template: - **Header:** Contains the navigation, site logo and title (if shown), and sign-in link when the user isn't logged in. - **Main Content:** Contains the portlets or fragments for the page. - **Footer:** contains additional information, such as the copyright or author. ![Figure 1: The page layout is broken into three key sections.](../../../images/portal-layout-sections.png) ## Portlets or Fragments The `#content` `Section` makes up the majority of the page. Portlets or fragments are contained inside the `#main-content` `div`. @product@ 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 @product@ and its native portlets, see the [User & Admin documentation](/documentation/user). 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 [Resources Importer](/docs/7-2/frameworks/-/knowledge_base/f/importing-resources-with-a-theme), or they can be [embedded in the page's theme](/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes). See the [portlet tutorials section](/docs/7-2/frameworks/-/knowledge_base/f/portlets) for more information on creating and developing portlets. You can target the elements and IDs shown in the table below to style the page: | Element | ID | Description | | --- | --- | --- | | `div` | `#wrapper` | The container div for the page contents | | `header` | `#banner` | The page's header | | `section` | `#content` > `#main-content` | The main contents of the page (portlets or fragments) | | `footer` | `#footer` | The page's footer | ![Figure 2: Each section of the page has elements and IDs that you can target for styling.](../../../images/portal-layout-elements.png) As shown in the diagram above, you can also add [fragments](/docs/7-2/frameworks/-/knowledge_base/f/page-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). @product@ 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. ## Layout Templates, Page Templates, and Site Templates The page layout within the `#content` Section is determined by the [Layout Template](/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro). Several layout templates are included out-of-the-box. You can also [create custom layout templates manually](/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro) or with the [Liferay Theme Generator's layout sub-generator](/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator). Layout templates can be pre-configured depending on the [page type](/docs/7-2/user/-/knowledge_base/u/creating-pages) 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 [Site Templates](/docs/7-2/user/-/knowledge_base/u/building-sites-from-templates), which can define the pages, page templates, layout templates, and theme(s) to use for site pages. ## Product Navigation Sidebars and Panels The main page layout also contains a few notable sidebars an administrative user can trigger through the Control Menu. These are listed below: - **Add Menu:** For adding portlets (widgets) and fragments (if applicable) to the page - **Control Menu:** Provides the main navigation for accessing the Add Menu, Product Menu, and Simulation Panel - **Product Menu:** Contains administrative apps, configuration settings, and user account settings, profile, and dashboard page - **Simulation Panel:** Simulates how the page appears on different devices ![Figure 3: Remember to account for the product navigation sidebars and panels when styling your site.](../../../images/portal-layout-nav-control-menu.png) ![Figure 4: The Add Menu pushes the main contents to the left.](../../../images/portal-layout-nav-add-menu.png) ![Figure 5: The Product Menu pushes the main contents to the right.](../../../images/portal-layout-nav-product-menu.png) ![Figure 6: The Simulation Panel pushes the main contents to the left.](../../../images/portal-layout-nav-simulation-panel.png) 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 [Product Navigation articles](/docs/7-2/customization/-/knowledge_base/c/product-navigation) for more information on customizing these menus. ## Related Topics - [Creating Layout Templates with the Layouts Sub-generator](/docs/7-2/reference/-/knowledge_base/r/creating-layout-templates-with-the-themes-generator) - [Bundling Layout Templates with a Theme](/docs/7-2/frameworks/-/knowledge_base/f/including-layout-templates-with-a-theme) - [Installing the Liferay Theme Generator and Creating a Theme](/docs/7-2/reference/-/knowledge_base/r/installing-the-theme-generator-and-creating-a-theme) ================================================ FILE: en/developer/customization/articles/03-architecture/07-bundle-classloading-flow.markdown ================================================ --- header-id: bundle-classloading-flow --- # Bundle Classloading Flow [TOC levels=1-4] The OSGi container searches several places for imported classes. It's important to know where it looks and in what order. @product@'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. ![Figure 1: This flow chart illustrates classloading in a bundle.](../../images/bundle-classloading-flow-chart.png) Here is the algorithm for classloading in a bundle: 1. If the class is in a `java.*` package, delegate loading to the parent classloader. Otherwise, continue. 2. If the class is in the OSGi Framework's boot delegation list, delegate loading to the parent classloader. Otherwise, continue. 3. If the class is in one of the packages the bundle imports from a wired exporter, the exporting bundle's classloader loads it. A *wired exporter* is another bundle's classloader that previously loaded the package. If the class isn't found, continue. 4. If the class is imported by one of the bundle's required bundles, the required bundle's classloader loads it. 5. If the class is in the bundle's classpath (manifest header `Bundle-ClassPath`), the bundle's classloader loads it. Otherwise, continue. 6. If the class is in the bundle's fragments classpath, the bundle's classloader loads it. 7. If the class is in a package that's dynamically imported using `DynamicImport-Package` and a wire is established with the exporting bundle, the exporting bundle's classloader loads it. Otherwise, the class isn't found. Congratulations! Now you know how @product@ finds and loads classes for OSGi bundles. ================================================ FILE: en/developer/customization/articles/03-architecture/08-finding-extension-points.markdown ================================================ --- header-id: finding-extension-points --- # Finding Extension Points [TOC levels=1-4] @product@ provides many features that help users accomplish their tasks. Sometimes, however, you may find it necessary to [customize a built-in feature](/docs/7-2/customization/-/knowledge_base/c/liferay-customization). It's easy to **find** an area you want to customize, but it may seem like a daunting task to figure out **how** to customize it. @product@ 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. 1. Locate the bundle (module) that provides the functionality you want to change. 2. Find the components available in the module. 3. Discover the extension points for the components you want to customize. 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 @product@'s [Application Manager](/docs/7-2/user/-/knowledge_base/u/managing-and-configuring-apps#using-the-app-manager) and [Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell). ## 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 *import*, *user*, *security, *and *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. 1. Open the App Manager by navigating to *Control Panel* → *Apps* → *App Manager*. The top level lists independent apps and independent modules. 2. 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 *security* is found in the Liferay CE Portal Security app. Select it. 3. The Security application has several modules to inspect. Select the *Liferay Portal Security LDAP Implementation* module. ![Figure 1: The module name can be found using the App Manager.](../../images/ldapimplementation-module.png) 4. 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. ![Figure 2: The component name can be found using the App Manager.](../../images/usermodellistener-component.png) | **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. Next, you'll use the Gogo shell to inspect the component for extension points. ## 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 [Liferay Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) or in [Gogo shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell). This article assumes you're using the Gogo shell. Execute the following command: ```bash scr:info [COMPONENT_NAME] ``` For the LDAP example component you copied previously, the command would look like this: ```bash scr:info com.liferay.portal.security.ldap.internal.messaging.UserImportMessageListener ``` 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: ```bash - _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) ``` The `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 `dynamic` and the policy option is `greedy`. If the policy is `static` and the policy option is `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 [OSGi specification](https://osgi.org/download/r6/osgi.enterprise-6.0.0.pdf), in particular, sections 112.3.5 and 112.3.6. See [Overriding OSGi Services](/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services) to learn how to override a component's service reference. **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 @product@: - Instances of `org.osgi.util.tracker.ServiceTracker` - Uses of Liferay's `Registry.getServiceTracker` - Uses of Liferay's `ServiceTrackerMap` or `ServiceTrackerCollection` - 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. 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. ================================================ FILE: en/developer/customization/articles/100-troubleshooting-customizations/01-troubleshooting-customizations-intro.markdown ================================================ --- header-id: troubleshooting-faq --- # Troubleshooting Customizations [TOC levels=1-4] 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 doesn't the package I use from the fragment host resolve? 

    Why does my web content break when I refresh the page? 

    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 SettingsTemplate Engines and setting Resource Modification Check to 0.

    As best practice, however, we recommend that you don't use taglibs in cacheable web content.

    ================================================ FILE: en/developer/customization/articles/100-troubleshooting-customizations/jsp-fragment-unresolved-requirement.markdown ================================================ --- header-id: why-is-a-package-i-use-from-the-fragment-host-unresolved --- # Why doesn't the package I use from the fragment host resolve? [TOC levels=1-4] 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 `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 `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 `com.liferay.portal.search.web.internal.custom.facet.display.context`: ```javascript <%@ 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" %> ``` 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: ``` Import-Package: !com.liferay.portal.search.web.internal.*,* ``` ================================================ FILE: en/developer/customization/articles/100-troubleshooting-customizations/jsp-fragments.markdown ================================================ --- header-id: why-arent-jsp-overrides-i-made-using-fragments-showing --- # Why Aren't JSP overrides I Made Using Fragments Showing? [TOC levels=1-4] | **Important:** It's strongly recommended to | [customize JSPs using @product@'s API](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps). | 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. The fragment module must specify the exact version of the host module. A @product@ 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 `bnd.bnd` file from a fragment module uses `Fragment-Host` to specify the host module and host module version: ``` 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" ``` [Finding versions of deployed modules](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) is straightforward. ## Related Topics [JSP Overrides using Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) [Customizing JSPs](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps) [Finding Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) ================================================ FILE: en/developer/customization/articles/100-troubleshooting-customizations/using-osgi-services-from-ext-plugins.markdown ================================================ --- header-id: using-osgi-services-from-ext-plugins --- # Using OSGi Services from EXT Plugins [TOC levels=1-4] [`ServiceTrackers`](/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services) are the best way for Ext plugins to access OSGi services. They account for the possibility of OSGi services coming and going. ## Related Topics [Detecting Unresolved OSGi Components](/docs/7-2/appdev/-/knowledge_base/a/detecting-unresolved-osgi-components) [Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) ================================================ FILE: en/developer/customization/articles/110-contributing/01-contributing-to-liferay-portal-intro.markdown ================================================ --- header-id: contributing-to-liferay-portal --- # Contributing to Liferay Portal [TOC levels=1-4] 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 [Slack Chat](https://liferay-community.slack.com) 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. ## Building Liferay Portal from source The first step to contributing to Liferay Portal is to clone the `liferay-portal` repo from GitHub and build the platform from source code. Please follow the instructions for [building Liferay Portal from source code](https://portal.liferay.dev/participate/fix-a-bug/building-liferay-source). To better understand the code structure, please also read [How the source is organized](https://portal.liferay.dev/participate/fix-a-bug/how-the-source-is-organized). ## Tooling [Liferay tooling](/docs/7-2/reference/-/knowledge_base/r/tooling) facilitates creating customizations and debugging code. Consider using these Liferay development tools: - [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/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. - [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace): a generated Gradle/Maven environment built to hold and manage Liferay Portal projects. - [Liferay Dev Studio](/docs/7-2/reference/-/knowledge_base/r/liferay-dev-studio): an Eclipse-based IDE supporting development for Liferay Portal. - [Liferay IntelliJ Plugin](/docs/7-2/reference/-/knowledge_base/r/intellij): a plugin providing support for Liferay Portal development with IntelliJ IDEA. - [Liferay Theme Generator](/docs/7-2/reference/-/knowledge_base/r/theme-generator): a generator that creates themes, layouts templates, and themelets for Liferay Portal development. - [Liferay JS Generator](/docs/7-2/reference/-/knowledge_base/r/js-generator): a generator that creates JavaScript portlets with JavaScript tooling. The [Configure an IDE for use with the Liferay Source](https://portal.liferay.dev/participate/fix-a-bug/ide-support) page, explains how to set up the project in your favorite IDE. ## Additional Resources [Liferay Community Site](https://liferay.dev) [Liferay Community Slack Chat](https://liferay-community.slack.com/) [Liferay Community Slack Chat Self Invite](https://liferay.dev/chat) [Contributor License Agreement](https://www.liferay.com/legal/contributors-agreement) [General GitHub documentation](http://help.github.com/) [GitHub pull request documentation](http://help.github.com/send-pull-requests/) ================================================ FILE: en/developer/customization/articles/50-creating-model-listeners/01-creating-model-listeners-intro.markdown ================================================ --- header-id: model-listeners --- # Model Listeners [TOC levels=1-4] Model Listeners implement the [`ModelListener` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/ModelListener.html). 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 `create`, `remove`, or `update` attempt on an entity's database table or a mapping table (for example, `users_roles`). Here are some supported use cases: - Audit Listener: In a separate database, record information about updates to an entity's database table. - Cache Clearing Listener: Clear caches that you've added to improve the performance of custom code. - Validation Listener: Perform additional validation on a model's attribute values before they are persisted to the database. - Entity Update Listener: Do some additional processing when an entity table is updated. For example, notify users when changes are made to their account. There are also use cases that are not recommended, since they're likely to break unpredictably and give you headaches: - Setting a model's attributes in an `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 [service wrapper](/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers) instead. - Wrapping a model. Model listeners are not called when fetching records from the database. - Creating worker threads to do parallel processing and querying data you updated via your listener. Model listeners are called *before* the database transaction is complete (even the `onAfter...` methods), so the queries could be executed before the database transaction completes. 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: - Implement `ModelListener` - Register the service in Liferay's OSGi runtime ## Creating a Model Listener Class Create a `-ModelListener` class that extends the [`BaseModelListener` class](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/BaseModelListener.html). ```java package ...; import ...; public class CustomEntityListener extends BaseModelListener { // Override one or more methods from the ModelListener interface. } ``` In the body of the class, override any methods from the [`ModelListener` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/model/ModelListener.html). The available methods are listed and described at the end of this article. In your model listener class, the parameterized type (for example, `CustomEntity` in the snippet above) tells the listener's `ServiceTrackerCustomizer` which model class to register the listener against. ## Register the Model Listener Service Register the service with Liferay's OSGi runtime for immediate activation. If using Declarative Services, set `service= ModelListener.class` and `immediate=true` in the Component: ```java @Component( immediate = true, service = ModelListener.class ) ``` That's all there is to preparing a model listener. Now learn what model events you can respond to. ## Listening For Persistence Events The `ModelListener` interface provides lots of opportunity to listen for model events: - **`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. - **`onAfterCreate`:** Use this method to do something after the persistence layer's `create` method is called. - **`onAfterRemove`:** Use this method to do something after the persistence layer's `remove` method is called. - **`onAfterRemoveAssociation`:** If there's an association between two models (if they have a mapping table), do something after an association record is removed. - **`onAfterUpdate`:** Use this method to do something after the persistence layer's `update` method is called. - **`onBeforeAddAssociation`:** If there's an association between two models (if they have a mapping table), do something before an addition to the mapping table. - **`onBeforeCreate`:** Use this method to do something before the persistence layer's `create` method is called. - **`onBeforeRemove`:** Use this method to do something before the persistence layer's `remove` method is called. - **`onBeforeRemoveAssociation`:** If there's an association between two models (if they have a mapping table), do something before a removal from the mapping table. - **`onBeforeUpdate`:** Use this method to do something before the persistence layer's `update` method is called. Look in Liferay source file `portal-kernel/src/com/liferay/portal/kernel/service/persistence/impl/BasePersistenceImpl.java`, particularly the `remove` and `update` methods, and you'll see how model listeners are accounted for before (for the `onBefore...` case) and after (for the `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. ## Related Topics - [Upgrading Model Listener Hooks](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-model-listener-hooks) - [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) - [Service Builder Persistence](/docs/7-2/appdev/-/knowledge_base/a/service-builder) ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/01-overriding-jsps-intro.markdown ================================================ --- header-id: customizing-jsps --- # Customizing JSPs [TOC levels=1-4] There are several different ways to customize JSPs in portlets and the core. @product@'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. ## Using @product@'s API to Override a JSP Here are API-based approaches to overriding JSPs in @product@: **Approach** | **Description** | **Cons/Limitations** | :----------- | :-------------- | :-------------- | [Dynamic includes](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes) | Adds content at dynamic include tags. | Limited to JSPs that have `dynamic-include` tags (or tags whose classes inherit from `IncludeTag`). Only inserts content in the JSPs at the dynamic include tags. | [Portlet filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-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. | ## Overriding a JSP Without Using @product@'s API It's strongly recommended to customize JSPs using @product@'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 @product@'s API: **Approach** | **Description** | **Cons/Limitations** | :----------- | :-------------- | :-------------- | [OSGi fragment](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-osgi-fragments) | Completely overrides a module's JSP using an OSGi fragment | Changes to the original JSP or module can cause runtime errors. | [Custom JSP bag](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-custom-jsp-bag) | Completely override a @product@ core JSP or one of its corresponding `-ext.jsp` files. | For @product@ core JSPs only. Changes to the original JSP or module can cause runtime errors. | All the JSP customization approaches are available to you. It's time to customize some JSPs! ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/02-customizing-jsps-with-dynamic-includes.markdown ================================================ --- header-id: customizing-jsps-with-dynamic-includes --- # Customizing JSPs with Dynamic Includes [TOC levels=1-4] The [`liferay-util:dynamic-include` tag](@platform-ref@/7.2-latest/taglibs/util-taglib/liferay-util/dynamic-include.html) 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. | **Note**: If the JSP you want to customize has no `liferay-util:dynamic-include` | tags (or tags whose classes inherit from `IncludeTag`), you must use a | different customization approach, such as | [portlet filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters). Blogs entries contain a good example of how dynamic includes work. For reference, you can download the [example module](https://portal.liferay.dev/documents/113763090/114000186/example-dynamic-include-blogs-master.zip). Follow these steps: 1. Find the `liferay-util:dynamic-include` tag where you want to insert content and note the tag's key. The Blogs app's `view_entry.jsp` has a dynamic include tag at the top and another at the very bottom. ```markup <%@ include file="/blogs/init.jsp" %> ... JSP content is here ``` Here are the Blogs view entry dynamic include keys: - `key="com.liferay.blogs.web#/blogs/view_entry.jsp#pre"` - `key="com.liferay.blogs.web#/blogs/view_entry.jsp#post"` 2. [Create a module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) (e.g., `blade create my-dynamic-include`). The module will hold your dynamic include implementation. 3. Specify compile-only dependencies, like these Gradle dependencies, in your module build file: ```properties 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" } ``` 4. Create an OSGi component class that implements the [`DynamicInclude` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/servlet/taglib/DynamicInclude.html). Here's an example dynamic include implementation for Blogs: ```java 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"); } } ``` Giving the class an `@Component` annotation that has the service attribute `service = DynamicInclude.class` makes the class a `DynamicInclude` service component. ```java @Component( immediate = true, service = DynamicInclude.class ) ``` In the `include` method, add your content. The example `include` method writes a heading. ```java @Override public void include( HttpServletRequest request, HttpServletResponse response, String key) throws IOException { PrintWriter printWriter = response.getWriter(); printWriter.println( "

    Added by Blogs Dynamic Include!


    "); } ``` In the `register` method, specify the dynamic include tag to use. The example register method targets the dynamic include at the top of the Blogs `view_entry.jsp`. ```java @Override public void register(DynamicIncludeRegistry dynamicIncludeRegistry) { dynamicIncludeRegistry.register( "com.liferay.blogs.web#/blogs/view_entry.jsp#pre"); } ``` Once you've [deployed your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project), the JSP dynamically includes your content. Congratulations on injecting dynamic content into a JSP! ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/03-jsp-overrides-using-portlet-filters.markdown ================================================ --- header-id: jsp-overrides-using-portlet-filters --- # JSP Overrides Using Portlet Filters [TOC levels=1-4] 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 [example module](https://portal.liferay.dev/documents/113763090/114000186/example-portlet-filter-customize-jsp-master.zip). Follow these steps: 1. Create a new module and make sure it specifies these compile-only dependencies, shown here in Gradle format: ```properties 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" } ``` 2. Create an OSGi component class that implements the `javax.portlet.filter.RenderFilter` interface. Here's an example portlet filter implementation for Blogs: ```java 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 = "\n

    Added by Blogs Render Filter!

    \n"; String newText3 = text.substring(index); String newText = newText1 + newText2 + newText3; response.getWriter().write(newText); } } } } ``` 3. Make your class a `PortletFilter` service component by giving it the `@Component` annotation that has the service attribute `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 `@Component` annotation: ```java @Component( immediate = true, property = { "javax.portlet.name=" + PortletKeys.BLOGS }, service = PortletFilter.class ) ``` 4. Override the `doFilterMethod` to operate on the request or response to produce the content you want. The example appends a paragraph stating `Added by Blogs Render Filter!` to the portlet content: ```java @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 = "\n

    Added by Blogs Render Filter!

    \n"; String newText3 = text.substring(index); String newText = newText1 + newText2 + newText3; response.getWriter().write(newText); } } } ``` The example uses a `RenderResponseWrapper` extension class called `BufferedRenderResponseWrapper`. `BufferedRenderResponseWrapper` is a helper class whose `toString` method returns the current response text and whose `getWriter` method lets you write data to the response before it's sent back to the client. ```java 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; } ``` Once you've [deployed your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project), 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. ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/04-jsp-overrides-using-fragments.markdown ================================================ --- header-id: jsp-overrides-using-osgi-fragments --- # JSP Overrides Using OSGi Fragments [TOC levels=1-4] You can completely override JSPs using OSGi fragments. This approach is powerful but can make things unstable when the host module is upgraded: 1. By overriding an entire JSP, you might not account for new content or new widgets essential to new host module versions. 2. 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). 3. Liferay cannot guarantee that JSPs overridden by fragments can be upgraded. Using OSGi fragments to override JSPs is a bad practice, equivalent to using Ext plugins to customize @product@. They should only be used as a last resort. Liferay's API based approaches to overriding JSPs (i.e., [Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes) and [Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-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: - The host module's symbolic name and version in the OSGi header `Fragment-Host` declaration. - The original JSP with any modifications you need to make. For more information about fragment modules, you can refer to section 3.14 of the [OSGi Alliance's core specification document](https://osgi.org/specification/osgi.core/7.0.0/framework.module.html). ## 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: 1. The Bundle Symbolic Name of the host module. This is the module containing the original JSP. 2. The exact version of the host module to which the fragment belongs. Both are declared using the OSGi manifest header `Fragment-Host`. ```properties Fragment-Host: com.liferay.login.web;bundle-version="[1.0.0,1.0.1)" ``` Supplying a specific host module version is important. If that version of the module isn't present, your fragment won't attach itself to a host, 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. ## Provide the Overridden JSP There are two possible naming conventions for targeting the host original JSP: `portal` or `original`. For example, if the original JSP is in the folder `/META-INF/resources/login.jsp`, then the fragment bundle should contain a JSP with the same path, using the following pattern: ```markup ``` 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 `login.jsp` for example, you'd put your own `login.jsp` in ```markup my-jsp-fragment/src/main/resources/META-INF/resources/login.jsp ``` If you must post-process the output, you can update the pattern to include @product@'s buffering mechanism. Below is an example that overrides the original `create_account.jsp`: ```markup <%@ 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 %> ``` ## Using Fragment Host Internal Packages To use an internal (unexported) host package, the fragment must explicitly exclude the package from its `Import-Package:` manifest header. For example, this `Import-Package` header excludes packages that match `com.liferay.portal.search.web.internal.*`. ```groovy Import-Package: !com.liferay.portal.search.web.internal.*,* ``` Unless you explicitly exclude the package, bnd adds the package to the `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 `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. ![Figure 1: Liferay's applications are swimming in the OSGi runtime, waiting for your fragment modules to clean their teeth, so to speak.](../../images/sharks.jpg) ## Related Topics - [JSP Overrides Using Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/05-core-jsp-overrides-using-customjspbag.markdown ================================================ --- header-id: jsp-overrides-using-custom-jsp-bag --- # JSP Overrides Using Custom JSP Bag [TOC levels=1-4] Liferay's API based approaches to overriding JSPs (i.e., [Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes) and [Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-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 @product@. If you're maintaining existing Custom JSP Bags, however, this tutorial explains how they work. | **Important:** Liferay cannot guarantee that JSPs overridden using Custom JSP | Bag can be upgraded. A Custom JSP Bag module must satisfy these criteria: - Provides and specifies a custom JSP for the JSP you're extending. - Includes a [`CustomJspBag`](@platform-ref@/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html) implementation for serving the custom JSPs. The module provides transportation for this code into Liferay's OSGi runtime. After you [create your new module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project), continue with providing your custom JSP. ## Providing a Custom JSP Create your JSPs to override @product@ core JSPs. If you're using the Maven [Standard Directory Layout](https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html), place your JSPs under `src/main/resources/META-INF/jsps`. For example, if you're overriding portal-web/docroot/html/common/themes/bottom-ext.jsp place your custom JSP at [your module]/src/main/resources/META-INF/jsps/html/common/themes/bottom-ext.jsp | **Note:** If you place custom JSPs somewhere other than | `src/main/resources/META-INF/jsps` in your module, assign that location to a | `-includeresource: META-INF/jsps=` directive in your module's `bnd.bnd` file. | For example, if you place custom JSPs in a folder `src/META-INF/custom_jsps` in | your module, specify this in your `bnd.bnd`: | | -includeresource: META-INF/jsps=src/META-INF/custom_jsps ## Implement a Custom JSP Bag @product@ (specifically the [`CustomJspBagRegistryUtil` class](@platform-ref@/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBagRegistryUtil.html)) loads JSPs from [`CustomJspBag`](@platform-ref@/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html) services. Here are steps for implementing a custom JSP bag. 1. In your module, create a class that implements [`CustomJspBag`](@platform-ref@/7.2-latest/javadocs/portal-impl/com/liferay/portal/deploy/hot/CustomJspBag.html). 2. Register your class as an OSGi service by adding an `@Component` annotation to it, like this: ```java @Component( immediate = true, property = { "context.id=BladeCustomJspBag", "context.name=Test Custom JSP Bag", "service.ranking:Integer=100" } ) ``` - **`immediate = true`:** Makes the service available on module activation. - **`context.id`:** Your custom JSP bag class name. Replace `BladeCustomJspBag` with your class name. - **`context.name`:** A more human readable name for your service. Replace it with a name of your own. - **`service.ranking:Integer`:** A priority for your implementation. The container chooses the implementation with the highest priority. 3. Implement the `getCustomJspDir` method to return the folder path in your module's JAR where the JSPs reside (for example, `META-INF/jsps`). ```java @Override public String getCustomJspDir() { return "META-INF/jsps/"; } ``` 4. Create an `activate` method and the following fields. The method adds the URL paths of all your custom JSPs to a list when the module is activated. ```java @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; ``` 5. Implement the `getCustomJsps` method to return the list of this module's custom JSP URL paths. ```java @Override public List getCustomJsps() { return _customJsps; } ``` 6. Implement the `getURLContainer` method to return a new `com.liferay.portal.kernel.url.URLContainer`. Instantiate the URL container and override its `getResources` and `getResource` methods. The `getResources` method looks up all the paths to resources in the container by a given path. It returns a `HashSet` of `Strings` for the matching custom JSP paths. The `getResource` method returns one specific resource by its name (the path included). ```java @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; } }; ``` 7. Implement the `isCustomJspGlobal` method to return `true`. ```java @Override public boolean isCustomJspGlobal() { return true; } ``` Now your module provides custom JSPs and a custom JSP bag implementation. When you deploy it, @product@ uses its custom JSPs in place of the core JSPs they override. ## Extend a JSP If you want to add something to a core JSP, see if it has an empty `-ext.jsp` and override that instead of the whole JSP. It keeps things simpler and more stable, since the full JSP might change significantly, breaking your customization in the process. By overriding the `-ext.jsp`, you're only relying on the original JSP including the `-ext.jsp`. For an example, open `portal-web/docroot/html/common/themes/bottom.jsp`, and scroll to the end. You'll see this: ```markup ``` If you must add something to `bottom.jsp`, override `bottom-ext.jsp`. Since @product@ 7.0, the content from the following JSP files formerly in `html/common/themes` are inlined to improve performance. - `body_bottom-ext.jsp` - `body_top-ext.jsp` - `bottom-ext.jsp` - `bottom-test.jsp` They're no longer explicit files in the code base. But you can still create them in your module to add functionality and content. Remember, this type of customization is a last resort. Your override may break due to the nature of this implementation, and core functionality in Liferay can go down with it. If the JSP you want to override is in another module, refer to the API based approaches to overriding JSPs mentioned at the beginning of the article. ## Site Scoped JSP Customization In Liferay Portal 6.2, you could use [Application Adapters](/docs/6-2/tutorials/-/knowledge_base/t/customizing-sites-and-site-templates-with-application-adapters) to scope your core JSP customizations to a specific Site. Since the majority of JSPs were moved into modules for @product@ 7.0, the use case for this has shrunk considerably. If you must scope a core JSP customization to a Site, prepare an application adapter [as you would have for Liferay Portal 6.2](/docs/6-2/tutorials/-/knowledge_base/t/customizing-sites-and-site-templates-with-application-adapters), and deploy it to @product-ver@. It will still work. However, note that this approach is deprecated in @product-ver@ and won't be supported at all in Liferay 8.0. ## Related Topics - [Upgrading Core JSP Hooks](/docs/7-1/tutorials/-/knowledge_base/t/upgrading-core-jsp-hooks) - [JSP Overrides Using Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) ================================================ FILE: en/developer/customization/articles/50-customizing-jsps/06-overriding-inline-content-using-jsps.markdown ================================================ --- header-id: overriding-inline-content-using-jsps --- # Overriding Inline Content Using JSPs [TOC levels=1-4] Some @product@ core content, such as tag library tags, can only be overridden using JSPs ending in `.readme`. The suffix `.readme` facilitates finding them. The code from these JSPs is now inlined (brought into @product@ Java source files) to improve performance. @product@ ignores JSP files with the `.readme` suffix. If you add code to a JSP `.readme` file and remove the `.readme` suffix, @product@ uses that JSP instead of the core inline content. This tutorial shows you how to make these customizations. | **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 | `.readme` files can be upgraded. | **Warning:** Modifying a @product@ tag library tag affects all uses of that tag | in your @product@ installation. Here's how to override inline content using JSPs: 1. Create a [Custom JSP Bag](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-custom-jsp-bag) for deploying your JSP. Note the module folder you're storing the JSPs in: the default folder is `[your module]/src/main/resources/META-INF/jsps/` | **Note:** you can develop your JSP anywhere, but a Custom JSP Bag module | provides a straightforward way to build and deploy it. 2. Download the @product@ source code or browse the source code on [GitHub (Liferay Portal CE)](https://github.com/liferay/liferay-portal/tree/7.2.x). 3. Search the source code for a `.jsp.readme` file that overrides the tag you're customizing. | **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 @product@ application's `portal-web/docroot/html/common/themes` folder. 4. Copy the `.jsp.readme` file into your project and drop the `.readme` suffix. Use the same relative file path @product@ uses for the `.jsp.readme` file. For example, if the file in @product@ is portal-web/docroot/html/taglib/aui/fieldset/start.jsp.readme use file path [your module]/src/main/resources/META-INF/jsps/html/taglib/aui/fieldset/start.jsp 5. 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 `*Tag.java` file under `util-taglib/src/com/liferay/taglib/[tag library]/`. 6. Develop your new logic, keeping in mind the current inline logic you're replacing. 7. Deploy your JSP. @product@ 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 `.jsp.readme` file to override core inline content! ## Example: Overriding the fieldset Taglib Tag This example demonstrates changing the `liferay:aui` tag library's `fieldset` tag. Browsing the @product@ web application or the source code at `portal-web/docroot/html/taglib/aui/fieldset` reveals these files: - `start.jsp.readme` - `end.jsp.readme` They can override the logic that creates the start and end of the `fieldset` tag. The `FieldsetTag.java` class's `processStart` and `processEnd` methods implement the current inline content. Here's the [`processStart`](https://github.com/liferay/liferay-portal/blob/7.2.x/util-taglib/src/com/liferay/taglib/aui/FieldsetTag.java#L86-L141) method: ```java @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; } ``` The code above does this: 1. Write `
    `; else write `
    `. Replicating the current logic in your custom JSP helps you set up the tag properly for customizing. The `init.jsp` for `fieldset` initializes all the variables required to create the starting tag. You can use the variables in the `start.jsp`. The logic from `FieldsetTag`'s `processStart` method converted to JSP code for `start.jsp` (renamed from `start.jsp.readme`) would look like this: ```markup <%@ include file="/html/taglib/aui/fieldset/init.jsp" %>
    <%= InlineUtil.buildDynamicAttributes(dynamicAttributes) %>>
    "> ``` | **Tip:** A `*Tag.java` file's history might reveal original JSP code that was | inlined. For example, the logic from `fieldset` tag's | [`start.jsp`](https://github.com/liferay/liferay-portal/blob/df22ba66eff49b76404cfda908d3cd024efbebd9/portal-web/docroot/html/taglib/aui/fieldset/start.jsp) | was inlined in | [this commit](https://github.com/liferay/liferay-portal/commit/7fba0775bcc1d1a0bc4d107cabfb41a90f15937c#diff-2ad802b4c0d8f7a2da45b895e89d6e46). On deploying the `start.jsp`, the `fieldset` tags render the same as they did before. This is expected because it uses the same logic as `FieldsetTag`'s `processStart` method. ![Figure 1: @product@'s home page's search and sign in components are in a `fieldset`.](../../images/jsp-readme-inline-fieldset.png) The `fieldset` starting logic is ready for customization. To test that this works, you'll print the word *test* surrounded by asterisks before the end of the `fieldset` tag's starting logic. Insert this line before the `start.jsp`'s last `div` tag: ```markup ``` Redeploy the JSP and refresh the page to see the text printed above the `fieldset`'s fields. ![Figure 2: Before the `fieldset`'s nested fields, it prints *test* surrounded by asterisks.](../../images/jsp-readme-override-inline-fieldset.png) You know how to override specific @product@ core inline content using Liferay's `.jsp.readme` files. ## Related Topics - [Customizing JSPs with Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes) - [JSP Overrides Using Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) ================================================ FILE: en/developer/customization/articles/50-customizing-widgets/01-intro.markdown ================================================ --- header-id: customizing-widgets --- # Customizing Widgets [TOC levels=1-4] 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 [Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-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., *Web Content* and *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 - *Asset Publisher* - *Blogs* - *Breadcrumb* - *Categories Navigation* - *Language Selector* - *Media Gallery* - *Navigation Menu* - *RSS Publisher* - *Site Map* - *Tags Navigation* - *Wiki* To leverage the Widget Template API, follow these steps: - register your portlet to use Widget Templates - define your display template definitions - define permissions - expose the Widget Template functionality to users The detailed steps are in the [Implementing Widget Templates](/docs/7-2/customization/-/knowledge_base/c/implementing-widget-templates) article. Here's a high level overview of what you'll do. ## Implementing the TemplateHandler Interface To register your portlet to use Widget Templates, you must implement the [`TemplateHandler`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateHandler.html) 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: `getClassName()`: Defines the type of entry your portlet is rendering. `getName()`: Declares the name of your Widget Template type (typically, the name of the portlet). `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., `com_liferay_wiki_web_portlet_WikiPortlet`). `getTemplateVariableGroups()`: Defines the variables exposed in the template editor. `getTemplatesConfigPath()`: Defines the configuration file containing the display template definition. Next, you must define your display template definition(s). ## 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 `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. ## 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 `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 @product@. You can visit [this article](/docs/7-2/customization/-/knowledge_base/c/implementing-widget-templates) for step-by-step instructions on how to complete this. Next, you'll learn how to expose Widget Template selection for users. ## Exposing the Widget Template Selection To expose the Widget Template option to your users, use the `` tag in the JSP file that controls your portlet's configuration. This tag requires the following parameters: `className`: your entity's class name. `contextObjects`: accepts a `Map` with any object you want to the template context. `displayStyle`: your portlet's display style. `displayStyleGroupId`: your portlet's display style group ID. `entries`: accepts a list of your entities (e.g., `List`). The variables `displayStyle` and `displayStyleGroupId` are preferences that your portlet stores when you use this taglib and your portlet uses the [`BaseJSPSettingsConfigurationAction`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BaseJSPSettingsConfigurationAction.html) or [`DefaultConfigurationAction`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/DefaultConfigurationAction.html). Otherwise, you must obtain the value of those parameters and store them manually in your configuration class. ## 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. @product@ provides some system settings, which can be accessed by navigating to *Control Panel* → *Configuration* → *System Settings* → *Template Engines* → *FreeMarker Engine*, to define the restricted classes, packages, and variables. In particular, you may want to add `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 @product@'s advanced template editor! To navigate to the template editor for Widget Templates, go to the Site Admin menu and select *Configuration* → *Widget Templates* and then click *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 [Styling Widgets with Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates). Finally, don't forget to run performance tests and tune the template cache options by modifying the *Resource modification check* field in *System Settings* → *Template Engines* → *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. ================================================ FILE: en/developer/customization/articles/50-customizing-widgets/02-implementing-widget-templates.markdown ================================================ --- header-id: implementing-widget-templates --- # Implementing Widget Templates [TOC levels=1-4] [Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-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. ![Figure 1: By using a custom display template, your portlet's display can be customized.](../../images/widget-template-dropdown.png) To add Widget Template support to your portlet, follow the steps below. 1. Create and register a custom `*PortletDisplayTemplateHandler` component. Liferay provides the [`BasePortletDisplayTemplateHandler`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portletdisplaytemplate/BasePortletDisplayTemplateHandler.html) as a base implementation for you to extend. You can check the [`TemplateHandler`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/template/TemplateHandler.html) interface Javadoc to learn about each template handler method. The `@Component` annotation ties your handler to a specific portlet by setting the property `javax.portlet.name` to your portlet's name. The same property should be found in your portlet class. For example, ```java @Component( immediate = true, property = { "javax.portlet.name="+ AssetCategoriesNavigationPortletKeys.ASSET_CATEGORIES_NAVIGATION }, service = TemplateHandler.class ) ``` The Site Map widget sets the `@Component` annotation like this: ```java @Component( immediate = true, property = "javax.portlet.name=" + SiteNavigationSiteMapPortletKeys.SITE_NAVIGATION_SITE_MAP, service = TemplateHandler.class ) public class SiteNavigationSiteMapPortletDisplayTemplateHandler extends BasePortletDisplayTemplateHandler { } ``` You'll continue stepping through the Site map widget's `TemplateHandler` implementation next. 2. Override the base class' `getClassName()`, `getName(...)`, and `getResourceName()` methods: ```java @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; } ``` These methods return the template handler's class name, the template handler's name (via [resource bundle](/docs/7-2/frameworks/-/knowledge_base/f/localization)), and the resource name associated with the Widget Template, respectively. 3. Override the `getTemplateVariableGroups(...)` method to return your widget template's script variable groups. These are used to display hints in the template editor palette. ```java @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; } ``` For this example, the *Pages* and *Site Map Display Context* fields are added to the default variables in the template editor palette. ![Figure 2: You can click a variable to add it to the template editor.](../../images/widget-template-fields.png) 4. Set your display template configuration file path: ```java @Override protected String getTemplatesConfigPath() { return "com/liferay/site/navigation/site/map/web/portlet/template" + "/dependencies/portlet-display-templates.xml"; } ``` This method returns the XML file containing the display template definitions available for your portlet. You'll create this file next. 5. Create your `portlet-display-templates.xml` file to define your display template definitions. For example, ```xml ``` 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 *Multi Column Layout* option is available. ![Figure 3: You can choose the Widget Template you want to apply from the widget's Configuration menu.](../../images/widget-config-display.png) This template is created using FreeMarker. You'll create this template option next. 6. 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: ```markup <#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) /> <#macro displayPages depth pages > <#if pages?has_content && ((depth < displayDepth?number) || (displayDepth?number == 0))> ``` This template definition enforces page permissions, formats how the pages are displayed (multi column), and provides clickable links for each page. 7. Your widget must define permissions for creating and managing display templates. Add the action key `ADD_PORTLET_DISPLAY_TEMPLATE` to your portlet's `/src/main/resources/resource-actions/default.xml` file: ```xml ... yourportlet ADD_PORTLET_DISPLAY_TEMPLATE ADD_TO_PAGE CONFIGURATION VIEW ... ... ``` 8. If your widget hasn't defined Liferay permissions before, create a file named `portlet.properties` in the `/resources` folder and add the following contents providing the path to your `default.xml`: ```properties include-and-override=portlet-ext.properties resource.actions.configs=resource-actions/default.xml ``` 9. Now expose the Widget Template selector to your users. Include the `` 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 `` in your configuration JSP file like this: ```markup
    ``` In this JSP, the `` tag specifies the Display Template drop-down menu to be used in the widget's Configuration menu. 10. 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: ```markup <% String displayStyle = GetterUtil.getString(portletPreferences.getValue("displayStyle", StringPool.BLANK)); long displayStyleGroupId = GetterUtil.getLong(portletPreferences.getValue("displayStyleGroupId", null), scopeGroupId); %> ``` 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 `` 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: ```markup <%-- The code that renders the default view should be inserted here. --%> ``` In this step, you initialized variables dealing with the display settings (`displayStyle` and `displayStyleGroupId`) and passed them to the tag along with other parameters. As an example, the Site Map widget implements the `` tag in its `view.jsp` like this: ```markup <%= siteNavigationSiteMapDisplayContext.buildSiteMap() %> ``` This logic builds the site's navigation map when the widget is added to a page. 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 [Styling Widgets with Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-widgets-with-widget-templates) section for more details on using Widget Templates. ================================================ FILE: en/developer/customization/articles/50-dynamic-includes/01-dynamic-include-extension-points-intro.markdown ================================================ --- header-id: dynamic-includes --- # Dynamic Includes [TOC levels=1-4] 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 [create a module to inject your content](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps-with-dynamic-includes). 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: Extension Point | Purpose | :---------: | :--------------: | [bottom](/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes) | Load additional HTML or scripts in the bottom of the theme's body | [top_head](/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes) | Load additional links in the theme's head | [top_js](/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include) | Load additional JS files in the theme's head | [WYSIWYG](/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes) | Add resources to the editor, listen to events, update the configuration, etc. | ================================================ FILE: en/developer/customization/articles/50-dynamic-includes/02-wysiwyg-editor-dynamic-includes.markdown ================================================ --- header-id: wysiwyg-editor-dynamic-includes --- # WYSIWYG Editor Dynamic Includes [TOC levels=1-4] All WYSIWYG editors share the same dynamic include extension points for these things: - Adding resources, plugins, etc. to the editor: com.liferay.frontend.editor.`editorType`.web#`editorName`#additionalResources - Accessing the editor instance to listen to events, configure it, etc: com.liferay.frontend.editor.`editorType`.web#`editorName`#onEditorCreate The table below shows the `editorType`, variable, and `editorName`s for each editor: editorType | variable | editorName | :---------: | :--------------: | :---------: | alloyeditor | alloyEditor | alloyeditor |   |   | alloyeditor_bbcode |   |   | alloyeditor_creole | ckeditor | ckEditor | ckeditor |   |   | ckeditor_bbcode |   |   | ckeditor_creole | tinymce | tinyMCEEditor | tinymce |   |   | tinymce_simple | The example below alerts the user when he/she pastes content into the CKEditor. `*DynamicInclude` Java Class: ```java @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; } ``` Example JavaScript: ```javascript // ckEditor variable is already available in the execution context ckEditor.on( 'paste', function(event) { event.stop(); alert('Please, do not paste code here!'); } ); ``` Now you know how to use the WYSIWYG editor dynamic includes. ## Related Topics - [Bottom JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes) - [Top Head JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes) - [Top JS Dynamic Include](/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include) ================================================ FILE: en/developer/customization/articles/50-dynamic-includes/03-top-head-jsp-dynamic-include.markdown ================================================ --- header-id: top-head-jsp-dynamic-includes --- # Top Head JSP Dynamic Includes [TOC levels=1-4] The `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: /html/common/themes/top_head.jsp#pre Alternatively, you can load additional links in the theme's head, after the existing ones: /html/common/themes/top_head.jsp#post The example below injects a link into the top of the `top_head.jsp`: ```java @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"); } } ``` Page Source: ```html ... ... ``` Note that the link's `href` attribute's value `/o/my-custom-dynamic-include/` is provided by the OSGi module's `Web-ContextPath` (`/my-custom-dynamic-include` in the example). Now you know how to use the `top_head.jsp` dynamic includes. ## Related Topics - [Bottom JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes) - [Top JS Dynamic Include](/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include) - [WYSIWYG Editor Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes) ================================================ FILE: en/developer/customization/articles/50-dynamic-includes/04-top-js-dynamic-include.markdown ================================================ --- header-id: top-js-dynamic-include --- # Top JS Dynamic Include [TOC levels=1-4] The `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: /html/common/themes/top_js.jspf#resources The example below injects a JavaScript file into the top of the `top_js.jspf`: `*DynamicInclude` Java Class: ```java @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 = " ... ``` Note that the JavaScript `src` attribute's value `/o/my-custom-dynamic-include/...` is provided by the OSGi module's `Web-ContextPath` (`/my-custom-dynamic-include` in the example). Now you know how to use the `top_js.jspf` dynamic include. ## Related Topics - [Bottom JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/bottom-jsp-dynamic-includes) - [Top Head JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes) - [WYSIWYG Editor Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes) ================================================ FILE: en/developer/customization/articles/50-dynamic-includes/05-bottom-jsp-dynamic-include.markdown ================================================ --- header-id: bottom-jsp-dynamic-includes --- # Bottom JSP Dynamic Includes [TOC levels=1-4] The `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: /html/common/themes/bottom.jsp#pre Alternatively, load HTML or scripts in the bottom of the theme's body, after the existing ones: /html/common/themes/bottom.jsp#post The example below includes an additional script for the Simulation panel in the bottom of the theme's body, after the existing ones. `SimulationDeviceDynamicInclude` Java class: ```java @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"); } ``` `simulation_device_dynamic_include.tmpl`: ```javascript ``` When the Simulation panel is open, the script adds the `lfr-has-simulation-panel` class to the theme's body. Page Source: ```html ``` Now you know how to use the `bottom.jsp` dynamic includes. ## Related Topics - [Top Head JSP Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/top-head-jsp-dynamic-includes) - [Top JS Dynamic Include](/docs/7-2/customization/-/knowledge_base/c/top-js-dynamic-include) - [WYSIWYG Editor Dynamic Includes](/docs/7-2/customization/-/knowledge_base/c/wysiwyg-editor-dynamic-includes) ================================================ FILE: en/developer/customization/articles/50-lifecycle-events/01-waiting-on-lifecycle-events-intro.markdown ================================================ --- header-id: waiting-on-lifecycle-events --- # Waiting on Lifecycle Events [TOC levels=1-4] 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 [`ModuleServiceLifecycle` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/module/framework/ModuleServiceLifecycle.html) defines these names for the lifecycle event services: - [DATABASE_INITIALIZED](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.DATABASE_INITIALIZED) - [PORTAL_INITIALIZED](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.PORTAL_INITIALIZED) - [SPRING_INITIALIZED](@platform-ref@/7.2-latest/javadocs/portal-kernel/constant-values.html#com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle.SPRING_INITIALIZED) Here you'll learn how to wait on lifecycle event services to act on them from within a component or non-component class. ## Taking action from a component [Declarative Services (DS)](https://osgi.org/specification/osgi.cmpn/7.0.0/service.component.html) facilitates waiting for OSGi services and acting on them once they're available. Here's a component whose `doSomething` method is invoked once the `ModuleServiceLifecycle.PORTAL_INITIALIZED` lifecycle event service and other services are available. ```java @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. } } ``` Here's how to act on services in your component: 1. For each lifecycle event service and OSGi service your component uses, add a field of that service type and add an `@Reference` annotation to that field. The OSGi framework binds the services to your fields. This field, for example, binds to a standard OSGi service. ```java @Reference SomeOsgiService _someOsgiService; ``` 2. To bind to a particular lifecycle event service, target its name as the [`ModuleServiceLifecycle` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/module/framework/ModuleServiceLifecycle.html) defines. This field, for example, targets database initialization. ```java @Reference(target = ModuleServiceLifecycle.DATABASE_INITIALIZED) ModuleServiceLifecycle _dataInitialized; ``` 3. Create a method that's triggered on the event(s) and add the `@Activate` annotation to that method. It's invoked when all the service objects are bound to the component's fields. Your component fires (via its `@Activate` method) after all its service dependencies resolve. DS components are the easiest way to act on lifecycle event services. ## Taking action from a non-component class Classes that aren't DS components can use a `org.osgi.util.tracker.ServiceTracker` or `org.osgi.util.tracker.ServiceTrackerCustomizer` as a [service callback handler](/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker#creating-a-service-tracker-that-tracks-service-events-using-a-callback-handler) for the lifecycle event. If you depend on multiple services, add logic to your `ServiceTracker` or `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 `org.osgi.framework.FrameworkUtil` to create an `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 `ModuleServiceLifecycle.PORTAL_INITIALIZED`. ```java 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); ``` 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. ## Related Topics [Service Trackers](/docs/7-1/tutorials/-/knowledge_base/t/service-trackers) [@product@ Startup Phases](/docs/7-1/reference/-/knowledge_base/r/liferay-startup-phases) ================================================ FILE: en/developer/customization/articles/50-liferay-forms/01-forms-intro.markdown ================================================ --- header-id: liferay-forms --- # Liferay Forms [TOC levels=1-4] The [Liferay Forms](/docs/7-2/user/-/knowledge_base/u/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 1. Store form entry data in an alternative format. The default storage type is JSON. 2. [Coming Soon] Create new form field types. ## Liferay Forms Extension Points Here's a compilation of the Liferay Forms application's extension points that are ready for your customization: - Create a Form Storage Adapter by implementing a [`StorageAdapter`](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) or by extending the Abstract implementation, [`BaseStorageAdapter`](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). - Create a Form Field Type by implementing a [`DDMFormFieldType`](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), [`DDMFormFieldTypeSettings`](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), and a [`DDMFormFieldTemplateContextContributor`](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). - Create custom validation rules for form fields by implementing a [DDMFormFieldValueValidator](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). ================================================ FILE: en/developer/customization/articles/50-liferay-forms/02-form-storage-adapters.markdown ================================================ --- header-id: form-storage-adapters --- # Form Storage Adapters [TOC levels=1-4] 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 *CRUD* operations performed on form entries (read, update, and delete operations). The default implementation of the storage service is called `JSONStorageAdapter`, and as its name implies, it implements the `StorageAdapter` interface to provide JSON storage of form entry data. The Dynamic Data Mapping (DDM) backend can *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 `StorageAdapter` interface. The interface follows the *CRUD* approach, so implementing it requires that you write methods to create, read, update and delete form values. | **Note:** The `StorageAdapter` interface and it's abstract implementation, | | `BaseStorageAdapter`, are deprecated in @product-ver@. In the future your code | should be migrated to implement the `DDMStorageAdapter` interface. If you need a | storage adapter, the current extension of `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. 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. ![Figure 1: Choose a Storage Type for your form records.](../../images/forms-storage-type.png) ## Storage Adapter Methods Before handling the CRUD logic, write a `getStorageType` method. `getStorageType` : Return a human readable String, as `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 `StorageAdapterRegistry`'s Map of storage adapters. ### The CRUD Methods `doCreate`: Return a `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 `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: `DDMStructureVersionLocalService` and `DDMStorageLinkLocalService`. Lastly, the form values in the `DDMFormValues` object must be serialized (converted) into the right storage format. If JSON works for your use case, feel free to use the `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. `doGetDDMFormValues` : Return the form values (`DDMFormValues`) for a form. You'll call the `deserialize` method after retrieving them, to take them from the storage format (e.g., JSON) to a proper `DDMFormValues` object. You can use the Liferay Forms `DDMFormValuesJSONDeserializer` if you're retrieving JSON data. `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. `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 `DDMStorageLinkLocalService`. `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 `ddmStructureId` and delete all the DDM structure storage links that were created for it. ### Validating Form Entries Because the Storage Adapter handles User entered data during the `add` and `update` operations, it's important to validate that the entries include only appropriate data. Add a `validate` method to the `StorageAdapter`, calling the Liferay Forms' `DDMFormValuesValidator` method to do the heavy lifting. ```java protected void validate( DDMFormValues ddmFormValues, ServiceContext serviceContext) throws Exception { boolean validateDDMFormValues = GetterUtil.getBoolean( serviceContext.getAttribute("validateDDMFormValues"), true); if (!validateDDMFormValues) { return; } _ddmFormValuesValidator.validate(ddmFormValues); } ``` Make sure to do three things: 1. Retrieve the value of the `boolean validateDDMFormValues` attribute from the service context. 2. If `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 *Submit* button on the form, so the auto-save process sets the `validateDDMFormValues` attribute to `false`. 3. Otherwise, call the validate method from the `DDMFormValuesValidator` service. All the Java code for the logic discussed here is shown in the next article, [Creating Form Storage Adapters](/docs/7-2/customization/-/knowledge_base/c/creating-a-form-storage-adapter). ## Enabling the Storage Adapter The storage adapter is enabled at the individual form level. Create a new form, and select the Storage Adapter _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. 1. Go to the Site Menu → Content → Forms, and click the *Add* button (![Add](../../images/icon-add.png)). 2. In the Form Builder view, click the *Options* button (![Options](../../images/icon-options.png)) and open the *Settings* window. 3. From the select list field called *Select a Storage Type*, choose the desired type and click _Done_. Now all the form's entries are stored in the desired format. ================================================ FILE: en/developer/customization/articles/50-liferay-forms/03-creating-form-storage-adapters.markdown ================================================ --- header-id: creating-a-form-storage-adapter --- # Creating a Form Storage Adapter [TOC levels=1-4] There's only one class to create when implementing a Form Storage Adapter, and it extends the base `StorageAdapter` implementation. ```java @Component(service = StorageAdapter.class) public class FileSystemStorageAdapter extends BaseStorageAdapter { ``` The only method without a base implementation in the abstract class is `getStorageType`. For file system storage, it can return `"File System"`. ```java @Override public String getStorageType() { return "File System"; } ``` ## Storage Adapter CRUD Operations The CRUD operations must be created to properly handle the Form Records. ### Create Next override the `doCreateMethod` to return a `long` that identifies each form record with a unique file ID: ```java @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; ``` These are the utility methods invoked in the create method: ```java 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; ``` ### Read To retrieve the form record's values from the `File` object where they were written, override `doGetDDMFormValues`: ```java @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; ``` ### Update Override the `doUpdate` method so the record's values can be overwritten. This example calls the `saveFile` utility method provided earlier: ```java @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); } ``` ### Delete Override the `doDeleteByClass` method to delete the `File` representing the form record, using the `classPK`, and to delete the class storage links: ```java @Override protected void doDeleteByClass(long classPK) throws Exception { DDMStorageLink storageLink = _ddmStorageLinkLocalService.getClassStorageLink(classPK); FileUtil.delete(getFile(storageLink.getStructureId(), classPK)); _ddmStorageLinkLocalService.deleteClassStorageLink(classPK); } ``` Provide form record deletion logic to be called when deleting all the records and storage links associated with a form (using its `ddmStructureId`): ```java @Override protected void doDeleteByDDMStructure(long ddmStructureId) throws Exception { FileUtil.deltree(getStructureFolder(ddmStructureId)); _ddmStorageLinkLocalService.deleteStructureStorageLinks(ddmStructureId); } ``` ## Beyond CRUD: Validation Add a `validate` method to the `StorageAdapter`: ```java protected void validate( DDMFormValues ddmFormValues, ServiceContext serviceContext) throws Exception { boolean validateDDMFormValues = GetterUtil.getBoolean( serviceContext.getAttribute("validateDDMFormValues"), true); if (!validateDDMFormValues) { return; } _ddmFormValuesValidator.validate(ddmFormValues); } ``` Deploy your storage adapter and it's ready to use. ================================================ FILE: en/developer/customization/articles/50-overriding-language-keys/01-overriding-language-keys-intro.markdown ================================================ --- header-id: overriding-language-keys --- # Overriding Language Keys [TOC levels=1-4] Core and portlet module `Language*.properties` files implement site internationalization. They're fully customizable, too. This section demonstrates this in the following topics: - [Overriding Liferay's Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys) - [Overriding a Module's Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys) ================================================ FILE: en/developer/customization/articles/50-overriding-language-keys/02-overriding-liferays-language-keys.markdown ================================================ --- header-id: overriding-global-language-keys --- # Overriding Global Language Keys [TOC levels=1-4] Language files contain [translations of your application's user interface messages](/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application). 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: 1. [Determine the language keys to override](#determine-the-language-keys-to-override) 2. [Override the keys in a new language properties file](#override-the-keys-in-a-new-language-properties-file) 3. [Create a Resource Bundle service component](#create-a-resource-bundle-service-component) | **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 | [overriding a module's language keys](/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys) | is different from the process for overriding Liferay's language keys. ## Determine the language keys to override So how do you find global language keys? They're in the `Language[xx_XX].properties` files in the source code or your bundle. - From the source: `/portal-impl/src/content/Language[xx_XX].properties` - From a bundle: `portal-impl.jar` All language properties files contain properties you can override, like the language settings properties: ```properties ## ## 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 ... ``` There are also many simple keys you can override to update default messages and labels. ```properties ## ## Category titles ## category.admin=Admin category.alfresco=Alfresco category.christianity=Christianity category.cms=Content Management ... ``` For example, Figure 1 shows a button that uses Liferay's `publish` default language key. `publish=Publish` ![Figure 1: Messages displayed in Liferay's user interface can be customized.](../../images/standard-publish.png) Next, you'll learn how to override this key. ## 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 `Language.properties` file) in your module's `src/main/resources/content` folder. In your file, define the keys your way. For example, you could override the `publish` key. ```properties publish=Publish Override ``` To enable your change, you must create a resource bundle service component to reference your language file. ### Create a Resource Bundle service component In your module, create a class that extends `java.util.ResourceBundle` for the locale you're overriding. Here's an example resource bundle class for the `en_US` locale: ```java @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); } ``` The class's `_resourceBundle` field is assigned a `ResourceBundle`. The call to `ResourceBundle.getBundle` needs two parameters. The `content.Language_en_US` parameter is the language file's qualified name with respect to the module's `src/main/resources` folder. The second parameter is a `control` that sets the language syntax of the resource bundle. To use language syntax identical to Liferay's syntax, import Liferay's `com.liferay.portal.kernel.language.UTF8Control` class and set the second parameter to `UTF8Control.INSTANCE`. The class's `@Component` annotation declares it an OSGi `ResourceBundle` service component. It's `language.id` property designates it for the `en_US` locale. ```java @Component( property = { "language.id=en_US" }, service = ResourceBundle.class ) ``` The class overrides these methods: - **`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 `Object`. - **`getKeys`:** Returns an `Enumeration` of the resource bundle's keys. Your resource bundle service component redirects the default language keys to your module's language key overrides. | **Note**: Global language key overrides for multiple locales require a separate | module for each locale. Each module's `ResourceBundle` extension class (like the | `MyEnUsResourceBundle` class above) must specify its locale in the `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: | | ```java | @Component( | property = { "language.id=es_ES" }, | service = ResourceBundle.class | ) | ``` | | Resource bundle assignment: | | ```java | private final ResourceBundle _resourceBundle = ResourceBundle.getBundle( | "content.Language_es_ES", UTF8Control.INSTANCE); | ``` **Important**: If your module [uses language keys from another module](/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module) and [overrides any of that other module's keys](/docs/7-2/frameworks/-/knowledge_base/f/using-a-language-module), 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, [deploy your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project) and visit the portlets and pages that use the keys. ![Figure 2: This button uses the overridden `publish` key.](../../images/localized-publish.png) That's all there is to overriding Liferay's language keys. ## Related Topics - [Upgrading Core Language Key Hooks](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-core-language-key-hooks) - [Overriding a Module's Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-a-modules-language-keys) ================================================ FILE: en/developer/customization/articles/50-overriding-language-keys/03-overriding-a-modules-language-keys.markdown ================================================ --- header-id: overriding-a-modules-language-keys --- # Overriding a Module's Language Keys [TOC levels=1-4] 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 [the process of overriding Liferay's language keys](/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys). Here is the process: 1. [Find the module and its metadata and language keys](#find-the-module-and-its-metadata-and-language-keys) 2. [Write your custom language key values](#write-custom-language-key-values) 3. [Prioritize your module's resource bundle](#prioritize-your-modules-resource-bundle) ## Find the module and its metadata and language keys In [Gogo shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-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: 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 List the bundle's headers by passing its ID to the `headers` command. 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! Note the `Bundle-SymbolicName`, `Bundle-Version`, and `Web-ContextPath`. The `Web-ContextPath` value, following the `/`, is the servlet context name. **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: - Bundle symbolic name: `com.liferay.blogs.web` - Bundle version: `4.0.16` - Servlet context name: `blogs-web` Next find the module's JAR file so you can examine its language keys. Liferay follows this module JAR file naming convention: [bundle symbolic name]-[version].jar For example, the Blogs Web version 4.0.16 module is in `com.liferay.blogs.web-4.0.16.jar`. Here's where to find the module JAR: - Liferay's [Nexus repository](https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/) - `[Liferay Home]/osgi/modules` - Embedded in an application's or application suite's LPKG file in `[Liferay Home]/osgi/marketplace`. The language property files are in the module's `src/main/resources/content` folder. Identify the language keys you want to override in the `Language[_xx].properties` files. Checkpoint: Make sure you have the required information for overriding the module's language keys: - Language keys - Bundle symbolic name - Servlet context name Next you'll write new values for the language keys. ## Write custom language key values Create a new module to hold a resource bundle loader and your custom language keys. In your module's `src/main/resources/content` folder, create [language properties files](/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application) 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. ## Prioritize Your Module's 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 `com.liferay.docs.l10n.myapp.lang` prioritizing its resource bundle over target module `com.liferay.blogs.web`'s resource bundle: ```properties 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 ``` The example `Provide-Capability` header has two parts: 1. `liferay.resource.bundle;resource.bundle.base.name="content.Language"` declares that the module provides a resource bundle with the base name `content.language`. 2. The `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: - `"(bundle.symbolic.name=com.liferay.docs.l10n.myapp.lang),(bundle.symbolic.name=com.liferay.blogs.web)"`: The service aggregates resource bundles from bundles `com.liferay.docs.l10n.myapp.lang` and `com.liferay.blogs.web`. Aggregate as many bundles as desired. Listed bundles are prioritized in descending order. - `bundle.symbolic.name=com.liferay.blogs.web;resource.bundle.base.name="content.Language"`: Override the `com.liferay.blogs.web` bundle's resource bundle named `content.Language`. - `service.ranking:Long="2"`: The resource bundle's service ranking is `2`. The OSGi framework applies this service if it outranks all other resource bundle services that target `com.liferay.blogs.web`'s `content.Language` resource bundle. - `servlet.context.name=blogs-web`: The target resource bundle is in servlet context `blogs-web`. [Deploy your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project) to see the language keys you've overridden. | **Tip:** If your override isn't showing, use | [Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-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 `com.liferay.blogs.web`'s resource bundle, for example, execute this | Gogo Shell command: | | services "(bundle.symbolic.name=com.liferay.login.web)" | | Search the results for resource bundle aggregate services whose ranking is | higher. 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 [override Liferay's language keys](/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys) too. ## Related Topics - [Upgrading Core Language Key Hooks](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-core-language-key-hooks) - [Overriding Global Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-global-language-keys) ================================================ FILE: en/developer/customization/articles/50-overriding-liferay-services/01-overriding-liferay-services-intro.markdown ================================================ --- header-id: overriding-service-builder-services-service-wrappers --- # Overriding Liferay Services (Service Wrappers) [TOC levels=1-4] Why might you need to customize Liferay services? Perhaps you've added a new field to Liferay's `User` object and you want its value to be saved whenever the `addUser` or `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 [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/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 [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli) to create a `servicewrapper` project type with the command below (replace the class and package names with your own): ```bash blade create -t service-wrapper -p com.liferay.docs.serviceoverride -c UserLocalServiceOverride -s com.liferay.portal.kernel.service.UserLocalServiceWrapper service-override ``` As an example, here's the `UserLocalServiceOverride` class that's generated with the Service Wrapper Template: ```java 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); } } ``` Notice that you must specify the fully qualified class name of the service wrapper class that you want to extend. The `service` argument was used in full in this import statement: ```java import com.liferay.portal.service.UserLocalServiceWrapper; ``` 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: ```java public class UserLocalServiceOverride extends UserLocalServiceWrapper {...} ``` The bottom line is that when using `blade create` to create a service wrapper project, you must specify a fully qualified class name as the `service` argument. (This is also true when using `blade create` to create a service project.) For information about creating service projects, please see [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder). The generated `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 `UserLocalServiceOverride` class and add the following methods: ```java @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); } ``` 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. ```java @Reference(unbind = "-") private void serviceSetter(UserLocalService userLocalService) { setWrappedService(userLocalService); } ``` [Build and deploy your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project). Congratulations! You've created and deployed a Liferay service wrapper! ## Related Topics - [Upgrading Service Wrappers](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-service-wrapper-hooks) - [Installing Blade CLI](/docs/7-2/reference/-/knowledge_base/r/installing-blade-cli) - [Creating Projects with Blade CLI](/docs/7-1/tutorials/-/knowledge_base/t/creating-projects-with-blade-cli) ================================================ FILE: en/developer/customization/articles/50-overriding-lpkg-files/01-overriding-lpkg-files-intro.markdown ================================================ --- header-id: overriding-lpkg-files --- # Overriding lpkg Files [TOC levels=1-4] Applications are delivered through Liferay Marketplace as *lpkg* files. This is a simple compressed file format that contains .jar files for deploying to @product@. 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 [customize](/docs/7-2/customization/-/knowledge_base/c/liferay-customization) 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: 1. Shut down @product@. 2. Create a folder called `override` in the [`Liferay Home]/osgi/marketplace` folder](/docs/7-2/deploy/-/knowledge_base/d/liferay-home). 3. Name your updated .jar the same as the .jar in the original .lpkg, minus the version information. For example, if you're overriding the `com.liferay.amazon.rankings.web-1.0.5.jar` from the `Liferay CE Amazon Rankings.lpkg`, you'd name your .jar `com.liferay.amazon.rankings.web.jar`. 4. Copy this .jar into the `override` folder you created in step one. 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: 1. Make your customization and package it in a .jar file. 2. Name your .jar the same as the original .jar, minus the version information. For example, a customized `com.liferay.portal.profile-1.0.4.jar` should be `com.liferay.portal.profile.jar`. 3. Copy the .jar into the `[Liferay Home]/osgi/static` folder. Now start @product@. Note that any time you add and remove .jars this way, @product@ 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: @product@ uses the original .jar on its next startup. ================================================ FILE: en/developer/customization/articles/50-overriding-mvc-commands/01-overriding-mvc-commands-intro.markdown ================================================ --- header-id: overriding-liferay-mvc-commands --- # Overriding Liferay MVC Commands [TOC levels=1-4] MVC Commands are used to break up the controller layer of [Liferay MVC applications](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) 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 [reluctant references to the original command must be configured to reference the new one](/docs/7-2/customization/-/knowledge_base/c/overriding-osgi-services). 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: - MVCActionCommand: [Add logic](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcactioncommand) - MVCRenderCommand: - [Add logic](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand#adding-logic-to-an-existing-mvc-render-command) - [Redirect to a different JSP](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand#redirecting-to-a-new-jsp) - MVCResourceCommand: [Add logic](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcresourcecommand) This section demonstrates each MVC command customization option. Since the steps for adding logic are generally the same across MVC command types, start with [adding logic](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands). ================================================ FILE: en/developer/customization/articles/50-overriding-mvc-commands/02-adding-logic-to-mvc-commands.markdown ================================================ --- header-id: adding-logic-to-mvc-commands --- # Adding Logic to MVC Commands [TOC levels=1-4] You can completely override MVC commands, or any OSGi service for that matter, but *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: 1. [Implement the interface](#step-1-implement-the-interface) 2. [Publish as a component](#step-2-publish-as-a-component) 3. [Refer to the original implementation](#step-3-refer-to-the-original-implementation) 4. [Add the logic, and call the original](#step-4-add-the-logic) ## 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 `EditEntryMVCActionCommand`, you would extend base class `BaseMVCActionCommand`. ```java public class CustomBlogsMVCActionCommand extends BaseMVCActionCommand {...} ``` Check the MVC command interfaces for existing base classes: - [`MVCActionCommand`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCActionCommand.html) - [`MVCRenderCommand`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html) - [`MVCResourceCommand`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCResourceCommand.html) Next make your class a service component. ## Step 2: Publish as a component The Declarative Services `@Component` annotation facilitates customizing MVC commands. All the customization options require publishing your MVC command class as a component. For example, this `@Component` annotation declares an `MVCActionCommand` service. ```java @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 { ... } ``` It publishes `CustomBlogsMVCActionCommand` as a service component for the `MVCActionCommand` class. Upon resolving, it's activated immediately because `immediate = true`. The component is invoked in the Blogs Admin portlet by the command URL `/blogs/edit_entry`. Its service ranking of `100` prioritizes it ahead of the original service component, whose ranking is `0`. Here's what you need to specify in an `@Component` annotation for your custom MVC command: - `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. - `mvc.command.name`: this property declares the command URL that maps to this custom MVC command component. - `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 `0` ranking. - `service`: this attribute specifies the service (interface) to override. - `immediate`: set this attribute to `true` to activate your component immediately upon resolution. You can refer back to this list as you add `@Component` annotations to your custom MVC commands. Next reference the original implementation. ## Step 3: Refer to the original implementation Use a field annotated with `@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 `EditEntryMVCActionCommand`. ```java @Reference( target = "(component.name=com.liferay.blogs.web.internal.portlet.action.EditEntryMVCActionCommand)") protected MVCActionCommand mvcActionCommand; ``` Here's how to add the reference: 1. Declare the field as the MVC command interface type that it is. For example, the `mvcActionCommand` field is type `MVCActionCommand`. 2. Add the `@Reference` annotation. 3. In the annotation, define a `target` attribute that filters on a `component.name` equal to the default service implementation class's fully qualified name. When your custom component resolves, the OSGi runtime assigns the targeted service to your field. It's time to add your custom logic. ## 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 `BaseMVCActionCommand`'s method `doProcessAction`. ```java @Override protected void doProcessAction( ActionRequest actionRequest, ActionResponse actionResponse) throws Exception { // Add custom logic here ... // Call the original service implementation mvcActionCommand.processAction(actionRequest, actionResponse); } ``` 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. ================================================ FILE: en/developer/customization/articles/50-overriding-mvc-commands/03-overriding-mvcrendercommands.markdown ================================================ --- header-id: overriding-mvcrendercommand --- # Overriding MVCRenderCommands [TOC levels=1-4] You can override [`MVCRenderCommand`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html) for any portlet that uses Liferay's MVC framework and publishes an `MVCRenderCommand` component. For example, Liferay's Blogs application has a class called `EditEntryMVCRenderCommand`, with this component: ```java @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 ) ``` This MVC render command can be invoked from any of the portlets specified by the `javax.portlet.name` parameter, by calling a render URL that names the MVC command: ```markup ``` 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 `javax.portlet.name` of the portlets where you want the override to take effect. For example, if you want to override the `/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: ```java @Component( immediate = true, property = { "javax.portlet.name=" + BlogsPortletKeys.BLOGS_ADMIN, "mvc.command.name=/blogs/edit_entry", "service.ranking:Integer=100" }, service = MVCRenderCommand.class ) ``` Note the last property listed, `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 `0`. After that, it's up to you to do whatever you'd like. MVC render commands can be customized for these purposes: - [Adding Logic to an Existing MVC Render Command](#adding-logic-to-an-existing-mvc-render-command) - [Redirecting to a new JSP](#redirecting-to-a-new-jsp) Start by exploring how to add logic to an existing MVC render command. ## Adding Logic to an Existing MVC Render Command You can add logic to an MVC render command following the [general steps for MVC commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands). Specifically for MVC render commands, you must directly implement the [`MVCRenderCommand` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderCommand.html) and override its `render` method. For example, this custom MVC render command has a placeholder (i.e., at comment `//Do something here`) for adding logic to the `render` method: ```java 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; } ``` The example references an `EditEntryMVCRenderCommand` implementation of `MVCRenderCommand`. In the `render` method, you'd replace the placeholder with new logic and then invoke the original implementation's logic by calling its `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. ## Redirecting to a New JSP `MVCRenderCommand`'s `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 [`MVCRenderConstants.MVC_PATH_VALUE_SKIP_DISPATCH` class](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCRenderConstants.html). Then you need to initiate your own dispatching process, directing the request to your JSP path. Here's how that might look in practice: ```java 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; } ``` 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 `bnd.bnd` file: ```properties Web-ContextPath: /custom-code-web ``` Follow these steps to fetch the portlet's servlet context in your custom MVC render command: 1. Add a `ServletContext` field. ```java protected ServletContext servletContext; ``` 2. Add the `@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 `.web`. For example, this servlet context reference filters on a module whose symbolic name is `com.custom.code.web`. ```java @Reference(target = "(osgi.web.symbolicname=com.custom.code.web)") protected ServletContext servletContext; ``` Implement your `render` method this way: 1. Get a request dispatcher to your module's custom JSP: ```java RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher("/custom_edit_entry.jsp"); ``` 2. Include the HTTP servlet request and response in the request dispatcher. ```java 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); } ``` 3. Return the request dispatcher via the constant `MVC_PATH_VALUE_SKIP_DISPATCH`. ```java return MVCRenderConstants.MVC_PATH_VALUE_SKIP_DISPATCH; ``` After deploying your module, the [portlets targeted by your custom `MVCRenderCommand` component](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands#step-2-publish-as-a-component) render your new JSP. ## Related Topics - [Adding Logic to MVC Commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands) - [Converting StrutsActionWrappers to MVCCommands](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-struts-action-hooks) ================================================ FILE: en/developer/customization/articles/50-overriding-mvc-commands/04-override-mvcactioncommands.markdown ================================================ --- header-id: overriding-mvcactioncommand --- # Overriding MVCActionCommands [TOC levels=1-4] 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 [adding logic to MVC commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands). It involves [registering your custom MVC action command as an OSGi component](/docs/7-1/tutorials/-/knowledge_base/t/adding-logic-to-mvc-commands#publish-as-a-component) with the same properties as the original, but with a higher service ranking. Custom MVC action commands typically extend the [`BaseMVCActionCommand` class](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/BaseMVCActionCommand.html), and override its `doProcessAction` method, which returns `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 `MVCActionCommand` override checks whether the `delete` action is invoked on a blog entry, and prints a message to the log, before continuing with the original processing: ```java @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; } ``` Adding MVC action command logic before existing logic is straightforward and maintains loose coupling between new and old code. ## Related Topics - [Adding Logic to MVC Commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands) - [Overriding MVCRenderCommands](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand) - [Converting StrutsActionWrappers to MVCCommands](/docs/7-2/tutorials/-/knowledge_base/t/upgrading-struts-action-hooks) ================================================ FILE: en/developer/customization/articles/50-overriding-mvc-commands/05-override-mvcresourcecommands.markdown ================================================ --- header-id: overriding-mvcresourcecommand --- # Overriding MVCResourceCommands [TOC levels=1-4] 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 [adding logic to MVC commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands) and it is similar to the ones described for `MVCRenderCommand` and `MVCActionCommand`. There's a couple things to keep in mind: - The service to specify in your component is `MVCResourceCommand.class` - As with overriding `MVCRenderCommand`, there's no base implementation class to extend. Implement the [`MVCResourceCommand` interface](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/bridges/mvc/MVCResourceCommand.html) yourself. - Keep your code decoupled from the original code by adding your logic to the original `MVCResourceCommand`'s logic by getting a reference to the original and returning a call to its `serveResource` method: ```java return mvcResourceCommand.serveResource(resourceRequest, resourceResponse); ``` The following example overrides the behavior of `com.liferay.login.web.portlet.action.CaptchaMVCResourceCommand`, from the Liferay's Login portlet's `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. ```java @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; } ``` And that, as they say, is that. Even if you don't own the source code of an application, you can [override its MVC commands](/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands) just by knowing the component class name. ## Related Topics - [Adding Logic to MVC Commands](/docs/7-2/customization/-/knowledge_base/c/adding-logic-to-mvc-commands) - [Overriding MVCRenderCommands](/docs/7-2/customization/-/knowledge_base/c/overriding-mvcrendercommand) ================================================ FILE: en/developer/customization/articles/50-overriding-osgi-services/01-overriding-osgi-services-intro.markdown ================================================ --- header-id: overriding-osgi-services --- # Overriding OSGi Services [TOC levels=1-4] 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. @product@'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: 1. [Get the service and service reference details](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override) 2. [Create a custom service](/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service) 3. [Configure components to use your custom service](/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service) | **Note:** The | [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) | services in | [portal-impl](@platform-ref@/7.2-latest/javadocs/portal-impl/) | are Spring beans that Liferay makes available as OSGi services. Start with examining the service you want to override. ================================================ FILE: en/developer/customization/articles/50-overriding-osgi-services/02-examining-an-service-to-override.markdown ================================================ --- header-id: examining-an-osgi-service-to-override --- # Examining an OSGi Service to Override [TOC levels=1-4] Creating and injecting a custom service in place of an existing service requires three things: - Understanding the service interface - The existing service - The references to the service 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. ## Gathering Information on a Service 1. Since component service references are extension points, start with determining the service you want to override and components that use that service. 2. 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 [Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) command `scr:info [componentName]` lists the component's attributes and service references. Here's an example `scr:info` command and results (abbreviated with `...`) that describe component `override.my.service.reference.OverrideMyServiceReference` (from sample module [override-my-service-reference](https://portal.liferay.dev/documents/113763090/114000186/override-my-service-reference.zip)) and its reference to a service of type `override.my.service.reference.service.api.SomeService`: > 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 ... The `scr:info` results, like the ones above, contain information relevant to injecting a custom service. Here's what you'll do with the information: 1. [Copy the service interface name](#step-1-copy-the-service-interface-name) 2. [Copy the existing service name](#step-2-copy-the-existing-service-name) 3. [Gather reference configuration details (if reconfiguration is necessary)](#step-3-gather-reference-configuration-details-if-reconfiguration-is-needed) Start with the service interface. ## Step 1: Copy the Service Interface Name The reference's *Interface Name* is the service interface's fully qualified name. ... Reference: _someService Interface Name: override.my.service.reference.service.api.SomeService ... **Copy and save the interface name**, because it's the type your custom service must implement. | Javadocs for @product@ service interfaces are at these locations: | | - [@product@ core Javadocs](@platform-ref@/7.2-latest/javadocs/) | - [@product@ app Javadocs](@app-ref@) | - [MVNRepository](https://mvnrepository.com/) | and | [Maven Central](https://search.maven.org/) | (for Liferay and non-Liferay artifact Javadocs). ## 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 `src:info` result's Component Configuration section lists the existing service's fully qualified name. For example, the `OverrideMyServiceReferencePortlet` component's references `_someService` is bound to a service component whose fully qualified name is `override.my.service.reference.service.impl.SomeServiceImpl`. Component Configuration: ... SatisfiedReference: _someService ... Bound to: 6840 Properties: ... component.name = override.my.service.reference.service.impl.SomeServiceImpl **Copy the `component.name`** so you can reference the service in your [custom service](/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service). Here's an example of referencing the service above. ```java @Reference ( target = "(component.name=override.my.service.reference.service.impl.SomeServiceImpl)" ) private SomeService _defaultService; ``` ## 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. - If the reference's policy option is `greedy`, it binds to the matching, highest ranking service right away. The reference need not be reconfigured to adopt your service. - If policy is `static` and its policy option is `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): 1. The component is reactivated 2. The component's existing referenced service is unavailable 3. The component's reference is modified so that it does not match the existing service but matches your service [Reconfiguring the reference](/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service) can be the quickest way for the component to adopt a new service. **Gather these details:** - *Component name:* Find this at *Component Description* → *Name*. For example, Component Description: Name: override.my.service.reference.portlet.OverrideMyServiceReferencePortlet ... - *Reference name:* The *Reference* value (e.g., `Reference: _someService`). - *Cardinality:* Number of service instances the reference can bind to. | **Note**: Declarative Services makes all components configurable through OSGi | Configuration Admin. Each `@Reference` annotation in the source code has a name | property, either *explicitly* set in the annotation or *implicitly* derived from | the name of the member on which the annotation is used. | | - If no reference name property is used and the `@Reference` is on a field, | then the reference name is the field name. If `@Reference` is on a field | called `_someService`, for example, then the reference name is | `_someService`. | - If the `@Reference` is on a method, then heuristics derive the reference | name. Method name suffix is used and prefixes such as `set`, `add`, and | `put` are ignored. If `@Reference` is on a method called | `setSearchEngine(SearchEngine se)`, for example, then the reference name is | `SearchEngine`. After [creating your custom service](/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-service) (next), you'll use the details you collected here to [configure the component to use your custom service](/docs/7-2/customization/-/knowledge_base/c/reconfiguring-components-to-use-your-service). Congratulations on getting the details required for overriding the OSGi service! ## Related Topics - [OSGi Services and Dependency Injection with Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) - [Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) ================================================ FILE: en/developer/customization/articles/50-overriding-osgi-services/03-creating-a-custom-osgi-service.markdown ================================================ --- header-id: creating-a-custom-osgi-service --- # Creating a Custom OSGi Service [TOC levels=1-4] It's time to implement your OSGi service. Make sure to [examine the service and service reference details](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override), 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 `CustomServiceImpl` implements service interface (from sample module [`overriding-service-reference`](https://portal.liferay.dev/documents/113763090/114000186/overriding-service-reference.zip)) `SomeService`, declares itself an OSGi service of the `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: ```java @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; } ``` Here are the steps to create a custom OSGi service: 1. [Create a module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project). 2. Create your custom service class so that it `implements` the [service interface](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override#step-1-copy-the-service-interface-name) you want. In the example above, `CustomServiceImpl implements SomeService`. Step 5 (later) demonstrates implementing the interface methods. 3. Make your class a Declarative Services component that is the best match for references to the service interface: - Use an `@Component` annotation and `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 `SomeService.class`. - Use a `service.ranking:Integer` component property to rank your service higher than existing services. The `"service.ranking:Integer=100"` property above sets the example's ranking to `100`. 4. If you want to invoke the existing service implementation, declare a field that uses a Declarative Services reference to the existing service. Use the [`component.name` you copied when you examined the service](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override#step-2-copy-the-existing-service-name) to target the existing service. The example above refers to an existing service like this: ```java @Reference ( target = "(component.name=override.my.service.reference.service.impl.SomeServiceImpl)" ) private SomeService _defaultService; ``` The field lets you invoke the existing service in your custom service. 5. Override the interface's methods. Optionally, delegate work to the existing service implementation (see previous step). The example custom service's `doSomething` method delegates work to the original service implementation. 6. Register your custom service with the OSGi runtime framework by [deploying your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project). Components that reference the service type you implemented and whose reference policy option is `greedy` bind to your custom service immediately. Components bound to an existing service and whose reference policy option is `reluctant` can be dynamically reconfigured to use your service. That's demonstrated next. ## Related Topics [OSGi Services and Dependency Injection with Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) ================================================ FILE: en/developer/customization/articles/50-overriding-osgi-services/04-reconfiguring-components-to-use-your-osgi-service-reference.markdown ================================================ --- header-id: reconfiguring-components-to-use-your-service --- # Reconfiguring Components to Use Your OSGi Service [TOC levels=1-4] In many cases, assigning your [custom service (service)](/docs/7-2/customization/-/knowledge_base/c/creating-a-custom-osgi-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 [service reference policy option](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override#step-3-gather-reference-configuration-details-if-reconfiguration-is-needed) is the key to determining the service. Here are the policy options: `greedy`: The component uses the matching, highest ranking service as soon as it's available. `reluctant`: The component uses the matching, highest ranking service available in the following events: - the component is (re)activated - the component's existing referenced service becomes unavailable - the component's reference is modified so that it no longer matches the existing bound service 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 @product@'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 `override-my-service-reference` and `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. - `override-my-service-reference` ([download](https://portal.liferay.dev/documents/113763090/114000186/override-my-service-reference.zip)): This module's portlet component `OverrideMyServiceReferencePortlet`'s field `_someService` references a service of type `SomeService`. The reference's policy is static and reluctant. By default, it binds to an implementation called `SomeServiceImpl`. - `overriding-service-reference` ([download](https://portal.liferay.dev/documents/113763090/114000186/overriding-service-reference.zip)): Provides a custom `SomeService` implementation called `CustomServiceImpl`. The module's configuration file overrides `OverrideMyServiceReferencePortlet`'s `SomeService` reference so that it binds to `CustomServiceImpl`. You're ready to reconfigure a component's service reference to target your custom service. ## Reconfiguring the Service Reference @product@'s Configuration Admin lets you use configuration files to swap in service references on the fly. 1. [Create a system configuration file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) named after the referencing component. Follow the name convention `[component].config`, replacing `[component]` with the [component name](/docs/7-2/customization/-/knowledge_base/c/examining-an-osgi-service-to-override#step-3-gather-reference-configuration-details-if-reconfiguration-is-needed). The configuration file name for the example component `override.my.service.reference.portlet.OverrideMyServiceReferencePortlet` is: override.my.service.reference.portlet.OverrideMyServiceReferencePortlet.config 2. In the configuration file, add a reference target entry that filters on your custom service. Follow this format for the entry: ```properties [reference].target=[filter] ``` Replace `[reference]` with the name of the reference you're overriding. Replace `[filter]` with service properties that filter on your custom service. This example filters on the `component.name` service property: ```properties _someService.target="(component.name\=overriding.service.reference.service.CustomServiceImpl)" ``` This example filters on the `service.vendor` service property: ```properties _someService.target="(service.vendor\=Acme, Inc.)" ``` 3. Optionally, you can add a `cardinality.minimum` entry to specify the number of services the reference can use. Here's the format: ```properties [reference].cardinality.minimum=[int] ``` Here's an example cardinality minimum: ```properties _someService.cardinality.minimum=1 ``` 4. Deploy the configuration by copying the configuration file into the folder `[Liferay_Home]/osgi/configs`. Executing `scr:info` on your component shows that the custom service is now bound to the reference. For example, executing `scr:info override.my.service.reference.portlet.OverrideMyServiceReferencePortlet` reports the following information: ... 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) ... The example component's `_someService` reference targets the custom service component `overriding.service.reference.service.CustomServiceImpl`. `CustomServiceImpl` references default service `SomeServiceImpl` to delegate work to it. ![Figure 1: Because the example component's service reference is overridden by the configuration file deployment, the portlet indicates it's calling the custom service.](../../images/overriding-service-refs-result.png) @product@ processed the configuration file and injected the service reference, which in turn bound the custom service to the referencing component! ## Related Topics - [OSGi Services and Dependency Injection with Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) - [Using Felix Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell) ================================================ FILE: en/developer/customization/articles/50-portlet-filters/01-portlet-filters-intro.markdown ================================================ --- header-id: portlet-filters --- # Portlet Filters [TOC levels=1-4] Portlet filters intercept requests and responses at the start of the [portlet request processing phase](/docs/7-2/frameworks/-/knowledge_base/f/portlets). Portlet filters are commonly used for these things: - Transform content - Add or modify request and response attributes - Suspend a portlet phase to get user input - Audit portlet activity The [`javax.portlet.filter`](http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/package-frame.html) package defines a portlet filter interface for each phase. Here are the steps for developing a portlet filter: 1. Implement the [portlet filter interface](http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/package-frame.html) for the phase it's intercepting. Here are common interface methods to override: `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 `FilterChain` parameter can be used to invoke the next filter in the phase. `init`: Initialize the filter. The `FilterConfig` parameter can be used to prepare the filter. `destroy`: Perform any filter cleanup. 2. Target the desired portlet(s). 3. Choose how to prioritize the filter among other filters in the phase: - OSGi Declarative Service Component portlet filters use a service ranking property. High ranking filters execute before lower ones. - `` element order in a portlet application's `portlet.xml` file. - The `ordinal` element value of a filter class annotated with `@PortletLifecycleFilter`. Low ordinal value filters execute before higher ones. Below is demonstrated applying multiple filters to a portlet's render phase. The filters are [OSGi Declarative Service (DS) Components](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services), but filters can also be applied to a portlet using a `portlet.xml` descriptor or a `@PortletLifecycleFilter` annotation. See the Portlet 3.0 Specification for details. The sample code is available [here](https://portal.liferay.dev/learn/code-samples/-/cs/list/7.2/java8/workspace-gradle/modules/applications/portlets/render-filter-portlet). ## Sample Portlet The sample portlet `MembersListPortlet` is a [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) that lists names and email addresses when users click its *Load Users* button. The information is based on `Person` objects that the portlet class passes to the View template via a request attribute called `MembersListPortlet.MEMBERLIST_ATTRIBUTE`. ```java public void loadUsers(ActionRequest actionRequest, ActionResponse actionResponse) { actionRequest.setAttribute(MembersListPortlet.MEMBERLIST_ATTRIBUTE, createStaticUserList()); } ``` Two render filters are applied to the portlet: 1. Render filter 1 hides parts of the user email addresses (e.g., for privacy) by modifying the request object. 2. Render filter 2 logs portlet render phase statistics. Adding the `MemberList` portlet to a page and clicking the `Load Users` button renders each `Person`'s name and partially hidden email address, thanks to the filter `EncodingPersonEmailsRenderFilter`. ``` Sievert Shayne Sievert.Sha...@...mple.com Vida Jonas Vida.Jo...@...mple.com ... ``` If you set the portlet's log level to `debug`, it prints the render phase statistics. ``` 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 ``` The first filter modifies portlet content via the request object. ## Render filter 1 hides parts of user email addresses `EncodingPersonEmailsRenderFilter` is a `RenderFilter` that hides parts of user email addresses by modifying a request attribute. Here is the class: ```java @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() { } } ``` The `@Component` annotation declares the filter to be an OSGi DS Component. Here are its elements and properties: `immediate = true` sets the component ready to start upon being installed. `service = PortletFilter.class` defines the component to be a `PortletFilter` service. `javax.portlet.name = + MembersListPortlet.MEMBERSLIST_PORTLET_NAME` links the filter to the target portlet. Note, multiple portlets can be listed. `service.ranking:Integer=1` sets the filter to execute after filters that are ranked higher than `1`. `EncodingPersonEmailsRenderFilter` *implements* the [`RenderFilter`](http://docs.liferay.com/portlet-api/3.0/javadocs/javax/portlet/filter/RenderFilter.html) interface, overriding the `doFilter`, `init`, and `destroy` methods. `doFilter` modifies the attribute `MembersListPortlet.MEMBERLIST_ATTRIBUTE`'s list of `Person`s by replacing parts of their email addresses with ellipses (`...`). It delegates the `ofuscatePersonEmail` method to do the modifications. Then `doFilter` invokes `chain.doFilter(request, response)` to execute the next `RenderFilter` or next portlet processing phase. | **Note:** Filters can also intercept and block the execution of a portlet | phase. In the `doFilter` method, this is usually done by throwing an | exception or by not calling the next element in the filter chain. ## RenderFilter 2 Logs Statistics `MembersListStatsRenderFilter` is a `RenderFilter` that logs the number of times the portlet is rendered and the average render time. Here's the code: ```java @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); } ``` As with `EncodingPersonEmailsRenderFilter`, it's an OSGi DS Component that is a `PortletFilter` service, starts upon installation, applies to the `MembersListPortlet`, and has a service ranking. Since its ranking is `100`, it is executed before render filter `EncodingPersonEmailsRenderFilter`. `MembersListStatsRenderFilter`'s `doFilter()` method audits the render phase in these ways: 1. Notes the render phase start time. 2. Executes `chain.doFilter(request, response)` to invoke all of the other `RenderFilter`s in the `FilterChain`. 3. Increments the number of times the portlet renders. 4. Calculates the average render time. 5. Logs the times rendered and average render time. Consider creating your own filters to intercept portlet processing phases. ## Related Topics [Portlets](/docs/7-2/frameworks/-/knowledge_base/f/portlets) [JSP Overrides Using Portlet Filters](/docs/7-2/customization/-/knowledge_base/c/jsp-overrides-using-portlet-filters) [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) ================================================ FILE: en/developer/customization/articles/50-product-navigation/01-intro.markdown ================================================ --- header-id: product-navigation --- # Product Navigation [TOC levels=1-4] @product@'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: - Product Menu - Control Menu - Simulation Menu - User Personal Menu ![Figure 1: The main product navigation menus include the Product Menu, Control Menu, Simulation Menu and User Personal Menu.](../../images/product-navigation-summary.png) 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 *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. ## Product Menu By default, Liferay's Product Menu consists of two main sections: Control Panel and Site Administration. These sections are called *Panel Categories*. For instance, the Control Panel is a single Panel Category, and when clicking on it, you see six child Panel Categories: *Users*, *Sites*, *Apps*, *Configuration*, and *Workflow*. Clicking a child Panel Category shows *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 [Customizing the Product Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu) articles. ## 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 @product@. ![Figure 2: 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.](../../images/control-menu-home.png) 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 → *Content & Data* → *Web Content*, you see a Control Menu with different functionality tailored for that option. ![Figure 3: When switching your context to web content, the Control Menu adapts to provide helpful options for that area.](../../images/control-menu-web-content.png) 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 [Customizing the Control Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu) articles. ## 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. ![Figure 4: The Simulation Menu offers a device preview application.](../../images/simulation-menu-preview.png) 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 [`SimulationPanelCategory`](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) class, a hidden category needed to hold the `DevicePreviewPanelApp`. This is the app and functionality you see in the Simulation Menu by default. For more information, read the [Extending the Simulation Menu](/docs/7-2/customization/-/knowledge_base/c/extending-the-simulation-menu) article. ## 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. ![Figure 5: By default, the User Personal Menu contains the signed-in user's avatar, which opens the user's settings when selected.](../../images/user-personal-menu.png) 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: [Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes). The User Personal Menu can be seen as a placeholder in every Liferay theme. By default, Liferay provides one sample *User Personal Bar* portlet that fills that placeholder, but the portlet Liferay provides can be replaced by other portlets. | **Note:** You can add the User Personal Bar to your theme by adding the | following snippet into your `portal_normal.ftl`: | | ``` | <@liferay.user_personal_bar /> | ``` For more information, read the [Customizing the User Personal Bar and Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-user-personal-bar-and-menu) article. ================================================ FILE: en/developer/customization/articles/50-product-navigation/02-customizing-the-product-menu/01-customizing-the-product-menu-intro.markdown ================================================ --- header-id: customizing-the-product-menu --- # Customizing the Product Menu [TOC levels=1-4] Customizing the Product Menu can be completed by adding Panel Categories and Panel Apps. | **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 | [Theme Contributors](/docs/7-2/frameworks/-/knowledge_base/f/packaging-independent-ui-resources-for-your-site) | article for more details. To create these entities, you must implement the [`PanelCategory`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/PanelCategory.html) and [`PanelApp`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/PanelApp.html) interfaces. ## PanelCategory Interface The `PanelCategory` interface requires you to implement the following methods: - `getNotificationCount`: returns the number of notifications to be shown in the Panel Category. - `include`: renders the body of the Panel Category. - `includeHeader`: renders the Panel Category header. - `isActive`: whether the panel is selected. - `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. You can reduce the number of methods you must implement if you extend a base class that already implements the `PanelCategory` interface. The recommended way to do this is by extending the [`BasePanelCategory`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BasePanelCategory.html) or [`BaseJSPPanelCategory`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelCategory.html) abstract classes. Typically, the `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 `include()` or `includeHeader()` from the `PanelCategory` interface. If you plan to use JSPs as the front-end technology, extend a base class called `BaseJSPPanelCategory` that already implements the methods `include()` and `includeHeader()` for you. | **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 `PanelCategory` to use other technologies such as FreeMarker. More information on provided base classes for your `PanelCategory` implementation are described next. ### BasePanelCategory If you need something simple for your Panel Category like a name, extending `BasePanelCategory` is probably sufficient. For example, the [`ControlPanelCategory`](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) extends `BasePanelCategory` and specifies a `getLabel` method to set and display the Panel Category name. ```java @Override public String getLabel(Locale locale) { return LanguageUtil.get(locale, "control-panel"); } ``` ### BaseJSPPanelCategory If you need more complex functionality, extend `BaseJSPPanelCategory` and use JSPs to render the Panel Category. For example, the [`SiteAdministrationPanelCategory`](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) specifies the `getHeaderJspPath` and `getJspPath` methods. You could create a JSP with the UI you want to render and specify its path in methods like these: ```java @Override public String getHeaderJspPath() { return "/sites/site_administration_header.jsp"; } @Override public String getJspPath() { return "/sites/site_administration_body.jsp"; } ``` 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 `PanelApp` interface. ## PanelApp Interface The `PanelApp` interface requires you to implement the following methods: - `getNotificationCount`: returns the number of notifications for the user. - `getPortlet`: returns the portlet associated with the application. - `getPortletId`: returns the portlet's ID associated with the application. - `getPortletURL`: returns the URL used to render a portlet based on the servlet request attributes. - `include`: Returns `true` if the application successfully renders. - `setGroupProvider`: sets the group provider associated with the application. - `setPortlet`: sets the portlet associated with the application. You can reduce the number of methods you must implement if you extend a base class that already implements the `PanelCategory` interface. The recommended way to do this is by extending the [`BasePanelApp`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BasePanelApp.html) or [`BaseJSPPanelApp`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html) abstract classes. If you want to use JSPs to render that UI, extend `BaseJSPPanelApp`. This provides additional methods you can use to incorporate JSP functionality into your app's listing in the Product Menu. | **Note:** JSPs are not the only way to provide front-end functionality to your | Panel Apps. You can create your own class implementing `PanelApp` to use other | technologies such as FreeMarker. The `BlogsPanelApp` is a simple example of how to specify your portlet as a Panel App. This class extends `BasePanelApp`, overriding the `getPortletId` and `setPortlet` methods. These methods specify and set the Blogs portlet as a Panel App. This is how those methods look for the Blogs portlet: ```java @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); } ``` 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. ================================================ FILE: en/developer/customization/articles/50-product-navigation/02-customizing-the-product-menu/02-adding-custom-panel-categories.markdown ================================================ --- header-id: adding-custom-panel-categories --- # Adding Custom Panel Categories [TOC levels=1-4] As you navigate the Product Menu, you can see that Panel Apps like *Web Content* and *Settings* are organized into Panel Categories such as *Content & Data* and *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: 1. Create the OSGi structure and metadata. 2. Implement Liferay's Frameworks. 3. Define the Panel Category. ## Creating the OSGi Module First you must create the project. 1. Create an OSGi module using your favorite third party tool, or use [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli). Blade CLI offers a [Panel App](/docs/7-2/reference/-/knowledge_base/r/panel-app-template) template, which is for creating a Panel Category and Panel App. 2. Create a unique package name in the module's `src` directory and create a new Java class in that package. To follow naming conventions, give your class a unique name followed by `PanelCategory` (e.g., `ControlPanelCategory`). ## Implementing Liferay's 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: 1. Insert the `@Component` annotation declaring the panel category keys directly above the class's declaration: ```java @Component( immediate = true, property = { "panel.category.key=" + [Panel Category Key], "panel.category.order:Integer=[int]" }, service = PanelCategory.class ) ``` You can view an example of a similar `@Component` annotation for the `UserPanelCategory` class below: ```java @Component( immediate = true, property = { "panel.category.key=" + PanelCategoryKeys.ROOT, "panel.category.order:Integer=200" }, service = PanelCategory.class ) ``` The `property` element designates two properties that should be assigned for your category. The `panel.category.key` specifies the parent category for your custom category. You can find popular parent categories to assign in the [`PanelCategoryKeys`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/PanelCategoryKeys.html) class. For instance, if you wanted to create a child category in the Control Panel, you could assign `PanelCategoryKeys.CONTROL_PANEL`. Likewise, if you wanted to create a root category, like the Control Panel or Site Administration, you could assign `PanelCategoryKeys.ROOT`. The `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. | **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. Finally, your `service` element should specify the `PanelCategory.class` service. 2. Implement the `PanelCategory` interface. See the [`PanelCategory` Interface](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu#panelcategory-interface) section for more details. Extending one of the provided base classes ([BasePanelCategory](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu#basepanelcategory) or [BaseJSPPanelCategory](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu#basejsppanelcategory)) is a popular way to implement the `PanelCategory` interface. 3. 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 `bnd.bnd` file has defined a web context path: ``` Bundle-SymbolicName: com.sample.my.module.web Web-ContextPath: /my-module-web ``` Then reference the Servlet context using the symbolic name of your module like this: ```java @Override @Reference( target = "(osgi.web.symbolicname=com.sample.my.module.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { super.setServletContext(servletContext); } ``` 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. ================================================ FILE: en/developer/customization/articles/50-product-navigation/02-customizing-the-product-menu/03-adding-custom-panel-apps.markdown ================================================ --- header-id: adding-custom-panel-apps --- # Adding Custom Panel Apps [TOC levels=1-4] After you have created a Panel Category, create a Panel App to go in it: 1. Create an OSGi module using your favorite third party tool, or use [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli). Blade CLI offers a [Panel App](/docs/7-2/reference/-/knowledge_base/r/panel-app-template) template to help generate a basic Panel Category and Panel App. 2. Create a unique package name in the module's `src` directory, and create a new Java class in that package. To follow naming conventions, give your class a unique name followed by *PanelApp* (e.g., `JournalPanelApp`). 3. Directly above the class's declaration, insert the following annotation: ```java @Component( immediate = true, property = { "panel.app.order:Integer=INTEGER" "panel.category.key=" + PANEL_CATEGORY_KEY, }, service = PanelApp.class ) ``` You can view an example of a similar `@Component` annotation for the `JournalPanelApp` class below. ```java @Component( immediate = true, property = { "panel.app.order:Integer=100", "panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION_CONTENT }, service = PanelApp.class ) ``` These properties and attributes are similar to those discussed in the previous [article](/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-categories). The `panel.category.key` assigns your Panel App to a Panel Category. The `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 → *Content & Data*, add the following property: ```java "panel.category.key=" + PanelCategoryKeys.SITE_ADMINISTRATION_CONTENT ``` Visit the [PanelCategoryKeys](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/constants/PanelCategoryKeys.html) class for keys you can use to specify default Panel Categories in Liferay. Set the `service` attribute to `PanelApp.class`. 4. Implement the `PanelApp` interface. See the [`PanelApp` Interface](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu#panelapp-interface) section for more details. Extending one of the provided base classes ([BasePanelApp](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BasePanelApp.html) or [BaseJSPPanelApp](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html)) is a popular way to implement the `PanelApp` interface. See the [PanelApp Interface](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu#panelapp-interface) section for more information. 5. 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 `bnd.bnd` file has defined a web context path: ``` Bundle-SymbolicName: com.sample.my.module.web Web-ContextPath: /my-module-web ``` Then reference the Servlet context using the symbolic name of your module like this: ```java @Override @Reference( target = "(osgi.web.symbolicname=com.sample.my.module.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { super.setServletContext(servletContext); } ``` 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. ================================================ FILE: en/developer/customization/articles/50-product-navigation/03-customizing-the-control-menu/01-customizing-the-control-menu-intro.markdown ================================================ --- header-id: customizing-the-control-menu --- # Customizing the Control Menu [TOC levels=1-4] Liferay's Control Menu consists of three main sections: Sites (left portion), Tools (middle portion), and User (right portion). ![Figure 1: This image shows where your entry will reside depending on the category you select.](../../../images/control-menu-areas.png) | **Note:** You can add the Control Menu to a theme by adding the following | snippet into your `portal_normal.ftl`: | | ``` | <@liferay.control_menu /> | ``` | | 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 | [Customizing the User Personal Bar and Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-user-personal-bar-and-menu) | article for more information. You can reference a sample Control Menu Entry by visiting the [Control Menu Entry](/docs/7-2/reference/-/knowledge_base/r/control-menu-entry-template) article. ## ProductNavigationControlMenuEntry Interface To create a control menu entry, you must implement the [`ProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/ProductNavigationControlMenuEntry.html) interface. It's recommended to implement this interface by extending the [`BaseProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseProductNavigationControlMenuEntry.html) or [`BaseJSPProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseJSPProductNavigationControlMenuEntry.html) abstract classes. These base classes are covered in more detail next. ### BaseProductNavigationControlMenuEntry Typically, the `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 `include()` and `includeBody()` methods. The [`IndexingProductNavigationControlMenuEntry`](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) is a simple example for providing text and an icon. It extends the `BaseProductNavigationControlMenuEntry` class and is used when Liferay is indexing. The indexing entry is displayed in the *Tools* (middle) area of the Control Menu with a *Refresh* icon and text stating *The Portal is currently indexing*. ### BaseJSPProductNavigationControlMenuEntry If you use JSPs for generating the UI, you can extend `BaseJSPProductNavigationControlMenuEntry` to save time when creating/modifying a control menu entry. The [`ProductMenuProductNavigationControlMenuEntry`](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) creates an entry that appears in the *Sites* (left) area of the Control Menu. This class extends the `BaseJSPProductNavigationControlMenuEntry` class. This provides several more methods that use JSPs to define your entry's UI. There are two methods to notice: ```java @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"; } ``` The `getIconJspPath()` method provides the Product Menu icon (![Menu Closed](../../../images/icon-menu.png) → ![Menu Open](../../../images/icon-menu-open.png)), and the `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 `StagingProductNavigationControlMenuEntry` class calls its JSP like this: ```java @Override public String getIconJspPath() { return "/control_menu/entry.jsp"; } ``` The `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. ================================================ FILE: en/developer/customization/articles/50-product-navigation/03-customizing-the-control-menu/02-customizing-the-control-menu.markdown ================================================ --- header-id: creating-control-menu-entries --- # Creating Control Menu Entries [TOC levels=1-4] Now you'll create entries to customize the Control Menu. Make sure to read [Adding Custom Panel Categories](/docs/7-2/customization/-/knowledge_base/c/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: 1. Create the OSGi structure and metadata. 2. Implement Liferay's Frameworks. 3. Define the Control Menu Entry. ## Creating the OSGi Module First you must create the project. 1. Create a generic OSGi module. Your module must contain a Java class, `bnd.bnd` file, and build file (e.g., `build.gradle` or `pom.xml`). You'll create your Java class next if your project does not already define one. 2. Create a unique package name in the module's `src` directory and create a new Java class in that package. Give your class a unique name followed by *ProductNavigationControlMenuEntry* (e.g.,`StagingProductNavigationControlMenuEntry`). ## Implementing Liferay's Frameworks Next, you need to connect your OSGi module to Liferay's frameworks and use those to define information about your entry. 1. Directly above the class's declaration, insert this code: ```java @Component( immediate = true, property = { "product.navigation.control.menu.category.key=" + [Control Menu Category], "product.navigation.control.menu.category.order:Integer=[int]" }, service = ProductNavigationControlMenuEntry.class ) ``` The `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 [ProductNavigationControlMenuCategoryKeys](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/constants/ProductNavigationControlMenuCategoryKeys.html) class. For example, this property places your entry in the middle portion of the Control Menu: ```java "product.navigation.control.menu.category.key=" + ProductNavigationControlMenuCategoryKeys.TOOLS ``` 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 `1` appears to the left of an entry with order `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 `service` element should specify the `ProductNavigationControlMenuEntry.class` service. 2. Implement the [`ProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/ProductNavigationControlMenuEntry.html) interface. You can also extend the [`BaseProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseProductNavigationControlMenuEntry.html) or [`BaseJSPProductNavigationControlMenuEntry`](@app-ref@/product-navigation/latest/javadocs/com/liferay/product/navigation/control/menu/BaseJSPProductNavigationControlMenuEntry.html) abstract classes. See the [Customizing the Control Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu) article for more information on these classes. 3. 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 `bnd.bnd` file defines a web context path: ``` Bundle-SymbolicName: com.sample.my.module.web Web-ContextPath: /my-module-web ``` And then reference the Servlet context using the symbolic name of your module: ```java @Override @Reference( target = "(osgi.web.symbolicname=com.sample.my.module.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { super.setServletContext(servletContext); } ``` 4. 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 `isShow(HttpServletRequest)` method. For example, the `IndexingProductNavigationControlMenuEntry` class queries the number of indexing jobs when calling `isShow`. If the query count is `0`, the indexing entry doesn't appear in the Control Menu: ```java @Override public boolean isShow(HttpServletRequest request) throws PortalException { int count = _indexWriterHelper.getReindexTaskCount( CompanyConstants.SYSTEM, false); if (count == 0) { return false; } return super.isShow(request); } ``` The `StagingProductNavigationControlMenuEntry` class selects the pages to appear. The staging entry never appears if the page is an administration page (e.g., *Site Administration*, *Control Panel*, etc.): ```java @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; } ``` Excellent! You've created your entry in one of the three default sections in the Control Menu. ================================================ FILE: en/developer/customization/articles/50-product-navigation/03-customizing-the-control-menu/03-defining-icons-tooltips.markdown ================================================ --- header-id: defining-icons-and-tooltips --- # Defining Icons and Tooltips [TOC levels=1-4] 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. ## Control Menu Entry Icons You can provide a Lexicon or CSS icon in your `*ControlMenuEntry`. To use a Lexicon icon, you should override the methods in `ProductMenuProductNavigationControlMenuEntry` like this one: ```java public String getIconCssClass(HttpServletRequest request) { return ""; } public String getIcon(HttpServletRequest request) { return "lexicon-icon"; } public String getMarkupView(HttpServletRequest request) { return "lexicon"; } ``` Likewise, you can use a CSS icon by overriding the `ProductMenuProductNavigationControlMenuEntry` methods like this one: ```java public String getIconCssClass(HttpServletRequest request) { return "icon-css"; } public String getIcon(HttpServletRequest request) { return ""; } public String getMarkupView(HttpServletRequest request) { return ""; } ``` You can find these icons documented [here](https://clayui.com/docs/components/icons.html). ### Control Menu Entry Tooltips To provide a tooltip for the Control Menu entry, create a `getLabel` method like this: ```java @Override public String getLabel(Locale locale) { ResourceBundle resourceBundle = ResourceBundleUtil.getBundle( "content.Language", locale, getClass()); return LanguageUtil.get( resourceBundle, "the-portal-is-currently-reindexing"); } ``` You need to create a `Language.properties` to store your labels. You can learn more about resource bundles in the [Localization](/docs/7-2/frameworks/-/knowledge_base/f/localization) articles. ================================================ FILE: en/developer/customization/articles/50-product-navigation/04-extending-the-simulation-menu/01-extending-the-simulation-menu-intro.markdown ================================================ --- header-id: extending-the-simulation-menu --- # Extending the Simulation Menu [TOC levels=1-4] To provide your own functionality in the Simulation Menu, you must create a Panel App in `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 [Customizing the Product Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu) articles. Once you know how to create Panel Categories and Panel Apps, continue with this article. 1. Follow the steps documented in [Adding Custom Panel Apps](/docs/7-2/customization/-/knowledge_base/c/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 [Simulation Panel Entry template](/docs/7-2/reference/-/knowledge_base/r/simulation-panel-entry-template). You can also refer to the [Simulation Panel App sample](/docs/7-2/reference/-/knowledge_base/r/simulation-panel-app) for a working example. 2. Since this article assumes you're providing more functionality to the existing simulation category, set the simulation category in the `panel.category.key` of the `@Component` annotation: ```java "panel.category.key=" + SimulationPanelCategory.SIMULATION ``` To use this constant, you must add a dependency on [`com.liferay.product.navigation.simulation`](https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.product.navigation.simulation/). Be sure to also specify the order to display your new Panel App, which was explained in [Adding Custom Panel Apps](/docs/7-2/customization/-/knowledge_base/c/adding-custom-panel-apps). 3. This article assumes you're using JSPs. Therefore, you should extend the [`BaseJSPPanelApp`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/BaseJSPPanelApp.html) abstract class, which implements the [`PanelApp`](@app-ref@/application-list/latest/javadocs/com/liferay/application/list/PanelApp.html) 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 `include()` method to use any front-end technology you want, if you want to use a technology other than JSP (e.g., FreeMarker). 4. Define your simulation view. For instance, in `DevicePreviewPanelApp`, the `getJspPath` method points to the `simulation-device.jsp` file in the `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: ```java @Override @Reference( target = "(osgi.web.symbolicname=com.liferay.product.navigation.simulation.device)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { super.setServletContext(servletContext); } ``` As explained in [Customizing The Product Menu](/docs/7-2/customization/-/knowledge_base/c/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: ```java @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); } ``` 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 `DevicePreviewPanelApp`, except it points to a different portlet and JSP. For more information on Segments, see the [Segmentation and Personalization](/docs/7-2/user/-/knowledge_base/u/segmentation-and-personalization) section. ![Figure 1: The Simulation Menu also displays Segments to help simulate different user experiences.](../../../images/segments-preview.png) 5. 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 `aui:script` block of your custom simulation view's JavaScript, you can use this code: ```js var iframe = A.one('#simulationDeviceIframe'); ``` Then you can modify the device preview frame URL like this: ```js iframe.setAttribute('src', newUrlWithCustomParameters); ``` Now that you know how to extend the necessary Panel Categories and Panel Apps to modify the Simulation Menu, [create a module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) of your own and customize the Simulation Menu so it's most helpful for your needs. ================================================ FILE: en/developer/customization/articles/50-product-navigation/05-providing-the-user-personal-bar/01-customizing-the-user-personal-menu-intro.markdown ================================================ --- header-id: customizing-the-user-personal-bar-and-menu --- # Customizing the User Personal Bar and Menu [TOC levels=1-4] The User Personal Bar is a portlet, but it's also an important concept in @product@. In a fresh bundle using the default theme, it's the section of screen occupied by the User's avatar and the Personal Menu. ![Figure 1: By default, the User Personal Bar contains the signed-in user's avatar, which opens the Personal Menu when selected.](../../../images/user-personal-bar.png) 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 [portlet](/docs/7-2/customization/-/knowledge_base/c/using-a-custom-portlet-in-place-of-the-user-personal-bar) or customize it by adding entries to the existing portlet's menu. This section covers these topics: - Replacing the default User Personal Bar portlet with a custom portlet. - Customizing the default User Personal Bar. ## Displaying the Personal Menu Starting with @product-ver@, 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 `portal_normal.ftl`: ```markup <@liferay.user_personal_bar /> ``` 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: ```markup ``` | **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 *Control Panel* → *Configuration* → *Instance Settings* → | *Users* and select *Personal Menu*. Enable the *Show in Control Menu* toggle | and click *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 *one* of these | mechanisms to display the User Personal Bar. Unlike the Product Menu, the Personal Menu can be customized without creating panel categories and panel apps. See [Customizing the Personal Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-personal-menu) for details. ================================================ FILE: en/developer/customization/articles/50-product-navigation/05-providing-the-user-personal-bar/02-using-a-custom-portlet-as-the-user-personal-bar.markdown ================================================ --- header-id: using-a-custom-portlet-in-place-of-the-user-personal-bar --- # Using a Custom Portlet in Place of the User Personal Bar [TOC levels=1-4] 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 [portlets](/docs/7-2/frameworks/-/knowledge_base/f/portlets) if you need guidance. 1. [Create an OSGi module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project). 2. Create a unique package name in the module's `src` directory and create a new Java class in that package. 3. Above the class declaration, insert the following annotation: ```java @Component( immediate = true, property = { "model.class.name=" + PortalUserPersonalBarApplicationType.UserPersonalBar.CLASS_NAME, "service.ranking:Integer=10" }, service = ViewPortletProvider.class ) ``` The `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 [Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/embedding-portlets-in-themes) articles that you can request portlets in several different ways (e.g., *Edit*, *Browse*, etc.). You should also specify the service rank for your new portlet so it overrides the default. Make sure to set the `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 `service` element should be `ViewPortletProvider.class`. 4. Update the class's declaration to extend the [`BasePortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html) abstract class and implement `ViewPortletProvider`: ```java public class ExampleViewPortletProvider extends BasePortletProvider implements ViewPortletProvider { } ``` 5. Specify the portlet you want in the User Personal Bar by declaring the following method in your class: ```java @Override public String getPortletName() { return PORTLET_NAME; } ``` Replace the `PORTLET_NAME` text with the portlet to provide when one is requested by the theme template. For example, the default portlet uses `com_liferay_product_navigation_user_personal_bar_web_portlet_ProductNavigationPersonalBarPortlet` If you want to inspect the entire module used for Liferay's User Personal Bar, see the [product-navigation-user-personal-bar-web](https://github.com/liferay/liferay-portal/tree/7.2.0-ga1/modules/apps/product-navigation/product-navigation-user-personal-bar-web) module. ## Related Topics - [Customizing the Product Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu) - [Customizing the Control Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu) ================================================ FILE: en/developer/customization/articles/50-product-navigation/05-providing-the-user-personal-bar/03-customizing-the-personal-menu.markdown ================================================ --- header-id: customizing-the-personal-menu --- # Customizing the Personal Menu [TOC levels=1-4] The Personal Menu is a portlet in @product@, 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 `PersonalMenuEntry` [interface](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). If you're adding a portlet entry to the Personal Menu, the process is slightly different. Both approaches are covered below. ## Adding an Entry to the Personal Menu Follow these steps. `SignOutPersonalMenuEntry.java` is used as an example throughout these steps: 1. [Create an OSGi module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) and place a new Java class into a package in its `src` folder. 2. In the `@Component` annotation, specify the two properties shown below to place your new entry in the Personal Menu: - `product.navigation.personal.menu.group`: determines the section where the entry will be placed. - `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 `group` property a value other than those for the four default sections (100, 200, 300, and 400). ![Figure 1: The Personal Menu is organized into four sections.](../../../images/user-personal-menu-sections.png) Here's an example: ```java @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 { ``` 3. Include the interface's methods. `SignoutPersonalMenuEntry` uses `getLabel` and `getPortletURL`, which are the only two that are mandatory. `getLabel` retrieves a language key to label the entry in the UI: ```java @Override public String getLabel(Locale locale) { return LanguageUtil.get(locale, "sign-out"); } ``` `getPortletURL` returns the URL for the portlet or page you want to access with the entry: ```java public String getPortletURL(HttpServletRequest httpServletRequest) throws PortalException { ThemeDisplay themeDisplay = (ThemeDisplay)httpServletRequest.getAttribute( WebKeys.THEME_DISPLAY); return themeDisplay.getURLSignOut(); } } ``` That's all you need to implement the interface. However, the `PersonalMenuEntry` interface includes a number of other methods that you can use if you need them: `getIcon`: identify an icon to display in the entry. `isActive`: indicate whether the entry is currently active. `isShow`: write logic to determine under what circumstances the entry is displayed. Learn how to add a portlet entry to the Personal Menu next. ## Adding a Portlet Entry to the Personal Menu If you're adding a portlet to the Personal Menu, you can extend the `BasePersonalMenuEntry` class to save time. Follow these steps: 1. [Create an OSGi module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project) and place a new Java class into a package in its `src` folder. 2. In the `@Component` annotation, specify the two properties shown below to place your new entry in the Personal Menu: - `product.navigation.personal.menu.group`: determines the section where the entry will be placed. - `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 `group` property a value other than those for the four default sections (100, 200, 300, and 400). An example is shown below: ```java @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 { ``` 3. Override the `getPortletId()` method to provide the portlet's ID, as shown in the example below: ```java public class MyAccountPersonalMenuEntry extends BasePersonalMenuEntry { @Override public String getPortletId() { return UsersAdminPortletKeys.MY_ACCOUNT; } } ``` The `BasePersonalMenuEntry` class automatically determines the label, portlet URL, state, and visibility based on the portlet ID. Once you've completed your implementation and deployed your module, your new entry is displayed in the personal menu. ## Related Topics - [Customizing the Product Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-product-menu) - [Customizing the Control Menu](/docs/7-2/customization/-/knowledge_base/c/customizing-the-control-menu) ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/01-intro.markdown ================================================ --- header-id: customization-with-ext --- # Customization with Ext [TOC levels=1-4] **Customization with Ext projects is deprecated and should only be used if absolutely necessary.** There are two types of Ext projects deployable to @product@: - *Ext plugins*: used to customize @product@'s core functionality. For example, overwriting a class in a core artifact like `com.liferay.portal.kernel`. - *Ext modules*: used to customize OSGi modules. For example, overwriting a JSP in the `com.liferay.login.web` module (see the [Login Web Ext sample](/docs/7-2/reference/-/knowledge_base/r/login-web-ext)). Ext projects are powerful tools used to extend @product@'s default core and/or module projects. They, however, increase the complexity of your @product@ instance because they are a fork of the default project. Ext projects are not recommended unless there is absolutely no other way to accomplish your task. @product-ver@ provides many extension points that let you customize almost every detail of @product@. If there's a way to customize what you want with an extension point, do it that way instead. See the [More Extensible, Easier to Maintain](/docs/7-1/tutorials/-/knowledge_base/t/benefits-of-liferay-7-for-liferay-6-developers#more-extensible-easier-to-maintain) section for more details on the advantages of using @product@'s extension points. Before deciding to use an Ext project, weigh the cost. Ext projects let you use internal APIs and even let you overwrite @product@ core files. This puts your deployment at risk of being incompatible with security, performance, or feature updates released by Liferay. When upgrading to a new version of @product@ (even if it's a maintenance version or a service pack), you have to review all changes and manually modify your Ext projects to merge your changes with @product@'s. Before diving into Ext projects, first consider if an Ext project is even necessary at all. ## Making the Decision to Use Ext Projects There are many parts of @product@ that now provide an extension point via OSGi bundle. You should follow this three step process to decide whether an Ext project is necessary: 1. Find the OSGi extension point that you need. 2. If an OSGi extension point does not exist, use an Ext project. 3. Research new extension points after every release of @product@. When a new version of @product@ provides the extension point you need, always use the extension point to replace the existing Ext project. So how do you find an OSGi extension point? Your first step is to examine the custom projects that extend popular @product@ extension points stored in the [Liferay Blade Samples](https://github.com/liferay/liferay-blade-samples) repository. For more information on these sample projects, see [Sample Projects](/docs/7-2/reference/-/knowledge_base/r/sample-projects). Usable extension points are also documented throughout Liferay's Developer Network categorized by the @product@ section involved. For example, [Overriding MVC Commands](/docs/7-2/customization/-/knowledge_base/c/overriding-liferay-mvc-commands) describes how to extend a @product@ extension point. Want to learn how to [customize JSPs](/docs/7-2/customization/-/knowledge_base/c/customizing-jsps)? Those processes are documented too! You're now equipped to make an informed decision on using Ext projects. ## Licensing and Contributing @product@ is Open Source software licensed under the [LGPL 2.1 license](http://www.gnu.org/licenses/lgpl-2.1.html). If you reuse any code snippet and redistribute it, whether publicly or to a specific customer, make sure your modifications are compliant with the license. One common way is to make the source code of your modifications are available to the community under the same license. Make sure you read the license text yourself to find the option that best fits your needs. If your goal in making changes is fixing a bug or improving @product@, it could be of interest to a broader audience. Consider contributing it back to the project. That benefits all users of the product including you, since you won't have to maintain the changes with each newly released version of @product@. You can notify Liferay of bugs or improvements at [issues.liferay.com](http://issues.liferay.com). Check out Liferay's [Participation](/participate/feedback/overview) information to learn all the ways that you can contribute to Liferay projects. In summary, an Ext project is a powerful way to extend @product@. There are no limits to what you can customize, so use it carefully. Before using an Ext project, see if you can implement all or part of the desired functionality through [Widget Templates](/docs/7-2/user/-/knowledge_base/u/styling-apps-and-assets) or extension points, without introducing the complexity that's inherent with Ext projects. If you need to use an Ext project, make your customization as small as possible and follow the instructions in this section carefully to avoid issues. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/01-intro.markdown ================================================ --- header-id: customizing-core-functionality-with-ext --- # Customizing Core Functionality with Ext [TOC levels=1-4] | **Ext plugins are deprecated for @product-ver@ and should only be used if | absolutely necessary.** | | The following app servers should be used for Ext plugin development in | @product@: | | - Tomcat 9.x | | 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: | | - Providing custom implementations for any beans declared in @product@'s | Spring files (when possible, use | [service wrappers](/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers) | instead of an Ext plugin). @product-ver@ removed many beans, so make sure | your overridden beans are still relevant if converting your legacy Ext | plugin. | - Overwriting a class in a @product-ver@ core JAR. For a list of core JARs, | see the | [Finding Core @product@ Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) | section. | - Modifying @product@'s `web.xml` file. | - Adding to @product@'s `web.xml` file. | | **Note:** In previous versions of Liferay Portal, you needed an Ext plugin to | specify classes as portal property values (e.g., | `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 | @product-ver@ since all lifecycle events can use OSGi services with no need to | edit these legacy properties. Ext plugins are used to customize @product@'s core functionality. You can learn more about what the core encompasses in the [Finding Core @product@ Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts) article section. In this section, you'll learn how to - [Create an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/creating-an-ext-plugin) - [Develop an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/developing-an-ext-plugin) - [Deploy an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) - [Redeploy an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/redeploying-an-ext-plugin) You can also dive into the [Anatomy of an Ext Plugin](/docs/7-2/customization/-/knowledge_base/c/anatomy-of-an-ext-plugin) to familiarize yourself with its structure. You'll start by creating an Ext plugin. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/02-creating-an-ext-plugin.markdown ================================================ --- header-id: creating-an-ext-plugin --- # Creating an Ext Plugin [TOC levels=1-4] An Ext plugin is a powerful tool for extending @product@. Because it increases the complexity of your @product@ installation, you should only use an Ext plugin if you're sure you can't accomplish your goal in a different way. You can create Ext plugins using the pre-configured `war-core-ext` project template/archetype. See the [`war-core-ext`](/docs/7-2/reference/-/knowledge_base/r/war-core-ext) project template article for information on how to create an Ext plugin, its folder structure, and other important details. It's recommended to create and develop your Ext plugin in a [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace). Workspace is preconfigured with an `ext` folder, which applies important settings (via the `LiferayExtPlugin`) to your Ext plugin when it's deployed to @product@. You'll learn more about this in the [Set Up the Build Environment](/docs/7-2/customization/-/knowledge_base/c/developing-an-ext-plugin#set-up-the-build-environment) section. Next you'll learn the anatomy of an Ext plugin. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/03-anatomy-of-an-ext-plugin.markdown ================================================ --- header-id: anatomy-of-an-ext-plugin --- # Anatomy of an Ext Plugin [TOC levels=1-4] There are two ways you can structure your Ext plugin. The [`war-core-ext`](/docs/7-2/reference/-/knowledge_base/r/war-core-ext) project template/archetype creates the default layout: - `[project name]-ext/` - `src/` - `extImpl/` - `resources/` - `extKernel/` - `resources/` - `extUtilBridges/` - `resources/` - `extUtilJava/` - `resources/` - `extUtilTaglib/` - `resources/` - `main/` - `webapp/` - `WEB-INF/` - `ext-web/` - `docroot/` You can also follow the legacy layout that was used for Ext plugins created with the Plugins SDK in past versions: - `[project name]-ext/` - `docroot/` - `WEB-INF/` - `ext-impl/` - `src/` - `ext-kernel/` - `src/` - `ext-util-bridges/` - `src/` - `ext-util-java/` - `src/` - `ext-util-taglib/` - `src/` - `ext-web/` - `docroot/` - `WEB-INF/` Although the folder names are slightly different, they work the same. This article will refer to the default structure and naming. Here are detailed explanations of the subfolders: - `extImpl`: Contains the custom implementation classes and classes that override core classes from `portal-impl.jar`. - `extKernel`: Contains classes that should be available to other plugins. In advanced scenarios, this folder can be used to hold classes that overwrite classes from `portal-kernel.jar`. - `extUtilBridges`, `extUtilJava` and `extUtilTaglib`: These folders are needed only in scenarios where you must customize these Liferay libraries: `util-bridges.jar`, `util-java.jar` and `util-taglib.jar`, respectively. If you're not customizing any of these libraries, you can ignore these folders. - `main/webapp/WEB-INF/ext-web/docroot`: Contains the web application's configuration files, including `WEB-INF/struts-config-ext.xml`, which lets you customize Liferay's core struts classes. Note that for @product-ver@, there are very few entities left to override in the `struts-config.xml` file. Any JSPs that you're customizing also belong here. By default, several files are also added to the plugin. Here are the most significant files: - `build.gradle`/`pom.xml`: The build file for the Ext plugin project. - [`src/main/webapp/WEB-INF/liferay-plugin-package.properties`](@platform-ref@/7.1-latest/propertiesdoc/liferay-plugin-package_7_1_0.properties.html): Contains plugin-specific properties, including the plugin's display name, version, author, and license type. - `src/main/webapp/WEB-INF/ext-web/docroot/WEB-INF` files: - `liferay-portlet-ext.xml`: This file is similar to `portlet-ext.xml`, but is for additional definition elements specific to Liferay. The `liferay-portlet.xml` file contains very few definition elements in @product-ver@ because portlets were modularized and moved out of core. To override the remaining definition elements, copy the complete definition of the desired portlet from `liferay-portlet.xml` in Liferay's source code, then apply the necessary changes. - `portlet-ext.xml`: Used to overwrite the definition of a build-in portlet. The `portlet.xml` file contains very few portlet configurations in @product-ver@ because portlets were modularized and moved out of core. To override this file, copy the complete definition of the desired portlet from `portlet-custom.xml` in Liferay's source code, then apply the necessary changes. - `struts-config-ext.xml` and `tiles-defs-ext.xml`: These files are used to customize the struts actions used by core portlets. Since most portlets were modularized and moved out of core in @product-ver@, the `struts-config.xml` and `tiles-defs.xml` files are sparsely used. - `web.xml`: Used to overwrite web application configurations and servlet information for @product-ver@. | **Note:** After creating an Ext plugin, remove the files from | `docroot/WEB-INF/ext-web/docroot/WEB-INF` that you don't need to customize. | Removing files you're not customizing prevents incompatibilities that could | manifest from @product@ updates. Great! Now you're familiar with an Ext plugin's folder structure and its most significant files. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/04-developing-an-ext-plugin.markdown ================================================ --- header-id: developing-an-ext-plugin --- # Developing an Ext Plugin [TOC levels=1-4] An Ext plugin changes @product@ itself when the plugin is deployed; it's not a separate component that you can easily remove at any time. For this reason, the Ext plugin development process is different from other project types. It's important to remember that once an Ext plugin is deployed, some of its files are copied *inside* the Liferay installation; the only way to remove the changes is by *redeploying* an unmodified @product@ application. You're also responsible for checking that patches and fix packs do not conflict with your Ext plugin. Additionally, Ext plugins aren't hot deployable. To deploy an Ext plugin, you must restart your server. Additional steps are also required to deploy or redeploy to production systems. It's strongly recommended to only develop/deploy one Ext plugin. This means that all your customizations should live inside one Ext plugin. Liferay Workspace does not check for conflicts among multiple Ext plugins stored in the `/ext` folder, so do **not** develop/deploy multiple Ext plugins at once. You can deploy and redeploy your Ext plugin during your development phase. Redeployment involves manually removing the Ext plugin and *cleaning* your application server (i.e., remove it ); this is recommended so any changes made to the Ext plugin during development are properly applied, and files removed from your plugin by previous changes aren't left behind in the @product@ application. Because of this added complexity, you should use another plugin type to accomplish your goals whenever possible. Now it's time to set up the build environment. ## Set Up the Build Environment If you're leveraging [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace), you should create/develop your Ext module project in the `/ext` folder (default); you can specify a different Ext folder name in workspace's `gradle.properties` by adding ```properties liferay.workspace.ext.dir=EXT_DIR ``` 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 `build.gradle`: ```gradle apply plugin: 'com.liferay.ext.plugin' ``` Next you'll explore an Ext plugin's advanced configuration files. ## Using Advanced Configuration Files @product@ uses several internal configuration files for its own architecture; in addition, there are configuration files for the libraries and frameworks @product@ depends on (e.g., Spring). Configuration could be accomplished using fewer files with more properties in each, but maintenance and use is made easier by splitting up the configuration properties into several files. For advanced customization needs, it may be useful to override the configuration specified in multiple configuration files. @product@ provides a clean way to do this from an Ext plugin without modifying the original files. All the configuration files in @product@ are listed below by their path in an Ext plugin folder. Here are descriptions of what each file is for and the path to the original file in @product@: - `extImpl/resources/META-INF/ext-model-hints.xml` - **Description:** Overrides the default properties of the fields of the data models used by @product@'s core portlets. These properties determine how the form fields for each model are rendered. - **Original file in @product@:** `portal-impl/src/META-INF/portal-model-hints.xml` - `extImpl/resources/META-INF/ext-spring.xml` - **Description:** Overrides the Spring configuration used by @product@ and any of its core portlets. It's most commonly used to configure specific data sources or swap the implementation of a default service with a custom one. - **Original file in @product@:** `portal-impl/src/META-INF/*-spring.xml` - `extImpl/resources/META-INF/portal-log4j-ext.xml` - **Description:** Allows overriding the Log4j configuration. It can be used to configure appenders for log file location, naming, and rotation. See the [Log4j XML Configuration Primer](https://wiki.apache.org/logging-log4j/Log4jXmlFormat). [Increasing or decreasing the log level of a class or class hierarchy](/docs/7-2/appdev/-/knowledge_base/a/adjusting-module-logging) is best done outside of an Ext plugin, in @product@'s' UI or a Log4j XML file in a module or the `osgi/log4j` folder. - **Original file in Liferay:** `portal-impl/src/META-INF/portal-log4j.xml` - `ext-web/docroot/WEB-INF/portlet-ext.xml` - **Description:** Overrides the core portlets' declarations. It's most commonly used to change the init parameters or the roles specified. - **Original file in @product@:** `portal-web/docroot/WEB-INF/portlet-custom.xml` - `ext-web/docroot/WEB-INF/liferay-portlet-ext.xml` - **Description:** Overrides the Liferay-specific core portlets' declarations. It core portlets included in @product@. Refer to the [liferay-portlet-app_7_0_0.dtd](@platform-ref@/7.2-latest/definitions/liferay-portlet-app_7_2_0.dtd.html) file for details on all the available options. Use this file with care; the code of the portlets may be assuming some of these options to be set to certain values. - **Original file in @product@:** `portal-web/docroot/WEB-INF/liferay-portlet.xml` - `ext-web/docroot/WEB-INF/liferay-display.xml` - **Description:** Overrides the portlets that are shown in the interface for adding applications and the categories in which they're organized. - **Original file in @product@:** `portal-web/docroot/WEB-INF/liferay-display.xml` Next you'll deploy your Ext plugin. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/05-deploying-an-ext-plugin.markdown ================================================ --- header-id: deploying-an-ext-plugin --- # Deploying an Ext Plugin [TOC levels=1-4] Deploying an Ext plugin is similar to deploying any other @product@ project. For example, you'll step through deploying an Ext plugin in Liferay Workspace. 1. From your Ext plugin root folder, run `blade deploy` (Gradle or Maven) or `mvn verify` (Maven only). The `deploy` target creates a `.war` file with your changes and then deploys it to your server. A `BUILD SUCCESSFUL` message indicates your plugin is now being deployed. Your console window running @product@ shows a message like this: ```bash Extension environment for sample-ext has been applied. You must reboot the server and redeploy all other plugins. ``` 2. Restart the server so that it detects and publishes your Ext plugin. Your server must be restarted after the initial deployment and every redeployment. If any changes applied through the Ext plugin affect the deployment process itself, you must redeploy all other plugins. Even if the Ext plugin doesn't affect the deployment process, it's a best practice to redeploy all your other plugins following initial deployment of the Ext plugin. When the server starts, it detects the `.war` file, inspects it, and copies its contents to the appropriate destinations inside @product@. 3. Once your server restarts, open @product@ to see the customizations you made with your Ext plugin. Frequently, you'll want to add further customizations to your original Ext plugin. You can make unlimited customizations to an Ext plugin that has already been deployed, but the redeployment process is different from other project types. You'll learn more on redeploying your Ext plugin next. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/02-customizing-core-functionality-with-ext/06-redeploying-an-ext-plugin.markdown ================================================ --- header-id: redeploying-an-ext-plugin --- # Redeploying an Ext Plugin [TOC levels=1-4] After editing an Ext plugin, you must follow a slightly different process to redeploy your Ext plugin. This section assumes you're redeploying an Ext plugin in Liferay Workspace. 1. Stop the @product@ server. 2. Delete your Ext plugin bundle, which resides in your app server's `webapps` folder. 3. (Optional) If you removed part(s) of your plugin, if there are changes to your plugin that can affect plugin deployment, or if you want to start with a clean @product@ environment, you must **also** clean your application server. You can clean the application server by deleting your Liferay Home and regenerating the bundle. This is done in Liferay Workspace by calling `blade server init`. 4. From your Ext plugin root folder, run `blade deploy` (Gradle or Maven) or `mvn verify` (Maven only). The `deploy` target creates a `.war` file with your changes and then deploys it to your server. A `BUILD SUCCESSFUL` message indicates your plugin is now being deployed. Your console window running @product@ shows a message like this: ```bash Extension environment for sample-ext has been applied. You must reboot the server and redeploy all other plugins. ``` 5. After your plugin is published to @product@, verify that your customizations are available. You're all set to redeploy Ext plugins! ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/03-customizing-osgi-modules-with-ext/01-intro.markdown ================================================ --- header-id: customizing-osgi-modules-with-ext --- # Customizing OSGi Modules with Ext [TOC levels=1-4] An Ext module is a powerful tool for extending @product@'s OSGi modules. For example, if you want to overwrite a default module's JSP to display a different view, you can create an Ext module to customize the original module's JSP (for example, see the [Login Web Ext sample](/docs/7-2/reference/-/knowledge_base/r/login-web-ext)). Because this increases the complexity of your @product@ installation, you should only use an Ext module if you're sure you can't accomplish your goal in a different way (e.g., leveraging an extension point). The following app servers should be used for Ext module development in @product@: - Tomcat 9.x In this section, you'll learn how to - [Create an Ext module](/docs/7-2/customization/-/knowledge_base/c/creating-an-ext-module) - [Develop an Ext module](/docs/7-2/customization/-/knowledge_base/c/developing-an-ext-module) - [Deploy an Ext module](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-module) You'll start by creating an Ext module. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/03-customizing-osgi-modules-with-ext/02-creating-an-ext-module.markdown ================================================ --- header-id: creating-an-ext-module --- # Creating an Ext Module [TOC levels=1-4] You can create an Ext module based off the pre-configured `modules-ext` project template/archetype. See the [`modules-ext`](/docs/7-2/reference/-/knowledge_base/r/modules-ext-template) project template article for information on how to create an Ext module, its folder structure, and other important details. It's recommended to create and develop your Ext module in a [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace). Workspace is preconfigured with an `ext` folder, which applies important settings (via the `LiferayOSGiExtPlugin`) to your Ext module when it's deployed to @product@. You'll learn more about this in the next section. You can also create an Ext module leveraging the `modules-ext` project template in Dev Studio or IntelliJ. ## Dev Studio To create an Ext module in Dev Studio, 1. Right click your Liferay Workspace and select *New* → *Liferay Module Ext Project*. 2. Define the project name and the original module name (i.e., BSN form, like `com.liferay.login.web`) you intend to override. ![Figure 1: You must define your Ext module's name and the module you intend to override.](../../../images/ext-module-dev-studio.png) Note, you must have the `target.platform.index.sources` property enabled in your workspace's `gradle.properties` file to browse for the original module name. 3. Click *Finish* to create the Module Ext project. Awesome! You've created an Ext module project in Dev Studio! ## IntelliJ To create an Ext module in IntelliJ, 1. Go to *File* → *New* → *Liferay Module*. 2. Select the *Liferay Modules Ext* option from the left menu. 3. Define the original module name (i.e., BSN form, like `com.liferay.login.web`) you intend to override. Then click *Next*. ![Figure 2: You must define the module you intend to override.](../../../images/ext-module-intellij.png) Note, you must have the `target.platform.index.sources` property enabled in your workspace's `gradle.properties` file to browse for the original module name. 4. Define the module name. 5. Click *Finish*. Great! You know how to create an Ext module and are familiar with its folder structure and most significant files. Next, you'll learn how to develop your Ext module to customize @product@. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/03-customizing-osgi-modules-with-ext/03-developing-an-ext-module.markdown ================================================ --- header-id: developing-an-ext-module --- # Developing an Ext Module [TOC levels=1-4] You can create your own Ext module project by - Declaring the original module name and version. - Providing the source code that will replace the original. To declare the original module in the `build.gradle` file properly (only supports Gradle), you must specify the original module's Bundle Symbolic Name and the original module's exact version. For example, overriding the `com.liferay.login.web` module would be configured like this: ```gradle originalModule group: "com.liferay", name: "com.liferay.login.web", version: "3.0.4" ``` If you're leveraging [Liferay Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace), you should put your Ext module project in the `/ext` folder (default); you can specify a different Ext folder name in workspace's `gradle.properties` by adding ```properties liferay.workspace.ext.dir=EXT_DIR ``` 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 `build.gradle`: ```gradle apply plugin: 'com.liferay.osgi.ext.plugin' ``` Then you must provide your own code intended to replace the original one. **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: - CSS - Java - JavaScript - Language files (`Language.properties`) - Scss - Soy - etc. The [Ext Gradle Plugin](https://github.com/liferay/liferay-portal/blob/master/modules/sdk/gradle-plugins/src/main/java/com/liferay/gradle/plugins/LiferayOSGiExtPlugin.java) helps compile your code into the JAR. For example, `.scss` files are compiled into `.css` files, which are included in your module's JAR file artifact. This is done by the `buildCSS` task. Once you're finished developing your Ext module, you must deploy it. Continue on to learn how. ================================================ FILE: en/developer/customization/articles/99-customization-with-ext/03-customizing-osgi-modules-with-ext/04-deploying-an-ext-module.markdown ================================================ --- header-id: deploying-an-ext-module --- # Deploying an Ext Module [TOC levels=1-4] Before deploying your Ext module, you must stop the original bundle you intend to override. This is because an Ext module's generated JAR includes the original bundle source plus your modified source files. | **Note:** Stopping the original bundle before deploying your Ext module is only | necessary if you've already started @product@. Follow the instructions below to deploy your Ext module to a @product@ instance: 1. Connect to your portal instance using [Gogo Shell](/docs/7-2/customization/-/knowledge_base/c/using-the-felix-gogo-shell). 2. Search for the bundle ID of the original bundle to override. To find the `com.liferay.login.web` bundle, execute this command: ```bash lb -s | grep com.liferay.login.web ``` This returns output similar to this: ```bash 423|Active | 10|com.liferay.login.web (3.0.4) ``` Make note of the ID (e.g., `423`). 3. Stop the bundle: ```bash stop 423 ``` Once the original bundle is stopped, deploy the Ext module. Note that you cannot leverage Blade or Gradle's `deploy` command to do this. The `deploy` command deploys the module to the `osgi\marketplace\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 `deploy` folder manually. You're all set to deploy Ext modules! ================================================ FILE: en/developer/customization/articles-dxp/50-workflow/01-workflow-intro.markdown ================================================ --- header-id: customizing-workflow --- # Customizing Workflow [TOC levels=1-4] 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 @product@ version 7.2, a new set of workflow features was introduced around the concept of [Workflow Metrics](/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars). 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. ================================================ FILE: en/developer/customization/articles-dxp/50-workflow/02-sla-calendars.markdown ================================================ --- header-id: creating-sla-calendars --- # Creating SLA Calendars [TOC levels=1-4] By default, an internal calendar assumes the [SLA deadline clock](/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars) 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 `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: ```java public interface WorkflowMetricsSLACalendar { public Duration getDuration( LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime); public LocalDateTime getOverdueLocalDateTime( LocalDateTime nowLocalDateTime, Duration remainingDuration); public String getTitle(Locale locale); } ``` 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! ![Figure 1: Write a Custom SLA Calendar if the default, 24/7 calendar isn't sufficient.](../../images/workflow-custom-sla-calendar.png) ## Dependencies Along with some artifacts you're probably used to depending on (like `com.liferay.portal.kernel`), you'll need the `com.liferay.portal.workflow.metrics.sla.api-[version].jar` artifact. For @product@ version 7.2.10-GA1, here's an example Gradle build dependency declaration: ```groovy 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" ``` ## Implementation Steps Implement a `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 [SLA definition](/docs/7-2/customization/-/knowledge_base/c/creating-sla-calendars). 1. Declare the component and the class: ```java 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 { ``` The component property `sla.calendar.key` is required to identify this calendar. 2. Override `getDuration` to return the time `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 _2019-05-13T16:00:00_ and finished at _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. ```java @Override public Duration getDuration( LocalDateTime startLocalDateTime, LocalDateTime endLocalDateTime) { return Duration.between(startLocalDateTime, endLocalDateTime); } ``` 3. `getOverdueLocalDateTime` must return the date (as a `LocalDateTime`) when this SLA is considered overdue given the parameter values. For example, given that `nowLocalDateTime`=_2019-05-13T17:00:00_ and `remainingDuration`=_24H_, The 24/7 calendar returns a `localDateTime` of _2019-05-14T17:00:00_ as the overdue date. Given the same parameters, the 9-17 weekdays calendar should return _2019-05-17T09:00:00_. The remaining duration of time left in the SLA is available in the method as a `Duration` object; your job is to write logic that considers your calendar and create a `LocalDateTime` with the proper overdue date/time. ```java @Override public LocalDateTime getOverdueLocalDateTime( LocalDateTime nowLocalDateTime, Duration remainingDuration) { return nowLocalDateTime.plus(remainingDuration); } ``` 4. Use `getTitle` to provide the title for the given locale. Make sure you [properly localize](/docs/7-2/frameworks/-/knowledge_base/f/localizing-your-application) this extension by providing a `Language.properties` file and any `Language_xx.properties` files for translation of the value. At runtime, the User's locale is used to return the correct translation. ```java @Override public String getTitle(Locale locale) { return _language.get(locale, "default"); } @Reference private Language _language; ``` If the 24/7 default calendar works for you, use it. Otherwise create your own `WorkflowMetricsSLACalendar`s. ================================================ FILE: en/developer/customization/articles-dxp/99-customization-with-ext/02-customizing-core-functionality-with-ext/01-intro.markdown ================================================ --- header-id: customizing-core-functionality-with-ext --- # Customizing Core Functionality with Ext [TOC levels=1-4] | **Ext plugins are deprecated for @product-ver@ and should only be used if | absolutely necessary.** | | The following app servers should be used for Ext plugin development in | @product@: | | - Tomcat 9.x | | 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: | | - Providing custom implementations for any beans declared in @product@'s | Spring files (when possible, use | [service wrappers](/docs/7-2/customization/-/knowledge_base/c/overriding-service-builder-services-service-wrappers) | instead of an Ext plugin). @product-ver@ removed many beans, so make sure | your overridden beans are still relevant if converting your legacy Ext | plugin | ([how to](/docs/7-2/customization/-/knowledge_base/c/extending-core-classes-using-spring-with-ext-plugins)). | - Overwriting a class in a @product-ver@ core JAR. For a list of core JARs, | see the | [Finding Core @product@ Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts#finding-core-artifact-attributes) | section | ([how to](/docs/7-2/customization/-/knowledge_base/c/overriding-core-classes-with-ext-plugins)). | - Modifying @product@'s `web.xml` file | ([how to](/docs/7-2/customization/-/knowledge_base/c/modifying-the-web-xml-with-ext-plugins)). | - Adding to @product@'s `web.xml` file | ([how to](/docs/7-2/customization/-/knowledge_base/c/adding-to-the-web-xml-with-ext-plugins)). | | **Note:** In previous versions of Liferay Portal, you needed an Ext plugin to | specify classes as portal property values (e.g., | `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 | @product-ver@ since all lifecycle events can use OSGi services with no need to | edit these legacy properties. Ext plugins are used to customize @product@'s core functionality. You can learn more about what the core encompasses in the [Finding Core @product@ Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts#finding-core-artifact-attributes) article section. In this section, you'll learn how to - [Create an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/creating-an-ext-plugin) - [Develop an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/developing-an-ext-plugin) - [Deploy an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) - [Redeploy an Ext plugin](/docs/7-2/customization/-/knowledge_base/c/redeploying-an-ext-plugin) You can also dive into the [Anatomy of an Ext Plugin](/docs/7-2/customization/-/knowledge_base/c/anatomy-of-an-ext-plugin) to familiarize yourself with its structure. You'll start by creating an Ext plugin. ================================================ FILE: en/developer/customization/articles-dxp/99-customization-with-ext/04-extending-core-classes-using-spring-with-ext-plugins.markdown ================================================ --- header-id: extending-core-classes-using-spring-with-ext-plugins --- # Extending Core Classes Using Spring with Ext Plugins [TOC levels=1-4] A supported use case for using Ext plugins in @product@ is extending its core classes (e.g., `portal-impl`, `portal-kernel`, etc.) using Spring. You can reference the [Finding Core Liferay Portal Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts#finding-core-artifact-attributes) section for help distinguishing core classes. Make sure you've reviewed the generalized [Customization with Ext Plugins](/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext) section before creating an Ext plugin. As an example, you'll create a sample Ext plugin that extends the [PortalImpl](https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/PortalImpl.html) core class residing in the `portal-impl.jar`. You'll override the `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. 1. Navigate to your Liferay Workspace's root folder and run the following command: ```bash blade create -t war-core-ext portal-impl-extend-spring-ext ``` Your Ext plugin is generated and now resides in the workspace's `/ext` folder with the name you assigned. 2. Displaying the server node name in your @product@ installation is set to `false` by default. You'll need to enable this property. To do this, navigate into your Liferay bundle's root folder and create a `portal-ext.properties` file. In that file, insert the following property: ```properties web.server.display.node=true ``` Now your server's node name will be displayed once your Liferay bundle is restarted. 3. In the `/extImpl/java` folder, create the folder structure representing the package name you want your new class to reside in (e.g., `com/liferay/portal/util`). Then create your new Java class: ```java package com.liferay.portal.util; public class SamplePortalImpl extends PortalImpl { @Override public String getComputerName() { return "SAMPLE_EXT_INSTALLED_" + super.getComputerName(); } } ``` The method defined in the extension class overrides the `PortalImpl.getComputerName()` method. The `"SAMPLE_EXT_INSTALLED_"` String is now prefixed to your server's node name. 4. In your Ext plugin's `/extImpl/resources` folder, create a `META-INF/ext-spring.xml` file. In this file, insert the following code: ```xml ``` 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., `com.liferay.portal.util.SamplePortalImpl`) to the bean tag's `class` attribute and the fully defined original class name (e.g., `com.liferay.portal.util.PortalImpl`) to the bean tag's `id` attribute. When your Ext plugin is deployed, your new service (e.g., `SamplePortalImpl`) will extend the core `PortalImpl` class. Awesome! You've created an Ext plugin that extends a core class in @product@! Follow the instructions in the [Deploy the Plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) article to deploy it to your server. ================================================ FILE: en/developer/customization/articles-dxp/99-customization-with-ext/05-overriding-core-classes-with-ext-plugins.markdown ================================================ --- header-id: overriding-core-classes-with-ext-plugins --- # Overriding Core Classes with Ext Plugins [TOC levels=1-4] A supported use case for using Ext plugins in @product@ is overriding its core classes (e.g., `portal-impl`, `portal-kernel`, etc.). You can reference the [Finding Core Liferay Portal Artifacts](/docs/7-2/customization/-/knowledge_base/c/finding-artifacts#finding-core-artifact-attributes) section for help distinguishing core classes. Make sure you've reviewed the generalized [Customization with Ext Plugins](/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext) section before creating an Ext plugin. As an example, you'll create a sample Ext plugin that overwrites the [PortalImpl](https://docs.liferay.com/ce/portal/7.2-latest/javadocs/portal-impl/com/liferay/portal/util/PortalImpl.html) core class residing in the `portal-impl.jar`. You'll edit the `PortalImpl.getComputerName()` method, which returns your server's node name. The Ext plugin will override the entire `PortalImpl` class, adding the method modifying the server's returned node name. 1. Navigate to your Liferay Workspace's root folder and run the following command: ```bash blade create -t war-core-ext portal-impl-override ``` Your Ext plugin is generated and now resides in the workspace's `/ext` folder with the name you assigned. 2. Displaying the server node name in your @product@ installation is set to `false` by default. You'll need to enable this property. To do this, navigate into your Liferay bundle's root folder and create a `portal-ext.properties` file. In that file, insert the following property: ```properties web.server.display.node=true ``` Now your server's node name will be displayed once your Liferay bundle is restarted. 3. In the `/extImpl/java` folder, create the folder structure matching the class's folder structure you'd like to override (e.g., `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. 4. Copy all of the original class's (e.g., `PortalImpl`) logic into your new class. Then modify the method you want to customize. For this example, you want to edit the `getComputerName()` method. Therefore, replace it with the method below: ```java @Override public String getComputerName() { return "sample_portalimpl_ext_installed_successfully_" + _computerName; } ``` The method defined in the new class overrides the `PortalImpl.getComputerName()` method. The `sample_portalimpl_ext_installed_successfully_` String is now prefixed to your server's node name. When your Ext plugin is deployed, your new Java class will override the core `PortalImpl` class. Awesome! You've created an Ext plugin that overrides a core class in @product@! Follow the instructions in the [Deploy the Plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) article to deploy it to your server. ================================================ FILE: en/developer/customization/articles-dxp/99-customization-with-ext/06-adding-to-the-web-xml-with-ext-plugins.markdown ================================================ --- header-id: adding-to-the-web-xml-with-ext-plugins --- # Adding to the web.xml with Ext Plugins [TOC levels=1-4] A supported use case for using Ext Plugins in @product@ is adding additional functionality to its `web.xml` file. Before beginning, make sure you've reviewed the generalized [Customization with Ext Plugins](/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext) section. As an example, you'll create a sample Ext plugin that adds to your @product@'s existing `web.xml` file (e.g., in the `/tomcat-[version]/webapps/ROOT/WEB-INF` folder). You'll add a new printout in the console during startup. 1. Navigate to your Liferay Workspace's root folder and run the following command: ```bash blade create -t war-core-ext add-printout ``` Your Ext plugin is generated and now resides in the workspace's `/ext` folder with the name you assigned. 2. For your @product@ installation to recognize new functionality in the `web.xml`, you must create a class that implements the [ServletContextListener](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/ServletContextListener.html) interface. This class will initialize a servlet context event for which you'll add your new functionality. In the `extImpl/java` folder, create the folder structure representing the package name you want your new class to reside in (e.g., `com/liferay/portal/servlet/context`). Then create your new Java class: ```java 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"); } } ``` The above class includes two methods that initialize and destroy your servlet context event. Be sure to add the new `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 `contextInitialized(...)` method: ```java System.out.println("EXT_ADD_ENTRY_WEBXML_INSTALLED_SUCCESSFULLY"); ``` 3. Now that you've defined a servlet context event, you should add a listener to your `web.xml` that listens for it. In the `ext-web/docroot/WEB-INF` folder, open the `web.xml` file, which was generated for you by default. 4. Add the following tag between the tags: ```xml com.liferay.portal.servlet.context.ExtAddEntryWebXmlPortalContextLoaderListener ``` Excellent! Now when your Ext plugin is deployed, your @product@ installation will create a `ServletContextListener` instance, which will initialize a custom servlet context event. This event will be recognized by the `web.xml` file, which will add the new functionality to your @product@ installation. Follow the instructions in the [Deploy the Plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) article for help deploying the Ext plugin to your server. ================================================ FILE: en/developer/customization/articles-dxp/99-customization-with-ext/07-modifying-the-web-xml-with-ext-plugins.markdown ================================================ --- header-id: modifying-the-web-xml-with-ext-plugins --- # Modifying the web.xml with Ext Plugins [TOC levels=1-4] A supported use case for using Ext Plugins in @product@ is modifying its `web.xml` file. Before beginning, make sure you've reviewed the generalized [Customization with Ext Plugins](/docs/7-2/customization/-/knowledge_base/c/customizing-core-functionality-with-ext) section. As an example, you'll create a sample Ext plugin that modifies @product@'s existing `web.xml file` (e.g., in the `/tomcat-[version]/webapps/ROOT/WEB-INF` folder). You'll modify the session timeout configuration, which is set to 30 (minutes) by default: ```xml 30 true ``` The Ext plugin will update the session timeout to one minute. 1. Navigate into your Liferay Workspace's `/ext` folder and run the following command: ```bash blade create -t war-core-ext modify-session-timeout ``` Your Ext plugin is generated and now resides in the workspace's `/ext` folder with the name you assigned. 2. In the `ext-web/docroot/WEB-INF` folder, open the `web.xml` file, which was generated for you by default. 3. Insert the following logic between the `` tags: ```xml 1 true ``` Notice that the `` tag has been updated to `1`. | **Note:** You can configure an uninterrupted session by setting the | `` tag to `-1`. Leaving a session permanently active is a | risk and is not recommended for production environments, but is useful for | testing. That's it! Now when your Ext plugin is deployed, your @product@ installation will timeout after one minute of inactivity. Follow the instructions in the [Deploy the Plugin](/docs/7-2/customization/-/knowledge_base/c/deploying-an-ext-plugin) article for help deploying the Ext plugin to your server. ================================================ FILE: en/developer/customization/build.xml ================================================ ================================================ FILE: en/developer/frameworks/articles/01-frameworks-intro.markdown ================================================ --- header-id: frameworks --- # Frameworks [TOC levels=1-4] 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: [**A fully-fledged permissions system:**](/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions) Implement permissions the way they're implemented with the applications that ship with @product@ for a consistent, seamless, and robust experience. [**Assets:**](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) 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. [**Search:**](/docs/7-2/frameworks/-/knowledge_base/f/search) If you have a data-driven application, you can add search capabilities by integrating with Liferay's search indexer. [**A configuration system with auto-generated or custom UI:**](/docs/7-2/frameworks/-/knowledge_base/f/configurable-applications) 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. [**File management:**](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api) Will your applications work with files? Use Liferay's Documents and Media API to manage them. [**Import/Export:**](/docs/7-2/frameworks/-/knowledge_base/f/content-publication-management) Use Liferay's import/export system to make your application's data portable or to stage it for publication to production systems. [**Web Fragments:**](/docs/7-2/frameworks/-/knowledge_base/f/page-fragments) Provide your content managers with dynamic chunks of functionality they can use as building blocks for web pages. [**Workflow:**](/docs/7-2/frameworks/-/knowledge_base/f/the-workflow-framework) Run the data from your application through an approval process. This really just scratches the surface. From [pop-up list selectors](/docs/7-2/frameworks/-/knowledge_base/f/item-selector) to a [social networking API](/docs/7-2/frameworks/-/knowledge_base/f/social-api), as a Liferay developer, you have access to tons of frameworks that make your life easier. ================================================ FILE: en/developer/frameworks/articles/application-security/01-intro.markdown ================================================ --- header-id: application-security --- # Application Security [TOC levels=1-4] 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: - Resources, Roles, and Permissions - Custom SSO Providers - Authentication Pipelines - Service Access Policies - Authentication Verifiers Read on to learn about implementing Liferay's security framework! ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/01-application-permissions-intro.markdown ================================================ --- header-id: defining-application-permissions --- # Defining Application Permissions [TOC levels=1-4] 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 @product@, 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: *Resources*, *Actions*, and *Permissions*. **Action**: An operation that can be performed by a user. For example, users can perform these actions on the Bookmarks application: `ADD_TO_PAGE`, `CONFIGURATION`, and `VIEW`. Users can perform these actions on Bookmarks entry entities: `ADD_ENTRY`, `DELETE`, `PERMISSIONS`, `UPDATE`, and `VIEW`. **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 `hF5f`, a globally scoped Wiki page, a Bookmarks entry of the site X, and a Message Boards post with the ID `5052`. **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 `ResourceAction` table contains both the name of a portlet or entity and the name of an action. For example, the `VIEW` action with respect to *viewing the Bookmarks application* is associated with the `com_liferay_bookmarks_web_portlet_BookmarksPortlet` portlet ID. The `VIEW` actions with respect to *viewing a Bookmarks Folder* or *viewing a Bookmarks entry* are associated with the `com.liferay.bookmarks.model.BookmarksFolder` and `com.liferay.bookmarks.model.BookmarksEntry` entities, respectively. To do permissions, therefore, you define *Users* (Roles) who have *Permission* to perform *Actions* on *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 *DRAC*: 1. Define all resources and their permissions. 2. Register all defined resources in the permissions system. 3. Associate the necessary permissions with resources. 4. Check permission before returning resources. The next four tutorials show these steps in detail. ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/02-defining-permissions.markdown ================================================ --- header-id: defining-resources-and-permissions --- # Defining Resources and Permissions [TOC levels=1-4] Your first step in implementing permissions is to define the resources and the permissions that protect them. There are two different kinds of resources: *portlet resources* and *model resources*. Portlet resources represent portlets. The names of portlet resources are the portlet IDs from the portlets' `@Component` properties or if you're using a WAR file, `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 *portlet* resource and then for the *model* resources. Model resources represent models, such as blog entries. Resources are named using the fully qualified class names of the entities they represent. | **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 *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). You define resources and their permissions using an XML file. By convention, this file is called `default.xml` and exists in a module's `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. ## Defining Portlet Resource Permissions Define the portlet resources first; here's an example using Liferay's Blogs application. 1. Start with the DTD declaration: ```xml ``` 2. The root tag contains all the resources to be declared: ```xml ``` 3. Inside these tags, define your resources. The Blogs application defines two portlet resources: ```xml 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 ``` 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: `ADD_PORTLET_DISPLAY_TEMPLATE`, `ADD_TO_PAGE`, `CONFIGURATION`, and `VIEW`. The Blogs Admin portlet has an additional permission: `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 `guest-unsupported` tag for defining permissions guests can *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. ## Defining Model Resource Permissions Defining permissions for models is a similar process. Create a `default.xml` file in your service module's `src/main/resources/resource-actions` folder. In this file, you must define top-level function permissions and individual entity permissions using the same `` tag. This can be confusing, so some explanation is in order. Model permissions for what Liferay calls the *root model* are defined separately from permissions on stored entities, which Liferay calls the *model*. This makes sense when you think about the functions users can perform: - Creating something new - Editing something that exists 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 *root model* permission, whether that functionality is exposed in a portlet or not. Permission on an existing resource is a *model* permission. Now you're ready to define both your root model and model permissions. 1. First, create the skeleton for your file: ```xml ``` 2. Inside the `` tags, use a `` tag to define permissions for the root model: ```xml 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 ``` The model name (`com.liferay.blogs`) is just a package name. The `true` tag defines this as a root model. The `` 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. 3. Finally, define your model permissions: ```xml 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 ``` Note the lack of a `` tag, the fully qualified class name for the model, and the permissions that operate on an entity with a primary key. ## Enabling Your Permissions Configuration Your last step is to enable your permission definitions. Each module that contains a `default.xml` permissions definition file must also have a `portlet.properties` file with a property that defines where to find the permissions definition file. For your service and your web modules, create a `portlet.properties` file in `src/main/resources` and make sure it has this property: ```properties resource.actions.configs=resource-actions/default.xml ``` Once you've defined portlet permissions, root model permissions, and model permissions, you've completed step 1 (the *D* in DRAC). Congratulations! You're now ready to *register* the resources you've now defined in the permissions system. ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/03-registering-permissions.markdown ================================================ --- header-id: registering-permissions --- # Registering Permissions [TOC levels=1-4] 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. ## 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. 1. Open your `-LocalServiceImpl` class. 2. 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: ```java resourceLocalService.addResources( entry.getCompanyId(), entry.getGroupId(), entry.getUserId(), BlogsEntry.class.getName(), entry.getEntryId(), false, addGroupPermissions, addGuestPermissions); ``` 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 - Whether the permission is a portlet resource - Whether the default group permissions defined in `default.xml` should be added - Whether the default guest permissions defined in `default.xml` should be added 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 `@Reference` annotation. If you're building a WAR-style plugin, you need a [service tracker](/docs/7-2/frameworks/-/knowledge_base/f/service-trackers-for-osgi-services) to gain access to the service. Note that your model classes must also implement Liferay's `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 `deleteEntry()` method: ```java resourceLocalService.deleteResource( entry.getCompanyId(), BlogsEntry.class.getName(), ResourceConstants.SCOPE_INDIVIDUAL, entry.getEntryId()); ``` 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. ## 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. 1. In your service bundle, create a package that by convention ends in `internal.security.permission.resource`. For example, the Blogs application's package is named `com.liferay.blogs.internal.security.permission.resource`. 2. Create a class in this package called `[Entity Name]ModelResourcePermissionRegistrar`. For example, the Blogs application's class is named `BlogsEntryModelResourcePermissionRegistrar`. 3. This class is a component class that requires overriding the `activate` method to register the permissions logic you want for your entities. For example, this is how the Blogs application registers its permissions: ```java @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; } ``` We call these types of classes Registrars because the classes' job is to configure, register and unregister the `ModelResourcePermission`. 1. The `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 `service.ranking` property can also be set to a value greater than zero to override other module's model resource permissions. 2. This registrar uses two portal-kernel permission logic classes for Staging and Workflow. Custom logic classes can be reused or composed inline since `ModelResourcePermissionLogic` is a `@FunctionalInterface`. Permission logic classes are executed in order of when they are accepted in the `Consumer`. 3. `ModelResourcePermissionLogic` classes return `true` when users have permission for the action, `false` when they are denied permission for the action, and `null` when wanting to delegate responsibility to the next permission logic. If all permission logics return null then the `PermissionChecker.hasPermission` method is called to determine if the action is allowed for the user. This class uses an `@Reference` with the target filter to inject the appropriate `PortletResourcePermission`. `BlogsConstants.RESOURCE_NAME` evaluates to `com.liferay.blogs`, which is defined in the `default.xml` you created earlier. If you were to reference this `ModelResourcePermission`, you'd use a target filter matching the `model.class.name` property set in the `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 *DRAC* by registering your permissions. Now you're ready to provide users the interface to associate permissions with resources. ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/04-associate-permissions-resources.markdown ================================================ --- header-id: associating-permissions-with-resources --- # Associating Permissions with Resources [TOC levels=1-4] 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: 1. ``: Returns a URL to the permission settings configuration page. 2. ``: Shows an icon to the user. These are defined in the theme and one of them (see below) is used for permissions. The Blogs application uses these tags like this: ```markup ``` For the `` tag, specify these attributes: `modelResource`: The fully qualified class name of the entity class. This class name gets translated into a more readable name as specified in `Language.properties`. `Language.properties`: The entity class in the example above is the Blogs entry class for which the fully qualified class name is `com.liferay.blogs.model.BlogsEntry`. `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. `resourcePrimKey`: Your entity's primary key. `var`: The name of the variable to which the resulting URL string is assigned. The variable is then passed to the `` tag so the permission icon has the proper URL link. There's an optional attribute called `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 *DRAC*. Your next step is to check for permissions in the appropriate areas of your application. ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/05-check-permissions.markdown ================================================ --- header-id: checking-permissions --- # Checking Permissions [TOC levels=1-4] 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: 1. Add permission checks to your service calls. 2. Create permission helper classes in your web module. 3. Add permission checks to your web application. These things are covered next. ## Add Permission Checks to Your Service Calls A best practice is to create methods in your `-ServiceImpl` classes that call the same methods in your `-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 `-LocalServiceImpl` class) from your permissions logic (contained in the `-ServiceImpl` class). 1. Open your entity's `-ServiceImpl` class. 2. Use the `ModelResourcePermissionFactory` and the `PortletResourcePermissionFactory` to reference permission checkers that can check permissions as you've defined them in `default.xml`. Here's how the Blogs portlet does this: ```java 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); ``` You declare the class, the variable, and for the portlet resource, the resource name from `default.xml`. In the Blogs application, `BlogsConstants.RESOURCE_NAME` is a `String` with the value `com.liferay.blogs`. You must use `ModelResourcePermissionFactory.getInstance()` in the service because Service Builder is wired with Spring, so `@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 `"_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 `static` and `volatile`, and should never be used outside of `-ServiceImpl` classes. 3. Check permissions in the appropriate places. For example, adding a blog entry requires the `ADD_ENTRY` permission, so the Blogs application does this: ```java @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); } ``` 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 `default.xml` as constants in an `ActionKeys` class. If `ActionKeys` doesn't have an action key appropriate for your application, extend Liferay's class and add your own keys. 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. ## 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: 1. Create a package with the suffix `web.internal.security.permission.resource`. For example, the Blogs application has the package `com.liferay.blogs.web.internal.security.permission.resource`. 2. Create a component class with at least one static method for checking permissions. For example, here's the `BlogsPermission` class: ```java @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; } ``` Note the `@Reference` annotation that tells the OSGi container to supply an object via the permission registrar you created previously. The `_portletResourcePermission` field is static, while the setter method is an instance method: this is how Liferay avoids having service references in JSPs. The procedure for creating a model permission helper is similar: 1. In the same package, create a component class with at least one static method for checking permissions. For example, here's the `BlogsEntryPermission` class: ```java @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; } ``` As you can see, this class is almost the same as the portlet permission class. The real difference is in the `@Reference` annotation that specifies the fully qualified class name of the model, rather than the resource name from `default.xml`. 2. Save both files. Now you're ready to use these helper classes to check permissions in your web module. ## 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: 1. When you have a function you want to protect, wrap it in an `if` statement that uses the permission helper class. For example, the Blogs application has many functions protected by permissions, including `ADD_ENTRY` and `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: ```markup
    ``` 2. Do this for any function. For example, the Permissions function you added in [step 3](/docs/7-2/frameworks/-/knowledge_base/f/associating-permissions-with-resources) should definitely be protected by permissions: ```markup ``` This prevents anyone without the permission to set permissions from seeing the permissions button. Say that three times fast! That's all there is to it! You've now learned all the steps in *DRAC*: 1. Define permissions 2. Register permissions 3. Associate permissions with resources 4. Check permissions Follow these steps, and your applications can take advantage of Liferay's integrated and well-tested permissions system. ================================================ FILE: en/developer/frameworks/articles/application-security/02-application-permissions/06-using-portal-roles-in-a-portlet.markdown ================================================ --- header-id: using-portal-roles-in-a-portlet --- # Using JSR Roles in a Portlet [TOC levels=1-4] Roles in @product@ are the primary means for granting or restricting access to content. If you've decided *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. ## JSR Portlet Security The portlet specification defines a means to specify Roles used by portlets in their `docroot/WEB-INF/portlet.xml` descriptors. The Role names themselves, however, are not standardized. When these portlets run in @product@, 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 `portlet.xml` file references the *administrator*, *guest*, *power-user*, and *user* Roles: ```xml 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 ``` An OSGi-based `guestbook-web` module project defines Roles without an XML file, in the portlet class's `@Component` annotation: ```java @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 ) ``` 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 `portlet.xml`. Your `portlet.xml` Roles must be mapped to specific Roles that have been created. These mappings allow @product@ to resolve conflicts between Roles with the same name that are from different portlets (e.g. portlets from different developers). | **Note:** Each Role named in a portlet's `` element is given | permission to add the portlet to a page. ## Mapping Portlet Roles to Portal Roles To map the Roles to @product@, you must use the `docroot/WEB-INF/liferay-portlet.xml` Liferay-specific configuration file. For an example, see the mapping defined in the Guestbook project's `liferay-portlet.xml` file. ```xml administrator Administrator guest Guest power-user Power User user User ``` If a portlet definition references the Role `power-user`, that portlet is mapped to the Liferay Role called *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 `system.roles` property in your `portal-ext.properties` file: ```properties system.roles=my-role,your-role,our-role ``` This prevents Roles from being created accidentally. Once Roles are mapped to the portal, you can use methods as defined in the portlet specification: - `getRemoteUser()` - `isUserInRole()` - `getUserPrincipal()` For example, you can use the following code to check if the current User has the `power-user` Role: ```java if (renderRequest.isUserInRole("power-user")) { // ... } ``` By default, Liferay doesn't use the `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. ## Related Topics [Liferay Permissions](/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions) [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) [Portlets](/docs/7-2/frameworks/-/knowledge_base/f/portlets) [Understanding ServiceContext](/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext) ================================================ FILE: en/developer/frameworks/articles/application-security/04-authentication-pipelines/01-intro.markdown ================================================ --- header-id: authentication-pipelines --- # Authentication Pipelines [TOC levels=1-4] 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 @product@ supports out of the box. Here's how authentication works under most circumstances: 1. Users provide their credentials to the Login Portlet to begin an authenticated session in a browser. 2. Alternatively, credentials are provided to @product@'s API endpoints, where they are sent in an HTTP BASIC Auth header. 3. Alternatively, credentials can be provided by another system. These are managed by `AutoLogin` components. 4. 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 *Authentication Pipeline*. You can add `Authenticator`s to the pipeline to support any system. 5. 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. This structure lets you support an authentication mechanism and/or accept credentials from a system that @product@ 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: - **Auto Login:** the easiest of the three, this enables authentication to @product@ using credentials provided in the HTTP header from another system. - **Authentication Pipelines:** if you must check credentials against other systems instead of or in addition to @product@'s database, you can create a pipeline. - **Custom Login Portlet:** if you want to change the user's sign-in experience completely, you can implement your own Login portlet. Read on to discover how to customize your users' sign-in experience. ================================================ FILE: en/developer/frameworks/articles/application-security/04-authentication-pipelines/02-auto-login.markdown ================================================ --- header-id: auto-login --- # Auto Login [TOC levels=1-4] While @product@ supports a wide variety of [authentication mechanisms](/docs/7-2/deploy/-/knowledge_base/d/securing-product), 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. ## Creating an Auto Login Component Create a [Declarative Services component](/docs/7-2/reference/-/knowledge_base/r/creating-a-project). The component should implement the `com.liferay.portal.kernel.security.auto.login.AutoLogin` interface. Here's an example template: ```java 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 */ } } ``` As you can see, you have access to the `HttpServletRequest` and the `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 `login` method is where this all happens. This method must return a `String` array with three items in this order: - The user ID - The user password - A boolean flag that's `true` if the password is encrypted and `false` if it's not (`Boolean.TRUE.toString()` or `Boolean.FALSE.toString()`). Sending redirects is an optional `AutoLogin` feature. Since `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: - `AutoLogin.AUTO_LOGIN_REDIRECT`: This key causes `AutoLoginFilter` to stop the filter chain's execution and redirect immediately to the location specified in the attribute's value. - `AutoLogin.AUTO_LOGIN_REDIRECT_AND_CONTINUE`: This key causes `AutoLoginFilter` to set the redirect and continue executing the remaining filters in the chain. Auto Login components are useful ways of providing an authentication mechanism to a system that @product@ doesn't yet support. You can write them fairly quickly to provide the integration you need. ## Related Topics [Password-Based Authentication Pipelines](/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines) [Writing a Custom Login Portlet](/docs/7-2/frameworks/-/knowledge_base/f/writing-a-custom-login-portlet) ================================================ FILE: en/developer/frameworks/articles/application-security/04-authentication-pipelines/03-authentication-pipelines.markdown ================================================ --- header-id: password-based-authentication-pipelines --- # Password-Based Authentication Pipelines [TOC levels=1-4] By default, once a user submits credentials, those credentials are checked against @product@'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 `Authenticator` and insert it as a step in the authentication pipeline. Because the `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 [Auto Login](/docs/7-2/frameworks/-/knowledge_base/f/auto-login) or an [Auth Verifier](/docs/7-2/deploy/-/knowledge_base/d/authentication-verifiers). `Authenticator`s let you do these things: - Log into @product@ with a user name and password maintained in an external system - Make secondary user authentication checks - Perform additional processing when user authentication fails Read on to learn how to create an `Authenticator`. ## Anatomy of an Authenticator `Authenticator`s are implemented for various steps in the authentication pipeline. Here are the steps: 1. `auth.pipeline.pre`: Comes before default authentication to the database. In this step, you can skip credential validation against the database. Implemented by `Authenticator`. 2. Default (optional) authentication to the database. 3. `auth.pipeline.post`: Further (secondary, tertiary) authentication checks. Implemented by `Authenticator`. 4. `auth.failure`: Perform additional processing after authentication fails. Implemented by `AuthFailure`. To create an `Authenticator`, create a module and add a component that implements the interface: ```java @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; } } ``` This example has been stripped down so you can see its structure. First, note the `@Component` annotation's contents: - `immediate = true`: sets the component to start immediately - `key=auth.pipeline.post`: sets the `Authenticator` to run in the `auth.pipeline.post` phase. To run the `auth.pipeline.pre` phase, substitute `auth.pipeline.pre`. - `service = Authenticator.class`: implements the `Authenticator` service. All `Authenticator`s must do this. 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 `AuthException` in case the `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. ## Creating an Authenticator This example is an `Authenticator` that only allows users whose email addresses end with *@liferay.com* or *@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 `Authenticator` and one to validate email addresses. This example shows the two module approach. To create an `Authenticator`, create a module for your implementation. The most appropriate Blade template for this is the [service template](/docs/7-2/reference/-/knowledge_base/r/using-the-service-template). Once you have the module, creating the `Activator` is straightforward: 1. Add the `@Component` annotation to bind your `Activator` to the appropriate authentication pipeline phase. 2. Implement the `Authenticator` interface and provide the functionality you need. 3. Deploy your module. If you're using [Blade CLI](/docs/7-2/reference/-/knowledge_base/r/blade-cli), do this via `blade deploy`. For this example, you'll do this twice: once for the email address validator module and once for the `Authenticator` itself. The `Authenticator` project contains the interface for the validator, and the validator project contains the implementation. Here's what the `Authenticator` module structure looks like: ![Figure 1: The Authenticator module contains the validator's interface and the authenticator.](../../../images/auth-pipeline-authenticator-project.png) Since the `Authenticator` is the most relevant, examine it first: ```java 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); } ``` This time, rather than stubs, the three authentication methods contain functionality. The `authenticateByEmailAddress` method directly checks the email address provided by the Login Portlet. The other two methods, `authenticateByScreenName` and `authenticateByUserId` call `UserLocalService` to look up the user's email address before checking it. The OSGi container injects this service because of the `@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 `AuthException` and logging the error. Why would you want to do it this way? To err gracefully. Because this is an `auth.pipeline.post` `Authenticator`, you presumably have other `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: ```java package com.liferay.docs.emailaddressauthenticator.validator; import aQute.bnd.annotation.ProviderType; @ProviderType public interface EmailAddressValidator { public boolean isValidEmailAddress(String emailAddress); } ``` This defines a single method for checking the email address. Next, you'll address the validator module. ![Figure 2: The validator project implements the Validator Interface and depends on the authenticator module. ](../../../images/auth-pipeline-validator-project.png) This module contains only one class. It implements the Validator interface: ```java 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"})); } ``` This code checks to make sure that the email address is from the *@liferay.com* or *@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 `settings.gradle` and a `build.gradle`. The `settings.gradle` file defines the location of the project (the `Authenticator`) the validator depends on: ```groovy include ':emailAddressAuthenticator' project(':emailAddressAuthenticator').projectDir = new File(settingsDir, '../com.liferay.docs.emailAddressAuthenticator') ``` Since this project contains the interface, it must be on the classpath at compile time, which is when `build.gradle` is running: ```groovy 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" } } ``` Note the line in the dependencies section that refers to the `Authenticator` project defined in `settings.gradle`. When these projects are deployed, the `Authenticator` you defined runs, enforcing logins for the two domains specified in the validator. ## Related Topics [Auto Login](/docs/7-2/frameworks/-/knowledge_base/f/auto-login) [Writing a Custom Login Portlet](/docs/7-2/frameworks/-/knowledge_base/f/writing-a-custom-login-portlet) ================================================ FILE: en/developer/frameworks/articles/application-security/04-authentication-pipelines/04-custom-login-portlet.markdown ================================================ --- header-id: writing-a-custom-login-portlet --- # Writing a Custom Login Portlet [TOC levels=1-4] 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 [portlets](/docs/7-2/frameworks/-/knowledge_base/f/portlets). This tutorial shows only the relevant parts of a [Liferay MVC Portlet](/docs/7-2/appdev/-/knowledge_base/a/liferay-mvc-portlet) that authenticates the user. You'll learn how to call the [authentication pipeline](/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines) and then redirect the user to a location of your choice. ## Authenticating to @product@ | **Note:** When developing a login portlet, set the session timeout portal | property like this: | | session.timeout.auto.extend.offset=45 | | This is needed because the default (as of | [LPS-68543](https://issues.liferay.com/browse/LPS-68543)) setting is `0`, | causing the browser to execute an `extend_session` call. This may force users | attempting to log in to make the attempt twice. It has only one view, which is used for logging in or showing the user who is already logged in: ```markup <%@ 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"); %> ``` 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): ```markup @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()); } } ``` The only tricky/unusual code here is the need to grab the `HttpServletRequest` and the `HttpServletResponse`. This is necessary to call @product@'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. ## Related Topics [Password-Based Authentication Pipelines](/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines) [Auto Login](/docs/7-2/frameworks/-/knowledge_base/f/auto-login) ================================================ FILE: en/developer/frameworks/articles/application-security/06-service-access-policies.markdown ================================================ --- header-id: service-access-policies --- # Service Access Policies [TOC levels=1-4] Service access policies provide web service security beyond user authentication to remote services. Together with [permissions](/docs/7-2/frameworks/-/knowledge_base/f/defining-application-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 @product@ 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. ## 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. ![Figure 1: The authorization module maps the credentials or token to the proper Service Access Policy.](../../images/service-access-policies-arch.png) Service Access policies are created in the Control Panel by administrators. If you want to start creating policies yourself, see [this article on service access policies](/docs/7-2/deploy/-/knowledge_base/d/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: - It uses [custom remote API authentication](/docs/7-2/frameworks/-/knowledge_base/f/auto-login) (tokens) and require certain services to be available for clients using the tokens. - It requires its services be made available to guest users, with no authentication necessary. - It contains a [remote service authorization layer](/docs/7-2/frameworks/-/knowledge_base/f/password-based-authentication-pipelines) that needs to drive access to remote services based on granted privileges. ## API Overview Liferay provides an Interface and a `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 [Liferay Sync](/docs/7-2/user/-/knowledge_base/u/administering-liferay-sync). 1. The Interface and `ThreadLocal` are available in the [package `com.liferay.portal.kernel.security.service.access.policy`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/package-summary.html). This package provides classes for basic access to policies. For example, you can use the [singleton `ServiceAccessPolicyManagerUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/ServiceAccessPolicyManagerUtil.html) to obtain Service Access Policies configured in the system. You can also use the [`ServiceAccessPolicyThreadLocal` class](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/service/access/policy/ServiceAccessPolicyThreadLocal.html) 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 [`AuthVerifier` implementation](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/security/auth/verifier/AuthVerifier.html). For example, in the case of the OAuth app, an `AuthVerifier` implementation assigns the policy chosen by the user in the authorization step. 2. The API ships with the product as OSGi modules: - `com.liferay.portal.security.service.access.policy.api.jar` - `com.liferay.portal.security.service.access.policy.service.jar` - `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. 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 @product@'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 - can use `com.liferay.portal.security.service.access.policy.api.jar` and `com.liferay.portal.security.service.access.policy.service.jar` to create policies programmatically. - should use the method `ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName()` to grant the associated policy during a web service request. - can use `ServiceAccessPolicyManagerUtil` to display list of supported policies when authorizing the remote application, to associate the token with an existing policy. ## Service Access Policy Example [Liferay Sync's](https://www.liferay.com/supporting-products/liferay-sync) `sync-security` module is a service access policy module. It uses `com.liferay.portal.security.service.access.policy.service` to create the `SYNC_DEFAULT` and `SYNC_TOKEN` policies programmatically. For service calls to Sync's remote API, these policies grant access to Sync's `com.liferay.sync.service.SyncDLObjectService#getSyncContext` and `com.liferay.sync.service.*`, respectively. Here's the code in the `sync-security` module that defines and creates these policies: ```java @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()); } } ... } ``` 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 *Control Panel* → *Configuration* → *Service Access Policy*. The `sync-security` module must then grant the appropriate policy when needed. Since every authenticated call to Liferay Sync's remote API requires access to `com.liferay.sync.service.*`, the module must grant the `SYNC_TOKEN` policy to such calls. The module does this with the method `ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName`, as shown in this code snippet: ```java if ((permissionChecker != null) && permissionChecker.isSignedIn()) { ServiceAccessPolicyThreadLocal.addActiveServiceAccessPolicyName( String.valueOf( SyncSAPEntryActivator.SAP_ENTRY_OBJECT_ARRAYS[1][0])); } ``` Now every authenticated call to Sync's remote API, regardless of authentication method, has access to `com.liferay.sync.service.*`. To see the full code example, [click here](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). Nice! Now you know how to integrate your apps with the Service Access Policies. ================================================ FILE: en/developer/frameworks/articles/assets/01-asset-framework-intro.markdown ================================================ --- header-id: asset-framework --- # Asset Framework [TOC levels=1-4] 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 *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 [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/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: - Extensively render your assets. - Associate tags to custom content types. Users can create and assign new tags or use existing tags. - Associate categories to custom content types. - Manage tags from the Control Panel. Administrators can even merge tags. - Manage categories from the Control Panel. This includes the ability to create category hierarchies. - Relate assets to one another. There are several steps to creating an asset and taking full advantage of the asset framework. ## 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 [Liferay's permissions framework](/docs/7-2/frameworks/-/knowledge_base/f/defining-application-permissions) about a new resource. All you have to do is invoke a method of the asset framework that associates an `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. ## 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: - An edit feature for modifying an asset. - View an asset in its original context (e.g., a blog in the Blogs application; a post in the Message Boards application). - Embed images, videos, and audio. - Restrict access to users who do not have permissions to interact with the asset. - Allow users to comment on the asset. You can dictate your asset's rendering capabilities by providing the *Asset Renderer* framework. There are two prerequisites for asset enabling an application: 1. The application must store asset data. Applications that store a data model meet this requirement. 2. The application must contain at least one non-instanceable portlet. `Edit` links for the asset cannot be generated without a non-instanceable portlet. 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: 1. Create an asset renderer for your custom asset. 2. Create an asset renderer factory to create an instance of the asset renderer for each asset entity. ## Asset Features Once you have done the necessary work to persist your assets and render them, you can enable Tags, Categories, and Related Assets. ### Tags and Categories Tags and Categories are two ways that you can organize and connect assets. Tags are simple *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. ![Figure 1: Adding category and tag input options lets authors aggregate and label custom entities.](../../images/asset-fw-categories-and-tags-options.png) ### 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. ![Figure 2: You and your users can find it helpful to relate assets to entities, such as this blogs entry.](../../images/asset-related-content-asset-publisher.png) ## Implementing Asset Priority The [Asset Publisher](/docs/7-2/user/-/knowledge_base/u/publishing-assets) 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. ![Figure 3: The Priority field lets users set an asset's priority.](../../images/web-content-categorization.png) Ready to implement assets? The rest of the tutorials show you how. ================================================ FILE: en/developer/frameworks/articles/assets/02-adding-updating-and-deleting-assets.markdown ================================================ --- header-id: adding-updating-and-deleting-assets --- # Adding, Updating, and Deleting Assets [TOC levels=1-4] This section shows you how to enable assets for your custom entities and implement indexes for them. It's time to get started! ## Preparing Your Project for the Asset Framework In your project's `service.xml` file, add an asset entry entity reference for your custom entity. Add the following `reference` tag before your custom entity's closing `` tag. ```xml ``` Then [run Service Builder.](/docs/7-2/appdev/-/knowledge_base/a/service-builder) Now you're ready to implement adding and updating assets! ## Adding and Updating Assets Your `-LocalServiceImpl` Java class inherits from its parent base class an `AssetEntryLocalService` instance; it's assigned to the variable `assetEntryLocalService`. To add your custom entity as a Liferay asset, you must invoke the `assetEntryLocalService`'s `updateEntry` method. Here's what the [`updateEntry`](@platform-ref@/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-) method's signature looks like: ```java 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 ``` Here are descriptions of each of the `updateEntry` method's parameters: `userId`: identifies the user updating the content. `groupId`: identifies the scope of the created content. If your content doesn't support scopes (extremely rare), pass `0` as the value. `createDate`: the date the entity was created. `modifiedDate`: the date of this change to the entity. `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 `[YourClassName].class.getName()`. `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. `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. `classTypeId`: identifies the particular variation of this class, if it has any variations. Otherwise, use `0`. `categoryIds`: represent the categories selected for the entity. The asset framework stores them for you. `tagNames`: represent the tags selected for the entity. The asset framework stores them for you. `listable`: specifies whether the entity can be shown in dynamic lists of content (such as asset publisher configured dynamically). `visible`: specifies whether the entity is approved. `startDate`: the entity's publish date. You can use it to specify when an Asset Publisher should show the entity's content. `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. `publishDate`: the date the entity will start to be shown. `expirationDate`: the date the entity will no longer be shown. `mimetype`: the Multi-Purpose Internet Mail Extensions type, such as [ContentTypes.TEXT_HTML](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html#TEXT_HTML), used for the content. `title`: the entity's name. `description`: a `String`-based textual description of the entity. `summary`: a shortened or truncated sample of the entity's content. `url`: a URL to optionally associate with the entity. `layoutUuid`: the universally unique ID of the layout of the entry's default display page. `height`: this can be set to `0`. `width`: this can be set to `0`. `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 [`WikiPageLocalServiceImpl`](https://github.com/liferay/liferay-portal/blob/master/modules/apps/wiki/wiki-service/src/main/java/com/liferay/wiki/service/impl/WikiPageLocalServiceImpl.java) Java class demonstrates invoking the `updateEntry` method on the wiki page entity called `WikiPage`. In your `add-` method, you could invoke `updateEntry` after adding your entity's resources. Likewise, in your `update-` method, you could invoke `updateEntry` after calling the `super.update-` method. The code below is called in the `WikiPageLocalServiceImpl` class's `updateStatus(...)` method. ```java 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); ``` Immediately after invoking the `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. | **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(); Next, you'll learn what's needed to delete an entity that's associated with an asset. ## 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 `-LocalServiceImpl` Java class, open your `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. ```java assetEntryLocalService.deleteEntry( ENTITY.class.getName(), assetEntry.getEntityId()); Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer(ENTITY.class); indexer.delete(assetEntry); ``` In your `-LocalServiceImpl` class, you can write similar code. Replace the *ENTITY* class name and variable with your entity's name. | **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! ![Figure 1: It can be useful to show custom entities, like this wiki page entity, in a JSP or in an Asset Publisher.](../../images/basic-asset-in-asset-publisher.png) Great! Now you know how to add, update, and delete assets in your apps! ================================================ FILE: en/developer/frameworks/articles/assets/03-rendering-an-asset/01-creating-asset-renderer-intro.markdown ================================================ --- header-id: creating-an-asset-renderer --- # Creating an Asset Renderer [TOC levels=1-4] In this tutorial, you'll learn how to create an `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 [`BlogsEntryAssetRenderer`](@app-ref@/collaboration/latest/javadocs/com/liferay/blogs/web/asset/BlogsEntryAssetRenderer.html) class, which configures the asset renderer framework for the Blogs application. 1. Create a new package in your existing project for your asset-related classes. For instance, the `BlogsEntryAssetRenderer` class resides in the `com.liferay.blogs.web` module's `com.liferay.blogs.web.asset` package. 2. Create your `-AssetEntry` class for your application in the new `-.asset` package and have it implement the [`AssetEntry`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/asset/kernel/model/AssetEntry.html) interface. Consider the `BlogsEntryAssetRenderer` class as an example: ```java public class BlogsEntryAssetRenderer extends BaseJSPAssetRenderer implements TrashRenderer { ``` The `BlogsEntryAssetRenderer` class extends the [`BaseJSPAssetRenderer`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/asset/kernel/model/BaseJSPAssetRenderer.html), which is an extension class intended for those who plan on using JSP templates to generate their asset's HTML. The `BaseJSPAssetRenderer` class implements the `AssetRenderer` interface. You'll notice the asset renderer also implements the [`TrashRenderer`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/trash/TrashRenderer.html) interface. This is a common practice for many applications, so they can use @product@'s Recycle Bin. 3. Define the asset renderer class's constructor, which typically sets the asset object to use in the asset renderer class. ```java public BlogsEntryAssetRenderer( BlogsEntry entry, ResourceBundleLoader resourceBundleLoader) { _entry = entry; _resourceBundleLoader = resourceBundleLoader; } ``` The `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 [Overriding Language Keys](/docs/7-2/customization/-/knowledge_base/c/overriding-language-keys) tutorial. Also, make sure to define the `_entry` and `_resourceBundleLoader` fields in the class: ```java private final BlogsEntry _entry; private final ResourceBundleLoader _resourceBundleLoader; ``` 4. 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: ```java @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(); } ``` The `getAssetObject()` method sets the `BlogsEntry` that was set in the constructor as your asset to track. Likewise, the `getType()` method references the blogs asset renderer factory for the type of asset your asset renderer renders. Of course, the asset renderer type is `blog`, which you'll set in the factory later. 5. 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. ```java @Override public String getPortletId() { AssetRendererFactory assetRendererFactory = getAssetRendererFactory(); return assetRendererFactory.getPortletId(); } ``` The `getPortletId()` method instantiates an asset renderer factory for a `BlogsEntry` and retrieves the portlet ID for the portlet used to display blogs entries. 6. If you want to enable workflow for your asset, add the following method similar to what was done for the Blogs application: ```java @Override public int getStatus() { return _entry.getStatus(); } ``` This method retrieves the workflow status for the asset. 7. Another feature many developers want for their asset is comments. This is enabled for the Blogs application with the following method: ```java @Override public String getDiscussionPath() { if (PropsValues.BLOGS_ENTRY_COMMENTS_ENABLED) { return "edit_entry_discussion"; } else { return null; } } ``` 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 *Options* (![Options](../../../images/icon-options.png)) → *Configuration* → *Setup* → *Display Settings* section. 8. At a minimum, you should create a title and summary for your asset. Here's how the `BlogsEntryAssetRenderer` does it: ```java @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); } ``` These two methods return information about your asset, so the asset publisher can display it. The title and summary can be anything. The `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 `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. 9. 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: ```java @Override public String getUrlTitle() { return _entry.getUrlTitle(); } ``` 10. Insert the `isPrintable()` method, which enables the Asset Publisher's printing capability for your asset. ```java @Override public boolean isPrintable() { return true; } ``` 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 *Options* → *Configuration* → *Setup* → *Display Settings* section. ![Figure 1: Enable printing in the Asset Publisher to display the Print icon for your asset.](../../../images/asset-publisher-printing.png) 11. 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 `BlogsEntryAssetRenderer` class: ```java @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); } ``` Before you can check if a user has permission to view your asset, you must use the `getUserId()` and `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. Awesome! You've learned how to set up the blogs asset renderer to - connect to an asset - connect to the asset's portlet - use workflow management - use a comments section - retrieve the asset's title and summary - generate the asset's unique URL - display a print icon - check permissions for the asset Now you need to create the templates to render the HTML. The `BlogsEntryAssetRenderer` is configured to use JSP templates to generate HTML for the Asset Publisher. You'll learn more about how to do this next. ================================================ FILE: en/developer/frameworks/articles/assets/03-rendering-an-asset/02-jsp-templates-asset-renderer.markdown ================================================ --- header-id: configuring-jsp-templates-for-an-asset-renderer --- # Configuring JSP Templates for an Asset Renderer [TOC levels=1-4] An asset can be displayed in several different ways in the Asset Publisher. There are three templates to implement provided by the `AssetRenderer` interface: - `abstract` - `full_content` - `preview` Besides these supported templates, you can also create JSPs for buttons for direct access and manipulation of the asset. For example, - Edit - View - View in Context The `BlogsEntryAssetRenderer` customizes the `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. 1. Add the `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 `BlogsEntryAssetRenderer` uses this method: ```java @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; } } ``` Blogs assets provide `abstract.jsp` and `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 `com.liferay.blogs.web` module's `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 `AssetRenderer` interface, `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. 2. Now that you've added the path to your JSP, you must include that JSP. Since the `BlogsEntryAssetRenderer` class extends the `BaseJSPAssetRenderer`, it already has an `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: ```java @Override public boolean include( HttpServletRequest request, HttpServletResponse response, String template) throws Exception { request.setAttribute(WebKeys.BLOGS_ENTRY, _entry); return super.include(request, response, template); } ``` 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 `include(...)` method, however, follows the best practice for MVC portlets. ![Figure 1: The abstract and full content views are rendered differently for blogs.](../../../images/blogs-asset-views.png) 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. 1. Blogs assets provide an Edit button for editing the asset. Provide this by adding the following method to the `BlogsEntryAssetRenderer` class: ```java @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; } ``` The Asset Publisher loads the blogs asset using the Blogs application. Then the `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? 2. You can specify how to view your asset by providing methods similar to the methods outlined below in the `BlogsEntryAssetRenderer` class: ```java @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()); } ``` The `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 `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 *Options* → *Configuration* → *Setup* → *Display Settings* section and choosing between *Show Full Content* and *View in Context* for the Asset Link Behavior drop-down menu. The Blogs application provides `abstract` and `full_content` JSP templates that override the ones provided by the `AssetRenderer` interface. The third template, `preview`, could also be customized. You can view the default `preview.jsp` template rendered in the *Add* → *Content* menu. ![Figure 2: The `preview` template displays a preview of the asset in the Content section of the Add menu.](../../../images/preview-template-asset-renderer.png) You've learned all about implementing the `AssetRenderer`'s provided templates and customizing them to fit your needs. Next, you'll put your asset renderer into action by creating a factory. ================================================ FILE: en/developer/frameworks/articles/assets/03-rendering-an-asset/03-creating-factory-asset-renderer.markdown ================================================ --- header-id: creating-a-factory-for-the-asset-renderer --- # Creating a Factory for the Asset Renderer [TOC levels=1-4] 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 `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. 1. Create an `-AssetRenderFactory` class in the same folder as its asset renderer class. For blogs, the [`BlogsEntryAssetRendererFactory`](@app-ref@/collaboration/latest/javadocs/com/liferay/blogs/web/asset/BlogsEntryAssetRendererFactory.html) class resides in the `com.liferay.blogs.web` module's `com.liferay.blogs.web.asset` package. The factory class should extend the `BaseAssetRendererFactory` class and the asset type should be specified as its parameter. You can see how this was done in the `BlogsEntryAssetRendererFactory` class below ```java public class BlogsEntryAssetRendererFactory extends BaseAssetRendererFactory { ``` 2. Create an `@Component` annotation section above the class declaration. This annotation is responsible for registering the factory instance for the asset. ```java @Component( immediate = true, property = {"javax.portlet.name=" + BlogsPortletKeys.BLOGS}, service = AssetRendererFactory.class ) public class BlogsEntryAssetRendererFactory extends BaseAssetRendererFactory { ``` There are a few annotation elements you should set: - The `immediate` element directs the factory to start in @product@ when its module starts. - The `property` element sets the portlet that is associated with the asset. The Blogs portlet is specified, since this is the Blogs asset renderer factory. - The `service` element should point to the `AssetRendererFactory.class` interface. | **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. 3. Create a constructor for the factory class that presets private attributes of the factory. ```java public BlogsEntryAssetRendererFactory() { setClassName(BlogsEntry.class.getName()); setLinkable(true); setPortletId(BlogsPortletKeys.BLOGS); setSearchable(true); } ``` `linkable`: other assets can select blogs assets as their related assets. `searchable`: blogs can be found when searching for assets. Setting the class name and portlet ID links the asset renderer factory to the entity. 4. Create the asset renderer for your asset. This is done by calling its constructor. ```java @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; } ``` 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 `getAssetRenderer(...)` method you must create. You'll set those variables and learn what they're doing next. a. 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: ```java @Reference(unbind = "-") protected void setBlogsEntryLocalService( BlogsEntryLocalService blogsEntryLocalService) { _blogsEntryLocalService = blogsEntryLocalService; } private BlogsEntryLocalService _blogsEntryLocalService; ``` The setter method is annotated with the `@Reference` tag. b. You must specify the resource bundle loader since it was specified in the `BlogsEntryAssetRenderer`'s constructor: ```java @Reference( target = "(bundle.symbolic.name=com.liferay.blogs.web)", unbind = "-" ) public void setResourceBundleLoader( ResourceBundleLoader resourceBundleLoader) { _resourceBundleLoader = resourceBundleLoader; } private ResourceBundleLoader _resourceBundleLoader; ``` Make sure the `osgi.web.symbolicname` in the `target` property of the `@Reference` annotation is set to the same value as the `Bundle-SymbolicName` defined in the `bnd.bnd` file of the module the factory resides in. c. The asset renderer `type` integer is set for the asset renderer, but why an integer? @product@ needs to differentiate when it should display the latest *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 - `0` for the latest version of the asset - `1` for the latest approved version of the asset d. 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: ```java @Reference( target = "(osgi.web.symbolicname=com.liferay.blogs.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { _servletContext = servletContext; } private ServletContext _servletContext; ``` 5. 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: ```java public static final String TYPE = "blog"; @Override public String getType() { return TYPE; } @Override public String getClassName() { return BlogsEntry.class.getName(); } ``` 6. Set the Lexicon icon for the asset: ```java @Override public String getIconCssClass() { return "blogs"; } ``` You can find a list of all available Lexicon icons [here](https://liferay.design/lexicon/core-components/icons/). 7. Add methods that generate URLs to add and view the asset. ```java @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; } ``` If you're paying close attention, you may have noticed the `getURLView(...)` method was also implemented in the `BlogsEntryAssetRenderer` class. The asset renderer's `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). 11. Set the global permissions for all blogs assets: ```java @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); } ``` 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! ================================================ FILE: en/developer/frameworks/articles/assets/04-entering-and-displaying-tags-and-categories.markdown ================================================ --- header-id: implementing-asset-categorization-and-tagging --- # Implementing Asset Categorization and Tagging [TOC levels=1-4] Now it's time to get started with Tags and Categories. ## 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 [edit_entry.jsp](https://github.com/liferay/liferay-portal/blob/master/modules/apps/blogs/blogs-web/src/main/resources/META-INF/resources/blogs/edit_entry.jsp) for the Blogs portlet: ```markup ... ... ... ``` The `liferay-asset:asset-categories-selector` and `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 `liferay-ui:asset-categories-error` and `liferay-ui:asset-tags-error` tags show messages for errors occurring during the asset category or tag input process. The `aui:fieldset` tag uses a container that lets users hide or show the category and tag input options. For styling purposes, the `aui:fieldset-group` tag is given the `lexicon` markup view. ## 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: ```markup
    ... ``` The `portletURL` parameter is used for both tags and categories. Each tag that uses this parameter becomes a link containing the `portletURL` *and* `tag` or `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 `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. ================================================ FILE: en/developer/frameworks/articles/assets/05-relating-assets.markdown ================================================ --- header-id: relating-assets --- # Relating Assets [TOC levels=1-4] After you complete [Adding, Updating, and Deleting Assets](/docs/frameworks/7-2/-/knowledge_base/frameworks/adding-updating-and-deleting-assets) for your application you can go ahead and begin relating your assets! ## 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. 1. In your portlet's `service.xml`, put the following line of code below any finder method elements and then run Service Builder: ```xml ``` 2. Modify the `add-`, `delete-`, and `update-` methods in your `-LocalServiceImpl` to persist the asset relationships. You'll use your `-LocalServiceImpl`'s `assetLinkLocalService` instance variable to execute persistence actions. For example, consider the Wiki application. When you update wiki assets and statuses, both methods utilize the `updateLinks` via your instance variable `assetLinkLocalService`. Here's the `updateLinks` invocation in the Wiki application's `WikiPageLocalServiceImpl.updateStatus(...)` method: ```java assetLinkLocalService.updateLinks( userId, assetEntry.getEntryId(), assetLinkEntryIds, AssetLinkConstants.TYPE_RELATED); ``` To call the `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 `AssetEntry` variable (e.g., one called `assetEntry`) the value returned from invoking `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 `com.liferay.portlet.asset.model.AssetLinkConstants`. 3. In your `-LocalServiceImpl` class' `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: ```java AssetEntry assetEntry = assetEntryLocalService.fetchEntry( ENTITY.class.getName(), ENTITYId); assetLinkLocalService.deleteLinks(assetEntry.getEntryId()); ``` Make sure to replace the *ENTITY* place holders for your custom `-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. ## 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 `liferay-ui:input-asset-links` inside a collapsible panel. This code is placed inside the `aui:fieldset` tags of the JSP. 1. Add the `liferay-asset:input-asset-links` tag to your form. Here's how it's added in the Blogs application: ```markup ``` The following screenshot shows the Related Assets menu for an application. Note that it is contained in a collapsible panel titled Related Assets. ![Figure 1: Your portlet's entity is now available in the Related Assets *Select* menu.](../../images/related-assets-select-menu.png) 2. 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 `docroot/WEB-INF/src/content/Language.properties` in your portlet. You can refer to the [Overriding Language Keys](/docs/frameworks/7-2/-/knowledge_base/frameworks/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 `Language.properties` file shows in the Related Assets menu. 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! ## 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. 1. You must get the `AssetEntry` object associated with your entity: ```markup <% long insultId = ParamUtil.getLong(renderRequest, "insultId"); Insult ins = InsultLocalServiceUtil.getInsult(insultId); AssetEntry assetEntry = AssetEntryLocalServiceUtil.getEntry(Insult.class.getName(), ins.getInsultId()); %> ``` 2. Use the `liferay-asset:asset-links` tag to show the entity's related assets. For this tag, you retrieve the `assetEntryId` from the `assetEntry` object, retrieve your asset's `className`, and get the entity's primary key (`classPK`) from the specific `entry`. The tag then retrieves any other assets linked to your asset. ```markup ``` 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. ================================================ FILE: en/developer/frameworks/articles/assets/06-implementing-asset-priority.markdown ================================================ --- header-id: implementing-asset-priority --- # Implementing Asset Priority [TOC levels=1-4] This asset priority field isn't enabled when you create an asset. You must manually add support for it. You'll learn how below. ## 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: ```markup [0] ``` 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. ## 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 `ServiceContext` variable `serviceContext`. Retrieve it with `serviceContext.getAssetPriority()`, and then pass it as the last argument to the `assetEntryLocalService.updateEntry` call in your `-LocalServiceImpl`. You can see an example of this in [the `BlogsEntryLocalServiceImpl` class](https://github.com/liferay/liferay-portal/blob/master/modules/apps/blogs/blogs-service/src/main/java/com/liferay/blogs/service/impl/BlogsEntryLocalServiceImpl.java) of @product@'s Blogs app. The `updateAsset` method takes a `priority` argument, which it passes as the last argument to its `assetEntryLocalService.updateEntry` call: ```java @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); ... } ``` The `BlogsEntryLocalServiceImpl` class calls this `updateAsset` method when adding or updating a blog entry. Note that `serviceContext.getAssetPriority()` retrieves the priority: ```java updateAsset( userId, entry, serviceContext.getAssetCategoryIds(), serviceContext.getAssetTagNames(), serviceContext.getAssetLinkEntryIds(), serviceContext.getAssetPriority()); ``` Sweet! Now you know how to enable priorities for your app's assets. ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/01-intro.markdown ================================================ --- header-id: back-end-frameworks --- # Back-end Frameworks [TOC levels=1-4] Liferay's powerful back-end frameworks provide essential services behind the scenes. Here are some of the frameworks: - [Portlet Providers](#portlet-providers) - [Data Scopes](#data-scopes) - [Message Bus](#message-bus) You can use these frameworks to provide important functionality to your applications. ## 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. ### Portlet Provider Classes Portlet Provider classes are components that implement the [`PortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.html) interface, and are associated with an entity type. Once you've registered a Portlet Provider, you can invoke the [`PortletProviderUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html) class to retrieve the portlet ID or portlet URL from that Portlet Provider. As an example, examine the [`WikiEditPortletProvider`](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) class: ```java @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; } } ``` `WikiEditPortletProvider` extends [`BasePortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html), inheriting its `getPortletURL` methods. `WikiEditPortletProvider` must, however, implement `PortletProvider`'s `getPortletName` method, which returns the portlet's name `WikiPortletKeys.WIKI`. | **Note:** If you're creating a Portlet Provider for one of Liferay's portlets, | your `getPortletName` method should return the portlet name from that | portlet's `*PortletKeys` class, if such a class exists. The `@Component` annotation for `WikiEditPortletProvider` specifies these elements and properties: - `immediate = true` activates the component immediately upon installation. - `"model.class.name=com.liferay.wiki.model.WikiPage"` specifies the entity type the portlet operates on. - `"service.ranking:Integer=100"` sets the component's rank to `100`, prioritizing it above all Portlet Providers that specify the same `model.class.name` value but have a lower rank. - `service = EditPortletProvider.class` reflects the subinterface `PortletProvider` class this class implements ([`EditPortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/EditPortletProvider.html)). For step-by-step instructions on creating a Portlet Provider class, see [Creating Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/creating-portlet-providers). For instructions on using Portlet Providers to retrieve a portlet, see [Retrieving Portlets](/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets). ## Data Scopes Apps can restrict their data to specific *scopes*. Scopes provide a context for the application's data. **Global:** One data set throughout a portal instance. **Site:** One data set for each Site. **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 [Widget Scope](/docs/7-2/user/-/knowledge_base/u/widget-scope). To give your applications scope, you must manually add support for it. For instructions on this, see [Enabling and Accessing Data Scopes](/docs/7-2/frameworks/-/knowledge_base/f/enabling-and-accessing-data-scopes). ### 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. [Structures and Templates](/docs/7-2/user/-/knowledge_base/u/designing-uniform-content) 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 [`ThemeDisplay`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html) method `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 [Enabling and Accessing Data Scopes](/docs/7-2/frameworks/-/knowledge_base/f/enabling-and-accessing-data-scopes). ## 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. @product@ uses Message Bus in many places: - Auditing - Search engine integration - Email subscriptions - Monitoring - Document Library processing - Background tasks - Cluster-wide request execution - Clustered cache replication You can use it too! Here are some of Message Bus's most important features: - publish/subscribe messaging - request queuing and throttling - flow control - multi-thread message processing There are also tools, such as the Java SE's JConsole, that can monitor Message Bus activities. ![Figure 1: JConsole shows statistics on Message Bus messages sent, messages pending, and more.](../../images/message-bus-jconsole.png) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/02-creating-portlet-providers.markdown ================================================ --- header-id: creating-portlet-providers --- # Creating Portlet Providers [TOC levels=1-4] Follow these steps to create your own [Portlet Provider](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#portlet-providers): 1. [Create an OSGi module](/docs/7-2/reference/-/knowledge_base/r/creating-a-project). 2. Create a `PortletProvider` class in your module. Use the recommended class naming convention: `[Entity] + [Action] + PortletProvider` For example, here's a Portlet Provider class for viewing a `LanguageEntry`: `LanguageEntryViewPortletProvider` 3. Extend [`BasePortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BasePortletProvider.html) if you want to use its `getPortletURL` method implementations. 4. Implement one or more [`PortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.html) subinterfaces that match your action(s): - [`AddPortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/AddPortletProvider.html) - [`BrowsePortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/BrowsePortletProvider.html) - [`EditPortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/EditPortletProvider.html) - [`ManagePortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/ManagePortletProvider.html) - [`PreviewPortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PreviewPortletProvider.html) - [`ViewPortletProvider`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/ViewPortletProvider.html) 3. Make the class an OSGi Component by adding an annotation like this one: ```java @Component( immediate = true, property = { "model.class.name=CLASS_NAME", "service.ranking:Integer=10" }, service = {INTERFACE_1.class, ...} ) ``` The `immediate = true` element specifies that the component should be activated immediately upon installation. Assign to the `model.class.name` property the fully qualified class name of the entity the portlet operates on. Here's an example `model.class.name` property for the `WikiPage` entity: "model.class.name=com.liferay.wiki.model.WikiPage" Assign the `service` element to the `PortletProvider` subinterface(s) you're implementing (e.g., `ViewPortletProvider.class`, `BrowsePortletProvider.class`, etc.). This example sets `service` to `EditPortletProvider.class`: service = EditPortletProvider.class 4. If you're overriding an existing Portlet Provider, rank your Portlet Provider higher by specifying a `service.ranking:Integer` property with a higher integer value: property = { ..., "service.ranking:Integer=10" } 5. Implement the provider methods you want. Be sure to implement the `PortletProvider` method `getPortletName`. If you didn't extend `BasePortletProvider`, implement `PortletProvider`'s `getPortletURL` methods too. 6. [Deploy your module](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project). 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 [Retrieving Portlets](/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets). ## Related Topics [Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#portlet-providers) [Retrieving Portlets](/docs/7-2/frameworks/-/knowledge_base/f/retrieving-portlets) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/03-retrieving-portlets.markdown ================================================ --- header-id: retrieving-portlets --- # Retrieving Portlets [TOC levels=1-4] When a [Portlet Provider](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#portlet-providers) exists for an entity, you can use the [`PortletProviderUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html) class to retrieve the ID or URL of the portlet that performs the entity action you want. The Portlet Provider framework's [`PortletProvider.Action`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProvider.Action.html) Enum defines these action types: - `ADD` - `BROWSE` - `EDIT` - `MANAGE` - `PREVIEW` - `VIEW` The action type and entity type are key parameters in fetching a portlet's ID or URL. ## 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 [`PortletProviderUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html) method `getPortletId`. For example, this call gets the ID of a portlet for viewing Recycle Bin entries: ```java String portletId = PortletProviderUtil.getPortletId( "com.liferay.portlet.trash.model.TrashEntry", PortletProvider.Action.VIEW); ``` The `com.liferay.portlet.trash.model.TrashEntry` entity specifies Recycle Bin entries, and `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 `liferay-asset:asset_display` tag library tag whose `asset_display/preview.jsp` shows an *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 [`asset_display/preview.jsp`](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): ```markup <% Map data = new HashMap(); String portletId = PortletProviderUtil.getPortletId(assetEntry.getClassName(), PortletProvider.Action.ADD); data.put("portlet-id", portletId); %> ``` This code invokes `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 `data` map: ```java data.put("portlet-id", portletId); ``` Then it passes the `data` map to a new *Add* button that adds the portlet to the page: ```markup ``` ## Fetching a Portlet URL To get the URL of the portlet that performs an action on an entity, call one of [`PortletProviderUtil`'s](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/portlet/PortletProviderUtil.html) `getPortletURL` methods. These methods return a `javax.portlet.PortletURL` based on an `HttpServletRequest` or `PortletRequest`. You can also specify a `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 `asset-publisher-web` module's [`configuration/asset_entries.jsp`](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) file uses `PortletProviderUtil`'s `getPortletURL` method (at the end of the code below) to generate a corresponding Asset Browser URL: ```java 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); ``` ## Related Topics [Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#portlet-providers) [Creating Portlet Providers](/docs/7-2/frameworks/-/knowledge_base/f/creating-portlet-providers) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/04-enabling-accessing-scopes.markdown ================================================ --- header-id: enabling-and-accessing-data-scopes --- # Enabling and Accessing Data Scopes [TOC levels=1-4] Apps can restrict their data to specific scopes (e.g., Global, Site, Page). Here, you'll learn how to - [Enable Scoping](#enabling-scoping) - [Access Your App's Scope](#accessing-your-apps-scope) - [Access the Site Scope](#accessing-the-site-scope) For more detailed information about scoping, see [Data Scopes](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#data-scopes). ## Enabling Scoping 1. Scope your app's entities. In your service layer, your entities must have a `companyId` attribute of type `long` to enable scoping by portal instance, and a `groupId` attribute of type `long` to enable scoping by Site. Using [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) is the simplest way to do this. For instructions on this, see [Service Builder Persistence](/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project) and [Business Logic with Service Builder](/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder). 2. To enable scoping in your app, set the property `"com.liferay.portlet.scopeable=true"` in your portlet class's `@Component` annotation. For example, the [Web Content Display Portlet's portlet class](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) sets this component property: ```java @Component( immediate = true, property = { ... "com.liferay.portlet.scopeable=true", ..., }, service = Portlet.class ) public class JournalContentPortlet extends MVCPortlet { ... } ``` ## Accessing Your App's 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: 1. Via the `scopeGroupId` variable injected in JSPs that use the `` tag. This variable contains your app's current scope. For example, the Liferay Bookmarks app's [`view.jsp`](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) uses its `scopeGroupId` to retrieve the bookmarks and total number of bookmarks in the current scope: ```markup ... total = BookmarksEntryServiceUtil.getGroupEntriesCount(scopeGroupId, groupEntriesUserId); bookmarksSearchContainer.setTotal(total); bookmarksSearchContainer.setResults(BookmarksEntryServiceUtil.getGroupEntries(scopeGroupId, groupEntriesUserId, bookmarksSearchContainer.getStart(), bookmarksSearchContainer.getEnd())); ... ``` 2. By calling the `getScopeGroupId()` method on the request's [`ThemeDisplay`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html). This method returns your app's current scope. For example, the Liferay Blogs app's [`EditEntryMVCActionCommand`](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) class does this in its `subscribe` and `unsubscribe` methods: ```java 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()); } ``` If you know your app always needs the portal instance ID, use `themeDisplay.getCompanyId()`. 3. By calling the `getScopeGroupId()` method on a [`ServiceContext`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/ServiceContext.html) object. See [Understanding Service Context](/docs/7-2/frameworks/-/knowledge_base/f/understanding-servicecontext) for an example and more details. If you know your app always needs the portal instance ID, use the `ServiceContext` object's `getCompanyId()` method. ## Accessing the Site Scope To access the Site scope regardless of your app's current scope, use the [`ThemeDisplay`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html) method `getSiteGroupId()`. For more information on this use case, see [Accessing the Site Scope Across Apps](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#accessing-the-site-scope-across-apps). For example, the Web Content app's [`edit_feed.jsp`](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) uses the `getSiteGroupId()` method to get the Site ID, which is required to retrieve Structures: ```java ddmStructure = DDMStructureLocalServiceUtil.fetchStructure(themeDisplay.getSiteGroupId(), PortalUtil.getClassNameId(JournalArticle.class), ddmStructureKey, true); ``` ## Related Topics [Data Scopes](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#data-scopes) [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) [Service Builder Project](/docs/7-2/appdev/-/knowledge_base/a/creating-a-service-builder-project) [Business Logic with Service Builder](/docs/7-2/appdev/-/knowledge_base/a/business-logic-with-service-builder) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/01-intro.markdown ================================================ --- header-id: using-the-message-bus --- # Using the Message Bus [TOC levels=1-4] Here, you'll learn how to use the [Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/back-end-frameworks#message-bus) to send and receive messages in the portal. The following topics are covered: - [Messaging Destinations](#messaging-destinations) - [Message Listeners](#message-listeners) - [Sending Messages](#sending-messages) ## 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. ### Destination Configuration Each destination has a name and type and can have several other attributes. The destination type determines these things: - Whether there's a message queue. - The kinds of threads involved with a destination. - The message delivery behavior to expect at the destination. Here are the primary destination types: **Parallel Destination** - Messages sent here are queued. - 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. **Serial Destination** - Messages sent here are queued. - Worker threads from a thread pool deliver the messages to each registered message listener, one worker thread per message. **Synchronous Destination** - Messages sent here are directly delivered to message listeners. - The thread sending the message here also delivers the message to all message listeners. Preconfigured destinations exist for various purposes. The [`DestinationNames`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationNames.html) class defines `String` constants for each. For example, `DestinationNames.HOT_DEPLOY` (value is `"liferay/hot_deploy"`) is for deployment event messages. Since destinations are tuned for specific purposes, don't modify them. Destinations are based on [`DestinationConfiguration`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html) instances. The configuration specifies the destination type, name, and these destination-related attributes: **Maximum Queue Size**: Limits the number of the destination's queued messages. **Rejected Execution Handler**: A [`RejectedExecutionHandler`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/concurrent/RejectedExecutionHandler.html) instance can take action (e.g., log warnings) regarding rejected messages when the destination queue is full. **Workers Core Size**: Initial number of worker threads for processing messages. **Workers Max Size**: Limits the number of worker threads for processing messages. The `DestinationConfiguration` class provides these static methods for creating the various types of configurations. - `createParallelDestinationConfiguration(String destinationName)` - `createSerialDestinationConfiguration(String destinationName)` - `createSynchronousDestinationConfiguration(String destinationName)` You can also use the `DestinationConfiguration` [constructor](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html#DestinationConfiguration-java.lang.String-java.lang.String-) to create a configuration for any destination type, even your own. For instructions on creating your own destination, see [Creating a Destination](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-destination). ## Message Listeners If you're interested in messages sent to a destination, you need to *listen* for them. That is, you must create and register a message listener for the destination. To create a message listener, implement the [`MessageListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html) interface and override its `receive(Message)` method to process messages your way. ```java public void receive(Message message) { // Process messages your way } ``` Here are the ways to register your listener with Message Bus: **Automatic Registration as a Component**: Publish the listener to the OSGi registry as a [Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) component that specifies a destination. Message Bus automatically wires the listener to the destination. **Registering via MessageBus**: Obtain and use a [`MessageBus`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html) reference to directly register the listener to a destination. **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 [Registering Message Listeners](/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners). ## 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: - [Creating a Message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message) - [Sending a Message](/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message) - [Sending Messages Across a Cluster](/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/02-creating-destination.markdown ================================================ --- header-id: creating-a-destination --- # Creating a Destination [TOC levels=1-4] [Message Bus destinations](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#messaging-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. 1. Create an `activate(BundleContext)` method in your component. Then create a [`BundleContext`](https://osgi.org/javadoc/r4v43/core/org/osgi/framework/BundleContext.html) instance variable and set it to the `activate` method's `BundleContext`: ```java @Activate protected void activate(BundleContext bundleContext) { _bundleContext = bundleContext; } private final BundleContext _bundleContext; ``` You'll create and register your destination inside this `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. 2. Create a destination configuration by using one of [`DestinationConfiguration`'s](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationConfiguration.html) static `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 `DestinationConfiguration` constructor to create a destination configuration for parallel destinations. It then sets the destination configuration's maximum queue size and [`RejectedExecutionHandler`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/concurrent/RejectedExecutionHandler.html): ```java @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); } ``` 3. Create the destination by invoking the [`DestinationFactory`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationFactory.html) method `createDestination(DestinationConfiguration)`, passing in the destination configuration from the previous step. For example, this code does so via a `DestinationFactory` reference: ```java @Activate protected void activate(BundleContext bundleContext) { ... Destination destination = _destinationFactory.createDestination( destinationConfiguration); } ... @Reference private DestinationFactory _destinationFactory; ``` 4. Register the destination as an OSGi service by invoking the `BundleContext` method `registerService` with these parameters: - The destination class `Destination.class`. - Your `Destination` object. - A `Dictionary` of properties defining the destination, including the `destination.name`. ```java @Activate protected void activate(BundleContext bundleContext) { ... Dictionary properties = new HashMapDictionary<>(); properties.put("destination.name", destination.getName()); ServiceRegistration serviceRegistration = _bundleContext.registerService( Destination.class, destination, properties); } ``` 5. Manage the destination object and service registration resources using a collection such as a `Map>`. Keeping references to these resources is helpful for when you're ready to unregister and destroy them. ```java @Activate protected void activate(BundleContext bundleContext) { ... _serviceRegistrations.put(destination.getName(), serviceRegistration); } ... private final Map> _serviceRegistrations = new HashMap<>(); ``` 6. Add a `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: ```java @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(); } ``` Here's the full messaging configurator component class that contains the code in the above steps: ```java @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<>(); } ``` ## Related Topics [Message Bus Destinations](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#messaging-destinations) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/03-message-bus-event-listeners.markdown ================================================ --- header-id: message-bus-event-listeners --- # Message Bus Event Listeners [TOC levels=1-4] When [using Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-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 [destinations](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#messaging-destinations) and [message listeners](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#message-listeners) are added or removed. Here, you'll learn how. ## Listening for Destinations Message Bus notifies event listeners when destinations are added and removed. To register these listeners, publish a [`MessageBusEventListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBusEventListener.html) instance to the OSGi service registry (e.g., via an `@Component` annotation). Here's an example implementation of `MessageBusEventListener`. Use the `destinationAdded` and `destinationDestroyed` methods to implement any logic that you want to run when a destination is added or removed, respectively: ```java @Component( immediate = true, service = MessageBusEventListener.class ) public class MyMessageBusEventListener implements MessageBusEventListener { void destinationAdded(Destination destination) { ... } void destinationDestroyed(Destination destination) { ... } } ``` ## Listening for Message Listeners Message Bus notifies [`DestinationEventListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationEventListener.html) instances when message listeners for destinations are either registered or unregistered. To register an event listener to a destination, publish a `DestinationEventListener` service to the OSGi service registry, making sure to specify the destination's `destination.name` property. ```java @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) { ... } } ``` ## Related Topics [Using the Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/04-registering-message-listeners.markdown ================================================ --- header-id: registering-message-listeners --- # Registering Message Listeners [TOC levels=1-4] There are three ways to register a [message listener](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#message-listeners) with the Message Bus: 1. [Automatic Registration as a Component](#automatic-registration-as-a-component) 2. [Registering via a MessageBus Reference](#registering-via-a-messagebus-reference) 3. [Registering Directly to the Destination](#registering-directly-to-the-destination) 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. | **Note**: The | [`DestinationNames`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/DestinationNames.html) | class defines `String` constants for @product@'s preconfigured destinations. ## Automatic Registration as a Component You can specify a message listener in the [Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services) `@Component` annotation: ```java @Component ( immediate = true, property = {"destination.name=myCustom/Destination"}, service = MessageListener.class ) public class MyMessageListener implements MessageListener { ... public void receive(Message message) { // Handle the message } } ``` The Message Bus listens for [`MessageListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html) service components like this one to publish themselves to the OSGi service registry. The attribute `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 `destination.name` property specifies. If the destination is not yet registered, Message Bus queues the listener until the destination registers. ## Registering via a MessageBus Reference You can use a [`MessageBus`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html) reference to directly register message listeners to destinations. Here's a registrator that demonstrates this: ```java @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; } ``` The `_messageBus` field's `@Reference` annotation binds it to the `MessageBus` instance. The `activate` method creates the listener and uses the Message Bus to register the listener to a destination named `"myDestination"`. When this registrator component is destroyed, the `deactivate` method unregisters the listener. ## Registering Directly to the Destination You can use a [`Destination`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Destination.html) reference to register a listener to that destination. Here's a registrator that demonstrates this: ```java @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; } ``` The `_destination` field's `@Reference` annotation binds it to a destination named `someDestination`. The `activate` method creates the listener and registers it to the destination. When this registrator component is destroyed, the `deactivate` method unregisters the listener. ## Related Topics [Using the Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/05-creating-a-message.markdown ================================================ --- header-id: creating-a-message --- # Creating a Message [TOC levels=1-4] Before you can [send a message](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus#sending-messages) via the Message Bus, you must first create it. Here's how to create a message: 1. Call the [`Message`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Message.html) constructor to create a new `Message`: ```java Message message = new Message(); ``` 2. Populate the message with a `String` or `Object` payload: - String payload: `message.setPayload("Message Bus is great!")` - Object payload: `message.put("firstName", "Joe")` 3. To receive responses at a particular location, set both of these attributes: - Response destination name: `setResponseDestinationName(String)` - Response ID: `setResponseId(String)` ## Related Topics [Sending a Message](/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message) [Sending Messages Across a Cluster](/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster) [Using the Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/06-sending-a-message.markdown ================================================ --- header-id: sending-a-message --- # Sending a Message [TOC levels=1-4] Once you've [created a message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message), there are three ways to send it with the [Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus): - [Directly with `MessageBus`](#directly-with-messagebus) - [Asynchronously with `SingleDestinationMessageSender`](#asynchronously-with-singledestinationmessagesender) - [Synchronously with `SynchronousMessageSender`](#synchronously-with-synchronousmessagesender) ## Directly with MessageBus To send a message directly with [`MessageBus`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageBus.html), follow these steps: 1. Get a `MessageBus` reference: ```java @Reference private MessageBus _messageBus; ``` 2. [Create a message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message). For example: ```java Message message = new Message(); message.put("myId", 12345); message.put("someAttribute", "abcdef"); ``` 3. Call the `MessageBus` reference's `sendMessage` method with the destination and message: ```java _messageBus.sendMessage("myDestinationName", message); ``` Here's a class that contains this example: ```java @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; } ``` ## Asynchronously with SingleDestinationMessageSender The [`SingleDestinationMessageSender`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSender.html) interface wraps the Message Bus to send messages asynchronously. Follow these steps to use this interface to send asynchronous messages: 1. Create a [`SingleDestinationMessageSenderFactory`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSenderFactory.html) reference: ```java @Reference private SingleDestinationMessageSenderFactory _messageSenderFactory; ``` 2. Create a `SingleDestinationMessageSender` by calling the `SingleDestinationMessageSenderFactory` reference's `createSingleDestinationMessageSender` method with the message's destination: ```java SingleDestinationMessageSender messageSender = _messageSenderFactory.createSingleDestinationMessageSender("myDestinationName"); ``` 3. [Create a message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message). For example: ```java Message message = new Message(); message.put("myId", 12345); message.put("someValue", "abcdef"); ``` 4. Send the message by calling the `SingleDestinationMessageSender` instance's `send` method with the message: ```java messageSender.send(message); ``` Here's a class that contains this example: ```java @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; } ``` ### Synchronously with SynchronousMessageSender [`SynchronousMessageSender`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SynchronousMessageSender.html) sends a message to the Message Bus and blocks until receiving a response or the response times out. A `SynchronousMessageSender` has these [operating modes](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SynchronousMessageSender.Mode.html): `DEFAULT`: Delivers the message in a separate thread and also provides timeouts, in case the message is not delivered properly. `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 `SynchronousMessageSender`: 1. Get a [`SingleDestinationMessageSenderFactory`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/sender/SingleDestinationMessageSenderFactory.html) reference: ```java @Reference private SingleDestinationMessageSenderFactory _messageSenderFactory; ``` 2. Create a `SingleDestinationSynchronousMessageSender` by calling the `SingleDestinationMessageSenderFactory` reference's `createSingleDestinationSynchronousMessageSender` method with the destination and operating mode. Note that this example uses the `DEFAULT` mode: ```java SingleDestinationSynchronousMessageSender messageSender = _messageSenderFactory.createSingleDestinationSynchronousMessageSender( "myDestinationName", SynchronousMessageSender.Mode.DEFAULT); ``` 3. [Create a message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message). For example: ```java Message message = new Message(); message.put("myId", 12345); message.put("someValue", "abcdef"); ``` 4. Send the message by calling the `SingleDestinationSynchronousMessageSender` instance's `send` method with the message: ```java messageSender.send(message); ``` Here's a class that contains this example: ```java @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; } ``` ## Related Topics [Creating a Message](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-message) [Sending Messages Across a Cluster](/docs/7-2/frameworks/-/knowledge_base/f/sending-messages-across-a-cluster) [Using the Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus) ================================================ FILE: en/developer/frameworks/articles/back-end-frameworks/05-message-bus/07-sending-messages-cluster.markdown ================================================ --- header-id: sending-messages-across-a-cluster --- # Sending Messages Across a Cluster [TOC levels=1-4] To ensure a message sent to a destination is received by all cluster nodes, you must register a [`ClusterBridgeMessageListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cluster/messaging/ClusterBridgeMessageListener.html) 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 [Registering Message Listeners](/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners). Follow these steps to create a registrator class that registers a `ClusterBridgeMessageListener` to a destination: 1. Create the registrator class as an OSGi component: ```java @Component( immediate = true, service = MyMessageListenerRegistrator.class ) public class MyMessageListenerRegistrator { ... } ``` 2. Create a [`MessageListener`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/MessageListener.html) variable: ```java private MessageListener _clusterBridgeMessageListener; ``` 3. Create a [`Destination`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/messaging/Destination.html) reference and set its `destination.name` property to your destination. For example, this reference is for the destination `liferay/live_users`: ```java @Reference(target = "(destination.name=liferay/live_users)") private Destination _destination; ``` 4. In the registrator's `activate` method, create a new `ClusterBridgeMessageListener` and set it to the `MessageListener` variable you created earlier. Then set the `ClusterBridgeMessageListener`'s priority and register the `ClusterBridgeMessageListener` to the destination: ```java @Activate protected void activate() { _clusterBridgeMessageListener = new ClusterBridgeMessageListener(); _clusterBridgeMessageListener.setPriority(Priority.LEVEL5) _destination.register(_clusterBridgeMessageListener); } ``` The [`Priority`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cluster/Priority.html) enum has ten levels (`Level1` through `Level10`, with `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: `Thread.MIN_PRIORITY`, `Thread.MAX_PRIORITY`, and `Thread.NORM_PRIORITY`. 5. In the registrator's `deactivate` method, unregister the `ClusterBridgeMessageListener` from the destination: ```java @Deactivate protected void deactivate() { _destination.unregister(_clusterBridgeMessageListener); } ``` Here's the full registrator class for this example: ```java @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; } ``` ## Related Topics [Registering Message Listeners](/docs/7-2/frameworks/-/knowledge_base/f/registering-message-listeners) [Sending a Message](/docs/7-2/frameworks/-/knowledge_base/f/sending-a-message) [Using the Message Bus](/docs/7-2/frameworks/-/knowledge_base/f/using-the-message-bus) ================================================ FILE: en/developer/frameworks/articles/cache-configuration/01-cache-configuration-intro.markdown ================================================ --- header-id: cache-configuration --- # Cache Configuration [TOC levels=1-4] 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 [Ehcache](https://www.ehcache.org/). It's an independent framework used by @product@'s data access and template engine components. It manages two pools: **Multi-VM:** Cache is replicated among cluster nodes. `EntityCache` and `FinderCache` (described next) are in this pool because they must synchronize with data on all nodes. **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: - [Overriding Cache](/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache): Tuning existing cache. - [Caching Data](/docs/7-2/frameworks/-/knowledge_base/f/caching-data): Implementing cache for custom data. Start learning the Liferay cache configuration basics here. ## Cache Types You can cache any classes you like. Conveniently, @product@ caches [service entities](/docs/7-2/appdev/-/knowledge_base/a/defining-service-entities) and [service entity finder results](/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods) automatically by default. [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/service-builder) generates their caching code in the [service persistence layer](/docs/7-2/appdev/-/knowledge_base/a/understanding-the-code-generated-by-service-builder). The code operates on these cache types: **`EntityCache`:** Holds service entities by primary keys. The caching code maps entity primary keys to implementation objects. An entity's `*PersistenceImpl.fetchByPrimaryKey` method uses `EntityCache`. **`FinderCache`:** Holds parameterized service entity search results. The caching code associates [service entity finder](/docs/7-2/appdev/-/knowledge_base/a/defining-service-entity-finder-methods) 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 `fetchByValue`, `findByValue`, `countByValue`, `findAll`, and `countAll` methods use the FinderCache. ## Cache Configuration @product@ designates separate cache configurations for multi-VM and single-VM environments. Default `EntityCache` and `FinderCache` are specified programmatically, while Liferay's global cache configuration and custom cache configurations are specified via files. All configurations adhere to the [Ehcache XSD](http://www.ehcache.org/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. ### Initial Global Cache Configuration Liferay's portal cache implementation LPKG file (`Liferay [version] Foundation - Liferay [version] Portal Cache - Impl.lpkg`) found in the `[Liferay_Home]/osgi/marketplace` folder contains the initial global cache configuration. The LPKG file's `com.liferay.portal.cache.ehcache.impl-[version].jar` holds the configuration files: - `liferay-multi-vm.xml`: Maps to the multi-VM pool. - `liferay-single-vm.xml`: Maps to the single-VM pool. ### Module Cache Configuration Modules can configure (add or override) cache using configuration files in their `src/main/resources/META-INF` folder: - `module-multi-vm.xml`: Maps to the multi-VM cache manager. - `module-single-vm.xml`: Maps to the single-VM cache manager. For example, the @product@ Web Experience suite's `com.liferay.journal.service` module uses the following [`module-multi-vm.xml`](https://github.com/liferay/liferay-portal/blob/master/modules/apps/journal/journal-service/src/main/resources/META-INF/module-multi-vm.xml) to create a cache named `com.liferay.journal.util.JournalContent` in the multi-VM pool. ```xml ``` Portlet WARs can configure cache too. ### Portlet WAR Cache Configuration Ehcache configuration in a portlet WAR has these requirements: 1. The Ehcache configuration XML file must be in the application context (e.g., any path under `WEB-INF/src`). 2. The `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., `WEB-INF/src`). ```properties ehcache.single.vm.config.location=path/to/single/vm/config/file ehcache.multi.vm.config.location=path/to/multi/vm/config/file ``` For example, here's the [`test-cache-configuration-portlet`](https://github.com/liferay/liferay-plugins/blob/7.0.x/portlets/test-cache-configuration-portlet) WAR's structure: - `docroot/WEB-INF/src/` - `ehcache/` - [`liferay-single-vm-ext.xml`](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) - [`liferay-multi-vm-clustered-ext.xml`](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) - `portlet.properties` The `portlet.properties` file specifies these properties: ```properties ehcache.single.vm.config.location=ehcache/liferay-single-vm-ext.xml ehcache.multi.vm.config.location=ehcache/liferay-multi-vm-clustered-ext.xml ``` ## Cache Names and Registration A cache is identified by its name (e.g., ``). 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 `EntityCache` and `FinderCache`. ### EntityCache Names `EntityCache` uses this naming convention: `PREFIX + ENTITY_IMPL_CLASS_NAME` where the `PREFIX` is always this: ``` com.liferay.portal.kernel.dao.orm.EntityCache. ``` For example, the cache name for the `com.liferay.portal.kernel.model.User` entity starts with the `PREFIX` and ends with the implementation class name `com.liferay.portal.model.impl.UserImpl`: ``` com.liferay.portal.kernel.dao.orm.EntityCache.com.liferay.portal.model.impl.UserImpl ``` ### FinderCache Names `FinderCache` uses this naming convention: `PREFIX + ENTITY_IMPL_CLASS_NAME + [".LIST1"|".LIST2"]` where the `PREFIX` is always this: ``` com.liferay.portal.kernel.dao.orm.FinderCache. ``` Here are the `FinderCache` types and their name patterns. | Type | Pattern | Example | | ---- | ------- | ------- | | Entity instances matching query parameters. | `PREFIX + ENTITY_IMPL_CLASS_NAME` | `com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl` | | Paginated lists of entity instances matching query parameters. | `PREFIX + ENTITY_IMPL_CLASS_NAME + ".List1"` | `com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl.List1` | | Non-paginated lists of entity instances matching query parameters. | `PREFIX + ENTITY_IMPL_CLASS_NAME + ".List2"` | `com.liferay.portal.kernel.dao.orm.FinderCache.com.liferay.portal.model.impl.ClassNameImpl.List2` | Now that you have a basic understanding of cache in Liferay, continue with overriding an existing cache configuration or caching custom data. ================================================ FILE: en/developer/frameworks/articles/cache-configuration/02-overriding-cache.markdown ================================================ --- header-id: overriding-cache --- # Overriding Cache [TOC levels=1-4] @product@ 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. | **Warning:** Modifying an Ehcache element flushes its cache. Here is how to override a cache configuration: 1. 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 @product@'s cache configurations in the MBean of `net.sf.ehcache`. Please note that the caches listed in the MBean are more than what @product@'s cache configuration files specify because some caches are created purely through Java code. ![Figure 1: Caches configured in @product@ can be examined using JMX tools such as Zulu Mission Control \(Portal Process → MBean server → MBean Browser\)](../../images/zulu-mission-control.png) | **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. 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. 2. 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. | **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. 3. In the `src/main/resources/META-INF` folder, add an XML file for the type of cache (multi-VM or single-VM) you're overriding. `module-multi-vm.xml` file: ```xml ``` `module-single-vm.xml` file: ```xml ``` 4. In the `` element, add a `` element and set its `name` attribute to the name of the cache you're overriding. 5. Specify all existing `` element attributes you want to preserve. Hint: view the attributes in an MBean browser, as mentioned earlier. 6. Add or modify attributes to meet your needs. The `` element attributes are described in the [ehcache.xsd](http://www.ehcache.org/ehcache.xsd) and [Ehcache documentation](http://www.ehcache.org/documentation/2.8/configuration/index.html). 7. [Deploy the project](/docs/7-2/reference/-/knowledge_base/r/deploying-a-project). Congratulations! Your cache modification is in effect. ## Related Topics [Caching Data](/docs/7-2/frameworks/-/knowledge_base/f/caching-data) ================================================ FILE: en/developer/frameworks/articles/cache-configuration/03-caching-data.markdown ================================================ --- header-id: caching-data --- # Caching Data [TOC levels=1-4] [Liferay's caching framework](/docs/7-2/frameworks/-/knowledge_base/f/cache-configuration) helps you use Ehcache to cache any data. The [`SingleVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html) and [`MultiVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html) classes use Liferay's [`PortalCache`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/PortalCache.html) 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. | **Note:** If you want to modify cache for Service Builder Service Entities or | Entity Finder results, see | [Overriding Cache](/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache). ## 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 `Serializable` values. 1. Determine whether to create a cache [in a single VM or across multiple VMs](/docs/7-2/frameworks/-/knowledge_base/f/cache-configuration) (e.g., in a clustered environment). 2. Determine if it's necessary to serialize the data you're caching. - [`MultiVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html) requires both the cache key and cache value to be [`Serializable`](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html). - [`SingleVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html) typically requires only cache keys to be [`Serializable`](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html). Note that some Ehache features, such as `overflowToDisk`, require `Serializable` values too. ## Step 2: Implement a Cache Key Cache keys must be unique, [`Serializable`](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html) objects. They should relate to the values being cached. For example, in @product@'s `JournalContentImpl`, a `JournalContentKey` instance relates to each cached `JournalArticleDisplay` object. Here's the `JournalContentKey` class: ```java 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; } ``` `JournalContentKey`s constructor populates fields that collectively define unique keys for each piece of journal content. Note a cache key's characteristics: 1. A key instance's field values relate to the cached data and distinguish it from other data instances. 2. A key follows `Serializable` class best practices. - Overrides `Object`'s `equals` and `hashcode` methods. - Includes a private static final long `serialVersionUID` field. It is to be incremented when a new version of the class is incompatible with previous versions. Your cache key class is ready for caching data values. ## 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. @product@'s caching classes are easy to inject into a [Declarative Services (DS) Component](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services), but you can access them using [`ServiceTracker`](/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker)s too. These steps use fictitious key and value classes: `SomeKey` and `SomeValue`. 1. Name your cache. Cache names are arbitrary, but they must be unique in the cache pool, and typically identify the data type being cached. ```java protected static final String CACHE_NAME = SomeValue.class.getName(); ``` 2. Access the VM pool you're using. [`MultiVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/MultiVMPool.html) and [`SingleVMPool`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/SingleVMPool.html) are Declarative Service (DS) components. To access a pool from a DS component, apply the [`@Reference`](https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Reference.html) annotation to a pool field (see below). Otherwise, use a [`ServiceTracker`](/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker) to access the pool. ```java @Reference private MultiVMPool _multiVMPool; ``` 3. Declare a private static [`PortalCache`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/cache/PortalCache.html) instance. ```java private static PortalCache _portalCache; ``` 4. Initialize your `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 [`@Activate`](https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Activate.html)). 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. ```java @Activate public void activate() { _portalCache = (PortalCache) _multiVMPool.getPortalCache(CACHE_NAME); ... } ``` 5. 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 [`@Deactivate`](https://osgi.org/javadoc/r6/residential/org/osgi/service/component/annotations/Deactivate.html)). Use the VM pool to remove the cache. ```java @Deactivate public void deactivate() { _multiVMPool.removePortalCache(CACHE_NAME); } ``` 6. In your code that uses the cached data, implement your caching logic. Here's some example code: ```java SomeKey key = new SomeKey(...); SomeValue value = _portalCache.get( key); if (value == null) { value = createSomeValue(...); _portalCache.put(key, value); } // continue using the data ... ``` The code above constructs a key based on the data being used. Then, the key is used to check the `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. ## Step 4: Configure the Cache It's time to specify your Ehcache configuration. 1. Depending on the VM pool you're using, start your XML file in one of the following ways. Multi VM file: ```xml ``` Single VM file: ```xml ``` 2. Add a `` 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 `` element to fit your caching requirements. The [ehcache.xsd](http://www.ehcache.org/ehcache.xsd) and [Ehcache documentation](http://www.ehcache.org/documentation/2.8/configuration/index.html) describe the `` attributes. For example, the Liferay Web Experience suite's `com.liferay.journal.service` module uses this [`module-multi-vm.xml`](https://github.com/liferay/liferay-portal/blob/master/modules/apps/journal/journal-service/src/main/resources/META-INF/module-multi-vm.xml) file to configure its cache named `com.liferay.journal.util.JournalContent`. ```xml ``` 3. Deploy your project. Congratulations! Your data cache is in effect. ## Related Topics [Overriding Cache](/docs/7-2/frameworks/-/knowledge_base/f/overriding-cache) ================================================ FILE: en/developer/frameworks/articles/collaboration/00-intro.markdown ================================================ --- header-id: collaboration --- # Collaboration [TOC levels=1-4] Underlying the [collaboration suite](/docs/7-2/user/-/knowledge_base/u/collaboration) 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. [TOC levels=4 hierarchy] ## Item Selector An *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. ![Figure 1: Item Selectors select different kinds of entities.](../../images/item-selector-dialog-02.png) 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: 1. Selecting entities with an Item Selector. 2. Configuring an Item Selector to select your app's custom entity. 3. Adding a new *selection view* to customize the selection experience. ## Adaptive Media The [Adaptive Media](/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices) 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. ## Social API Users interact with content via @product@'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: **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. **Comments:** Comment on content. **Ratings:** Rate content. Administrators can also change the rating type (e.g., likes, stars, thumbs, etc.). **Flags:** Flag inappropriate content. ## 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 [user guide](/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media) 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: - Create files, folders, and shortcuts. - Delete entities. - Update entities. - Check out files for editing, and check them back in. - Copy and move entities. - Get entities. ================================================ FILE: en/developer/frameworks/articles/collaboration/01-item-selector/00-intro.markdown ================================================ --- header-id: item-selector --- # Item Selector [TOC levels=1-4] An *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: 1. Select Entities. 2. Create Custom Item Selector Criteria. 3. Create Custom Item Selector Views. ![Figure 1: Item Selectors select entities.](../../../images/item-selector-dialog-02.png) ## Understanding the Item Selector API's 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: **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. **Markup:** A markup file that renders the selection view. You can choose from JSP, FreeMarker, or even pure HTML and JavaScript. **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 [`ItemSelectorReturnType`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorReturnType.html). Such classes are named after the return type's data and suffixed with `ItemSelectorReturnType`. For example, the URL return type class is `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. **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 [`ItemSelectorCriterion`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorCriterion.html). Such classes are named for the entity they represent and suffixed with `ItemSelectorCriterion`. For example, the criterion class for images is `ImageItemSelectorCriterion`. If you create your own criterion class, extend [`BaseItemSelectorCriterion`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterion.html). This base class implements `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 *criteria*. The Item Selector uses its criteria to decide which selection views to show. | **Note:** For a list of the criterion classes and return types that @product@ | provides, see | [Item Selector Criterion and Return Types](/docs/7-2/reference/-/knowledge_base/r/item-selector-criterion-and-return-types). **Criterion Handler:** A class that gets the appropriate selection view. Each criterion requires a criterion handler. Criterion handler classes extend [`BaseItemSelectorCriterionHandler`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterionHandler.html) with the criterion's entity as a type argument. Criterion handler classes are named after the criterion's entity and suffixed by `ItemSelectorCriterionHandler`. For example, the image criterion handler class is `ImageItemSelectorCriterionHandler` and extends `BaseItemSelectorCriterionHandler`. ![Figure 2: Item Selector views (selection views) are determined by the return type and criterion, and rendered by the markup.](../../../images/item-selector-architecture.png) ## 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 `ItemSelector` reference and call its [`getItemSelectorURL`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelector.html#getItemSelectorURL-com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory-java.lang.String-com.liferay.item.selector.ItemSelectorCriterion...-) method with the following parameters: `RequestBackedPortletURLFactory`: A factory that creates portlet URLs. `ItemSelectedEventName`: A unique, arbitrary JavaScript event name that the Item Selector triggers when the entity is selected. `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: - You can invoke the URL object's `toString` method to get its value. - You can configure an Item Selector to use any number of criterion. The criterion can use any number of return types. - The order of the Item Selector's criteria determines the selection view order. For example, if you pass the Item Selector an `ImageItemSelectorCriterion` followed by a `VideoItemSelectorCriterion`, the Item Selector displays the image selection views first. - The return type order is also significant. A view uses the first return type it supports from each criterion's return type list. ## 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 `*ItemSelectorCriterion` class represents each selection view. ![Figure 3: An entity type can have multiple selection views.](../../../images/item-selector-tabs.png) ### The Selection View's Class The criterion and return types determine the selection view's class. This class is an `ItemSelectorView` component class that implements [`ItemSelectorView`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html) parameterized with the view's criterion. Remember these things when creating this class: - Configure the title by implementing the [`getTitle`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#getTitle-java.util.Locale-) method to return the localized title of the tab to display in the Item Selector dialog. - Configure the search options by implementing the [`isShowSearch()`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#isShowSearch--) method to return whether your view should show the search field. To implement search, this method must return `true`. The `renderHTML` method indicates whether a user performed a search based on the value of the `search` parameter. You can get the user's search keywords as follows: String keywords = ParamUtil.getString(request, "keywords"); - Make your view visible by implementing the [`isVisible()`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#isVisible-com.liferay.portal.kernel.theme.ThemeDisplay-) method to return `true`. Note that you can use this method to add conditional logic to disable the view. ================================================ FILE: en/developer/frameworks/articles/collaboration/01-item-selector/01-selecting-entities.markdown ================================================ --- header-id: selecting-entities-with-an-item-selector --- # Selecting Entities with an Item Selector [TOC levels=1-4] 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 [Item Selector introduction](/docs/7-2/frameworks/-/knowledge_base/f/item-selector). ## Get an Item Selector First, you must get an Item Selector for your use case. Follow these steps: 1. 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 @product@ provides, see [Item Selector Criterion and Return Types](/docs/7-2/reference/-/knowledge_base/r/item-selector-criterion-and-return-types). For example, if you need an Item Selector that selects images and returns their URLs, use `ImageItemSelectorCriterion` and `URLItemSelectorReturnType`. You can [create](/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types) criterion and/or return types if there aren't existing ones for your use case. 2. Use Declarative Services to get an `ItemSelector` OSGi Service Component: ```java import com.liferay.item.selector.ItemSelector; import org.osgi.service.component.annotations.Reference; ... @Reference private ItemSelector _itemSelector ``` The component annotations are available in the module [`org.osgi.service.component.annotations`](http://mvnrepository.com/artifact/org.osgi/org.osgi.service.component.annotations). 3. Create the factory you'll use to create the Item Selector's URL. To do this, invoke the `RequestBackedPortletURLFactoryUtil.create` method with the current request object. The request can be an `HttpServletRequest` or `PortletRequest`: ```java RequestBackedPortletURLFactory requestBackedPortletURLFactory = RequestBackedPortletURLFactoryUtil.create(request); ``` 4. Create a list of return types expected for the entity. For example, the return types list here consists of `URLItemSelectorReturnType`: ```java List desiredItemSelectorReturnTypes = new ArrayList<>(); desiredItemSelectorReturnTypes.add(new URLItemSelectorReturnType()); ``` 5. Create an object for the criterion. This example creates a new `ImageItemSelectorCriterion`: ```java ImageItemSelectorCriterion imageItemSelectorCriterion = new ImageItemSelectorCriterion(); ``` 6. Use the criterion's `setDesiredItemSelectorReturnTypes` method to set the return types list to the criterion: ```java imageItemSelectorCriterion.setDesiredItemSelectorReturnTypes( desiredItemSelectorReturnTypes); ``` 7. Call the Item Selector's `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): ```java PortletURL itemSelectorURL = _itemSelector.getItemSelectorURL( requestBackedPortletURLFactory, "sampleTestSelectItem", imageItemSelectorCriterion); ``` 8. Add the `itemSelectorURL` to the request to be able to retrieve it from the JSP: `request.setAttribute("itemSelectorURL", itemSelectorURL.toString())"` ## Using the Item Selector Dialog To open the Item Selector in your UI, you must use the JavaScript component `LiferayItemSelectorDialog` from [AlloyUI's](http://alloyui.com) `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: 1. Declare the AUI tag library: ```java <%@ taglib prefix="aui" uri="http://liferay.com/tld/aui" %> ``` 2. Define the UI element you'll use to open the Item Selector dialog. For example, this creates a *Choose* button with the ID `chooseImage`: ```markup ``` 3. Get the Item Selector's URL: ```java <% String itemSelectorURL = GetterUtil.getString(request.getAttribute("itemSelectorURL")); %> ``` 3. Add the `` tag and set it to use the `liferay-item-selector-dialog` module: ```markup ``` 4. Inside the `` 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 *Choose* button: ```javascript $('#chooseImage').on( 'click', function(event) { } ); ``` Inside the function, you must create a new instance of the `LiferayItemSelectorDialog` AlloyUI component and configure it to use the Item Selector. The next steps walk you through this. 5. Create the function's logic. First, create a new instance of the Liferay Item Selector dialog: ```javascript var itemSelectorDialog = new A.LiferayItemSelectorDialog( { ... } ); ``` 6. Inside the braces of the `LiferayItemSelectorDialog` constructor, first set set the `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): ```javascript eventName: 'ItemSelectedEventName', ``` 7. Immediately after the `eventName` setting, set the `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: ```javascript on: { selectedItemChange: function(event) { var selectedItem = event.newVal; if (selectedItem) { var itemValue = JSON.parse( selectedItem.value ); itemSrc = itemValue.url; } } }, ``` 8. Immediately after the `on` setting, set the `title` attribute to the dialog's title: ```javascript title: '', ``` 9. Immediately after the `title` setting, set the `url` attribute to the previously retrieved Item Selector URL. This concludes the attribute settings inside the `LiferayItemSelectorDialog` constructor: ```javascript url: '<%= itemSelectorURL.toString() %>' ``` 10. To conclude the logic of the function from step four, open the Item Selector dialog by calling its `open` method: ```javascript itemSelectorDialog.open(); ``` When the user clicks the *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: ```javascript <%@ 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(); } ); ``` ## Related Topics [Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/item-selector) [Creating Custom Criterion and Return Types](/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types) [Creating Custom Item Selector Views](/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-item-selector-views) ================================================ FILE: en/developer/frameworks/articles/collaboration/01-item-selector/02-creating-criterion-return-types.markdown ================================================ --- header-id: creating-custom-criterion-and-return-types --- # Creating Custom Criterion and Return Types [TOC levels=1-4] 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 [Item Selector introduction](/docs/7-2/frameworks/-/knowledge_base/f/item-selector). 1. Create your criterion class by extending [`BaseItemSelectorCriterion`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterion.html). Name the class after the entity it represents and suffix it with `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, [`JournalItemSelectorCriterion`](@app-ref@/web-experience/latest/javadocs/com/liferay/journal/item/selector/criterion/JournalItemSelectorCriterion.html) is the criterion class for `Journal` entities (Web Content) and passes primary key information to the view: ```java 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; } ``` 2. Create a criterion handler by creating an OSGi component class that implements [`BaseItemSelectorCriterionHandler`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/BaseItemSelectorCriterionHandler.html). This example creates a criterion handler for the `TaskItemSelectorCriterion` class. The `@Activate` and `@Override` tokens are required to activate this OSGi component: ```java @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); } } ``` 3. If you need a new return type, create it by implementing [`ItemSelectorReturnType`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorReturnType.html). Name your return type class after the return type's data and suffix it with `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: ```java /** * 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{ } ``` ## Related Topics [Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/item-selector) [Creating Custom Item Selector Views](/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-item-selector-views) [Selecting Entities with an Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/selecting-entities-with-an-item-selector) ================================================ FILE: en/developer/frameworks/articles/collaboration/01-item-selector/03-creating-custom-views.markdown ================================================ --- header-id: creating-custom-item-selector-views --- # Creating Custom Item Selector Views [TOC levels=1-4] 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 [Item Selector introduction](/docs/7-2/frameworks/-/knowledge_base/f/item-selector). ## Configuring Your Selection View's OSGi Module First, you must configure your selection view's OSGi module: 1. Add these dependencies to your module's `build.gradle`: ```groovy 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" } ``` 2. Add your module's information to the `bnd.bnd` file. For example, this configuration adds the information for a module called `My Custom View`: Bundle-Name: My Custom View Bundle-SymbolicName: com.liferay.docs.my.custom.view Bundle-Version: 1.0.0 3. Add a `Web-ContextPath` to your `bnd.bnd` to point to your module's resources: Include-Resource:\ META-INF/resources=src/main/resources/META-INF/resources Web-ContextPath: /my-custom-view If you don't have a `Web-ContextPath`, your module won't know where your resources are. The `Include-Resource` header points to the relative path for the module's resources. ## Implementing Your Selection View's Class Follow these steps to implement your selection view's class: 1. Create an `ItemSelectorView` component class that implements [`ItemSelectorView`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html) with the criterion as a type argument. In the `@Component` annotation, set the `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 `ItemSelectorView` with [`ImageItemSelectorCriterion`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/criteria/image/criterion/ImageItemSelectorCriterion.html) as a type argument. The `@Component` annotation sets the `item.selector.view.order` property to `200` and registers the class as an `ItemSelectorView` service: ```java @Component( property = {"item.selector.view.order:Integer=200"}, service = ItemSelectorView.class ) public class SampleItemSelectorView implements ItemSelectorView {... ``` 2. Create getter methods for the criterion class, servlet context, and return types. Do this by implementing the methods [`getItemSelectorCriterionClass()`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#getItemSelectorCriterionClass--), `getServletContext()`, and [`getSupportedItemSelectorReturnTypes()`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#getSupportedItemSelectorReturnTypes--), respectively: ```java @Override public Class getItemSelectorCriterionClass() { return ImageItemSelectorCriterion.class; } @Override public ServletContext getServletContext() { return _servletContext; } @Override public List getSupportedItemSelectorReturnTypes() { return _supportedItemSelectorReturnTypes; } ``` 3. Configure the selection view's title, search options, and visibility settings. Here's an example configuration for the `Sample Selector` selection view: ```java @Override public String getTitle(Locale locale) { return "Sample Selector"; } @Override public boolean isShowSearch() { return false; } @Override public boolean isVisible(ThemeDisplay themeDisplay) { return true; } ``` See [The Selection View's Class](/docs/7-2/frameworks/-/knowledge_base/f/item-selector#the-selection-views-class) for more information on these methods. 4. Implement the [`renderHTML`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/ItemSelectorView.html#renderHTML-javax.servlet.ServletRequest-javax.servlet.ServletResponse-T-javax.portlet.PortletURL-java.lang.String-boolean-) method to set your view's render settings and render its markup. Here's an example implementation of a `renderHTML` method that points to a JSP file (`sample.jsp`) to render the view. Note that `itemSelectedEventName` is passed as a request attribute so it can be used in the view markup. The view markup is specified via the `ServletContext` method `getRequestDispatcher`. Although this example uses a JSP, you can render the markup in another language such as FreeMarker. ```java @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); } ``` 5. Use the `@Reference` annotation to reference your module's class for the `setServletContext` method. In the annotation, use the `target` parameter to specify the available services for the servlet context. This example uses the `osgi.web.symbolicname` property to specify the `com.liferay.selector.sample.web` class as the default value. You should also use the `unbind = _` parameter to specify that there's no unbind method for this module. In the method body, set the servlet context variable: ```java @Reference( target = "(osgi.web.symbolicname=com.liferay.item.selector.sample.web)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { _servletContext = servletContext; } ``` 6. Define the `_supportedItemSelectorReturnTypes` list with the return types that this view supports (you referenced this list in step two). This example adds [`URLItemSelectorReturnType`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/criteria/URLItemSelectorReturnType.html) and [`FileEntryItemSelectorReturnType`](@app-ref@/collaboration/latest/javadocs/com/liferay/item/selector/criteria/FileEntryItemSelectorReturnType.html) 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: ```java private static final List _supportedItemSelectorReturnTypes = Collections.unmodifiableList( ListUtil.fromArray( new ItemSelectorReturnType[] { new FileEntryItemSelectorReturnType(), new URLItemSelectorReturnType() })); private ServletContext _servletContext; ``` For a real-world example of a view class, see [`SiteNavigationMenuItemItemSelectorView`](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). ## 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: - Render the entities for the user to select. - When an entity is selected, pass the return type information via a JavaScript event. The example view class in the previous section passes the JavaScript event name as a request attribute in the `renderHTML` method. You can therefore use this event name in the markup: ```javascript Liferay.fire( `<%= {ITEM_SELECTED_EVENT_NAME} %>', { data:{ the-data-your-client-needs-according-to-the-return-type } } ); ``` For a complete, real-world example, see [`layouts.jsp`](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) for the module [`com.liferay.layout.item.selector.web`](https://github.com/liferay/liferay-portal/tree/7.0.x/modules/apps/web-experience/layout/layout-item-selector-web). Even though this example is for a previous version of @product@, it still applies to @product-ver@. Here's a walkthrough of this `layouts.jsp` file: 1. First, some variables are defined. Note that `LayoutItemSelectorViewDisplayContext` is an optional class that contains additional information about the criteria and view: ```java <% LayoutItemSelectorViewDisplayContext layoutItemSelectorViewDisplayContext = (LayoutItemSelectorViewDisplayContext)request.getAttribute( BaseLayoutsItemSelectorView.LAYOUT_ITEM_SELECTOR_VIEW_DISPLAY_CONTEXT); LayoutItemSelectorCriterion layoutItemSelectorCriterion = layoutItemSelectorViewDisplayContext.getLayoutItemSelectorCriterion(); Portlet portlet = PortletLocalServiceUtil.getPortletById(company.getCompanyId(), portletDisplay.getId()); %> ``` 2. This snippet imports a CSS file for styling and places it in the `` of the page: ```markup " rel="stylesheet" type="text/css" /> ``` You can learn more about using the `liferay-util` taglibs in [Using the Liferay Util Taglib](/docs/7-2/reference/-/knowledge_base/r/using-the-liferay-util-taglib). 3. This snippet creates the UI to display the layout entities. It uses the [`liferay-layout:layouts-tree`](@app-ref@/layout/latest/taglibdocs/liferay-layout/layouts-tree.html) taglib along with the [Lexicon](https://lexicondesign.io/) design language to create [cards](https://clayui.com/docs/components/cards.html): ```html
    ``` This renders the following UI: ![Figure 1: The Layouts Item Selector view uses Lexicon and Liferay Layout taglibs to create the UI.](../../../images/layouts-item-selector-view.png) 4. This portion of the `aui:script` returns the path for the page: ```javascript 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(' > '); }; ``` 5. The following snippet passes the return type data when the layout (entity) is selected. Note the `url` and `uuid` variables retrieve the URL or UUID for the layout: ```javascript 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); ``` 6. This checks if the return type information is a URL or a UUID. It then sets the value for the JSON object's `data` attribute accordingly. The last line adds the `CKEditorFuncNum` for the editor to the JSON object's `data` attribute: ```markup data.value = url; data.value = uuid; } data.ckeditorfuncnum: <%= layoutItemSelectorViewDisplayContext.getCkEditorFuncNum() %>; ``` The `data-url` and `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: ![Figure 2: The URL and UUID can be seen in the `data-url` and `data-uuid` attributes of the Layout Item Selector's HTML.](../../../images/layouts-item-selector-html.png) 7. The JavaScript trigger event specified in the Item Selector return type is fired, passing the data JSON object with the required return type information: ```javascript Liferay.Util.getOpener().Liferay.fire( '<%= layoutItemSelectorViewDisplayContext.getItemSelectedEventName() %>', { data: data } ); }; ``` 8. Finally, the layout is set to the selected page: ```javascript var container = A.one('#treeContainerOutput'); if (container) { container.swallowEvent('click', true); var tree = container.getData('tree-view'); tree.after('lastSelectedChange', setSelectedPage); } ``` 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. ## Related Topics [Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/item-selector) [Creating Custom Criterion and Return Types](/docs/7-2/frameworks/-/knowledge_base/f/creating-custom-criterion-and-return-types) [Selecting Entities with an Item Selector](/docs/7-2/frameworks/-/knowledge_base/f/selecting-entities-with-an-item-selector) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/00-intro.markdown ================================================ --- header-id: documents-and-media-api --- # Documents and Media API [TOC levels=1-4] A powerful API underlies the [Documents and Media library](/docs/7-2/user/-/knowledge_base/u/managing-documents-and-media). 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. ## Getting Started with the Documents and Media API Before you start using the Documents and Media API, you must learn these things: [**Key Interfaces:**](#key-interfaces) The interfaces you'll use most while using the API. [**Getting a Service Reference:**](#getting-a-service-reference) A service reference is required for calling the API's services. [**Specifying Repositories:**](#specifying-repositories) How to specify which Documents and Media repository to work with. [**Specifying Folders:**](#specifying-folders) How to specify which Documents and Media folder to work with. ## Key Interfaces The Documents and Media API contains several key interfaces: **Documents and Media Services:** These interfaces expose all the available Documents and Media functionality: - [`DLAppLocalService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppLocalService.html): The local service. - [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html): The remote service. This service wraps the local service methods in permission checks. Note that Liferay used [Service Builder](/docs/7-2/appdev/-/knowledge_base/a/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. **Entity Interfaces:** These interfaces represent entities in the Documents and Media library. Here are the primary ones you'll use: - `FileEntry`: Represents a file. - `Folder`: Represents a folder. - `FileShortcut`: Represents a shortcut to a file. ## 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 `@Reference` annotation to [get a service reference in an OSGi component via Declarative Services](/docs/7-2/frameworks/-/knowledge_base/f/declarative-services). For example, this code gets a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` If you're using a standard web module (WAR file), use a [Service Tracker](/docs/7-2/frameworks/-/knowledge_base/f/using-a-service-tracker) to get a reference to the service instead. Getting the reference this way ensures that you leverage OSGi's [dependency management](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) 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 `*Util` classes: - [`DLAppServiceUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppServiceUtil.html) - [`DLAppLocalServiceUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppLocalServiceUtil.html) ## Specifying Repositories Many methods in the Documents and Media API contain a `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 `repositoryId`. You can also get the `repositoryId` via file (`FileEntry`), folder (`Folder`), and file shortcut (`FileShortcut`) entities. Each of these entities has a `getRepositoryId` method that gets its repository's ID. For example, this code gets the repository ID of the `FileEntry` object `fileEntry`: ```java long repositoryId = fileEntry.getRepositoryId(); ``` There may also be cases that require a `Repository` object. You can get one by creating a `RepositoryProvider` reference and passing the repository ID to its `getRepository` method: ```java @Reference private RepositoryProvider repositoryProvider; Repository repository = repositoryProvider.getRepository(repositoryId); ``` Even if you only have an entity ID (e.g., a file or folder ID), you can still use `RepositoryProvider` to get a `Repository` object. To do so, call the `RepositoryProvider` method for the entity type with the entity ID as its argument. For example, this code gets a folder's `Repository` by calling the `RepositoryProvider` method `getFolderRepository` with the folder's ID: ```java Repository repository = repositoryProvider.getFolderRepository(folderId); ``` See the `RepositoryProvider` [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/RepositoryProvider.html) 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. ## 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 `folderId` or `parentFolderId`. Also note that you can use the constant `DLFolderConstants.DEFAULT_PARENT_FOLDER_ID` to specify the root folder of your current repository. ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/01-creating-entities/00-intro.markdown ================================================ --- header-id: creating-files-folders-and-shortcuts --- # Creating Files, Folders, and Shortcuts [TOC levels=1-4] 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 `add[ModelName]`, where `[ModelName]` is the name of the entity's data model object. As the [intro](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api) explains, you'll use [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) to access the API. This service object contains the methods for adding these entities: - [Files](#files) - [Folders](#folders) - [File Shortcuts](#file-shortcuts) ## Files To create files (`FileEntry` entities) in the Documents and Media library, you must use the [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) interface's `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: - [`addFileEntry(..., byte[] bytes, ...)`](@platform-ref@/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-) - [`addFileEntry(..., File file, ...)`](@platform-ref@/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-) - [`addFileEntry(..., InputStream is, long size, ...)`](@platform-ref@/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-) Note that the following arguments are optional: `sourceFileName`: This keeps track of the uploaded file. It infers the content type if that file has an extension. `mimeType`: Defaults to a binary stream. If omitted, Documents and Media tries to infer the type from the file extension. `description`: The file's description to display in the portal. `changeLog`: Descriptions for file versions. `is` and `size`: In the method that takes an `InputStream`, you can use `null` for the `is` parameter. If you do this, however, you must use `0` for the `size` parameter. For step-by-step instructions on creating files with `addFileEntry`, see [Creating Files](/docs/7-2/frameworks/-/knowledge_base/f/creating-files). ## Folders To create folders (`Folder` entities) in the Documents and Media library, you must use the [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) interface's `addFolder` method: ```java addFolder(long repositoryId, long parentFolderId, String name, String description, ServiceContext serviceContext) ``` See this method's [Javadoc](@platform-ref@/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-) for a description of the parameters. Note that the `description` parameter is optional. For step-by-step instructions on creating folders with `addFolder`, see [Creating Folders](/docs/7-2/frameworks/-/knowledge_base/f/creating-folders). ### 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 *mount points*. You can create one via the API by setting the Service Context's `mountPoint` attribute to `true`, and then using that Service Context in the `addFolder` method: ```java serviceContext.setAttribute("mountPoint", true); ``` Note that the `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. ## File Shortcuts To create file shortcuts (`FileShortcut` entities) in the Documents and Media library, you must use the [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) interface's `addFileShortcut` method: ```java addFileShortcut(long repositoryId, long folderId, long toFileEntryId, ServiceContext serviceContext) ``` See this method's [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#addFileShortcut-long-long-long-com.liferay.portal.kernel.service.ServiceContext-) for a description of the parameters. Note that all this method's parameters are mandatory. Keep these things in mind when creating shortcuts: - 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. - You can't create folder shortcuts. - Shortcuts can only exist in the default Site repository. If you try to invoke `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. For step-by-step instructions on creating file shortcuts with `addFileShortcut`, see [Creating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/01-creating-entities/01-creating-files.markdown ================================================ --- header-id: creating-files --- # Creating Files [TOC levels=1-4] To create a file via the Documents and Media API, use one of the overloaded `addFileEntry` methods in [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html). The steps here show you how to do this, using the method that contains `InputStream` as an example. For detailed information on this and other `addFileEntry` methods, see [Creating Files, Folders, and Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts). For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to create a file via the Documents and Media API: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 [`UploadPortletRequest`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upload/UploadPortletRequest.html) and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java 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); } ``` 3. Call the service reference's `addFileEntry` method with the data from the previous step. Note that this example does so inside the previous step's try-with-resources statement: ```java try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) { ... FileEntry fileEntry = _dlAppService.addFileEntry( repositoryId, folderId, sourceFileName, contentType, title, description, changeLog, inputStream, size, serviceContext); } ``` The method returns a `FileEntry` object, which this example sets to a variable for later use. Note, however, that you don't have to do this. You can find the full code for this example in the `updateFileEntry` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `updateFileEntry` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) [Creating Folders](/docs/7-2/frameworks/-/knowledge_base/f/creating-folders) [Creating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/01-creating-entities/02-creating-folders.markdown ================================================ --- header-id: creating-folders --- # Creating Folders [TOC levels=1-4] To create folders (`Folder` entities) in the Documents and Media library, you must use the [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) interface's `addFolder` method. The steps here show you how to do this. For more detailed information, see [Creating Files, Folders, and Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts). For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to create a folder with the `DLAppService` method `addFolder`: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html): ```java 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); ``` 3. Call the service reference's `addFolder` method with the data from the previous step: ```java Folder folder = _dlAppService.addFolder( repositoryId, parentFolderId, name, description, serviceContext); ``` The method returns a `Folder` object, which this example sets to a variable for later use. Note, however, that you don't have to do this. You can find the full code for this example in the `updateFolder` method of @product@'s [`EditFolderMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `Folder` actions that the Documents and Media app supports. Also note that this `updateFolder` method, as well as the rest of `EditFolderMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Updating Folders](/docs/7-2/frameworks/-/knowledge_base/f/updating-folders) [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders) [Copying Folders](/docs/7-2/frameworks/-/knowledge_base/f/copying-folders) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/01-creating-entities/03-creating-file-shortcuts.markdown ================================================ --- header-id: creating-file-shortcuts --- # Creating File Shortcuts [TOC levels=1-4] To create file shortcuts (`FileShortcut` entities) in the Documents and Media library, you must use the [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) interface's `addFileShortcut` method. The steps here show you how to do this. For more detailed information, see [Creating Files, Folders, and Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts). For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to create a file shortcut with the `DLAppService` method `addFileShortcut`: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java 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); ``` 3. Call the service reference's `addFileShortcut` method with the data from the previous step: ```java FileShortcut fileShortcut = _dlAppService.addFileShortcut( repositoryId, folderId, toFileEntryId, serviceContext); ``` The method returns a `FileShortcut` object, which this example sets to a variable for later use. Note, however, that you don't have to do this. You can find the full code for this example in the `updateFileShortcut` method of @product@'s [`EditFileShortcutMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileShortcut` actions that the Documents and Media app supports. Also note that this `updateFileShortcut` method, as well as the rest of `EditFileShortcutMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Deleting File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts) [Updating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/00-intro.markdown ================================================ --- header-id: deleting-entities --- # Deleting Entities [TOC levels=1-4] You can delete entities with the Documents and Media API. Note that the exact meaning of *delete* depends on the portal configuration and the delete operation you choose. This is because the [Recycle Bin](/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets), which is enabled by default, can be used to recover deleted items. Deletions via [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html), however, are permanent. To send items to the Recycle Bin, you must use the Capabilities API. Here, you'll learn about deleting these entities: - [Files](#files) - [File Versions](#file-versions) - [File Shortcuts](#file-shortcuts) - [Folders](#folders) You'll also learn about using the [Recycle Bin](#recycle-bin). ## Files There are two `DLAppService` methods you can use to delete files: - [`deleteFileEntry(long fileEntryId)`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFileEntry-long-) - [`deleteFileEntryByTitle(long repositoryId, long folderId, String title)`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFileEntryByTitle-long-long-java.lang.String-) These methods differ only in how they identify a file for deletion. The combination of the `folderId` and `title` parameters in `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 [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files). ## 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 `DLAppService` method `deleteFileVersion`: ```java deleteFileVersion(long fileEntryId, String version) ``` See this method's [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFileVersion-long-java.lang.String-) for a description of the parameters. For step-by-step instructions on using this method, see [Deleting File Versions](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-versions). ### 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 [`FileVersionVersionComparator`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/FileVersionVersionComparator.html). The following example creates such a comparator and uses its `compare` method to identify old file versions. The code does so by iterating through each [approved](/docs/7-2/user/-/knowledge_base/u/workflow) version of the file (`fileVersion`). Each iteration uses the `compare` method to test that file version (`fileVersion.getVersion()`) against the same file's current version (`fileEntry.getVersion()`). If this comparison is greater than `0`, then the iteration's file version (`fileVersion`) is old and is deleted by `deleteFileVersion`: ```java 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()); } } ``` ## File Shortcuts To delete file shortcuts, use the `DLAppService` method [`deleteFileShortcut`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFileShortcut-long-) with the ID of the shortcut you want to delete: ```java deleteFileShortcut(long fileShortcutId) ``` For step-by-step instructions on using this method, see [Deleting File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts). ## Folders 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: - [`deleteFolder(long folderId)`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFolder-long-) - [`deleteFolder(long repositoryId, long parentFolderId, String name)`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#deleteFolder-long-long-java.lang.String-) Which method you use is up to you---they both delete a folder. For step-by-step instructions on using these methods, see [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders). ## Recycle Bin Instead of deleting entities, you can move them to the [Recycle Bin](/docs/7-2/user/-/knowledge_base/u/restoring-deleted-assets). 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 [Moving Entities to the Recycle Bin](/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/01-deleting-files.markdown ================================================ --- header-id: deleting-files --- # Deleting Files [TOC levels=1-4] To delete a file with the Documents and Media API, you must use one of the two `deleteFileEntry*` methods discussed in [Deleting Entities](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to delete a file: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the arguments of the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish. Also note that this example gets only the file entry ID because it uses `deleteFileEntry`: ```java long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId"); ``` If you want to use `deleteFileEntryByTitle` instead, you can also get the repository ID, folder ID, and title from the request. 3. Call the service reference's `deleteFileEntry*` method you wish to use with the data from the previous step. This example calls `deleteFileEntry` with the file entry's ID: ```java _dlAppService.deleteFileEntry(fileEntryId); ``` You can find the full code for this example in the `deleteFileEntry` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `deleteFileEntry` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Moving Entities to the Recycle Bin](/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin) [Creating Files](/docs/7-2/frameworks/-/knowledge_base/f/creating-files) [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/02-deleting-file-versions.markdown ================================================ --- header-id: deleting-file-versions --- # Deleting File Versions [TOC levels=1-4] To delete a file version with the Documents and Media API, you must use the `deleteFileVersion` method discussed in [Deleting Entities](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to use `deleteFileVersion` to delete a file version: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. 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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can do this any way you wish: ```java long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId"); String version = ParamUtil.getString(actionRequest, "version"); ``` 3. Use the service reference to call the `deleteFileVersion` method with the file entry ID and version from the previous step: ```java _dlAppService.deleteFileVersion(fileEntryId, version); ``` You can find the full code for this example in the `deleteFileEntry` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `deleteFileEntry` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files) [Deleting File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts) [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders) [Moving Entities to the Recycle Bin](/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/03-deleting-file-shortcuts.markdown ================================================ --- header-id: deleting-file-shortcuts --- # Deleting File Shortcuts [TOC levels=1-4] To delete a file shortcut with the Documents and Media API, you must use the `deleteFileShortcut` method discussed in [Deleting Entities](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to delete a file shortcut: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. 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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can do this any way you wish: ```java long fileShortcutId = ParamUtil.getLong(actionRequest, "fileShortcutId"); ``` 3. Use the service reference to call the `deleteFileShortcut` method with the file shortcut ID from the previous step: ```java _dlAppService.deleteFileShortcut(fileShortcutId); ``` You can find the full code for this example in the `deleteFileShortcut` method of @product@'s [`EditFileShortcutMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileShortcut` actions that the Documents and Media app supports. Also note that this `deleteFileShortcut` method, as well as the rest of `EditFileShortcutMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Moving Entities to the Recycle Bin](/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin) [Creating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts) [Updating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/04-deleting-folders.markdown ================================================ --- header-id: deleting-folders --- # Deleting Folders [TOC levels=1-4] To delete a folder with the Documents and Media API, you must use one of the two `deleteFolder` methods discussed in [Deleting Entities](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to delete a folder: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the arguments of the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), 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 `deleteFolder(folderId)`: ```java long folderId = ParamUtil.getLong(actionRequest, "folderId"); ``` If you want to use the other `deleteFolder` method, you can also get the repository ID, parent folder ID, and folder name from the request. 3. Call the service reference's `deleteFolder` method you wish to use with the data from the previous step. This example calls `deleteFolder` with the folder's ID: ```java _dlAppService.deleteFolder(folderId); ``` You can find the full code for this example in the `deleteFolders` method of @product@'s [`EditFolderMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `Folder` actions that the Documents and Media app supports. Also note that this `deleteFolders` method, as well as the rest of `EditFolderMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Moving Entities to the Recycle Bin](/docs/7-2/frameworks/-/knowledge_base/f/moving-entities-to-the-recycle-bin) [Creating Folders](/docs/7-2/frameworks/-/knowledge_base/f/creating-folders) [Updating Folders](/docs/7-2/frameworks/-/knowledge_base/f/updating-folders) [Copying Folders](/docs/7-2/frameworks/-/knowledge_base/f/copying-folders) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/02-deleting-entities/05-moving-to-recycle-bin.markdown ================================================ --- header-id: moving-entities-to-the-recycle-bin --- # Moving Entities to the Recycle Bin [TOC levels=1-4] 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 [Deleting Entities](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities). 1. Verify that the repository supports the Recycle Bin. Do this by calling the [repository object's](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api#specifying-repositories) `isCapabilityProvided` method with `TrashCapability.class` as its argument. This example does so in `if` statement's condition: ```java if (repository.isCapabilityProvided(TrashCapability.class)) { // The code to move the entity to the Recycle Bin // You'll write this in the next step } ``` 2. Move the entity to the Recycle Bin if the repository supports it. To do this, first get a `TrashCapability` reference by calling the repository object's `getCapability` method with `TrashCapability.class` as its argument. Then call the `TrashCapability` method that moves the entity to the Recycle Bin. For example, this code calls `moveFileEntryToTrash` to move a file to the Recycle Bin: ```java if (repository.isCapabilityProvided(TrashCapability.class)) { TrashCapability trashCapability = repository.getCapability(TrashCapability.class); trashCapability.moveFileEntryToTrash(user.getUserId(), fileEntry); } ``` See the `TrashCapability` [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/capabilities/TrashCapability.html) for information on the methods you can use to move other types of entities to the Recycle Bin. ## Related Topics [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files) [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders) [Deleting File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/03-updating-entities/00-intro.markdown ================================================ --- header-id: updating-entities --- # Updating Entities [TOC levels=1-4] Like [creating](/docs/7-2/frameworks/-/knowledge_base/f/creating-files-folders-and-shortcuts) and [deleting](/docs/7-2/frameworks/-/knowledge_base/f/deleting-entities) 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: - [Files](#files) - [Folders](#folders) - [File Shortcuts](#file-shortcuts) ## Files Updating a file is a bit more complicated than [creating one](/docs/7-2/frameworks/-/knowledge_base/f/creating-files). 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: - Always provide all metadata. - Only provide the file's content when you want to change it. [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) has three `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: - [`updateFileEntry(..., byte[] bytes, ...)`](@platform-ref@/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-) - [`updateFileEntry(..., File file, ...)`](@platform-ref@/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-) - [`updateFileEntry(..., InputStream is, long size, ...)`](@platform-ref@/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-) Keep these things in mind when using these methods: - To retain the original file's title and description, you must provide those parameters to `updateFileEntry`. Omitting them deletes any existing title and description. - If you supply `null` in place of the file's content (e.g., `bytes`, `file`, or `is`), the update automatically uses the file's existing content. Do this only if you want to update the file's metadata. - If you use `false` for the `majorVersion` parameter, the update increments the file version by `0.1` (e.g., from `1.0` to `1.1`). If you use `true` for this parameter, the update increments the file version to the next `.0` value (e.g., from `1.0` to `2.0`, `1.1` to `2.0`, etc.). For a step-by-step guide on using these `updateFileEntry` methods, see [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files). ## Folders You can use the Documents and Media API to [copy or move](/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities) 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 `DLAppService` method `updateFolder`: ```java updateFolder(long folderId, String name, String description, ServiceContext serviceContext) ``` All parameters except the description are mandatory. For a full description of this method and its parameters, see its [Javadoc](@platform-ref@/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-). For step-by-step instructions on using this method, see [Updating Folders](/docs/7-2/frameworks/-/knowledge_base/f/updating-folders). ## File Shortcuts You can update a file shortcut (`FileShortcut` entities) to change the file it points to or the folder it resides in. Do this via the `DLAppService` method `updateFileShortcut`: ```java updateFileShortcut(long fileShortcutId, long folderId, long toFileEntryId, ServiceContext serviceContext) ``` 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 [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#updateFileShortcut-long-long-long-com.liferay.portal.kernel.service.ServiceContext-). For step-by-step instructions on using this method, see [Updating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/updating-file-shortcuts). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/03-updating-entities/01-updating-files.markdown ================================================ --- header-id: updating-files --- # Updating Files [TOC levels=1-4] To update a file with the Documents and Media API, you must use one of the three `updateFileEntry` methods discussed in [Updating Entities](/docs/7-2/frameworks/-/knowledge_base/f/updating-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Note that the example in these steps uses the `updateFileEntry` method that contains `InputStream`, but you can adapt the example to the other methods if you wish: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 [`UploadPortletRequest`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/upload/UploadPortletRequest.html) and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java 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); } ``` 3. Call the service reference's `updateFileEntry` method with the data from the previous step. Note that this example does so inside the previous step's try-with-resources statement: ```java try (InputStream inputStream = uploadPortletRequest.getFileAsStream("file")) { ... FileEntry fileEntry = _dlAppService.updateFileEntry( fileEntryId, sourceFileName, contentType, title, description, changeLog, majorVersion, inputStream, size, serviceContext); } ``` The method returns a `FileEntry` object, which this example sets to a variable for later use. Note, however, that you don't have to do this. You can find the full code for this example in the `updateFileEntry` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `updateFileEntry` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Creating Files](/docs/7-2/frameworks/-/knowledge_base/f/creating-files) [Deleting Files](/docs/7-2/frameworks/-/knowledge_base/f/deleting-files) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/03-updating-entities/02-updating-folders.markdown ================================================ --- header-id: updating-folders --- # Updating Folders [TOC levels=1-4] To update a folder with the Documents and Media API, you must use the `updateFolder` method discussed in [Updating Entities](/docs/7-2/frameworks/-/knowledge_base/f/updating-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to update a folder: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java 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); ``` 3. Call the service reference's `updateFolder` method with the data from the previous step: ```java _dlAppService.updateFolder(folderId, name, description, serviceContext); ``` You can find the full code for this example in the `updateFolder` method of @product@'s [`EditFolderMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `Folder` actions that the Documents and Media app supports. Also note that this `updateFolder` method, as well as the rest of `EditFolderMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Creating Folders](/docs/7-2/frameworks/-/knowledge_base/f/creating-folders) [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders) [Copying Folders](/docs/7-2/frameworks/-/knowledge_base/f/copying-folders) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/03-updating-entities/03-updating-file-shortcuts.markdown ================================================ --- header-id: updating-file-shortcuts --- # Updating File Shortcuts [TOC levels=1-4] To update a file shortcut with the Documents and Media API, you must use the `updateFileShortcut` method discussed in [Updating Entities](/docs/7-2/frameworks/-/knowledge_base/f/updating-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to update a file shortcut: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java 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); ``` 3. Call the service reference's `updateFileShortcut` method with the data from the previous step: ```java _dlAppService.updateFileShortcut( fileShortcutId, folderId, toFileEntryId, serviceContext); ``` You can find the full code for this example in the `updateFileShortcut` method of @product@'s [`EditFileShortcutMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileShortcut` actions that the Documents and Media app supports. Also note that this `updateFileShortcut` method, as well as the rest of `EditFileShortcutMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Creating File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/creating-file-shortcuts) [Deleting File Shortcuts](/docs/7-2/frameworks/-/knowledge_base/f/deleting-file-shortcuts) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/04-file-checkout/00-intro.markdown ================================================ --- header-id: file-checkout-and-checkin --- # File Checkout and Checkin [TOC levels=1-4] Users can [check out files](/docs/7-2/user/-/knowledge_base/u/checking-out-and-editing-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: - [File Checkout](file-checkout) - [File Checkin](file-checkin) - [Canceling a Checkout](canceling-a-checkout) ## File Checkout Here's what happens when you check out a file: - 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. - Other users can't change or edit any version of the file. This state remains until you cancel or check in your changes. The main `DLAppService` method for checking out a file is this `checkOutFileEntry` method: ```java checkOutFileEntry(long fileEntryId, ServiceContext serviceContext) ``` 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 [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#checkOutFileEntry-long-com.liferay.portal.kernel.service.ServiceContext-). For step-by-step instructions on using this method, see [Checking Out Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files). ### Fine-tuning Checkout You can control how the checkout is performed by setting the following attributes in the `checkOutFileEntry` method's `ServiceContext` parameter: - `manualCheckInRequired`: By default, the system automatically checks out/in a file when a user edits it. Setting this attribute to `true` prevents this, therefore requiring manual checkout and checkin. - `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 `0`. - `fileVersionUuid`: This is used by [staging](/docs/7-2/user/-/knowledge_base/u/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. To set these attributes, use the `ServiceContext` method [`setAttribute(String name, Serializable value)`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/service/ServiceContext.html#setAttribute-java.lang.String-java.io.Serializable-). Here's an example of setting the `manualCheckInRequired` attribute to `true`: ```java serviceContext.setAttribute("manualCheckInRequired", Boolean.TRUE) ``` ## 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 `DLAppService` method for checking in a file is `checkInFileEntry`: ```java checkInFileEntry(long fileEntryId, boolean majorVersion, String changeLog, ServiceContext serviceContext) ``` For a full description of the method and its parameters, see its [Javadoc](@platform-ref@/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-). This method uses the private working copy to create a new version of the file. As [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) explains, the `majorVersion` parameter's setting determines how the file's version number is incremented. For step-by-step instructions on using this method, see [Checking In Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files). ## 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 `DLAppService` method `cancelCheckOut`: ```java cancelCheckOut(long fileEntryId) ``` For a full description of this method and its parameter, see its [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#cancelCheckOut-long-). 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 [Canceling a Checkout](/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/04-file-checkout/01-file-checkout.markdown ================================================ --- header-id: checking-out-files --- # Checking Out Files [TOC levels=1-4] To check out a file with the Documents and Media API, use the `checkOutFileEntry` method discussed in [File Checkout and Checkin](/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to check out a file: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId"); ServiceContext serviceContext = ServiceContextFactory.getInstance(actionRequest); ``` 3. Call the service reference's `checkOutFileEntry` method with the data from the previous step: ```java _dlAppService.checkOutFileEntry(fileEntryId, serviceContext); ``` You can find the full code for this example in the `checkOutFileEntries` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `checkOutFileEntries` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Checking In Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files) [Canceling a Checkout](/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout) [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/04-file-checkout/02-file-checkin.markdown ================================================ --- header-id: checking-in-files --- # Checking In Files [TOC levels=1-4] To check in a file with the Documents and Media API, use the `checkInFileEntry` method discussed in [File Checkout and Checkin](/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to use `checkInFileEntry` to check in a file: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId"); boolean majorVersion = ParamUtil.getBoolean(actionRequest, "majorVersion"); String changeLog = ParamUtil.getString(actionRequest, "changeLog"); ServiceContext serviceContext = ServiceContextFactory.getInstance(actionRequest); ``` 3. Call the service reference's `checkInFileEntry` method with the data from the previous step: ```java _dlAppService.checkInFileEntry( fileEntryId, majorVersion, changeLog, serviceContext); ``` You can find the full code for this example in the `checkInFileEntries` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `checkInFileEntries` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Checking Out Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files) [Canceling a Checkout](/docs/7-2/frameworks/-/knowledge_base/f/canceling-a-checkout) [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/04-file-checkout/03-cancel-checkout.markdown ================================================ --- header-id: canceling-a-checkout --- # Canceling a Checkout [TOC levels=1-4] To cancel a checkout with the Documents and Media API, use the `cancelCheckOut` method discussed in [File Checkout and Checkin](/docs/7-2/frameworks/-/knowledge_base/f/file-checkout-and-checkin). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to cancel a checkout: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. 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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get it any way you wish: ```java long fileEntryId = ParamUtil.getLong(actionRequest, "fileEntryId"); ``` 3. Call the service reference's `cancelCheckOut` method with the file's ID: ```java _dlAppService.cancelCheckOut(fileEntryId); ``` You can find the full code for this example in the `cancelFileEntriesCheckOut` method of @product@'s [`EditFileEntryMVCActionCommand`](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) class. This class uses the Documents and Media API to implement almost all the `FileEntry` actions that the Documents and Media app supports. Also note that this `cancelFileEntriesCheckOut` method, as well as the rest of `EditFileEntryMVCActionCommand`, contains additional logic to suit the specific needs of the Documents and Media app. ## Related Topics [Checking Out Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-out-files) [Checking In Files](/docs/7-2/frameworks/-/knowledge_base/f/checking-in-files) [Updating Files](/docs/7-2/frameworks/-/knowledge_base/f/updating-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/05-copying-moving-entities/00-intro.markdown ================================================ --- header-id: copying-and-moving-entities --- # Copying and Moving Entities [TOC levels=1-4] 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: - There's no way to copy files---you can only copy folders. However, copying a folder also copies its contents, which can include files. - Folders can only be copied within their current repository. 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: - [Copying Folders](#copying-folders) - [Moving Folders and Files](#moving-folders-and-files) ## 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 `DLAppService` method `copyFolder`: ```java copyFolder(long repositoryId, long sourceFolderId, long parentFolderId, String name, String description, ServiceContext serviceContext) ``` For a full description of the method and its parameters, see its [Javadoc](@platform-ref@/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-). For step-by-step instructions on using this method, see [Copying Folders](/docs/7-2/frameworks/-/knowledge_base/f/copying-folders). ## 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. | **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. To move a folder, use the `DLAppService` method `moveFolder`: ```java moveFolder(long folderId, long parentFolderId, ServiceContext serviceContext) ``` For a full description of this method and its parameters, see its [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#moveFolder-long-long-com.liferay.portal.kernel.service.ServiceContext-). This method is similar to `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 `DLAppService` method `moveFileEntry`: ```java moveFileEntry(long fileEntryId, long newFolderId, ServiceContext serviceContext) ``` For a full description of this method and its parameters, see its [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#moveFileEntry-long-long-com.liferay.portal.kernel.service.ServiceContext-). For step-by-step instructions on using `moveFolder` and `moveFileEntry`, see [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/05-copying-moving-entities/01-copying-folders.markdown ================================================ --- header-id: copying-folders --- # Copying Folders [TOC levels=1-4] To copy a folder with the Documents and Media API, use the `copyFolder` method discussed in [Copying and Moving Entities](/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to use `copyFolder` to copy a folder: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the `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---@product@ supplies a constant for specifying a repository's root folder. In the following code, [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) gets the folder's ID from the request (`javax.portlet.ActionRequest`), and the service reference's [`getFolder`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#getFolder-long-) method gets the corresponding folder object. The folder's `getGroupId()`, `getName()`, and `getDescription()` methods then get the folder's group ID, name, and description, respectively: ```java 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); ``` 3. Call the service reference's `copyFolder` method with the data from the previous step. Note that this example uses the [`DLFolderConstants`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html) constant `DEFAULT_PARENT_FOLDER_ID` to specify the repository's root folder as the destination folder: ```java _dlAppService.copyFolder( groupId, folderId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, folderName, folderDescription, serviceContext); ``` 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. ## Related Topics [Creating Folders](/docs/7-2/frameworks/-/knowledge_base/f/creating-folders) [Updating Folders](/docs/7-2/frameworks/-/knowledge_base/f/updating-folders) [Deleting Folders](/docs/7-2/frameworks/-/knowledge_base/f/deleting-folders) [Moving Folders and Files](/docs/7-2/frameworks/-/knowledge_base/f/moving-folders-and-files) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/05-copying-moving-entities/02-moving-folders-files.markdown ================================================ --- header-id: moving-folders-and-files --- # Moving Folders and Files [TOC levels=1-4] To move folders and files with the Documents and Media API, use the `moveFolder` and `moveFileEntry` methods discussed in [Copying and Moving Entities](/docs/7-2/frameworks/-/knowledge_base/f/copying-and-moving-entities). The steps here show you how. For general information on using the API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to use `moveFolder` and `moveFileEntry` to move a folder and a file, respectively. This example does both to demonstrate the procedures: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. 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 `javax.portlet.ActionRequest` and [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html), but you can get the data any way you wish: ```java // 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); ``` 3. Call the service reference's method(s). This example calls `moveFolder` to move a folder (`folderId`) to a different folder (`newFolderId`). It then calls `moveFileEntry` to move a file (`fileEntryId`) to the same destination folder: ```java _dlAppService.moveFolder(folderId, newFolderId, serviceContext); _dlAppService.moveFileEntry(fileEntryId, newFolderId, serviceContext); ``` ## Related Topics [Copying Folders](/docs/7-2/frameworks/-/knowledge_base/f/copying-folders) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/06-getting-entities/00-intro.markdown ================================================ --- header-id: getting-entities --- # Getting Entities [TOC levels=1-4] The Documents and Media API contains many methods for getting entities from a repository. Most methods in `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 `DLAppService` methods in its [reference documentation](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html). Here, you'll learn about getting these entities: - [Files](#files) - [Folders](#folders) - [Multiple Entity Types](#multiple-entity-types) ## Files 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: `getFileEntries`: Gets files from a specific repository. `getGroupFileEntries`: Gets files from a Site (group), regardless of repository. Since these method families are common, their methods share many parameters: `repositoryId`: The ID of the repository to get files from. To specify the default Site repository, use the `groupId` (Site ID). `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 `DLFolderConstants.DEFAULT_PARENT_FOLDER_ID`. `start` and `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 `QueryUtil.ALL_POS` for these parameters. `obc`: The comparator to use to order collection items. Comparators are [`OrderByComparator`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/OrderByComparator.html) implementations that sort collection items. `fileEntryTypeId`: The ID of the file type to retrieve. Use this to retrieve files of a specific type. `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 [`ContentTypes`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html). Note that the `obc` parameter must be an implementation of `OrderByComparator`. Although you can implement your own comparators, @product@ already contains a few useful implementations in the package [`com.liferay.document.library.kernel.util.comparator`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/package-summary.html): `RepositoryModelCreateDateComparator`: Sorts by creation date. `RepositoryModelModifiedDateComparator`: Sorts by modification date. `RepositoryModelReadCountComparator`: Sorts by number of views. `RepositoryModelSizeComparator`: Sorts by file size. `RepositoryModelTitleComparator`: Sorts by title. See [Getting Files](/docs/7-2/frameworks/-/knowledge_base/f/getting-files) for step-by-step instructions on using the above method families. ## Folders 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 *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 `includeMountFolders`. Setting this parameter to `true` includes mount folders in the results, while omitting it or setting it to `false` excludes them. For example, to get a list of a parent folder's subfolders from a repository, including any mount folders, use this [`getFolders`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#getFolders-long-long-boolean-) method: ```java getFolders(long repositoryId, long parentFolderId, boolean includeMountFolders) ``` Note that there are several other `getFolders` methods in `DLAppService`. Use the one that best matches your use case. See [Getting Folders](/docs/7-2/frameworks/-/knowledge_base/f/getting-folders) for step-by-step instructions on using these `getFolders` methods. ## 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 [`getFileEntriesAndFileShortcuts`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#getFileEntriesAndFileShortcuts-long-long-int-int-int-) method gets files and shortcuts from a given repository and folder. Its `status` parameter specifies a [workflow](/docs/7-2/user/-/knowledge_base/u/workflow) status. As before, the `start` and `end` parameters control pagination of the entities: ```java getFileEntriesAndFileShortcuts(long repositoryId, long folderId, int status, int start, int end) ``` For step-by-step instructions on calling this method and others like it, see [Getting Multiple Entity Types](/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types). To see all such methods, see the `DLAppService` [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html). ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/06-getting-entities/01-getting-files.markdown ================================================ --- header-id: getting-files --- # Getting Files [TOC levels=1-4] To get files with the Documents and Media API, use a method from the `getFileEntries` or `getGroupFileEntries` method families discussed in [Getting Entities](/docs/7-2/frameworks/-/knowledge_base/f/getting-entities). The steps here show you how, using this [`getFileEntries`](@platform-ref@/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-) method as an example: ```java List getFileEntries( long repositoryId, long folderId, String[] mimeTypes, int start, int end, OrderByComparator obc ) ``` For general information on using the Documents and Media API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to get a list of files. This example uses the above `getFileEntries` method to get all the PNG images from the root folder of a Site's default repository, sorted by title: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the method's arguments. You can do this any way you wish. As the next step describes, @product@ provides constants and a comparator for all the arguments this example needs besides the group ID. This example gets the group ID by using [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) with the request (`javax.portlet.ActionRequest`): ```java long groupId = ParamUtil.getLong(actionRequest, "groupId"); ``` It's also possible to get the group ID via the [`ThemeDisplay`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html). Calling the `ThemeDisplay` method `getScopeGroupId()` gets the ID of your app's current site (group): ```java ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); long groupId = themeDisplay.getScopeGroupId(); ``` 3. 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 `getFileEntries` method with the group ID from the previous step, and constants and a comparator for the remaining arguments: ```java List fileEntries = _dlAppService.getFileEntries( groupId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, new String[] {ContentTypes.IMAGE_PNG}, QueryUtil.ALL_POS, QueryUtil.ALL_POS, new RepositoryModelTitleComparator<>() ); ``` Here's a description of the arguments used in this example: `groupId`: Using the group ID as the repository ID specifies that the operation takes place in the default site repository. `DLFolderConstants.DEFAULT_PARENT_FOLDER_ID`: Uses the [`DLFolderConstants`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html) constant `DEFAULT_PARENT_FOLDER_ID` to specify the repository's root folder. `new String[] {ContentTypes.IMAGE_PNG}`: Uses the [`ContentTypes`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ContentTypes.html) constant `IMAGE_PNG` to specify PNG images. `QueryUtil.ALL_POS`: Uses the [`QueryUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/QueryUtil.html) constant `ALL_POS` for the start and end positions in the results. This specifies all results, bypassing pagination. `new RepositoryModelTitleComparator<>()`: Creates a new [`RepositoryModelTitleComparator`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/util/comparator/RepositoryModelTitleComparator.html), which sorts the results by title. Remember, this is just one of many `getFileEntries` and `getGroupFileEntries` methods. To see all such methods, see the `DLAppService` [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html). ## Related Topics [Getting Folders](/docs/7-2/frameworks/-/knowledge_base/f/getting-folders) [Getting Multiple Entity Types](/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/06-getting-entities/02-getting-folders.markdown ================================================ --- header-id: getting-folders --- # Getting Folders [TOC levels=1-4] To get folders with the Documents and Media API, use one of the `getFolders` methods in `DLAppService`. This is discussed in more detail in [Getting Entities](/docs/7-2/frameworks/-/knowledge_base/f/getting-entities). The steps here show you how to call these `getFolders` methods. As an example, this method is used to get a parent folder's subfolders: ```java getFolders(long repositoryId, long parentFolderId, boolean includeMountFolders) ``` For general information on using the Documents and Media API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/documents-and-media-api). Follow these steps to call a `getFolders` method: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. Get the data needed to populate the method's arguments any way you wish. This `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 (`javax.portlet.ActionRequest`) via [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html): ```java long groupId = ParamUtil.getLong(actionRequest, "groupId"); ``` It's also possible to get the group ID via the [`ThemeDisplay`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/theme/ThemeDisplay.html). Calling the `ThemeDisplay` method `getScopeGroupId()` gets the ID of your app's current Site (group). ```java ThemeDisplay themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY); long groupId = themeDisplay.getScopeGroupId(); ``` Note that getting the parent folder ID isn't necessary because this example uses the root folder, for which @product@ provides a constant. Also, the boolean value can be provided directly---it doesn't need to be retrieved from somewhere. 3. Call the service reference's `getFolders` method with the data from the previous step and any other values you want to provide. Note that this example uses `DLFolderConstants.DEFAULT_PARENT_FOLDER_ID` to specify the repository's root folder as the parent folder. It also uses `true` to include any mount folders in the results: ```java _dlAppService.getFolders(groupId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, true) ``` This is one of many methods you can use to get folders. The rest are listed in the `DLAppService` [Javadoc](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html). ## Related Topics [Getting Files](/docs/7-2/frameworks/-/knowledge_base/f/getting-files) [Getting Multiple Entity Types](/docs/7-2/frameworks/-/knowledge_base/f/getting-multiple-entity-types) ================================================ FILE: en/developer/frameworks/articles/collaboration/02-documents-media-api/06-getting-entities/03-getting-multiple-types.markdown ================================================ --- header-id: getting-multiple-entity-types --- # Getting Multiple Entity Types [TOC levels=1-4] There are several methods in [`DLAppService`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html) that get lists containing multiple entity types. This is discussed in more detail in [Getting Entities](/docs/7-2/frameworks/-/knowledge_base/f/getting-entities). The steps here show you how to use the [`getFileEntriesAndFileShortcuts`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/service/DLAppService.html#getFileEntriesAndFileShortcuts-long-long-int-int-int-) method, but you can apply them to other such methods as well. For general information on using the Documents and Media API, see [Documents and Media API](/docs/7-2/frameworks/-/knowledge_base/f/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: 1. Get a reference to `DLAppService`: ```java @Reference private DLAppService _dlAppService; ``` 2. 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 (`javax.portlet.ActionRequest`) via [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html): ```java long groupId = ParamUtil.getLong(actionRequest, "groupId"); ``` Getting the parent folder ID, workflow status, and start and end parameters isn't necessary because @product@ provides constants for them. The next step shows this in detail. 3. Call the service reference method with the data from the previous step and any other values you want to provide. This example calls `getFileEntriesAndFileShortcuts` with the group ID from the previous step and constants for the remaining arguments: ```java _dlAppService.getFileEntriesAndFileShortcuts( groupId, DLFolderConstants.DEFAULT_PARENT_FOLDER_ID, WorkflowConstants.STATUS_APPROVED, QueryUtil.ALL_POS, QueryUtil.ALL_POS ) ``` Here's a description of the arguments used in this example: - `groupId`: Using the group ID as the repository ID specifies that the operation takes place in the default site repository. - `DLFolderConstants.DEFAULT_PARENT_FOLDER_ID`: Uses the [`DLFolderConstants`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/document/library/kernel/model/DLFolderConstants.html) constant `DEFAULT_PARENT_FOLDER_ID` to specify the repository's root folder. - `WorkflowConstants.STATUS_APPROVED`: Uses the [`WorkflowConstants`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/workflow/WorkflowConstants.html) constant `STATUS_APPROVED` to specify only files/folders that have been approved via workflow. - `QueryUtil.ALL_POS`: Uses the [`QueryUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/dao/orm/QueryUtil.html) constant `ALL_POS` for the start and end positions in the results. This specifies all results, bypassing pagination. ## Related Topics [Getting Files](/docs/7-1/frameworks/-/knowledge_base/frameworks/getting-files) [Getting Folders](/docs/7-1/frameworks/-/knowledge_base/frameworks/getting-folders) ================================================ FILE: en/developer/frameworks/articles/collaboration/03-adaptive-media/00-intro.markdown ================================================ --- header-id: adaptive-media --- # Adaptive Media [TOC levels=1-4] The [Adaptive Media](/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices) app tailors the size and quality of images to the device displaying them. Here, you'll learn about these things: - [The Adaptive Media Taglib](#the-adaptive-media-taglib) - [Adaptive Media's Finder API](#adaptive-medias-finder-api) - [Image Scaling in Adaptive Media](#image-scaling-in-adaptive-media) ## The Adaptive Media Taglib To display adapted images in your apps, Adaptive Media offers a convenient tag library in the module [`com.liferay.adaptive.media.image.taglib`](https://github.com/liferay/com-liferay-adaptive-media/tree/master/adaptive-media-image-taglib). The only mandatory attribute for the taglib is `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 `class`, `style`, `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 [Displaying Adapted Images in Your App](/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app). ## Adaptive Media's 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 `` 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. ### Calling the API The entry point to Adaptive Media's API is [`AMImageFinder`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageFinder.html). To use it, you must first inject the OSGi component in your class (which must also be an OSGi component) as follows: ```java @Reference private AMImageFinder _amImageFinder; ``` This makes an `AMImageFinder` instance available. It has one method, `getAdaptiveMediaStream`, that returns a stream of [`AdaptiveMedia`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/AdaptiveMedia.html) objects. This method takes a `Function` that creates an [`AMQuery`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/finder/AMQuery.html) (the query for adapted images) via [`AMImageQueryBuilder`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html), which can search adapted images based on different attributes (e.g., width, height, order, etc.). The `AMImageQueryBuilder` methods you call depend on the exact query you want to construct. For example, here's a general `getAdaptiveMediaStream` call: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.methodToCall(arg).done()); ``` The argument to `getAdaptiveMediaStream` is a lambda expression that returns an `AMQuery` constructed via `AMImageQueryBuilder`. Note that `methodToCall(arg)` is a placeholder for the `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 `done()` call that follows this, however, isn't a placeholder--it creates and returns the `AMQuery` regardless of which `AMImageQueryBuilder` methods you call. For more information on creating `AMQuery` instances, see the `AMImageQueryBuilder` [Javadoc](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html). For step-by-step instructions on calling Adaptive Media's API, see [Finding Adapted Images](/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images). ### Adaptive Media API Constants When calling the Adaptive Media API, there are some constants you can use for specifying common attributes: - `AMImageAttribute.AM_IMAGE_ATTRIBUTE_WIDTH`: image width - `AMImageAttribute.AM_IMAGE_ATTRIBUTE_HEIGHT`: image height - `AMImageQueryBuilder.SortOrder.ASC`: ascending sort - `AMImageQueryBuilder.SortOrder.DESC`: descending sort ### 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 *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 [`String.compareTo`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#compareTo-java.lang.String-) method. ## Image Scaling in Adaptive Media As described in [Adaptive Media's user guide](/docs/7-2/user/-/knowledge_base/u/adapting-your-media-across-multiple-devices), Adaptive Media scales images to match the image resolutions defined by the @product@ administrator. The default scaling is usually suitable, but Adaptive Media contains an extension point that lets you replace the way it scales images. The [`AMImageScaler`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/scaler/AMImageScaler.html) interface defines Adaptive Media's image scaling logic. Out of the box, Adaptive Media provides two implementations of this interface: [`AMDefaultImageScaler`](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): The default image scaler. It's always enabled and uses `java.awt` for its image processing and scaling. [`AMGIFImageScaler`](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): A scaler that works only with GIF images. It depends on the installation of the external tool [gifsicle](https://www.lcdf.org/gifsicle/) in the @product@ instance. This scaler is disabled by default. Administrators can enable it in *Control Panel* → *System Settings*. You must register image scalers in @product@'s OSGi container using the `AMImageScaler` interface. Each scaler must also set the `mime.type` property to the MIME type it handles. For example, if you set a scaler's MIME type to `image/jpeg`, then that scaler can only handle `image/jpeg` images. If you specify the special MIME type `*`, the scaler can process any image. Note that `AMDefaultImageScaler` is registered using `mime.type=*`, while `AMGIFImageScaler` is registered using `mime.type=image/gif`. Both scalers, like all scalers, implement `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: 1. Select only the image scalers registered with the same MIME type as the image. 2. Select the enabled scalers from those selected in the first step (the `AMImageScaler` method `isEnabled()` returns `true` for enabled scalers). 3. Of the scalers selected in the second step, select the one with the highest `service.ranking`. If these steps return no results, they're repeated with the special MIME type `*`. Also note that if an image scaler is registered for specific MIME types and has a higher `service.ranking`, it's more likely to be chosen than if it's registered for the special MIME type `*` or has a lower `service.ranking`. For step-by-step instructions on creating your own image scaler, see [Creating an Image Scaler](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler). ================================================ FILE: en/developer/frameworks/articles/collaboration/03-adaptive-media/01-displaying-adapted-images.markdown ================================================ --- header-id: displaying-adapted-images-in-your-app --- # Displaying Adapted Images in Your App [TOC levels=1-4] Follow these steps to display adapted images in your app with the Adaptive Media [taglib](https://github.com/liferay/com-liferay-adaptive-media/tree/master/adaptive-media-image-taglib). For more information, see [The Adaptive Media Taglib](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media#the-adaptive-media-taglib). 1. Include the taglib dependency in your project. For example, if you're using Gradle you must add the following line in your project's `build.gradle` file: ```groovy provided group: "com.liferay", name: "com.liferay.adaptive.media.image.taglib", version: "1.0.0" ``` 2. Declare the taglib in your JSP: ```markup <%@ taglib uri="http://liferay.com/tld/adaptive-media-image" prefix="liferay-adaptive-media" %> ``` 3. Use the taglib wherever you want the adapted image to appear in your app's JSP files: ```markup ``` For example, this `view.jsp` uses the taglib to display the adapted images in a grid with the `col-md-6` [column container class](/docs/7-2/frameworks/-/knowledge_base/f/layout-templates-intro): ```markup <%@ 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); %>
    <% columns++; } %>
    ``` Looking at the generated markup, you can see that it uses the `` tag as described in [Creating Content with Adapted Images](/docs/7-2/user/-/knowledge_base/u/creating-content-with-adapted-images). ![Figure 1: The Adaptive Media Samples app shows all the site's adapted images.](../../../images/adaptive-media-sample.png) ## Related Topics [Adaptive Media](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media) [Finding Adapted Images](/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images) [Creating an Image Scaler](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler) ================================================ FILE: en/developer/frameworks/articles/collaboration/03-adaptive-media/02-finding-adapted-images.markdown ================================================ --- header-id: finding-adapted-images --- # Finding Adapted Images [TOC levels=1-4] If you need more control than the [Adaptive Media taglib](/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app) 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: - [Getting Adapted Images for File Versions](#getting-adapted-images-for-file-versions) - [Getting the Adapted Images for a Specific Image Resolution](#getting-the-adapted-images-for-a-specific-image-resolution) - [Getting Adapted Images in a Specific Order](#getting-adapted-images-in-a-specific-order) - [Using Approximate Attributes](#using-approximate-attributes) - [Using the Adaptive Media Stream](#using-the-adaptive-media-stream) For background information on these topics, see [Adaptive Media's Finder API](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media#adaptive-medias-finder-api). ## 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 [enabled image resolutions](/docs/7-2/user/-/knowledge_base/u/managing-image-resolutions): 1. Get an `AMImageFinder` reference: ```java @Reference private AMImageFinder _amImageFinder; ``` 2. To get adapted images for a specific file version, call the [`AMImageQueryBuilder`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.html) method `forFileVersion` with a [`FileVersion`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/model/FileVersion.html) object as an argument: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion).done()); ``` 3. To get the adapted images for the latest approved file version, use the `forFileEntry` method with a [`FileEntry`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/repository/model/FileEntry.html) object: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileEntry(fileEntry).done()); ``` To get adapted images regardless of status (enabled/disabled image resolutions), invoke the `withConfigurationStatus` method with the constant `AMImageQueryBuilder.ConfigurationStatus.ANY`: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion) .withConfigurationStatus(AMImageQueryBuilder.ConfigurationStatus.ANY).done()); ``` Use the constant `AMImageQueryBuilder.ConfigurationStatus.DISABLED` to get adapted images for only disabled image resolutions. ## Getting the Adapted Images for a Specific Image Resolution By providing an image resolution's UUID to `AMImageFinder`, you can get that resolution's adapted images. This UUID is defined when [adding the resolution](/docs/7-2/user/-/knowledge_base/u/adding-image-resolutions) in the Adaptive Media app. To get a resolution's adapted images, you must pass that resolution's UUID to the `forConfiguration` method. Follow these steps to get adapted images for an image resolution: 1. Get an `AMImageFinder` reference: ```java @Reference private AMImageFinder _amImageFinder; ``` 2. Call the [`AMImageQueryBuilder.ConfigurationStep`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/image/finder/AMImageQueryBuilder.ConfigurationStep.html) method `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 `hd-resolution`. It returns the adapted images regardless of whether the resolution is enabled or disabled: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(fileVersion) .forConfiguration("hd-resolution").done()); ``` ## Getting Adapted Images in a Specific Order It's also possible to define the order in which `getAdaptiveMediaStream` returns adapted images. Follow these steps to do so: 1. Get an `AMImageFinder` reference: ```java @Reference private AMImageFinder _amImageFinder; ``` 2. Call the `orderBy` method with your sort criteria just before calling the `done()` method. The `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 [constants](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media#adaptive-media-api-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: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(_fileVersion) .withConfigurationStatus(AMImageQueryBuilder.ConfigurationStatus.ANY) .orderBy(AMImageAttribute.AM_IMAGE_ATTRIBUTE_WIDTH, AMImageQueryBuilder.SortOrder.ASC) .done()); ``` ## Using Approximate Attributes You can use the API to get adapted images that match approximate attribute values. Follow these steps to do so: 1. Get an `AMImageFinder` reference: ```java @Reference private AMImageFinder _amImageFinder; ``` 2. Call the `with` method with your search criteria just before calling the `done()` method. The `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: ```java Stream> adaptiveMediaStream = _amImageFinder.getAdaptiveMediaStream( amImageQueryBuilder -> amImageQueryBuilder.forFileVersion(_fileVersion) .with(AMImageAttribute.AM_IMAGE_ATTRIBUTE_HEIGHT, 400).done()); ``` ## 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 [`AdaptiveMedia`](@app-ref@/adaptive-media/latest/javadocs/com/liferay/adaptive/media/AdaptiveMedia.html) stream, you can get the information you need from it. For example, this code prints the URI for each adapted image: ```java adaptiveMediaStream.forEach( adaptiveMedia -> { System.out.println(adaptiveMedia.getURI()); } ); ``` You can also get other values and attributes from the `AdaptiveMedia` stream. Here are a few examples: ```java // Get the InputStream adaptiveMedia.getInputStream() // Get the content length adaptiveMedia.getValueOptional(AMAttribute.getContentLengthAMAttribute()) // Get the image height adaptiveMedia.getValueOptional(AMImageAttribute.AM_IMAGE_ATTRIBUTE_HEIGHT) ``` ## Related Topics [Adaptive Media](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media) [Displaying Adapted Images in Your App](/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app) [Creating an Image Scaler](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-image-scaler) ================================================ FILE: en/developer/frameworks/articles/collaboration/03-adaptive-media/03-image-scaler.markdown ================================================ --- header-id: creating-an-image-scaler --- # Creating an Image Scaler [TOC levels=1-4] Adaptive Media scales images to match the image resolutions defined by the @product@ 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 [Image Scaling in Adaptive Media](/docs/7-2/frameworks/-/knowledge_base/f/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: 1. Create your scaler class to implement `AMImageScaler`. You must also annotate your scaler class with `@Component`, setting `mime.type` properties for each of the scaler's MIME types, and registering an `AMImageScaler` service. If there's more than one scaler for the same MIME type, you must also set the `@Component` annotation's `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 `service.ranking` isn't set, it defaults to `0`. | **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`. 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; ``` 2. Implement the `isEnabled()` method to return `true` when you want to enable the scaler. In many cases, you always want the scaler enabled, so you can simply return `true` in this method. This is the case with the image scaler in this example: ```java @Override public boolean isEnabled() { return true; } ``` This method gets more interesting when the scaler depends on other tools or features. For example, the `isEnabled()` method in `AMGIFImageScaler` determines whether gifsicle is enabled. This scaler must only be enabled when the tool it depends on, gifsicle, is also enabled: ```java @Override public boolean isEnabled() { return _amImageConfiguration.gifsicleEnabled(); } ``` 3. Implement the `scaleImage` method. This method contains the scaler's business logic and must return an `AMImageScaledImage` instance. For example, the `scaleImage` implementation in this example uses `AMImageConfigurationEntry` to get the maximum height and width values for the scaled image, and `FileVersion` to get the image to scale. The scaling is done via a private inner class, assuming that the methods `_scalePNG`, `_getScalePNGHeight`, `_getScalePNGWidth`, and `_getScalePNGSize` implement the actual scaling: ```java @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; } ``` This requires these imports: ```java 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; ``` ## Related Topics [Adaptive Media](/docs/7-2/frameworks/-/knowledge_base/f/adaptive-media) [Displaying Adapted Images in Your App](/docs/7-2/frameworks/-/knowledge_base/f/displaying-adapted-images-in-your-app) [Finding Adapted Images](/docs/7-2/frameworks/-/knowledge_base/f/finding-adapted-images) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/00-intro.markdown ================================================ --- header-id: social-api --- # Social API [TOC levels=1-4] You can use the social API to integrate @product@'s social features with your apps. Here, you'll learn about the following topics: - [Social Bookmarks](#social-bookmarks) - [Adding Comments to Your App](/docs/7-2/frameworks/-/knowledge_base/f/adding-comments-to-your-app) - [Ratings](#ratings) - [Flagging Inappropriate Asset Content](/docs/7-2/frameworks/-/knowledge_base/f/flagging-inappropriate-asset-content) ## Social Bookmarks To apply social bookmarks to your app's content, you must use the `liferay-social-bookmarks` taglib. This taglib contains the `liferay-social-bookmarks:bookmarks` tag, which adds the social bookmarks component. This tag contains these attributes: `className`: The entity's class name. `classPK`: The entity's primary key. `displayStyle`: The social bookmarks' display style. Possible values are `inline`, which displays them in a row, and `menu`, which hides them in a menu. `title`: A title for the content being shared. This attribute is often populated by calling the entity's `getTitle()` method (or other method that retrieves the title). `types`: A comma-delimited list of the social media services to use (e.g., `facebook,twitter`). To use every social media service available in the portal, omit this attribute or use `<%= null %>` for its value. `url`: A URL to the portal content being shared. The `PortalUtil` method `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 [Javadoc](@platform-ref@/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-). For instructions on using this tag, see [Applying Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks). For instructions on creating your own social bookmarks, see [Creating Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/creating-social-bookmarks). ![Figure 1: With `displayStyle` set to `inline`, the first three social bookmarks appear in a row and the rest appear in a menu.](../../../images/social-bookmarks-inline.png) ![Figure 2: With `displayStyle` set to `menu`, all social bookmarks appear in the *Share* menu.](../../../images/social-bookmarks-menu.png) ## Ratings [The asset framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) supports a content rating system. This feature appears in many of @product@'s built-in apps. For example, users can rate articles published in the Blogs app. There are three different rating types: - Likes - Stars (five, by default) - Thumbs (up/down) To enable ratings in your app, you must use the `liferay-ui:ratings` tag and set its `type` attribute to the rating type (`like`, `stars`, or `thumbs`). For instructions on this, see [Rating Assets](/docs/7-2/frameworks/-/knowledge_base/f/rating-assets). ### 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 [`PortletRatingsDefinition`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/definition/PortletRatingsDefinition.html) 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: `getDefaultRatingsType`: Returns the entity's default rating type, which portal and site admins can override. You can do this via the [`RatingsType`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/RatingsType.html) enum, which contains `LIKE`, `STARS`, or `THUMBS`. `getPortletId`: Returns the portlet ID of the main portlet that uses the entity. You can do this via the [`PortletKeys`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/PortletKeys.html) 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 [Implementing Rating Type Selection](/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection). Once you've done so, you can configure the default rating type via the Control Panel at *Configuration* → *Instance Settings* → *Social*. To override the default values for a site, go to Site Administration (your Site's menu) → *Configuration* → *Site Settings* → *Social*. ### 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: 1. When changing from stars to: **Like:** A value of 3, 4, or 5 stars is considered a like; a value of 1 or 2 stars is omitted. **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. 2. When changing from thumbs up/down to: **Like:** A like is considered a thumbs up. **Stars:** A thumbs down is considered 1 star; a thumbs up is considered 5 stars. 3. When changing from like to: **Stars:** A like is considered 5 stars. **Thumbs up/down:** A like is considered a thumbs up. 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 [`RatingsDataTransformer`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/transformer/RatingsDataTransformer.html). | **Note:** The portal doesn't provide a default `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. When implementing `RatingsDataTransformer`, implement the `transformRatingsData` method to transform the data. This method's arguments include the `RatingsType` variables `fromRatingsType` and `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 `ActionableDynamicQuery.PerformActionMethod` as an anonymous inner class in the `transformRatingsData` method, implementing the `performAction` method with your transformation's logic. For instructions on implementing `RatingsDataTransformer`, see [Customizing Rating Value Transformation](/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation). ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/01-applying-social-bookmarks.markdown ================================================ --- header-id: applying-social-bookmarks --- # Applying Social Bookmarks [TOC levels=1-4] 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. ![Figure 1: These social bookmarks are in the inline display style.](../../../images/social-bookmarks-inline.png) Follow these steps to add social bookmarks to your app: 1. Make sure your entity is [asset enabled](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework). 2. In your project's `build.gradle` file, add a dependency to the module [`com.liferay.social.bookmarks.taglib`](https://repository.liferay.com/nexus/content/repositories/liferay-public-releases/com/liferay/com.liferay.social.bookmarks.taglib/): ```groovy compileOnly group: "com.liferay", name: "com.liferay.social.bookmarks.taglib", version: "1.0.0" ``` 3. 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 [asset renderers](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer). The Asset Publisher displays social bookmarks in asset renderers by default. 4. In your view's JSP, include the `liferay-social-bookmarks` taglib declaration: ```markup <%@ taglib uri="http://liferay.com/tld/social-bookmarks" prefix="liferay-social-bookmarks" %> ``` 5. Get an instance of your entity. You can do this however you wish. This example uses [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) to get the entity's ID from the render request, then uses the entity's `-LocalServiceUtil` class to create an entity object: ```java <% long entryId = ParamUtil.getLong(renderRequest, "entryId"); entry = EntryLocalServiceUtil.getEntry(entryId); %> ``` 6. Use the `liferay-social-bookmarks:bookmarks` tag to add the social bookmarks component. See [Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/social-api#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: ```markup ``` ## Related Topics [Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/social-api#social-bookmarks) [Creating Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/creating-social-bookmarks) [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/02-creating-social-bookmarks.markdown ================================================ --- header-id: creating-social-bookmarks --- # Creating Social Bookmarks [TOC levels=1-4] By default, @product@ contains social bookmarks for Twitter, Facebook, and LinkedIn. You can also create your own social bookmark by registering a component that implements the [`SocialBookmark`](@app-ref@/social/latest/javadocs/com/liferay/social/bookmarks/SocialBookmark.html) interface from the module `com.liferay.social.bookmarks.api`. The steps here show you how to do this. ## Implementing the SocialBookmark Interface Follow these steps to implement the `SocialBookmark` interface: 1. Create your `*SocialBookmark` class and register a component that defines the `social.bookmarks.type` property. This property's value is what you enter for the `liferay-social-bookmarks:bookmarks` tag's `type` attribute when you use your social bookmark. For example, here's the definition for a Twitter social bookmark class: ```java @Component(immediate = true, property = "social.bookmarks.type=twitter") public class TwitterSocialBookmark implements SocialBookmark {... ``` 2. Create a [`ResourceBundleLoader`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ResourceBundleLoader.html) reference to help localize the social bookmark's name. ```java @Reference( target = "(bundle.symbolic.name=com.liferay.social.bookmark.twitter)" ) private ResourceBundleLoader _resourceBundleLoader; ``` 3. Implement the `getName` method to return the social bookmark's name as a string. This method takes a [`Locale`](https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html) object that you can use for localization via [`LanguageUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/language/LanguageUtil.html) and [`ResourceBundle`](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html): ```java @Override public String getName(Locale locale) { ResourceBundle resourceBundle = _resourceBundleLoader.loadResourceBundle(locale); return LanguageUtil.get(resourceBundle, "twitter"); } ``` 4. Implement the `getPostURL` method to return the share URL. This method constructs the share URL from a title and URL, and uses [`URLCodec`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/URLCodec.html) to encode the title in the URL: ```java @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); } ``` 5. Create a `ServletContext` reference: ```java @Reference( target = "(osgi.web.symbolicname=com.liferay.social.bookmark.twitter)" ) private ServletContext _servletContext; ``` 6. Implement the `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 [Clay icon](/docs/7-2/reference/-/knowledge_base/r/clay-icons). This example gets a `RequestDispatcher` for the JSP that contains a Clay icon (`page.jsp`), and then includes that JSP in the response: ```java @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); } ``` ## Creating Your JSP The `page.jsp` file referenced in the above `SocialBookmark` implementation uses [a Clay link](/docs/7-2/reference/-/knowledge_base/r/clay-labels-and-links) (`clay:link`) to specify and style the Twitter icon included with Clay. Follow these steps to create a JSP for your own social bookmark: 1. Add the `clay` and `liferay-theme` taglib declarations: ```markup <%@ taglib uri="http://liferay.com/tld/clay" prefix="clay" %> <%@ taglib uri="http://liferay.com/tld/theme" prefix="liferay-theme" %> ``` 2. Import [`GetterUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/GetterUtil.html) and `SocialBookmark`: ```markup <%@ page import="com.liferay.portal.kernel.util.GetterUtil" %> <%@ page import="com.liferay.social.bookmarks.SocialBookmark" %> ``` 3. From the request, get a `SocialBookmark` instance and the social bookmark's title and URL: ```java <% 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")); %> ``` The title and URL are set via the `liferay-social-bookmarks` taglib when [applying the social bookmark](/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks). 4. Add the Clay link. See the `clay:link` [documentation](https://clayui.com/docs/components/link.html) for a full description of its attributes. ```markup ``` This example sets the following `clay:link` attributes: `buttonStyle`: This example renders the [button's type](/docs/7-2/reference/-/knowledge_base/r/clay-buttons) as a secondary button. `elementClasses`: The custom CSS to use for styling the button (optional). `href`: The button's URL. You should specify this by calling your `SocialBookmark` instance's `getPostURL` method. `icon`: The button's icon. This example specifies the Twitter icon included in Clay (`twitter`). `title`: The button's title. This example uses the `SocialBookmark` instance's `getName` method. To see a complete, real-world example of a social bookmark implementation, see [Liferay's Twitter social bookmark code](https://github.com/liferay/liferay-portal/tree/7.2.x/modules/apps/social/social-bookmark-twitter). ## Related Topics [Applying Social Bookmarks](/docs/7-2/frameworks/-/knowledge_base/f/applying-social-bookmarks) [Using the Clay Taglib in Your Portlets](/docs/7-2/reference/-/knowledge_base/r/using-the-clay-taglib-in-your-portlets) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/03-adding-comments.markdown ================================================ --- header-id: adding-comments-to-your-app --- # Adding Comments to Your App [TOC levels=1-4] 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: 1. Make sure your entity is [asset enabled](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework). 2. 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 [implemented asset rendering](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer) you can display it in the full content view in the Asset Publisher app. 3. Include the `liferay-ui`, `liferay-comment`, and `portlet` taglib declarations in your JSP: ```markup <%@ 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" %> ``` 4. Use [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) to get the entity's ID from the render request. Then create an entity object using the `-LocalServiceUtil` class. Here's an example that does this for a guestbook entry in the example Guestbook app: ```java <% long entryId = ParamUtil.getLong(renderRequest, "entryId"); entry = EntryLocalServiceUtil.getEntry(entryId); %> ``` 5. Create a collapsible panel for the comments using the `liferay-ui:panel-container` and `liferay-ui:panel` tags. This lets users hide the discussion area: ```markup ``` 6. Create a URL for the discussion using the `portlet:actionURL` tag: ```markup ``` 7. Use the `liferay-comment:discussion` tag to add the discussion. To let the user return to the JSP after making a comment, set the tag's `redirect` attribute to the current URL. You can use `PortalUtil.getCurrentURL((renderRequest))` to get the current URL from the `request` object. In this example, the current URL was earlier set to the `currentURL` variable: ```markup ``` If you haven't already connected your portlet's view to the JSP for your entity, see [Configuring JSP Templates for an Asset Renderer](/docs/7-2/frameworks/-/knowledge_base/f/configuring-jsp-templates-for-an-asset-renderer). ## Related Topics [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) [Rating Assets](/docs/7-2/frameworks/-/knowledge_base/f/rating-assets) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/05-rating-assets.markdown ================================================ --- header-id: rating-assets --- # Rating Assets [TOC levels=1-4] 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 [Ratings](/docs/7-2/frameworks/-/knowledge_base/f/social-api#ratings). ![Figure 1: Users can rate content to let others know how they really feel about it.](../../../images/social-ratings-thumbs.png) 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. 1. Make sure your entity is [asset enabled](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework). 2. 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 [implemented asset rendering](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer) you can display them in the full content view in the Asset Publisher app. 3. In the JSP, include the `liferay-ui` taglib declaration: ```markup <%@ taglib prefix="liferay-ui" uri="http://liferay.com/tld/ui" %> ``` 4. Use [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) to get the entity's ID from the render request. Then create an entity object using the `-LocalServiceUtil` class. Here's an example that does this for a guestbook entry in the example Guestbook app: ```java <% long entryId = ParamUtil.getLong(renderRequest, "entryId"); entry = EntryLocalServiceUtil.getEntry(entryId); %> ``` 5. Use the `liferay-ui:ratings` tag to add the ratings component for the entity. This example uses the stars rating type: ```markup ``` ## Related Topics [Ratings](/docs/7-2/frameworks/-/knowledge_base/f/social-api#ratings) [Implementing Rating Type Selection](/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection) [Customizing Rating Value Transformation](/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/09-rating-type-selection.markdown ================================================ --- header-id: implementing-rating-type-selection --- # Implementing Rating Type Selection [TOC levels=1-4] 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 [Rating Type Selection](/docs/7-2/frameworks/-/knowledge_base/f/social-api#rating-type-selection). 1. Implement the [`PortletRatingsDefinition`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/definition/PortletRatingsDefinition.html) interface, registering the class as an OSGi component. In the `@Component` annotation, set the `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 `model.class.name` property is set to `com.liferay.portlet.blogs.model.BlogsEntry`: ```java @Component( property = { "model.class.name=com.liferay.portlet.blogs.model.BlogsEntry" } ) public class BlogsPortletRatingsDefinition implements PortletRatingsDefinition {... ``` 2. Implement the `PortletRatingsDefinition` methods `getDefaultRatingsType` and `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: ```java @Override public RatingsType getDefaultRatingsType() { return RatingsType.THUMBS; } @Override public String getPortletId() { return PortletKeys.BLOGS; } ``` ## Related Topics [Rating Type Selection](/docs/7-2/frameworks/-/knowledge_base/f/social-api#rating-type-selection) [Rating Assets](/docs/7-2/frameworks/-/knowledge_base/f/rating-assets) [Customizing Rating Value Transformation](/docs/7-2/frameworks/-/knowledge_base/f/customizing-rating-value-transformation) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/10-customizing-rating-transformation.markdown ================================================ --- header-id: customizing-rating-value-transformation --- # Customizing Rating Value Transformation [TOC levels=1-4] To customize rating value transformation, you must create an OSGi component that implements [`RatingsDataTransformer`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/transformer/RatingsDataTransformer.html). The steps here show you how. For a detailed explanation of these steps and rating value transformation, see [Rating Value Transformation](/docs/7-2/frameworks/-/knowledge_base/f/social-api#rating-value-transformation). 1. Create an OSGi component class that implements `RatingsDataTransformer`: ```java @Component public class DummyRatingsDataTransformer implements RatingsDataTransformer {... ``` 2. In this class, implement the `transformRatingsData` method. Note that it contains the `RatingsType` variables `fromRatingsType` and `toRatingsType`: ```java @Override public ActionableDynamicQuery.PerformActionMethod transformRatingsData( final RatingsType fromRatingsType, final RatingsType toRatingsType) throws PortalException { } ``` 3. In the `transformRatingsData` method, implement the interface `ActionableDynamicQuery.PerformActionMethod` as an anonymous inner class: ```java return new ActionableDynamicQuery.PerformActionMethod() { }; ``` 4. In the anonymous `ActionableDynamicQuery.PerformActionMethod` implementation, implement the `performAction` method to perform your transformation: ```java @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); } } ``` This example irreversibly transforms the rating type from likes to stars, resetting the value to `0`. The `if` statement uses the `fromRatingsType` and `toRatingsType` values to specify that the transformation only occurs when going from likes to stars. The transformation is performed via `RatingsEntry` and its `-LocalServiceUtil`. After getting a [`RatingsEntry`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/model/RatingsEntry.html) object, its `setScore` method sets the rating score to `0`. The [`RatingsEntryLocalServiceUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/ratings/kernel/service/RatingsEntryLocalServiceUtil.html) method `updateRatingsEntry` then updates the `RatingsEntry` in the database. Here's the complete class for this example: ```java @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); } } }; } } ``` ## Related Topics [Rating Value Transformation](/docs/7-2/frameworks/-/knowledge_base/f/social-api#rating-value-transformation) [Implementing Rating Type Selection](/docs/7-2/frameworks/-/knowledge_base/f/implementing-rating-type-selection) [Rating Assets](/docs/7-2/frameworks/-/knowledge_base/f/rating-assets) ================================================ FILE: en/developer/frameworks/articles/collaboration/05-social-api/11-flagging-assets.markdown ================================================ --- header-id: flagging-inappropriate-asset-content --- # Flagging Inappropriate Asset Content [TOC levels=1-4] The asset framework supports a system for flagging inappropriate content in apps. The steps here show you how to enable it in your app. ![Figure 1: Users can flag objectionable content.](../../../images/social-flags.png) Follow these steps to enable content flagging in your app: 1. Make sure your entity is [asset enabled](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework). 2. 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 [implemented asset rendering](/docs/7-2/frameworks/-/knowledge_base/f/creating-an-asset-renderer) you can display it in the full content view in the Asset Publisher app. 3. In your JSP, include the `liferay-flags` taglib declaration: ```markup <%@ taglib prefix="liferay-flags" uri="http://liferay.com/tld/flags" %> ``` 4. Use [`ParamUtil`](@platform-ref@/7.2-latest/javadocs/portal-kernel/com/liferay/portal/kernel/util/ParamUtil.html) to get the entity's ID from the render request. Then use your `-LocalServiceUtil` class to create an entity object: ```java <% long entryId = ParamUtil.getLong(renderRequest, "entryId"); entry = EntryLocalServiceUtil.getEntry(entryId); %> ``` 5. Use the [`liferay-flags:flags`](@app-ref@/collaboration/latest/taglibdocs/liferay-flags/flags.html) tag to add the flags component: ```markup ``` The `reportedUserId` attribute specifies the ID of the user who flagged the asset. ## Related Topics [Rating Assets](/docs/7-2/frameworks/-/knowledge_base/f/rating-assets) [Social API](/docs/7-2/frameworks/-/knowledge_base/f/social-api) [Asset Framework](/docs/7-2/frameworks/-/knowledge_base/f/asset-framework) ================================================ FILE: en/developer/frameworks/articles/configuration/01-configuration-intro.markdown ================================================ --- header-id: configurable-applications --- # Configurable Applications [TOC levels=1-4] 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. @product@'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. | **Note:** To see a working application configuration, deploy the | `configuration-action` [Blade | sample](https://github.com/liferay/liferay-blade-samples/tree/master/gradle/apps/configuration-action) | and navigate to System Settings (*Control Panel* → *Configuration* → | *System Settings*). In the Platorm section's Third Party category, click the | *Message display configuration* entry. | | Add the *Blade Message Portlet* to a page to test your configuration choices. Complete these three high level tasks to integrate your application with the configuration framework: 1. Provide a way to set configurations in the UI. 2. Set the scope where the application is configured. 3. Read configuration values in your business logic. ## Using a Configuration Interface You can take care of the first two steps by [Creating A Configuration Interface](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-configuration-interface). This Java interface does a number of things: - Just by existing, it gives you a UI in *System Settings*, so you don't have to write one yourself. Score! - It defines the configuration options that will appear in the UI. - It defines the type {`int`, `String`, etc.) of values each configuration takes. - It defines the scope of your configuration. Bonus in @product-ver@: if your configuration is scoped to anything other than `SYSTEM`, you get an additional UI generated for you in *Instance Settings*. More on scope in a minute. - It categorizes your configuration screen so that it can be easily found in *System Settings* and *Instance Settings*. If you skip this the screen will be put in a default location. A few things you need to know: **Typed Configuration** : The method described here uses *typed* configuration. The application configuration isn't just a list of key-value pairs. Values can have types, like `Integer`, a list of `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. **Configuration Scope** : Scope defines where a configuration value applies. Here are the most common configuration scopes: - `SYSTEM`: Configuration values apply throughout the system. - `COMPANY`: One set of configuration values is stored for each virtual instance, so each instance can be configured individually. - `GROUP`: Each group can be configured individually. - `PORTLET_INSTANCE`: this refers to apps that can be placed on a page as a widget. Each widget can be configured individually. **Configuration UIs** : When you create a configuration interface of any sort, a UI is generated for you in *System Settings*. If your configuration is scoped to `COMPANY`, `GROUP`, or `PORTLET_INSTANCE`, an additional UI is generated in *Instance Settings*. Note that while `GROUP` and `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. | **Note:** An Instance Settings UI is not currently generated for factory | configurations. You can track the progress of this issue | [here](https://issues.liferay.com/browse/LPS-94490). **Default Configurations** : Default values for any scoped configuration can be set at any wider scope. For example, if your configuration is scoped to the `GROUP`, you can set a system-wide default in *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. Read more about configuration scope [here](/docs/7-2/user/-/knowledge_base/u/system-settings#configuration-scope). When you complete your configuration interface, you're done with steps 1 and 2 above. ## 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 `COMPANY` or `GROUP` you must use [`ConfigurationProvider`](/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values) 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 `PORTLET_INSTANCE`, you can still use `ConfigurationProvider`, but using `PortletDisplay` is simpler and more convenient. See [`PortletDisplay`](/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values#accessing-the-portlet-instance-configuration-through-the-portletdisplay). If you only want your app to be configurable at the `SYSTEM` scope, you have a few options. `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: - Read with an [MVC portlet's JSP](/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-an-mvc-portlet#accessing-the-configuration-from-a-jsp) - With an [MVC Portlet's Portlet Class](/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-an-mvc-portlet#accessing-the-configuration-from-the-portlet-class) - With any other [Component Class](/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component) ## Further Customization At this point you may be asking, "But what if I don't *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. - Implement the `ConfigurationFormRenderer` [interface](/docs/7-2/frameworks/-/knowledge_base/f/configuration-form-renderer) to customize the auto-generated UI in system settings. - 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 `ConfigurationScreen` interface to implement your own. - If you're using a configuration interface but you don't want a UI to be generated---maybe you're using a `ConfigurationScreen` implementation instead, or maybe you just want configuration to be handled programatically or by [.config file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files) ---you can [just leave it out](/docs/frameworks/-/knowledge_base/7-2/customizing-the-system-settings-user-interface#excluding-a-configuration-ui-from-system-settings). - If you want the UI to render only under certain circumstances, you can write logic to [do that, too](/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface#excluding-a-configuration-ui). 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 @product@, see [Upgrading a Legacy App](/docs/7-2/frameworks/-/knowledge_base/f/upgrading-a-legacy-app). ================================================ FILE: en/developer/frameworks/articles/configuration/02-creating-a-configuration-interface.markdown ================================================ --- header-id: creating-a-configuration-interface --- # Creating A Configuration Interface [TOC levels=1-4] First, you'll learn how to create a configuration with no scope declaration. This automatically scopes your configuration to `SYSTEM`. 1. 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: ```java @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(); } ``` 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: **Meta.OCD:** Registers this class as a configuration with a specific id. **The ID must be the fully qualified configuration class name.** **Meta.AD:** Specifies [optional metadata](http://bnd.bndtools.org/chapters/210-metatype.html) 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 `deflt` property to specify a default value. | **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) 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). 2. To use the `Meta.OCD` and `Meta.AD` annotations in your modules, you must [specify a dependency](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies) on the bnd library. We recommend using bnd version 3. Here's an example of how to include this dependency in a Gradle project: ```groovy dependencies { compile group: "biz.aQute.bnd", name: "biz.aQute.bndlib", version: "3.1.0" } ``` | **Note:** The annotations `@Meta.OCD` and `@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 `@ObjectClassDefinition` and `@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. | **Also Note:** Your project depends on a `-metatype: *` declaration in its | metadata. If you're in a [Liferay | Workspace](/docs/7-2/reference/-/knowledge_base/r/liferay-workspace)(or | otherwise applying the [workspace plugin to your | build](/docs/7-2/reference/-/knowledge_base/r/gradle-plugins)), it's added | automatically at build time. Otherwise, add it manually in your module's | `bnd.bnd`. It's required to provide information about your app's configuration | options so that a configuration UI can be generated. When you register a configuration interface, a UI is auto-generated for it in *System Settings* → *Platform* → *Third Party*. That's the default location; read the next section to learn how to move it somewhere more intuitive. ================================================ FILE: en/developer/frameworks/articles/configuration/03-categorizing-configurations.markdown ================================================ --- header-id: categorizing-the-configuration --- # Categorizing the Configuration [TOC levels=1-4] By default, the configuration UI for your app is generated in *System Settings* → *Platform* → *Third Party*. You probably don't really want it there; by categorizing your configuration you can place it somewhere intuitive and easy to find. | **Note:** If you | [scope](/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations) your | configuration so that a UI is generated in Instance Settings as well, your | categorization will apply to that UI also. 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: 1. Content and Data 2. Platform 3. Security 4. Commerce 5. Other | **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. ## Specifying a Configuration Category Specify the category for your UI by placing an `@ExtendedObjectClassDefinition` annotation in your configuration interface. This example, which appears right before the interface's `@Meta.OCD` annotation, places the UI in the `dynamic-data-mapping` category in the Content management section: ```java @ExtendedObjectClassDefinition( category = "dynamic-data-mapping", scope = ExtendedObjectClassDefinition.Scope.GROUP ) ``` This annotation does two things: - Specifies the `dynamic-data-mapping` category in the Content Management section. - Sets the scope of the configuration. You'll learn more about this [next](/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations). The fully qualified class name of the `@ExtendedObjectClassDefinition` class is `com.liferay.portal.configuration.metatype.annotations.ExtendedObjectClassDefinition`. Note: The infrastructure used by System Settings assumes the `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 `ExtendedObjectClassConfiguration`. The `@ExtendedObjectClassDefinition` annotation is distributed through the `com.liferay.portal.configuration.metatype` module, which you can [configure as a dependency](/docs/7-2/customization/-/knowledge_base/c/configuring-dependencies). ## Creating New Sections and Categories If you don't like the default sections and categories, you can create your own by implementing the `ConfigurationCategory` interface. Here's code that creates the *Content and Data* section and the *Dynamic Data Mapping* category: ```java @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"; } ``` The `getCategorySection` method returns the String with the new section's key. Similarly, `getCategoryKey` returns the key for the new category. Provide localized values for these keys in your module's `src/main/resources/content/Language.properties` file. | **Note:** the language keys for categories and sections must follow | a specific format. Prefix each section language key with `category-section.` and | each category language key with `category.` For example: | | `category-section.content-and-data=Content and Data` | `category.dynamic-data-mapping=Dynamic Data Mapping` Next you'll specify the scope of your application's configuration. ================================================ FILE: en/developer/frameworks/articles/configuration/04-scoping-configurations.markdown ================================================ --- header-id: scoping-configurations --- # Scoping Configurations [TOC levels=1-4] Here's how to scope a configuration: 1. Set the scope in the configuration interface. 2. Enable the configuration for scoped retrieval by creating a configuration bean declaration. ## Step 1: Setting the Configuration Scope Use the `@ExtendedObjectClassDefinition` annotation to specify the configuration's scope. The scope you choose must match how the configuration object is retrieved through the [configuration provider](/docs/7-2/frameworks/-/knowledge_base/f/reading-scoped-configuration-values). Pass one of these valid scope options to `@ExtendedObjectClassDefinition`: `Scope.SYSTEM`: for system scope `Scope.COMPANY`: for virtual instance scope `Scope.GROUP`: for site scope `Scope.PORTLET_INSTANCE`: for the portlet instance scope Here is an example: ```java @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 { ``` ## Step 2: Enabling the Configuration for Scoped Retrieval To create a configuration bean declaration: 1. Register the configuration class by implementing `ConfigurationBeanDeclaration`. ```java @Component public class JournalGroupServiceConfigurationBeanDeclaration implements ConfigurationBeanDeclaration { ``` 2. 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. ```java @Override public Class getConfigurationBeanClass() { return JournalGroupServiceConfiguration.class; } ``` That's all there is to it. Now the configuration is scoped and supports scoped retrieval via `ConfigurationProvider`. See the next section for details on retrieval. ================================================ FILE: en/developer/frameworks/articles/configuration/05-reading-configurations-configuration-provider.markdown ================================================ --- header-id: reading-scoped-configuration-values --- # Reading Scoped Configuration Values [TOC levels=1-4] If your configuration is scoped to anything other than `SYSTEM`, you have two options for reading configuration values. - Use `ConfigurationProvider`. This works for any kind of configuration, and is the only way to read configuration values at the `COMPANY` and `GROUP` scopes. - Use `PortletDisplay`. This is the recommended approach for configurations at the `PORTLET_INSTANCE` scope, but only works at that scope. ## Using the Configuration Provider When using the Configuration Provider, instead of receiving the configuration directly, the class that wants to access it must 1. Receive a `ConfigurationProvider` to obtain the configuration. 2. Be registered with a `ConfigurationBeanDeclaration`. The tutorial on [scoping configurations](/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations) demonstrates how to register the configuration with a `ConfigurationBeanDeclaration`. After registering with a `ConfigurationBeanDeclaration`, you're ready to use a `ConfigurationProvider` to retrieve the scoped configuration. Here's how you obtain a reference to it: 1. Here's the approach for components: ```java @Reference(unbind = "-") protected void setConfigurationProvider(ConfigurationProvider configurationProvider) { _configurationProvider = configurationProvider; } ``` 2. Here's the approach for Service Builder services: ```java @ServiceReference(type = ConfigurationProvider.class) protected ConfigurationProvider configurationProvider; ``` 3. For Spring beans, it is possible to use the same mechanism as for Service Builder services (`@ServiceReference`). 4. For anything else, call the same methods from the utility class, `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 [scripting console](/docs/7-2/user/-/knowledge_base/u/running-scripts-from-the-script-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: ```java protected boolean isValidateLayoutReferences() throws PortalException { long companyId = CompanyThreadLocal.getCompanyId(); ExportImportServiceConfiguration exportImportServiceConfiguration = ConfigurationProviderUtil.getCompanyConfiguration( ExportImportServiceConfiguration.class, companyId); return exportImportServiceConfiguration.validateLayoutReferences(); } ``` To retrieve the configuration, use one of the following methods of the provider: `getCompanyConfiguration()` : Used when you want to support different configurations per virtual instance. In this case, the configuration is usually entered by an admin through *Control Panel* → *Configuration* → *Instance Settings*. `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. `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 `PortletDisplay` instead as shown below. `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. Here are a couple real world examples from Liferay's source code: ```java JournalGroupServiceConfiguration configuration = configurationProvider.getGroupConfiguration( JournalGroupServiceConfiguration.class, groupId); MentionsGroupServiceConfiguration configuration = _configurationProvider.getCompanyConfiguration( MentionsGroupServiceConfiguration.class, entry.getCompanyId()); ``` Next, you'll learn a nifty way to to access a portlet instance configuration from a JSP. ## 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 `PortletDisplay`, which is available as a request object. Here is an example of how to use it: ```java RSSPortletInstanceConfiguration rssPortletInstanceConfiguration = portletDisplay.getPortletInstanceConfiguration( RSSPortletInstanceConfiguration.class); ``` As you can see, it knows how to find the values and returns a typed bean containing them just by passing the configuration class. ================================================ FILE: en/developer/frameworks/articles/configuration/06-reading-configurations-portlet.markdown ================================================ --- header-id: reading-unscoped-configuration-values-from-an-mvc-portlet --- # Reading Unscoped Configuration Values from an MVC Portlet [TOC levels=1-4] If your configuration is scoped to `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: - Add a configuration to the request and read it from the view layer (commonly a JSP). - Read values directly from the portlet class. 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. ## 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 `*Portlet` class. 1. Imports first: ```java 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; ``` 2. MVC Portlet classes are Component classes. If you have a Bean Portlet or PortletMVC4Spring class, the configuration below goes in `portlet.xml` and `liferay-portlet.xml`. To mate the configuration with the Component, provide the `configurationPid` property with the FQCN of the configuration class. ```java @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 { ``` Note that you can specify more than one configuration PID here by enclosing the values in curly braces (`{}`) and placing commas between each PID. 3. Write an `activate` method annotated with `@Activate` and `@Modified`. This ensures that the method is invoked when the Component is started, and again whenever the configuration is changed. ```java @Activate @Modified protected void activate(Map properties) { _configuration = ConfigurableUtil.createConfigurable( ExampleConfiguration.class, properties); } private volatile ExampleConfiguration _configuration; ``` A volatile field `_configuration` is created by the `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. ## 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. 1. Add the configuration object to the request. Here's what it looks like in a simple portlet's `doView` method: ```java @Override public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException { renderRequest.setAttribute( ExampleConfiguration.class.getName(), _configuration); super.doView(renderRequest, renderResponse); } ``` The main difference between this example and the component class covered in the [next section](/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component) is that this class is a portlet class and it sets the configuration object as a request attribute in its `doView()` method. 2. Read configuration values from a JSP. First add these imports to the top of your `view.jsp` file: ```markup <%@ page import="com.liferay.docs.exampleconfig.ExampleConfiguration" %> <%@ page import="com.liferay.portal.kernel.util.GetterUtil" %> ``` 3. In the JSP, obtain the configuration object from the request object and read the desired configuration value from it. Here's a `view.jsp` file that does this: ```markup <%@ 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 %>

    *blue* is written in blue text. Note that *blue* is displayed by default since you specified it as the default in your `ExampleConfiguration` interface. If you go to *Control Panel* → *Configuration* → *System Settings* → *Platform* → *Third Party* and click on the *Example configuration* link, you can find the `Favorite color` setting and change its value. The JSP reads the configuration, and refreshing the UI reflects this update. ## Accessing the Configuration from the Portlet Class Now that you've seen a detailed example of accessing the configuration values in a JSP, there's not much more to cover when accessing the configuration directly in the `-Portlet` class. Wherever you require the value of a configuration property, call `_configuration.propertyName` and you have access to the currently configured value. For example, this code compares the `favoriteColor` configuration value with a `userFavoriteColor` that's fetched from the request object: ```java public boolean isFavoriteColorMatched { String userFavoriteColor = ParamUtil.getString(request, "userFavoriteColor"); if (_configuration.favoriteColor() == userFavoriteColor) { SessionMessages.add(request, "congratulateUser"); return true; } return false; } ``` It returns true and adds a success message if the two Strings match each other, but you can do anything that makes sense for your application's controller logic. That's all there is to reading configuration values in a Portlet. The next section covers reading configuration values from an OSGi Component class that is not part of a portlet. ================================================ FILE: en/developer/frameworks/articles/configuration/07-reading-configurations-component.markdown ================================================ --- header-id: reading-unscoped-configuration-values-from-a-component --- # Reading Unscoped Configuration Values from a Component [TOC levels=1-4] Follow these steps to read `SYSTEM` scoped or unscoped configuration values from a Component that isn't part of a portlet: 1. First set the `configurationPid` Component property as the fully qualified class name of the configuration class: ```java @Component(configurationPid = "com.liferay.dynamic.data.mapping.form.web.configuration.DDMFormWebConfiguration") ``` 2. Then provide an `activate` method, annotated with `@Activate` to ensure the method is invoked as soon as the Component is started, and `@Modified` so it's invoked whenever the configuration is modified. ```java @Activate @Modified protected void activate(Map properties) { _formWebConfiguration = ConfigurableUtil.createConfigurable( DDMFormWebConfiguration.class, properties); } private volatile DDMFormWebConfiguration _formWebConfiguration; ``` The `activate()` method calls the method `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 `volatile` field. Don't forget to make it `volatile` to prevent thread safety problems. 3. Once the activate method is set up, retrieve particular properties from the configuration wherever they're needed: ```java public void orderCar(String model) { order("car", model, _configuration.favoriteColor()); } ``` This is dummy code: don't try to find it in the Liferay source code. The String configuration value of `favoriteColor` is passed to the `order` method call, presumably so that whatever model car is ordered gets ordered in the configured favorite color. | **Note:** The bnd library also provides a class called | `aQute.bnd.annotation.metatype.Configurable` with a `createConfigurable()` | method. You can use that instead of Liferay's | `com.liferay.portal.configuration.metatype.bnd.util.ConfigurableUtil` without | any problems. Liferay's developers created the `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. 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. ================================================ FILE: en/developer/frameworks/articles/configuration/08-customizing-the-ui/01-customizing-the-configuration-user-interface-intro.markdown ================================================ --- header-id: customizing-the-configuration-user-interface --- # Customizing the Configuration User Interface [TOC levels=1-4] There are three ways to customize a configuration UI. - Provide a custom form for a configuration object. This modifies the auto-generated UI. - 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. - 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. ## Providing Custom Configuration Forms Customize your auto-generated UI by implementing the `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: ```java @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(); } ``` This example defines one configuration option, `symbols`, which takes an array of values. Implement `ConfigurationFormRenderer`'s three methods: 1. `getPid`: Return the configuration object's ID. This is defined in the `id` property in the `*Configuration` class's `@Meta.OCD` annotation. 2. `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. 3. `render`: Render the custom form's fields, using your desired method (for example, JSPs or another template mechanism). The `
    ` tag itself is provided automatically and shouldn't be included in the `ConfigurationFormRenderer`. Here's a complete `ConfigurationFormRenderer` implementation: ```java @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; } } ``` The above example generates a custom rendering (HTML) for the form in the `render()` method and reads the information entered in the custom form in the `getRequestParameters()` method. To see a complete demonstration, including JSP markup, read the dedicated tutorial on creating a [configuration form renderer](/docs/7-2/frameworks/-/knowledge_base/f/configuration-form-renderer). ## Creating a Completely Custom Configuration UI You get more flexibility if you create a completely custom UI using a `ConfigurationScreen` implementation. At a high level you must 1. Write a Component that declares itself an implementation of the `ConfigurationScreen` interface. 2. Implement `ConfigurationScreen`'s methods. 3. Create the UI by hand. Here's an example implementation: ```java @Component(immediate = true, service = ConfigurationScreen.class) public class SampleConfigurationScreen implements ConfigurationScreen { ``` First declare the class an implementation of `ConfigurationScreen`. ```java @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"; } ``` Second, set the category key, the configuration entry's key, and its localized name. This example puts the configuration entry, keyed `sample-configuration-screen`, into the `third-party` System Settings section. The String that appears in System Settings is _Sample Configuration Screen_. ```java @Override public String getScope() { return "system"; } ``` Third, set the [configuration scope](/docs/7-2/frameworks/-/knowledge_base/f/scoping-configurations). ```java @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; ``` The most important step is to write the `render` method. This example relies on the `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 `ConfigurationScreen` and implementation and the JSP markup to demonstrate its usage. ## Excluding a Configuration UI If you don't want a UI to be generated for you, you have two options. - If you don't want a UI generated no matter what, use the `generateUI` property. - If you only want the UI to render under specific circumstances (defined by logic you'll write yourself), use the configuration visibility SPI. ### Using `generateUI` To turn off auto-generating at all scopes, include the `ExtendedObjectClassDefinition` annotation property `generateUI` in your configuration interface. The property defaults to `true`; here is an example setting it to `false`: ```java @ExtendedObjectClassDefinition(generateUI=false) @Meta.OCD( id = "com.foo.bar.LowLevelConfiguration", ) public interface LowLevelConfiguration { public String[] foo(); public String bar(); } ``` Now no UI is auto-generated for this configuration. You can still manage the configuration via a `ConfigurationScreen` implementation, a [.config file](/docs/7-2/user/-/knowledge_base/u/understanding-system-configuration-files), or programmatically. ### Using the Configuration Visibility SPI The configuration visibility SPI involves implementing a single interface, `ConfigurationVisibilityController`. You can see the whole interface [here](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). To implement the interface, you must identify your configuration interface using an `@Component` property, then write your own logic for the interface's only method, `isVisible`. Here is a sample implementation from Liferay's source code: ```java @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; } ``` Note that the property `configuration.pid` identifies the configuration interface of the UI to be hidden. In this example, the configuration UI only renders when `systemSharingConfiguration.isEnabled` returns `true`. ================================================ FILE: en/developer/frameworks/articles/configuration/08-customizing-the-ui/02-configuration-form-renderer.markdown ================================================ --- header-id: configuration-form-renderer --- # Configuration Form Renderer [TOC levels=1-4] To replace an application's auto-generated configuration screen with a form built from scratch, you follow these steps: 1. Use a `DisplayContext` class to transfer data between back-end code and the desired JSP markup. 2. Implement the `ConfigurationFormRenderer` interface. 3. Render the configuration form. This tutorial demonstrates the use of a JSP and the previously created `DisplayContext` class. A generalized discussion on System Settings UI customization is found in a [separate section](/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface). This article demonstrates replacing the configuration UI for the _Language Template_ System Settings entry, found in *Control Panel* → *Configuration* → *System Settings* → *Localization* → *Language Template*. The same steps apply when replacing your custom application's auto-generated UI. ![Figure 1: The auto-generated UI for the Language Template configuration screen is sub-optimal. A select list with more human readable options is preferable.](../../../images/sys-settings-lang-template-default.png) Specifically, the text input field labeled *DDM Template Key* in the auto-generated UI is replaced with a select list field type called *Language Selection Style*, populated with all possible DDM Template Keys. ## Creating a `DisplayContext` A `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 `DisplayContext`'s setters are called from the Java class providing the render logic (in this case the `ConfigurationFormRenderer`'s `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 `LanguageTemplateConfigurationDisplayContext` class with these contents: ```java 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<>(); } ``` Next implement the `ConfigurationFormRenderer`. ## Implementing a `ConfigurationFormRenderer` First create the component and class declarations. Set the `service` property to `ConfigurationFormRenderer.class`: ```java @Component( configurationPid = "com.liferay.site.navigation.language.web.configuration.SiteNavigationLanguageWebTemplateConfiguration", immediate = true, service = ConfigurationFormRenderer.class ) public class LanguageTemplateConfigurationFormRenderer implements ConfigurationFormRenderer { ``` Next, write an `activate` method (decorated with `@Activate` and `@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 [reading configuration values from a component class](/docs/7-2/frameworks/-/knowledge_base/f/reading-unscoped-configuration-values-from-a-component) for more information. ```java @Activate @Modified public void activate(Map properties) { _siteNavigationLanguageWebTemplateConfiguration = ConfigurableUtil.createConfigurable( SiteNavigationLanguageWebTemplateConfiguration.class, properties); } private volatile SiteNavigationLanguageWebTemplateConfiguration _siteNavigationLanguageWebTemplateConfiguration; ``` Next override the `getPid` and `getRequestParameters` methods: ```java @Override public String getPid() { return "com.liferay.site.navigation.language.web.configuration." + "SiteNavigationLanguageWebTemplateConfiguration"; } ``` Return the full configuration ID, as specified in the `*Configuration` class's `@Meta.OCD` annotation. ```java @Override public Map getRequestParameters( HttpServletRequest request) { Map params = new HashMap<>(); String ddmTemplateKey = ParamUtil.getString(request, "ddmTemplateKey"); params.put("ddmTemplateKey", ddmTemplateKey); return params; } ``` In the `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 `render` method. The rendering approach demonstrated here uses a JSP. Recall that it's backed by a `DisplayContext` class set into the request object. The values set from this `render` method are available in the JSP via the `DisplayContext` object's getters. Loop through the DDM Template Keys for the given `groupId` and set them into the display context with the `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 `renderJSP` and pass in the `servletContext`, request, response, and the path to the JSP: ```java @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"); } ``` Specify the required service references at the bottom of the class. Be careful to target the proper servlet context, passing the `bundle-SymbolicName` of the module (found in its `bnd.bnd` file) into the `osgi.web.symbolicname` property of the reference target: ```java @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; ``` Once the configuration form renderer is implemented, you can write the JSP markup for the form. ## Writing the JSP Markup Now write the JSP: ```markup <%@ include file="/init.jsp" %> <% LanguageTemplateConfigurationDisplayContext languageTemplateConfigurationDisplayContext = (LanguageTemplateConfigurationDisplayContext)request.getAttribute(LanguageTemplateConfigurationDisplayContext.class.getName()); Admin: Instance Settings String currentTemplateName = languageTemplateConfigurationDisplayContext.getCurrentTemplateName(); %> <% for (String[] templateValue : languageTemplateConfigurationDisplayContext.getTemplateValues()) { %> <% } %> ``` 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 `getCurrentTemplateName` method is called, since the current template name is needed for the first option's `ddmTemplateKey` display value as soon as the form is rendered. This happens in the `` 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? ![Figure 2: A select list provides a more user friendly configuration experience than a text field.](../../../images/sys-settings-lang-template-custom.png) 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. ================================================ FILE: en/developer/frameworks/articles/configuration/08-customizing-the-ui/03-configuration-forms.markdown ================================================ --- header-id: using-ddm-form-annotations-in-configuration-forms --- # Using DDM Form Annotations in Configuration Forms [TOC levels=1-4] The auto-generated configuration form you get by just creating a [configuration interface](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-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, 1. Configure the module dependencies. 2. Write a `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 [Dynamic Data Mapping API](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) rather than the bndtools metatype specification. The fields here must match fields defined in the configuration interface. 3. Implement a [`ConfigurationDDMFormDeclaration`](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) to mark your configuration as having a `ConfigurationForm`. 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. ### Step 1: Declare the Dependencies In the `build.gradle` file, add `compileOnly` dependencies on the `dynamic-data-mapping-api` and `configuration-admin-api` module artifacts: ```groovy 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" ``` ### Step 2: Write the Configuration Form This step requires annotating the class with `@DDMForm` to set up the form, and annotating each method with `@DDMFormField`. Begin by creating the class body, annotating each configuration field (interface method) with `@DDMFormField`: ```java 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(); } ``` Once the field annotations are in place, lay out the form itself, right above the class declaration. This example shows the layout of the `UserFileUploadsConfigurationForm`, so that you can see the resulting form via the below screenshot: ```java @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 { ``` ![Figure 1: The DDM annotations are used to lay out this configuration form.](../../../images/configuration-ddm-form.png) Next, you must make sure the configuration framework knows about your slick form. ### Step 3: Write the Form Declaration Create a new implementation of `ConfigurationDDMFormDeclaration` to register your new configuration form class: ```java 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; } } ``` The `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 [Forms application](/docs/7-2/user/-/knowledge_base/u/forms). 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): [`UserFileUploadsConfigurationForm`](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) [`UserFileUploadsConfiguration.java`](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) [`UserFileUploadsConfigurationBeanDeclaration.java`](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) [`UserFileUploadsConfigurationDDMFormDeclaration.java`](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) ================================================ FILE: en/developer/frameworks/articles/configuration/10-upgrading-a-legacy-app.markdown ================================================ --- header-id: upgrading-a-legacy-app --- # Upgrading a Legacy App [TOC levels=1-4] If you have an app that was made configurable under an earlier version of @product@, 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 [Transitioning from Portlet Preferences to the Configuration API](/docs/7-0/tutorials/-/knowledge_base/t/transitioning-from-portlet-preferences-to-the-configuration-api). If you have an app with a configuration interface scoped to anything other than `SYSTEM` and a custom UI for saving configuration values to `PortletPreferences`, you have two options: - Keep using your custom UI. Deactivate the auto-generated UI in *Instance Settings* by setting the scope in your configuration interface to `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 [Excluding a Configuration UI](/docs/7-2/frameworks/-/knowledge_base/f/customizing-the-configuration-user-interface#excluding-a-configuration-ui) - Write an Upgrade Process to convert your configuration values in `PortletPreferences` to an instance-scoped OSGi configuration, using the `saveCompanyConfiguration` method in the `ConfigurationProvider` interface. | You don't have to use `saveCompanyConfiguration`, but doing so meets all | the necessary requirements for an upgrade process: it must be a factory | instance with a factory PID of `Unknown macro:[base-pid].scoped`, and it | must contain a `companyId` property. Then remove your custom UI. If you're reading configuration values using `ConfigurationProvider`'s `getCompanyConfiguration` method, the auto-generated UI picks up where you left off, with no need to reconfigure anything. ================================================ FILE: en/developer/frameworks/articles/configuration/11-dynamically-populating-select-fields.markdown ================================================ --- header-id: dynamically-populating-select-list-fields-in-the-configuration-ui --- # Dynamically Populating Select List Fields in the Configuration UI [TOC levels=1-4] You've always been able to provide a select list for your configuration options by entering each label and value directly in the `@Meta.AD` annotation of the [Configuration interface](/docs/7-2/frameworks/-/knowledge_base/f/creating-a-configuration-interface). ```java @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(); ``` Now, thanks to the [`ConfigurationFieldOptionsProvider` interface](@app-ref@/configuration-admin/latest/javadocs/com/liferay/configuration/admin/definition/ConfigurationFieldOptionsProvider.html), you can populate select list configurations dynamically, using custom logic. Follow these steps to populate the select list fields dynamically in your configuration UI: 1. Use an `@Component` annotation to register the `ConfigurationFieldOptionsProvider.class` service and include two properties: `configuration.field.name`: The name of the attribute in the configuration interface `configuration.pid`: The ID of the corresponding configuration interface (usually the fully qualified class name) For example, ```java @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 ) ``` 2. Implement the `ConfigurationFieldOptionsProvider` interface: ```java public class MyConfigurationFieldOptionsProvider implements ConfigurationFieldOptionsProvider { .. } ``` 3. The `getOptions` method returns a list of `Option`s consisting of the label and value fields. The labels provided here are translated to `optionLabels`, and the values as `optionValues`, in the configuration interface. ```java public List