07 June 2019

Now we are going to focus on converting the service layer. This can be broken down into the following steps.

My old service package path was mil.army.hrc.ikrome.feeds.service. For the new one I went with mil.army.usaac.ikrome.allfeeds. This is also the package I will use for AllFeeds-Web.

Now we need to find the servlet context and schema version for our service. In your Liferay deployment go to the Tomcat webapps directory for Liferay 6.2 and find the servlet context. Ours is allFeeds-portlet. We now connect to the database and run query as shown below. Record the schemaVersion and servletContextName. If no results are returned, record 0.0.1 for the schemaVersion and the servlet context found on the file system.

Database Query for Info

select * from Release_ where servletContextName = 'allFeeds-portlet'


servletContextName | schemaVersion
-------------------|---------------
allFeeds-portlet   | 1

To gather the model names we will need to look in the 6.2 service project. In this case it would be the allFeeds/allFeeds-portlet-service/src/main/java/. In subfolders will be the models from the old project. There will be subfolders matching the package-path in the service.xml. In this case our models are mil.army.hrc.ikrome.feeds.service.model.Location and mil.army.hrc.ikrome.feeds.service.model.Newsfeed.

File -> New -> Liferay Module Project. Enter the new module name “AllFeeds-Service”. Verify the location is correct for your modules.Select build type Maven and Liferay Version 7.1. For project template we will choose service-builder. Click Next and enter your package path. Click the Finish button.

Next we edit the pom.xml for AllFeeds-Service-service. Change the buildNumberIncrement to false and add a version number. You will also need to update the service.properties file. Also we have added additional relative paths to mergeModelHintsConfigs. This is so any changes manually made to portlet-model-hints.xml will be preserved even if we run the service builder build from the first two parent directories.

modules/AllFeeds-Service/AllFeeds-Service-service/pom.xml

...Stuff Omitted
<build>
	<plugins>
		<plugin>
			<groupId>com.liferay</groupId>
			<artifactId>com.liferay.portal.tools.service.builder</artifactId>
			<version>1.0.248</version>
			<configuration>
				<apiDirName>../AllFeeds-Service-api/src/main/java</apiDirName>
				<autoNamespaceTables>true</autoNamespaceTables>
                <buildNumberIncrement>false</buildNumberIncrement>
                <buildNumber>300000</buildNumber>
				<hbmFileName>src/main/resources/META-INF/module-hbm.xml</hbmFileName>
				<implDirName>src/main/java</implDirName>
				<mergeModelHintsConfigs>
				    src/main/resources/META-INF/portlet-model-hints.xml,
				    AllFeeds-Service/AllFeeds-Service-service/src/main/resources/META-INF/portlet-model-hints.xml,
				    AllFeeds-Service-service/src/main/resources/META-INF/portlet-model-hints.xml				
				</mergeModelHintsConfigs>
				<modelHintsFileName>src/main/resources/META-INF/portlet-model-hints.xml</modelHintsFileName>
				<osgiModule>true</osgiModule>
				<propsUtil>mil.army.usaac.ikrome.allfeeds.service.util.ServiceProps</propsUtil>
				<resourcesDirName>src/main/resources</resourcesDirName>
				<springFileName>src/main/resources/META-INF/spring/module-spring.xml</springFileName>
				<sqlDirName>src/main/resources/META-INF/sql</sqlDirName>
				<sqlFileName>tables.sql</sqlFileName>
			</configuration>
		</plugin>
	</plugins>
</build>
...Stuff Omitted

We can now copy over information from our old service.xml to our new service.xml. Make sure that our new package path is used. The majority of entity definitions can be copied over with no changes.

modules/AllFeeds-Service/AllFeeds-Service-service/service.xml

<?xml version="1.0"?>
<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 7.0.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_7_0_0.dtd">

<service-builder package-path="mil.army.usaac.ikrome.allfeeds">
	<namespace>newsfeed</namespace>
	 <entity name="Newsfeed" local-service="true" remote-service="true">
        <column name="NewsfeedKey" type="String" primary="true"></column>
        <column name="NewsfeedName" type="String"></column>
        <column name="NewsfeedValue" type="String" ></column>
    </entity>
    
    
    <entity name="Location" local-service="true" remote-service="true">
    	<column name="locationId" type="long" primary="true"></column>
    	<column name="companyId" type="long"></column>
    	<column name="groupId" type="long"></column>    	
    	<column name="zip" type="String"></column>    	
    	<column name="name" type="String"></column>
    	
    	<finder return-type="Collection" name="GroupId">
    		<finder-column name="groupId"></finder-column>
    	</finder>
    </entity>
</service-builder>

Now we want to run our service builder using maven so that code will be generated in AllFeeds-Service-Api and AllFeeds-Service-service.

