- Purpose
- Creating an Example Project
- Guidelines
- Review Process
Here are guidelines for tutorial code. They demonstrate starting an example project, describe tutorial code rules, and explain the code review process.
If you're specifically creating a REST API project, see REST API Project Guidelines.
Here you'll learn how to create an example project for a tutorial. It will be a Java project that uses Liferay Workspace.
Prerequisite: A Java 8 JDK (such as Azul Zulu Java 8). See the Compatibility Matrix.
Important: Use a dedicated branch (free of any new/modified articles) for your example code. Branches submitted for code review must contain only code changes--branches must not contain any new/modified articles.
Here's the structure for an example project that has the ID c3p1
(more on project IDs shortly). The project is for a tutorial article whose Markdown file will be implementing-something.md
:
[tutorial path] // This is the same folder your article will go in later.
└── implementing-something/resources/liferay-c3p1.zip/
└── c3p1-impl // Module
├── bnd.bnd
├── build.gradle
└── src/main/java/ // Your Java code goes here
You only need to create two module files:
bnd.bnd
build.gradle
Our update_tutorials.sh
script creates the rest. Create your own project next.
-
Decide on your project's unique, random-looking four-character ID, following the ID pattern below.
ID Pattern:
[a-z][1-9][a-z][1-9]
Example:
c3p1
For convenience, these guidelines use
xxxx
as an ID placeholder--replace it with your unique ID.Here's a way of generating an ID that fits the pattern.
tr -cd a-z1-9 < /dev/urandom | head -c 1000 | sed 's/.*\([a-z]\).*\([1-9]\).*\([a-z]\).*\([1-9]\).*/\1\2\3\4\n/'
Tip: Make sure your ID is unique by searching your
liferay-learn
branch for any project folders that use the ID. For example,find . -name liferay-c3p1.zip
Avoid these things in your ID:
- Zeros. Zeros are easy to confuse with alphabetical
o
andO
. - Your initials (e.g.,
j2b3
if your name is Joe Bloggs) - Duplicating characters (e.g.,
b1b5
duplicatesb
,a1z1
duplicates1
) - Sequential characters (e.g.,
a1b2
has sequential charactersa
andb
, and1
and2
) - Repeating part of another ID (e.g.,
a8q1
anda8q2
repeata8q
).
- Zeros. Zeros are easy to confuse with alphabetical
-
Create your
liferay-xxxx.zip
project folder in a[tutorial-name]/resources/
folder that's in the same location (shown as[tutorial path]
below) that you will eventually put the tutorial article Markdown file.[tutorial path] └── [tutorial-name]/resources/liferay-xxxx.zip
-
In your project, create a module(s) whose root folder follows these naming conventions:
Example Folder Name Description xxxx-api Contains an API. xxxx-impl Contains an implementation. xxxx-web Contains a web application, such as a portlet. Note: You can develop modules any way you like (e.g., manually, using Liferay Dev Studio, using Blade CLI, and more), but refrain from committing any extranious files (e.g., a
.project
file) by adding such files to the repository's.gitignore
file. -
In each module folder, create a
bnd.bnd
file. Please see Initial Bnd Content for example Bnd starting content for some different modules types. -
In your module, create a
build.gradle
file that depends onrelease.portal.api
orrelease.dxp.api
(for DXP-only features). For example,dependencies { compileOnly group: "com.liferay.portal", name: "release.portal.api" }
-
In your module, create a
src/main/java
folder for your Java code. -
Copy our Java Workspace template to your project and build your project by running the
update_examples.sh
script from theliferay-learn/docs
folder. For example,./update_examples.sh c3p1
Here's the file structure with the files added by
update_examples.sh
.
[tutorial path]
└── implementing-something/resources/liferay-c3p1.zip/
├── gradle.properties // Specifies the Liferay product/version to build against
├── gradlew // Gradle wrapper
├── gradlew.bat // Gradle wrapper (Windows)
├── settings.gradle // Specifies the artifact repository
├── source-formatter-suppressions.xml // Suppresses unneeded code format checks
└── c3p1-impl // Module
├── bnd.bnd
└── build.gradle
Congratulations! Your tutorial now has an example project that uses Liferay Workspace. Develop your example next. When you're done developing, follow the Review Process at the end of this article to submit your project.
Here are the rules and guidelines for example code.
Here are some examples that demonstrate example project types.
Example Type | Reference |
---|---|
API Implementation | writing-a-similar-results-contributor/resources/liferay-r1s1.zip |
API & OSGi Services | using-an-osgi-service/resources/liferay-j1h1.zip |
Portlet | using-mvc/using-a-jsp-and-mvc-portlet/resources/liferay-w3e7.zip |
Fragment (JS project) | adding-configuration-options-to-fragments/resources/liferay-c7f8.zip |
Java projects can have one or more modules. All of a project's modules go in the project folder (e.g., liferay-xxxx.zip
folder)
xxxx-web // Web Module
├── bnd.bnd
├── build.gradle
└── src/main/
└── java/ // Put Java classes here
└── com/acme/xxxx/DescribesWhatItDoes.java
xxxx-impl
├── bnd.bnd
├── build.gradle
└── src/main/
├── java/ // Put Java classes here
| └── com/acme/xxxx/internal/[same/path/as/inteface/]XXXXInterfaceName.java
└── resources/
└── content/
└── Language.properties // (Optional)
xxxx-web // Web Module
├── bnd.bnd
├── build.gradle
└── src/main/
├── java/ // Put Java classes here
| └── com/acme/xxxx/web/internal/portlet/XXXXPortlet.java
└── resources/
├── content/
| └── Language.properties
└── META-INF/resources/ // Put Templates (JSPs) here
├── view_1.jsp
└── view_2.jsp
In each module folder, create a bnd.bnd
file.
Here is example initial Bnd content for the different modules types.
Module Type | Bnd Content |
---|---|
API | Bundle-Name: Acme XXXX API Bundle-SymbolicName: com.acme.xxxx.api Bundle-Version: 1.0.0 |
Implementation | Bundle-Name: Acme XXXX Implementation Bundle-SymbolicName: com.acme.xxxx.impl Bundle-Version: 1.0.0 |
Portlet/Web App | Bundle-Name: Acme XXXX Web Bundle-SymbolicName: com.acme.xxxx.web Bundle-Version: 1.0.0 |
If you've defined APIs in your module, export their packages in your bnd.bnd
.
For example, the k8s2-api
module publishes its com.acme.k8s2.Greeter
interface by exporting the com.acme.k8s2
package in the module's bnd.bnd
:
Export-Package: com.acme.k8s2
Make sure your build.gradle
file sets a dependency on release.portal.api
, unless you're demonstrating a DXP-only feature--In which case use release.dxp.api
. Here's your typical build.gradle
starting content:
dependencies {
compileOnly group: "com.liferay.portal", name: "release.portal.api"
}
Follow the Source Formatter rules for package names found in Java Package Formatting.
An extension or implementation package should resemble the base class or interface package but be internal. The table below shows the class package and class name for an interface and an implementation of that interface--the similarities are in bold.
Class Type | Fully Qualified Class Name |
---|---|
Interface | com.liferay.dynamic.data.mapping.storage.DDMStorageAdapter |
Implementation | com.acme.r2f1.internal.dynamic.data.mapping.storage.R2F1DDMStorageAdapter |
Portlets use this package name pattern:
package com.acme.xxxx.web.internal;
If your class implements FragmentCollectionContributor
(directly or indirectly, by extending BaseFragmentCollectionContributor
), append your class prefix (XXXX
, XXXXAble
, or similar) to your package name. Here are some examples, with the class prefixes and corresponding appended package parts in bold.
Class | Package |
---|---|
L3M9FragmentCollectionContributor | com.acme.l3m9.internal.fragment.contributor.l3m9 |
CookieBannerFragmentCollectionContributor | com.liferay.fragment.collection.contributor.cookie.banner |
In most cases, prefix class names with the project ID (capitalized).
Class Type | Class Name |
---|---|
Interface | DescribeWhatItIs (e.g., Greeter ) |
Implementation | XXXXInterfaceName (e.g., C3P1Greeter ) |
Portlet | XXXXPortlet (e.g., C3P1Portlet ) |
Most tutorial code shouldn't need Javadoc. There are some Javadoc items that you should never include.
Never include Javadoc for these things:
- author
- copyright
In the interest in minimizing lines of code, avoid defining constants. Use literal values instead.
For example, if you're specifying a name for a @Component
property value or getName
method return value, use a literal String. If you're specifying a localized value, use a literal language key name.
Component property:
some.property.name=XXXX
getName
method:
return "XXXX";
The general rule is to only use @Component
properties that are necessary. Only use a property if testing shows that your component needs it.
Don't use the immediate = true
property. Use it only if testing shows that you need it. You should only need it if the class must perform an initialization before it's used.
Only use it if testing shows that your portlet needs it.
Only use it if testing shows that your portlet needs it.
The default is already com.liferay.portlet.instanceable=true
. Don't specify it in your portlet.
Use this pattern for your portlet display names:
javax.portlet.display-name=XXXX Portlet
If your project has multiple portlets, distinguish their display names by adding another name between XXXX
and Portlet
.
If your module uses localized content, specify the content using language property files (Language Keys). Put your default Language Keys in this file:
src/main/resources/content/Language.properties
If your module overrides or extends a Liferay module (target module) and you want to add language keys or override any of the target module language keys, do these two things:
Follow the instructions in the above links (especially 2) carefully.
Include the project ID (in uppercase) in labels and headings. For example, c1n4
is the project ID used in these Language Keys:
c1n4-commerce-product-type=C1N4 Commerce Product Type
c1n4-screen-navigation-entry=C1N4 Screen Navigation Entry
If you want to print messages, log them as INFO
messages. Give context by referencing the method you're in using Javadoc-style syntax.
Import:
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
Field:
private static final Log _log = LogFactoryUtil.getLog(
YourClass.class);
Logging Logic:
getUser(long userId) throws PortalException {
if (_log.isInfoEnabled()) {
_log.info("Invoke #getUser(long)");
}
...
}
Note: The log message automatically includes the class name. For example, INFO [http-nio-8080-exec-1][J1C2UserLocalServiceWrapper:line-number]
.
For an example, see J1C2UserLocalServiceWrapper.java.
If you want to log an exception, use the Log#error(Object, Throwable)
method. For example,
try {
// code throws MessageBusException
}
catch (MessageBusException messageBusException) {
_log.error(messageBusException, messageBusException);
}
Put templates in your web module's src/main/resources/META-INF/resources/
folder.
If your module uses a generic view JSP, name it view.jsp
. If it has more than one, name them view_1.jsp
, view_2.jsp
, etc.
Overview
- Run
update_examples.sh xxxx
(replacexxxx
with project ID) to compile and to run the source formatter. - Test your code; the code has to work.
- Code changes only; don't include article changes.
- Submit your code changes to
jhinkey
(Jim Hinkey) here inliferay-learn
.
cd docs
./update_examples.sh xxxx
Resolve any reported issues and commit any changes that you or the script made.
Verify and validate your example on your target Liferay version. If it only works on a specific version or if it works slightly differently on different versions, note it for mentioning later in the tutorial article.
Testing against a Liferay Docker image is typically easiest. Here are some references:
- Install Docker CLI (i.e,
docker
) - Get and start the latest DXP image
- Read Docker Container Basics
Branches submitted for code review must contain only code changes--branches must not contain any new/modified articles.
Tip: Use a dedicated branch (free of any new/modified articles) for your example code. However, if you've included article changes in your branch, back them up (e.g., copy the articles to your Desktop) and then remove them from your branch.
Send a pull request to jhinkey
(Jim Hinkey). He will review your code before sending it onward for final review and merging.
Thanks for submitting your example to Liferay!