5 August 2019

Liferay: Converting 6.2 Portlets to 7.1 Part 3. The Remote Service

We have Java code that accesses remote services. Primarily it is accessed through Liferay’s JSON Web Services. Using AJAX we then make calls to those services. Security is important since these calls occur on the client side. Converting Liferay 6.2 remote services can be broken down into the following steps. These steps will all occur in the modules/MyApplication-Service/MyApplication-Service-service module.

A resource actions file is needed to define permissions on the service. An example is below and needed permissions may vary based on project. Strangely it seems to require a portlet name. You will also need to create a portlet.properties file to point to the default.xml. Consult the legacy version which may be located src/main/resources/resource-actions/default.xml. Not all legacy projects have one.

modules/MyApplication-Service/MyApplication-Service-service/src/main/resources/META-INF/resource-actions/default.xml

<?xml version="1.0"?>
<!DOCTYPE resource-action-mapping PUBLIC "-//Liferay//DTD Resource Action Mapping 7.0.0//EN" "http://www.liferay.com/dtd/liferay-resource-action-mapping_7_0_0.dtd">
<resource-action-mapping>

	<model-resource>
		<model-name>com.codeexcursion.application.myapplication.service.model</model-name>
		<portlet-ref>
			<portlet-name>myapplication</portlet-name>
		</portlet-ref>
		<root>true</root>
		<permissions>
			<supports>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</supports>
			<site-member-defaults />
			<guest-defaults />
			<guest-unsupported>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</guest-unsupported>
		</permissions>
	</model-resource>

	<model-resource>
		<model-name>com.codeexcursion.application.myapplication.service.model.Newsfeed</model-name>
		<portlet-ref>
			<portlet-name>myapplication</portlet-name>
		</portlet-ref>
		<permissions>
			<supports>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</supports>
			<site-member-defaults />
			<guest-defaults />
			<guest-unsupported>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</guest-unsupported>
		</permissions>
	</model-resource>

	<model-resource>
		<model-name>com.codeexcursion.application.myapplication.service.model.Location</model-name>
		<portlet-ref>
			<portlet-name>myapplication</portlet-name>
		</portlet-ref>	
		<permissions>
			<supports>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</supports>
			<site-member-defaults />
			<guest-defaults />
			<guest-unsupported>
				<action-key>UPDATE</action-key>
				<action-key>DELETE</action-key>
				<action-key>VIEW</action-key>
			</guest-unsupported>
		</permissions>
	</model-resource>

</resource-action-mapping>

MyApplication-Service/MyApplication-Service-service/src/main/resources/portlet.properties

resource.actions.configs=META-INF/resource-actions/default.xml	

Now we will create a class which will contain String constants. You will need one constant for each model in the project. For this project that would be: Newsfeed and Location. The String literals should match entries in the default.xml. These constants will be used in our permissions classes. It would also be used in indexing and search classes.

com.codeexcursion.application.myapplication.constants.MyApplicationConstants

package com.codeexcursion.application.myapplication.constants;

public class MyApplicationConstants {
  public static final String NEWSFEED_RESOURCE_NAME = "com.codeexcursion.application.myapplication.service.model.Newsfeed";
  public static final String LOCATION_RESOURCE_NAME = "com.codeexcursion.application.myapplication.service.model.Location";
}

Our next step will be to create a permissions class for our model. Since our legacy code that doesn’t meet the requirements as discussed in Liferay From 6.2 to 7.1: The Model is the key to Services, Security and Search. This is a double issue for the Newsfeed model since it doesn’t have a primary key of the Long data type. The permissions interface ModelResourcePermissionOtherwise requires the primary key be long. This means we wil have to put a hack in place. We are going to all this trouble because we need the permission class in order to register the remotes services.

com.codeexcursion.application.myapplication.internal.security.permission.resource.NewsfeedModelResourcePermission

package com.codeexcursion.application.myapplication.internal.security.permission.resource;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.security.auth.PrincipalException;
import com.liferay.portal.kernel.security.permission.PermissionChecker;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermission;
import com.liferay.portal.kernel.security.permission.resource.PortletResourcePermission;

import org.osgi.service.component.annotations.Component;

import com.codeexcursion.application.myapplication.constants.MyApplicationConstants;
import com.codeexcursion.application.myapplication.model.Newsfeed;

@Component(
		immediate = true,
		property = "model.class.name=" + MyApplicationConstants.NEWSFEED_RESOURCE_NAME,
		service = ModelResourcePermission.class
	)