modules/AllFeeds-Service/AllFeeds-Service-service/

$ mvn service-builder:build

The generated code in AllFeeds-Service-Api will likely remain untouched. As shown in Part 1 the cutomizable impl files will be in AllFeeds-Service-service, for 7.1. Copy the code from 6.2 and modify as needed for 7.1. Keep in mind that your import statements will be different and that your IDE may insert the wrong ones as you copy from 6.2 to 7.1. Most likely the only differences in 7.1 will be the import statements.

Now we need to add the bundle activator class. We will need the servlet context and package path from earlier. The servlet context will be your old symbolic name and the package path appended with “.service” will be your new symbolic name. First we need to add dependencies to our pom.xml.

IKROme-Analysis-Tool-Service/IKROme-Analysis-Tool-Service-service/pom.xml

...Stuff omitted
<dependencies>
...Stuff omitted
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>org.osgi.core</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.osgi</groupId>
      <artifactId>osgi.cmpn</artifactId>
      <version>6.0.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>com.liferay</groupId>
      <artifactId>com.liferay.portal.upgrade.api</artifactId>
      <version>2.0.3</version>
      <scope>provided</scope>
    </dependency>
</dependencies>

The class should be placed in a package that is the package path appended with “.activator”.

AllFeedsServiceBundleActivator.java

package mil.army.usaac.ikrome.allfeeds.activator;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.module.framework.ModuleServiceLifecycle;
import com.liferay.portal.kernel.upgrade.UpgradeException;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.upgrade.release.BaseUpgradeServiceModuleRelease;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

public AllFeedsServiceBundleActivatorclass  implements BundleActivator{
	
	private ServiceTracker<Object, Object> serviceTracker;
	
	@Override
	public void start(BundleContext bundleContext) throws Exception {
		
		Filter filter = bundleContext.createFilter(
		        StringBundler.concat(
		            "(&(objectClass=", ModuleServiceLifecycle.class.getName(), ")",
		            ModuleServiceLifecycle.DATABASE_INITIALIZED, ")"));
		
		serviceTracker = new ServiceTracker<Object, Object>(bundleContext, filter, null) {
			
			@Override
	         public Object addingService(
	          ServiceReference<Object> serviceReference) {
				 
				 try {
		              BaseUpgradeServiceModuleRelease
		                 upgradeServiceModuleRelease =
		                   new BaseUpgradeServiceModuleRelease() {

		                    @Override
		                    protected String getNamespace() {
		                        return "newsfeed";
		                    }

		                    @Override
		                    protected String getNewBundleSymbolicName() {
		                        return "mil.army.usaac.ikrome.allfeeds.service";
		                    }

		                    @Override
		                    protected String getOldBundleSymbolicName() {
		                        return "allFeeds-portlet";
		                    }
		                                   

		                   };
		                   
		              upgradeServiceModuleRelease.upgrade();

		              return null;
		          }
		          catch (UpgradeException ue) {
		              throw new RuntimeException(ue);
		          }
			}
		};
		serviceTracker.open();
	}
	
	@Override
	public void stop(BundleContext context) throws Exception {
		serviceTracker.close();		
	}
	
}

Once our bundle activator class is complete we need to add a reference to it in our bnd.bnd file. Add an entry for Bundle-Activator.

modules/AllFeeds-Service/AllFeeds-Service-service/bnd.bnd

Bundle-Name: AllFeeds-Service-service
Bundle-SymbolicName: mil.army.usaac.ikrome.allfeeds.service
Bundle-Version: 1.0.0
Bundle-Activator:  mil.army.usaac.ikrome.allfeeds.activator.AllFeedsServiceBundleActivator
Liferay-Require-SchemaVersion: 1.0.0
Liferay-Service: true
-includeresource: META-INF/service.xml=service.xml
-liferay-service-xml: META-INF/service.xml
-plugin.service: com.liferay.ant.bnd.service.ServiceAnalyzerPlugin
-plugin.spring: com.liferay.ant.bnd.spring.SpringDependencyAnalyzerPlugin

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/AllFeeds-Service/AllFeeds-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>mil.army.usaac.ikrome.allfeeds.service.model.Newsfeed</model-name>
		<portlet-ref>
			<portlet-name>allfeeds</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>mil.army.usaac.ikrome.allfeeds.service.model.Location</model-name>
		<portlet-ref>
			<portlet-name>allfeeds</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>

</resource-action-mapping>

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

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

