The parent project of the multiple module web application uses the uses the <packaging>pom</packaging>
tag and several <module>
tags to indicate that it is controlling the build order of several artifacts. The control is provided through the reactor plugin.
The file structure that I use is:
/ear/ear/pom.xml/web/web/pom.xml.classpath.projectpom.xml
The ear directory is empty (except for pom.xml). The application.xml required for an EAR is generated by the was6 maven plugin. The directory structure of the web directory is not shown here but specified as the maven standard directory layout.
There are several fields in the various hidden control structures that must be internally consistent for the build and deploy to work. This is why I prefer to use a maven archetype (created yourself for your project team) project to generate web application projects from scratch. My experience in the field indicates that developers who copy and paste from existing projects often make mistakes which are very hard to diagnose.
These are the items that you need to address:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <version>2.3.2</version> <configuration> <modules> <webModule> <groupId>com.yourcompany.${artifactId}.web</groupId> <artifactId>${artifactId}Web</artifactId> <contextRoot>${artifactId}</contextRoot> </webModule> </modules> </configuration> </plugin>
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>was6-maven-plugin</artifactId> <executions> <execution> <phase>pre-integration-test</phase> <goals> <goal>installApp</goal> </goals> </execution> </executions> <configuration> <earFile>target/${artifactId}Ear-${version}.ear</earFile> <applicationName>${artifactId}</applicationName> </configuration> </plugin>
<was6.wasHome></was6.wasHome> <was6.targetServer></was6.targetServer> <was6.server></was6.server> <was6.conntype></was6.conntype> <was6.port></was6.port> <was6.username></was6.username> <was6.password></was6.password> <was6.host></was6.host>
You specify text values for each of the properties above that make sense on your machine. Note that was6.server and was6.targetServer will actually be the same value. You include them both because different goals of the was6 plugin used different parameter names to express the same logical value. Sometimes open source will contain idiosyncratic annoyances (but hey, you can’t beat the price!)
In the next post I will examine how the web module is processed when you are creating a portal theme or skin.
]]>Web Applications are WAR files that include java code and JSP files with a web.xml deployment descriptor that are intended to implement dynamic web functionality (as opposed to static web functionality which uses HTML files).
A portal team typically uses this packaging technique for the following artifacts:
The WebSphere Application server only understands EAR files. EAR files are collections of WAR and JAR files with an application.xml deployment descriptor. Developers that come from a Tomcat or JBoss (or similar J2EE application server) background may be accustomed to performing a hot deploy that simply involves them placing a WAR or EAR file in a specific directory. The WebSphere deployment is more involved because of clustering and vendor extensions that provide more robust enterprise features.
Now let’s assume you are creating a Spring web service implementation for your business logic. RAD automatically creates an associated EAR project for your WAR project when you choose dynamic web application during the project creation wizard. The EAR file is deployed on the WebSphere_Portal server instance of the WebSphere Application Server that includes your portal application.
The EAR file can be placed on the server using several techniques:
The wsadmin approach is the obvious choice for scripting releases. The administrative console approach is a multiple screen wizard that forces developers to choose values for many vendor extensions and security configurations that are typically beyond the domain knowledge of a component developer.
A best practice of maven is to follow the one artifact for each pom.xml convention. We have already seen that WebSphere expects an EAR file that contains a WAR file for a typical web application. The solution for this problem is to create a multiple module maven project that serves as the parent for the WAR and EAR files that are required.
The deployment of the EAR file is handled by the was6 maven plugin from codehaus. This plugin will require that your Continuous Integration (CI) server handling the deployments has a WebSphere Application Server installation on the same machine. This is required because the was6 plugin is a wrapper for the wsadmin client (which includes an IBM component that handles the secured communications with a remote WAS instance). The configuration of the was6 maven plugin always requires specifying a -DwasHome property value that points to the root of the WAS server installation. I am not aware of any set of JAR files that implement this functionality (unlike the xmlaccess tool discussed in my WebSphere Portal and Maven post that can be implemented by including jars as dependencies of your custom plugin).
Next up I will discuss the file structure of the project used for my proposed solution for web applications.
]]>WebSphere Portal 6.0.1 introduced a capability called the URI resolution service or POC servlet or the Resolver Framework depending on where you see it mentioned. The DeveloperWorks article called Accessing portal content with custom URLs is the most recent and informative post I have found describing this service.
This service was created to allow non-portal sites in your organization to link to your portal content (the acronym POC means Piece of Content). The service works by using the scheme portion of the URI to divert the request to a collection of handlers that use the chain of responsibility pattern to modify a Resolved object which ultimately communicates parameters to portal objects.
Some examples of where this concept is used include:
This service can also be used for a few use cases you may not expect:
I am going to explore these concepts in more detail in future posts.
You can find examples of resolvers by searching for the plugin.xml files that register them. This unix example would give you a list of EAR files in your installedApps directory:
find . -name plugin.xml -exec grep -q 'locationServiceHandler' {} \; -print
Notice the technique above doesn’t find resolvers that are contained in unexploded JAR files. The ${PortalServer}/base directory contains many jars that include plugin.xml files that register resolver implementations.
The following dump of the uriSchemeMap field of the ContentOperationsRegistryHomeImpl$RegistryBean shows that there are 59 of these resolvers in the WPS 7.0 server:
ll=[com.ibm.portal.resolver.model.language], lm=[com.ibm.portal.resolver.model.layout], tx=[com.ibm.portal.resolver.model.tx], pacdump=[wp.ac.pacdump], message=[com.ibm.wps.mmi.message], http=[com.ibm.wps.resolver.proxy.http], virtual=[virtual], mashupjs=[com.ibm.wps.mmi.js], state=[com.ibm.portal.resolver.state], catalog=[com.ibm.wps.mmi.catalog], wsrp=[com.ibm.wps.resolver.wsrp.rest, com.ibm.portal.resolver.wsrp.wsdl], skinlist=[com.ibm.wps.resolver.webdav.admin.sl], res=[com.ibm.portal.resolver.res], wp.operations=[com.ibm.wps.resolver.operations], prototype=[com.premierinc.prototype.Resolver], spa=[com.ibm.wps.spa.data], ai=[com.ibm.wps.ai.rest], model=[com.ibm.portal.resolver.model], data=[com.ibm.portal.resolver.data], resourcefeed=[com.ibm.portal.resolver.model.mint.resource], contentmapping=[com.ibm.wps.contentmapping.rest], wcmbw=[com.ibm.wps.wcm.blog.json], fs-type1=[com.ibm.wps.resolver.webdav.filestore.mi], trash=[com.ibm.portal.resolver.model.trash], rm=[com.ibm.portal.cp.resource], mashuptl=[com.ibm.wps.mmi.mashup_tl], fd=[com.ibm.wps.federation.lookup], nm=[com.ibm.wps.mmi.navigation, com.ibm.portal.resolver.model.navigation], bc=[com.ibm.portal.resolver.cai.bc], config=[com.ibm.wps.resolver.config], media-link=[com.ibm.portal.resolver.model.mint.media-link], mashup=[com.ibm.wps.resolver.mashup], cpum=[com.ibm.portal.cp.user], dialog=[wp.dialog], cai=[com.ibm.portal.resolver.cai], com.ibm.content.operations.registry.api=[com.ibm.content.operations.registry.api], fragment=[com.ibm.wps.resolver.aggregation], wm=[com.ibm.portal.resolver.model.wire], tos=[com.ibm.portal.cp.tagging.opensearch], sl=[com.ibm.portal.resolver.model.skin], addtostlist=[addtostlist.resolver], redirect=[com.ibm.portal.resolver.redirect], ca=[com.ibm.portal.resolver.cai], themeGraphic=[com.ibm.wps.theme.resolver.graphic], op=[com.ibm.wps.resolver.operation], com.ibm.wps.logging.resolver.requestresponselogger=[com.ibm.wps.resolver.impl.logging], personrecord=[personrecord.resolver], deploy=[com.ibm.wps.mmi.deploy], cl=[com.ibm.portal.resolver.model.client], cm=[com.ibm.portal.resolver.model.content], rtm=[com.ibm.portal.cp.rating], mashupai=[com.ibm.wps.mmi.mashupai], iwidget=[com.ibm.portal.resolver.iwidget], com.ibm.portal.friendly.name=[com.ibm.wps.resolver.friendly], distmap=[com.ibm.wps.resolver.xml.cache], tl=[com.ibm.portal.resolver.model.theme], tm=[com.ibm.portal.cp.tagging], portlet=[com.ibm.portal.resolver.portlet], skin=[com.ibm.wps.skin.rendering],
Releasing your portal project to a brand new environment can be a fairly large challenge.
I maintain the reason for the challenge is that there are so many tools and procedures required for this process. It is difficult to find somebody on your team that has deep administrative experience coupled with a development background so that communication with the developers can occur with maximum efficiency during the mad rush to production that many teams experience. There will often be one or more developers on the team who are only familiar with packaging and deploying these artifacts using RAD/Eclipse or some other Integrated Development Environment (IDE) toolset. I maintain that a release manager should struggle to maintain the maxim that nothing ever goes to production without a script.
Consider the fact that your portal project will require several of these tools for deployment:
Each one of these toolsets is feature rich and complicated. Your release manager generally has to rely on the reference material published in the various infocenters and the IBM forums that address questions in these areas. These tools also change at a fairly rapid pace. Finally some tools do not have remote execution capability (which makes automation from a single machine more difficult).
The most recent advice from IBM concerning this topic is published in a DeveloperWorks article named IBM WebSphere Portal Version 6.0 staging scenarios using ReleaseBuilder.
I want to draw attention to the WebSphere Portal artifacts not covered by ReleaseBuilder and Items that cannot be staged with ReleaseBuilder sections that contain 9 portal artifacts and 4 other items for a combined total of 13 items that are not addressed by the recommended strategy. The more recent Manual steps prior to using ReleaseBuilder lists 10 portal artifacts and 17 environment configurations for a combined total of 27 items that require attention. Now let’s analyze these lists to come up with a comprehensive strategy.
The checklists for portal artifacts differ by one because one list combines themes/skins while another lists them separately.
You use a parent pom.xml file that each portlet project inherits from to encapsulate your dependencies and maven plugin bindings. This keeps all this code out of sight from your portlet project.
This first code posting shows the plugin definition under a pluginManagement section. This section is used to create any dependencies used by the plugin itself:
... <pluginManagement> <plugins> <plugin> <groupId>com.mycompany</groupId> <artifactId>portlet-deploy</artifactId> <version>1.0.0-SNAPSHOT</version> <dependencies> <!-- Needed for ANT scp task --> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-jsch</artifactId> <version>1.7.1</version> </dependency> <!-- Needed for propertyregexp task --> <dependency> <groupId>ant-contrib</groupId> <artifactId>ant-contrib</artifactId> <version>1.0b3</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant-optional</artifactId> <version>1.5.3-1</version> </dependency> <!-- END: Needed for propertyregexp task --> <!-- For XSLT transformations --> <dependency> <groupId>ant</groupId>
<artifactId>ant-trax</artifactId> <version>1.6.5</version> </dependency> <!-- For xmlaccess scripts --> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>wp.xml.client</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>wp.base</artifactId> <version>7.0</version> </dependency> <!-- END: For xmlaccess scripts --> <!-- For pznload scripts --> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>pznpublish</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>wp.content.repository.client</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>pznruntime</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>pzncommon</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>pznnls</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>com.ibm.portal</groupId> <artifactId>workmanager</artifactId> <version>7.0</version> </dependency> <!-- END: For pznload scripts --> </dependencies> </plugin> </plugins> </pluginManagement> ...
The code above assumes that you loaded the necessary IBM portal jars using an artifactId name of the jar filename without the .jar extension. The post includes some extra jars needed for personalization functionality (PZN) which will be covered in a future post. Remember that the ellipses in the code (at beginning and end) represent the rest of your XML and should not be copied themselves.
The next section shows the code that binds your plugin to the maven lifecycle. The pre-integration-test phase is used so that integration test failures can prevent the installation of a flawed portlet in your local repository (on the developer machine) or worse in your snapshot repository (when the deploy goal is executed from your continuous integration server):
...
<plugins> <plugin> <groupId>com.mycompany</groupId> <artifactId>portlet-deploy</artifactId> <executions> <execution> <phase>pre-integration-test</phase> <goals> <goal>deploy</goal> </goals> </execution> </executions> </plugin> </plugins>
...
Note that the deployment will not work unless you use an IBM JDK (and not the Sun JDK included by default on most windows machines) to compile and deploy with. The configuration of this is beyond the scope of this post.
This concludes the basic infrastructure you need for deploying portlets using maven. However, there are other portal artifacts that can benefit from maven. The basic techniques of generating scripts from existing descriptor files generally remains the same for these other items.
If you made it this far you will probably be interested in Release Management for WebSphere Portal.
]]>I recommend using a maven plugin to package your logic. The reason is that the ANT antcall and XSLT tasks require file inputs (and do not support URIs). When you package these file resources into a jar (as you do with a plugin) then you can extract them to the correct relative filesystem locations as part of your mojo startup code. That means you don’t require developers to place these files into the projects themselves. This also means that you control the distribution of these key files and can replace them all at once in a new maven plugin version without having to email everyone that uses your artifacts.
The following source code example represents a java class that performs the setup and execution of your deployment logic:
package com.mycompany; import java.io.File; import java.io.IOException; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.tools.ant.Project; /** * Deploy the portlet onto a running WebSphere portal server. * * <p> * Algorithm: * <li>Copy resources that must be in project filesystem. * <li>Pass all maven properties required to the ANT project. * <li>Call the appropriate ANT target. * * @goal deploy * @phase install * @see http://docs.codehaus.org/display/MAVENUSER/Mojo+Developer+Cookbook * @see http://www.vineetmanohar.com/2009/11/3-ways-to-run-java-main-from-maven/ */ public class DeployMojo extends AbstractMojo { /** * @parameter default-value="${project}" */ private MavenProject project; /** * A WPS account with admin priveleges. * @parameter expression="${wpsuser}" default-value="wpadmin" */ private String wpsuser; /** * The password for wpsuser. * @parameter expression="${wpspassword}" default-value="wpadmin" */ private String wpspassword; /** * The port WPS is running on. Shown as WC_defaulthost in the WAS console. * @parameter expression="${wpsport}" default-value="10039" */ private String wpsport; /** * The web application root for the portal server. * @parameter expression="${wpscontext}" default-value="wps" */ private String wpscontext; /** * The DNS host name or IP address of the portal server host machine. * @parameter expression="${wpshost}" default-value="localhost" */ private String wpshost; /** * Skip the portlet deployment. * @parameter expression="${skipportal}" default-value="false" */ private boolean skipportal; /** * Set this property to perform a remote portal deployment. * The syntax is what you see after the @host: string in an scp command line. * For example: /opt/app/WebSphere/wp_profile/installableApps * @parameter expression="${remotedir}" */ private String remotedir; /** * A user account on the remote machine with write privileges to remotedir. * @parameter expression="${scpuser}" */ private String scpuser; /** * Password for the scpuser account. * @parameter expression="${scppassword}" */ private String scppassword; public void execute() throws MojoExecutionException, MojoFailureException { if (skipportal) { getLog().info("Skipping portal deployment."); return; } /* * Construct and configure an ANT project. */ Project antproj; try { antproj = AntDelegate.getProject(); AntDelegate.extract(new File("target/xmlaccess-input.xsl"), "/xmlaccess-input.xsl"); } catch (IOException e) { throw new MojoExecutionException("Problem reading jar resource.", e); } setProperties(antproj); /* * Execute the correct ANT target based on the plugin parameters. */ if (remotedir != null) { antproj.executeTarget("install.remote"); } else { antproj.executeTarget("install.local"); } } /** * Map all the maven environment properties into the ANT environment. */ private void setProperties(Project antproj) { antproj.setProperty("project.artifactId", project.getArtifactId()); antproj.setProperty("project.version", project.getVersion()); antproj.setProperty("wpsuser", wpsuser); antproj.setProperty("wpspassword", wpspassword); antproj.setProperty("wpsport", wpsport); antproj.setProperty("wpscontext", wpscontext); antproj.setProperty("wpshost", wpshost); antproj.setProperty("remotedir", remotedir); antproj.setProperty("scpuser", scpuser); antproj.setProperty("scppassword", scppassword); } }
The next source code example shows a class that handles common code for other mojos in your plugin:
package com.mycompany; import java.io.File; import java.io.IOException; import java.util.Calendar; import org.apache.commons.io.FileUtils; import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.Project; import org.apache.tools.ant.ProjectHelper; import org.apache.tools.ant.util.DateUtils; /** * Extract the build.xml file from the plugin jar and configure an ANT project. * @author kstclair */ public class AntDelegate { static public Project getProject() throws IOException { File buildfile = extract(new File("target/build.xml"), "/build.xml"); /* * Find and parse the buildfile */ Project antproj = new Project(); antproj.setProperty("ant.file", buildfile.getAbsolutePath()); /* * Add a logger to see results from the buildfile */ DefaultLogger consoleLogger = new DefaultLogger(); consoleLogger.setErrorPrintStream(System.err); consoleLogger.setOutputPrintStream(System.out); consoleLogger.setMessageOutputLevel(Project.MSG_INFO); antproj.addBuildListener(consoleLogger); antproj.init(); ProjectHelper helper = ProjectHelper.getProjectHelper(); antproj.addReference("ant.projectHelper", helper); helper.parse(antproj, buildfile); return antproj; } /** * Use apache commmons-io to extract the required build files from the plugin jar * to the target directory of the project. * @param myfile * @param myUrl * @return * @throws IOException */ static public File extract(File myfile, String uri) throws IOException { if (!myfile.exists()) { FileUtils.copyInputStreamToFile(AntDelegate.class.getResourceAsStream(uri), myfile); } return myfile; } /** * Add a timestamp extension to the user file. This is intended for the Hudson environment. * @param serverTimestamp * @param rulefile * @return */ static public String calcTimestamp(boolean serverTimestamp, String rulefile) { if (serverTimestamp) { String extension = DateUtils.format(Calendar.getInstance().getTime(), ".yyyyMMddHHmmss"); rulefile = rulefile + extension; } return rulefile; } }
Some readers will notice that I extract the build.xml and the xmlaccess-input.xsl in two different spots. The reason is that AntDelegate is used by other goals that are packaged with the maven plugin.
The mojo developer cookbook shows many of the common code snippets you will use. The following portions are included in the pom.xml that define your maven plugin so that maven knows you are creating a maven-plugin (ellipses indicate missing portions of the xml file):
<packaging>maven-plugin</packaging>...<dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>2.2.1</version> </dependency>...
Finally this snippet will ensure the maven generated site will produce good documentation for your plugin:
...<reporting> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <version>2.5.1</version> </plugin> </plugins> </reporting>...
The next post in this series will show how to bind this plugin to the correct build phase.
Next up: WebSphere Portal and Maven (Part 6)
]]>
So far you have a compiled portlet and an XSLT capable of producing an xmlaccess request input file based on your specific portlet.xml file.
Next you create an ANT script that is capable of submitting your request to the portal server. The script presented below diverges to distinguish between local portlet deployments (when you are deploying to a portal server on the same machine on which you are building) and remote portlet deployments (when you are deploying to a remote portal server). The divergence is because local portlet deployments do not require any copying of the generated WAR file. For remote deployments I use the ANT scp task to copy the WAR file to the remote machine.
I prefer the scp task because you can place the file into the ${wp_profile}/installableApps directory (which follows standard IBM practice). This method requires that you have a shell account on the remote machine with correct write privileges. I’ll admit that I have used the nexus/archiva snapshot repository URL as an input to the xmlaccess request when I didn’t have a shell account. This hack avoids the copying procedure, but I would avoid this method when possible because the logic to predict the URL is not as robust. You can plug any type of remote copying or URL based sharing method into this portion of the script.
The name of your web module in the wps admin console will default to ${artifactId}-${version}.war because that is derived from the filename of the deployed WAR file. Adjusting this would require some deeper level maven hacks that I like to avoid. However, the name of your web module in the AppServer admin console is under your control. I like to use ${MyCompany}_ as a prefix so that all your projects can be quickly filtered to distinguish them from the existing IBM modules.
Here is the ANT script:
<project default="install.local"> <target name="install.local" depends="private.local.init, private.deploy" /> <target name="install.remote" depends="private.remote.init, private.deploy" /> <target name="private.local.init"> <property name="file.url" value="file:///${basedir}/${project.artifactId}-${project.version}.war" /> </target> <target name="private.deploy"> <!-- Step 1: Generate xmlaccess input from portlet.xml --> <echo message="Step 1: Generate xmlaccess request: target/deployme.xml" /> <xslt in="../WebContent/WEB-INF/portlet.xml" out="deployme.xml" force="true" style="xmlaccess-input.xsl"> <param name="WAR_URL" expression="${file.url}" /> <param name="DISPLAY_NAME" expression="MyCompany_${project.artifactId}" /> </xslt> <!-- Step 2: Run the install/update xmlaccess script --> <!-- If the java process is forked then the CLASSPATH must be set --> <echo message="Step 2: Deploying portlet using xmlaccess input: target/deployme.xml" /> <java classname="com.ibm.wps.xmlaccess.XmlAccess" logError="true" resultproperty="xmlaccessReturnCode"> <arg line="-in target/deployme.xml" /> <arg line="-user ${wpsuser}" /> <arg line="-password ${wpspassword}" /> <arg line="-url http://${wpshost}:${wpsport}/${wpscontext}/config" /> <arg line="-out target/output.xml" /> </java> <!-- Step 3: Determine result of deployment attempt --> <echo message="Step 3: Determine script result..." /> <taskdef resource="net/sf/antcontrib/antlib.xml" /> <if> <not> <equals arg1="${xmlaccessReturnCode}" arg2="0" /> </not> <then> <length property="fileLength" file="output.xml" /> <loadfile property="output.result" srcFile="output.xml" /> <condition property="output.result" value="output.xml is empty"> <equals arg1="${fileLength}" arg2="0" /> </condition> <echo message="----- output.xml starts -----" /> <echo>${output.result}</echo> <echo message="----- output.xml ends ----/apps/IBM/Profiles-" /> <fail message="Execution of deployme.xml Failed"> <condition> <not> <equals arg1="${xmlaccessReturnCode}" arg2="0" /> </not> </condition> </fail> </then> <else> <echo message="Execution of deployme.xml OK" /> </else> </if> </target> <!--Use SCP to copy the WAR file to the remote machine --> <target name="private.remote.init"> <scp file="${project.artifactId}-${project.version}.war" todir="${scpuser}@${wpshost}:${remotedir}" password="${scppassword}" trust="true" /> <property name="file.url" value="file:///${remotedir}/${project.artifactId}-${project.version}.war" /> </target> </project>
The next post in the series will explain how the XSLT and and the ANT code is combined in a maven plugin to perform your portlet deployment.
Next up: WebSphere Portal and Maven (Part 5)
]]>
The deployment of a portlet to a portal server is accomplished using xmlaccess (also called the XML configuration interface). At this point I need to point out that the deploy goal of maven is a separate concept from a portlet deployment. The maven deploy goal is intended to move your packaged maven artifact to the maven repository server. A portlet deployment means that you are moving and installing your portlet onto a portal server where it can be placed onto a page and receive requests.
You actually perform your portlet deployment during the pre-integeration-test phase of the maven lifecycle. This is done here instead of the deploy phase because you would not want to share the code with other developers if your integration tests fail during the integration-test phase.
The input file of an xmlaccess request is an XML configuration file with specialized syntax to describe your intended operation. You want to modify the DeployPortlet.xml sample file included in your portal installation and replace it with parameters that match the portlet you wish to deploy.
I have created an XSLT file that can process the portlet.xml descriptor and produce a valid xml configuration file that you can send in an xmlaccess request. Readers should notice that this is a modified form of the DeployPortlet.xml sample file included with the portal. In a future post I will describe how and where this file gets executed during the build.
<?xml version="1.0"?><xsl:stylesheet version="2.0" exclude-result-prefixes='wps' xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wps="http://java.sun.com/xml/ns/portlet/portlet-app_2_0.xsd"> <xsl:output method="xml" indent="yes"/> <xsl:param name="WAR_URL"/> <xsl:param name="DISPLAY_NAME"/> <xsl:template match="/"> <xsl:comment>Generated version: 1.0</xsl:comment> <request type="update" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="PortalConfig_7.0.0.xsd"> <portal action="locate"> <xsl:apply-templates/> </portal> </request> </xsl:template> <xsl:template match="wps:portlet-app"> <web-app action="update" active="true" uid="{@id}.webmod"> <url><xsl:value-of select="$WAR_URL"/></url> <!-- Use the maven artifactId as the display name in the WAS console with a prefix to group together --> <display-name><xsl:value-of select="$DISPLAY_NAME"/></display-name> <servlet action="update" referenceid="{wps:portlet/wps:portlet-name}.servlet"/> <portlet-app action="update" uid="{@id}"> <xsl:apply-templates select="wps:portlet"/> </portlet-app> </web-app> </xsl:template> <xsl:template match="wps:portlet"> <portlet action="update" name="{wps:portlet-name}" /> </xsl:template> </xsl:stylesheet>
Next up: WebSphere Portal and Maven (Part 4)
]]>This post assumes that you have installed an automated build stack (which consists of maven, a maven compatible repository server, and a continuous integration (CI) server). This post also assumes that you have installed and configured the m2eclipse plugin for your RAD/Eclipse IDE. The details of these installations are beyond the scope of this series of posts.
A maven repository is software (like archiva or nexus) that stores your dependencies in a single remote server. Corporate development shops often choose to maintain an internal repository server which mirrors the maven central repository. The internal repository offers greater control, security, and faster internal downloads. IBM does not publish the portal dependencies to the maven central repository. (You will find that the majority of open sources projects will publish to maven central).
Since IBM does not publish the portlet dependencies you need an internal repository for compiling portlets (unless you use the system dependency scope and a private repository, but I don’t think this approach is as maintainable in a team development setting.)
So what are your dependencies?
I have taken two approaches for figuring this out:
What is the difference between these methods?
If I developed any bizarre classpath issues (like NoSuchMethodError) I would revert to the first method to ensure I was replicating the RAD wizard exactly.
You use the provided scope of maven because you want to compile your code using the jar but the actual implementation jars are provided by the portal server when you deploy the code to a running portal server. The following command deploys your dependency to a remote maven repository:
mvn deploy:deploy-file -DgroupId=com.ibm.portal -DartifactId=portletapi20 -Dversion=7.0 -Dpackaging=jar -Dfile=portletapi_20.jar -DrepositoryId=${repnamehere} -Durl=${repurlhere}
You should note that all these parameter values were items thought up by you. I find that using artifactId=${name}.jar without the jar extension and using a version number to correspond to your WebSphere Portal Server (WPS) version help maintain the logical link between these jars and their source. Finally ${repnamehere} and ${repurlhere} are values that make sense at your organization.
I always create a parent maven project of type pom that each portlet project inherits from (the reasons for this will become clearer in future posts). This parent project links the dependency as:
<dependency>
<groupId>com.ibm.portal</groupId>
<artifactId>portletapi_20</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
At this point your portlet project can compile and will be packaged as a WAR file suitable for deployment on your portal server.
Next up: WebSphere Portal and Maven (Part 3)
]]>