public class NewsfeedModelResourcePermission 
   implements ModelResourcePermission<Newsfeed> {

	@Override
	public void check(
			PermissionChecker permissionChecker,
			Newsfeed newsFeed, String actionId)
		throws PortalException {

		check(permissionChecker, 0, actionId);
	}
	
	@Override
	public void check(
			PermissionChecker permissionChecker, long newsfeedId,
			String actionId)
		throws PortalException {
		if (!contains(permissionChecker, newsfeedId, actionId)) {
			throw new PrincipalException.MustHavePermission(
				permissionChecker, Newsfeed.class.getName(),
				newsfeedId, actionId);
		}
	}

	@Override
	public boolean contains(
			PermissionChecker permissionChecker,
			Newsfeed newsfeed, String actionId)
		throws PortalException {

		return permissionChecker.isOmniadmin();
	}

	@Override
	public boolean contains(
			PermissionChecker permissionChecker, long newsfeedId,
			String actionId)
		throws PortalException {

		return permissionChecker.isOmniadmin();
	}

	@Override
	public String getModelName() {
		return Newsfeed.class.getName();
	}

	@Override
	public PortletResourcePermission getPortletResourcePermission() {
		return null;
	}
}

We now add the service class and load the permissions.

com.codeexcursion.application.myapplication.service.impl.NewsfeedServiceImpl

package com.codeexcursion.application.myapplication.service.impl;

import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.security.auth.PrincipalException;
import com.liferay.portal.kernel.security.permission.ActionKeys;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermission;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermissionFactory;

import java.util.List;

import org.json.JSONObject;

import com.codeexcursion.application.myapplication.model.Newsfeed;
import com.codeexcursion.application.myapplication.service.base.NewsfeedServiceBaseImpl;

public class NewsfeedServiceImpl extends NewsfeedServiceBaseImpl {
	/*
	 * NOTE FOR DEVELOPERS:
	 *
	 * Never reference this class directly. Always use {@link com.codeexcursion.application.myapplication.service.NewsfeedServiceUtil} to access the newsfeed remote service.
	 */
	
	private static volatile ModelResourcePermission<Newsfeed>
	  newsfeedModelResourcePermission =
		ModelResourcePermissionFactory.getInstance(
				NewsfeedServiceImpl.class,
			"newsfeedModelResourcePermission",
			Newsfeed.class);
	
	
	public Newsfeed addNewsfeed(String key, String displayName, String url) throws PortalException {
		newsfeedModelResourcePermission.check(getPermissionChecker(), 0, ActionKeys.UPDATE);
		return this.newsfeedLocalService.addNewsfeed(key, displayName, url);
	}
	
	public Newsfeed updateNewsfeed(String key, String displayName, String url) throws PortalException {
		newsfeedModelResourcePermission.check(getPermissionChecker(), 0, ActionKeys.UPDATE);
	  Newsfeed newsfeed = this.newsfeedLocalService.getNewsfeed(key);
		return this.newsfeedLocalService.updateNewsfeed(newsfeed);
	}	
	
	public Newsfeed deleteNewsfeed(String key) throws PortalException {
		newsfeedModelResourcePermission.check(getPermissionChecker(), 0, ActionKeys.DELETE);
		return this.newsfeedLocalService.deleteNewsfeed(key);
	}
	
	public List<Newsfeed> getAll() {
		return this.newsfeedLocalService.getNewsfeeds(QueryUtil.ALL_POS, QueryUtil.ALL_POS);
	}
}

Finally we make our registrar class.

com.codeexcursion.application.myapplication.internal.security.permission.resource.NewsfeedModelResourcePermission

package com.codeexcursion.application.myapplication.internal.security.permission.resource;

import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermission;
import com.liferay.portal.kernel.security.permission.resource.ModelResourcePermissionFactory;
import com.liferay.portal.kernel.service.ResourceLocalService;
import com.liferay.portal.kernel.util.HashMapDictionary;

import java.util.Dictionary;

import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;

import com.codeexcursion.application.myapplication.model.Newsfeed;
import com.codeexcursion.application.myapplication.service.impl.NewsfeedServiceImpl;


@Component(immediate = true)
public class MyApplicationModelResourcePermissionRegistrar {

	@Reference
	ResourceLocalService _resourceLocalService;	
	
	private static volatile ModelResourcePermission<Newsfeed> newsfeedModelResourcePermission =
			ModelResourcePermissionFactory.getInstance(
					NewsfeedServiceImpl.class,
				"newsfeedModelResourcePermission",
				Newsfeed.class);
	
	
  private ServiceRegistration<ModelResourcePermission> newsfeedRegistration;
  
  
  @Activate
  public void activate(BundleContext bundleContext) throws PortalException {
    Dictionary<String, Object> properties = new HashMapDictionary<>();

    properties.put("model.class.name", Newsfeed.class.getName());
    
    newsfeedRegistration = bundleContext.registerService(
        ModelResourcePermission.class,
        newsfeedModelResourcePermission,
        properties);  	
  }
  
  @Deactivate
  public void deactivate() throws PortalException {
  	newsfeedRegistration.unregister();
  	
  }
}
tags: java - liferay - portlet - service builder

Less Is More