Adding the upgrade step class is next. The counter upgrade is for tables with an int or long as a primary key. AllFeeds has one table that meets this criteria. This updates the counter service that produces the primary key values. Next we will update the models names in for class name service. AllFeeds has two tables and both should be updated. First we will need to find the full package path for our model from the 6.2 service builder project. This is mil.army.hrc.ikrome.feeds.service.model.Location and mil.army.hrc.ikrome.feeds.service.model.Newsfeed. The upgrade step code should be placed in the package path appended with “.upgrade”.

Note: The most reliable way to find the old model name is to query the Counter and ClassName_ tables. You can also go into the code in the old 6.2 service project.

AllFeedsUpgradeStep.java

package mil.army.usaac.ikrome.allfeeds.upgrade;

import com.liferay.counter.kernel.model.Counter;
import com.liferay.counter.kernel.service.CounterLocalService;
import com.liferay.portal.kernel.dao.jdbc.DataAccess;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.ClassName;
import com.liferay.portal.kernel.service.ClassNameLocalService;
import com.liferay.portal.kernel.upgrade.UpgradeProcess;

import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import mil.army.usaac.ikrome.allfeeds.model.Location;
import mil.army.usaac.ikrome.allfeeds.model.Newsfeed;


public class AllFeedsUpgradeStep extends UpgradeProcess{
	private static Log LOG = LogFactoryUtil.getLog(AllFeedsUpgradeStep.class);
	
	private static final String OLD_LOCATION_MODEL_NAME = "mil.army.hrc.ikrome.feeds.service.model.Location";
	private static final String OLD_NEWFEED_MODEL_NAME = "mil.army.hrc.ikrome.feeds.service.model.Newsfeed";

	private volatile CounterLocalService counterLocalService;
	private volatile ClassNameLocalService classNameLocalService;

	public AllFeedsUpgradeStep(
			CounterLocalService counterLocalService,
			ClassNameLocalService classNameLocalService) {		
		
		this.counterLocalService = Optional.of(counterLocalService).orElseThrow(IllegalArgumentException::new);
		this.classNameLocalService = Optional.of(classNameLocalService).orElseThrow(IllegalArgumentException::new);
		
	}
	
	@Override
	protected void doUpgrade() throws Exception {
		upgradeCounter(OLD_LOCATION_MODEL_NAME, Location.class.getName());
		
		upgradeClassName(OLD_LOCATION_MODEL_NAME, Location.class.getName());
		upgradeClassName(OLD_NEWFEED_MODEL_NAME, Newsfeed.class.getName());
	}
	
	private void upgradeCounter(String oldModelName, String newModelName) {
			Counter oldCounter = counterLocalService.fetchCounter(oldModelName);
			if (oldCounter != null) {
				setNewCounter(newModelName, oldCounter.getCurrentId());
				deleteCounter(oldCounter);
			}
	}	

	private void setNewCounter(String newModelName, long currentId) {
		Counter newCounter = counterLocalService.fetchCounter(newModelName);
		if(newCounter != null) {
			newCounter.setCurrentId(currentId);
			counterLocalService.updateCounter(newCounter);
		}
	}
	
	private void deleteCounter(Counter oldCounter) {
			counterLocalService.deleteCounter(oldCounter);
  }	
	
	protected void upgradeClassName(String oldName, String newName) {

			ClassName oldClassName = classNameLocalService.fetchClassName(oldName);			
			if (oldClassName != null) {
        deleteClassName(newName);
				oldClassName.setClassName(newName);
        classNameLocalService.updateClassName(oldClassName);
			}
	}
	
	protected void deleteClassName(String name) {
		ClassName className = classNameLocalService.fetchClassName(name);			
		if(className != null) {
			classNameLocalService.deleteClassName(className);
		}
	}	
}

Next is our upgrade class. This class will go into the same package as the previous class and will use the schema version we acquired earlier. It will also use the previous class. If you didn’t need the previous class you can replace it with the DummyUpgradeStep class.

AllFeedsUpgrade.java

package mil.army.usaac.ikrome.allfeeds.upgrade;

import com.liferay.counter.kernel.service.CounterLocalService;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.service.ClassNameLocalService;
import com.liferay.portal.upgrade.registry.UpgradeStepRegistrator;

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

@Component(immediate = false, service = UpgradeStepRegistrator.class)
public class AllFeedsUpgrade implements UpgradeStepRegistrator{
	
	private static Log LOG = LogFactoryUtil.getLog(AllFeedsUpgrade.class);
	
	@Reference
	private volatile CounterLocalService counterLocalService;
	
	@Reference
	private volatile ClassNameLocalService classNameLocalService;
	
	@Override
	public void register(Registry registry) {
		LOG.debug("AllFeedsUpgrade register method called.");
		registry.register(
	             "1", "1.0.0",
	            new AllFeedsUpgradeStep(counterLocalService, classNameLocalService));		
	}

}

Less Is More ~ Older posts are available in the archive.