Objects

Using Object Entry Manager

Reading and writing object entry data programmatically is best accomplished with the auto-generated headless APIs. However, when using traditional OSGi extension points or maintaining legacy service builder applications, you can access object entries using their native Java APIs to avoid the overhead of making HTTP calls from Java.

Using the ObjectEntryLocalService is never the right approach. Instead, Liferay provides the ObjectEntryManager, which has several advantages:

  • It executes actions and validations defined in the object definition.
  • For workflow-enabled object definitions, it sends the entry through the workflow.
  • It processes state management logic from the object definition’s state manager.
  • It checks user permissions to access or modify the entry.
  • It simplifies relationship persistence; you can link related entries using the relationship key in the input map rather than retrieving relationship IDs manually and managing mapping tables.

In this example a simple CRUD application based on service builder uses ObjectEntryManager to consume a custom object and displays its entries in a JSP.

Deploy the X4M2 Service Builder Application Code

Start a new Liferay instance by running

docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.132-ga132

Sign in to Liferay at http://localhost:8080. Use the email address test@liferay.com and the password test. When prompted, change the password to learn.

Then, follow these steps:

  1. Download and unzip the X4M2 Service Builder project.

    curl https://resources.learn.liferay.com/examples/liferay-x4m2.zip -O
    
    unzip liferay-x4m2.zip
    
  2. The X4M2 sample includes a shell script that loads a custom object definition and entries into Liferay. To run it, go to liferay-x4m2/curl in the terminal.

  3. Run the shell script:

    ./populate_x4m2.sh
    

    The script adds these resources:

    • Object Definition: Able
      • Object Entry 1:
        • External Reference Code: ABLE_1
        • Name: Able 1
      • Object Entry 2:
        • External Reference Code: ABLE_2
        • Name: Able 2
      • Object Entry 3:
        • External Reference Code: ABLE_3
        • Name: Able 3
  4. From the module root (liferay-x4m2.zip/), build and deploy the sample.

    ./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
    Note

    This command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.

  5. Confirm the deployment in the Liferay Docker container console.

    STARTED com.acme.x4m2.api_1.0.0 [1433]
    STARTED com.acme.x4m2.service_1.0.0 [1434]
    STARTED com.acme.x4m2.web_1.0.0 [1435]
    
  6. Open your browser to https://localhost:8080.

  7. Add the X4M2 Portlet to a page. You can find the example portlet under Sample Widgets.

    Right away you can see the object entries listed.

    Objects appear in the portlet's view.

  8. Add an entry with the X4M2 Portlet (e.g., enter x4m2 for the name and x4m2 for the description).

  9. Click Submit.

    X4M2 Service Builder entities appear under the object entries.

To display the object entries alongside the service builder entities, the getObjectEntries method from ObjectEntryManager was called.

Understanding the X4M2 Objects Code

There are several classes needed in addition to ObjectEntryManager:

ClassPurpose
ObjectDefinitionLocalServiceRetrieve the object definition for passing into the ObjectEntryManager methods.
DTOConverterRegistryPass this in the DTOConverterContext constructor so the system can determine the right converter to use for transforming internal database models into the specific DTOs (like ObjectEntry) that you are requesting.
DefaultDTOConverterContextPass this to ObjectEntryManager to provide the context needed for converting database models into DTOs, such as the current user, accepted languages, and which fields to include.
ObjectEntryManagerCall its methods to perform CRUD operations on Object entries and orchestrate the logic defined in the object (like validations and actions).

Here’s the whole method in the Portlet class that calls the ObjectEntrtyManager to retrieve object entries:

	}
	catch (Exception exception) {
		throw new PortletException(exception);
	}
}

private Collection<ObjectEntry> _getObjectEntries(
		long companyId, String externalReferenceCode, User user)
	throws Exception {

	Page<ObjectEntry> page = _objectEntryManager.getObjectEntries(
		companyId,
		_objectDefinitionLocalService.
			getObjectDefinitionByExternalReferenceCode(
				externalReferenceCode, companyId),
		String.valueOf(companyId), null,
		new DefaultDTOConverterContext(

The entries retrieved are added to the render request, in the render method:


@Override
public void render(
		RenderRequest renderRequest, RenderResponse renderResponse)
	throws PortletException {

	try {
		ThemeDisplay themeDisplay =
			(ThemeDisplay)renderRequest.getAttribute(WebKeys.THEME_DISPLAY);

		renderRequest.setAttribute(
			"objectEntries",
			_getObjectEntries(
				themeDisplay.getCompanyId(), "C_ABLE",
				themeDisplay.getUser()));

		renderRequest.setAttribute(
			"x4m2Entries",
			_x4m2EntryLocalService.getX4M2Entries(
				QueryUtil.ALL_POS, QueryUtil.ALL_POS));

The JSP is then responsible for rendering the entities, including the object entries:

	<c:when test="${not empty objectEntries}">
		<table>
			<tbody>
				<c:forEach items="${objectEntries}" var="objectEntry">
					<tr>
						<td>${objectEntry}</td>
					</tr>
				</c:forEach>
			</tbody>
		</table>
	</c:when>
	<c:otherwise>
		<em>There are no object entries.</em>
	</c:otherwise>
</c:choose>

<h5>X4M2 Entries</h5>

<c:choose>
	<c:when test="${not empty x4m2Entries}">
		<table>
			<tbody>
				<c:forEach items="${x4m2Entries}" var="x4m2Entry">
					<tr>
						<td>${x4m2Entry}</td>
					</tr>
				</c:forEach>
			</tbody>
		</table>
	</c:when>
	<c:otherwise>
		<em>There are no X4M2 entries.</em>
	</c:otherwise>
</c:choose>

In most cases, you should use the headless APIs to access custom object data. However, when needing to access objects services from Java in Liferay, you can use ObjectEntrymanager.