diff --git a/buildpatterns/maven/.gitignore b/buildpatterns/maven/.gitignore
new file mode 100644
index 0000000..cddd2a3
--- /dev/null
+++ b/buildpatterns/maven/.gitignore
@@ -0,0 +1 @@
+.classpath
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/.classpath b/buildpatterns/maven/headlessdesigner-maven-plugin/.classpath
deleted file mode 100644
index efcf778..0000000
--- a/buildpatterns/maven/headlessdesigner-maven-plugin/.classpath
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/pom.xml b/buildpatterns/maven/headlessdesigner-maven-plugin/pom.xml
index c3611c8..7f92c49 100644
--- a/buildpatterns/maven/headlessdesigner-maven-plugin/pom.xml
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/pom.xml
@@ -60,6 +60,16 @@
commons-io
2.4
+
+ com.ibm.sbt
+ com.ibm.commons
+ 9.0.0
+
+
+ com.ibm.sbt
+ com.ibm.commons.xml
+ 9.0.0
+
maven-plugin
@@ -181,6 +191,15 @@
+
+
+
+ artifactory.openntf.org
+ artifactory.openntf.org
+ https://artifactory.openntf.org/openntf
+
+
+
release
@@ -230,12 +249,16 @@
+
+ nexus-deploy
+
+
+
+ sonatype-nexus-staging-openntf
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
-
-
- sonatype-nexus-staging-openntf
- https://oss.sonatype.org/content/repositories/snapshots
-
-
The headless designer plugins enables you build XPages Application from the On-Disk-Project, invoking the IBM Domino Designer.
\ No newline at end of file
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/HeadlessDesignerBuilder.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/HeadlessDesignerBuilder.java
index f125ab1..2babcb1 100644
--- a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/HeadlessDesignerBuilder.java
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/HeadlessDesignerBuilder.java
@@ -2,6 +2,7 @@
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -24,6 +25,15 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
+import org.openntf.maven.design.ACL;
+import org.openntf.maven.design.ACLEntry;
+import org.openntf.maven.util.HDUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.ibm.commons.util.StringUtil;
+import com.ibm.commons.util.io.StreamUtil;
+import com.ibm.commons.xml.DOMUtil;
@Mojo(name = "ddehd")
@Execute(goal = "ddehd")
@@ -77,6 +87,12 @@ public class HeadlessDesignerBuilder extends AbstractDesignerPlugin {
@Parameter(defaultValue="${project}", readonly=true, required=true)
private MavenProject project;
+
+ /**
+ * The ACL for the generated database. This overrides any ACL present in the ODP.
+ */
+ @Parameter
+ private ACL acl;
/**
* Path to File with the build instructions for the headless designer. If
@@ -115,9 +131,13 @@ public void execute() throws MojoExecutionException, MojoFailureException {
// Create/overwrite the $TemplateBuild field if needed
if(StringUtils.isNotEmpty(templateBuildName)) {
- getLog().debug("Want to build $TemplateBuild for name=" + templateBuildName + ", version=" + templateBuildVersion);
+ getLog().info("Configuring template build name " + templateBuildName + ", version= " + templateBuildVersion);
configureTemplateBuild(tempOdp);
}
+ if(acl != null) {
+ getLog().info("Configuring ACL");
+ configureAcl(tempOdp);
+ }
installFeature();
enableFeatures();
@@ -159,11 +179,17 @@ private void executeDesigner(String fileName) throws MojoExecutionException {
sbDesignerArgs.append("\"");
getLog().debug("Designer call = " + sbDesignerArgs.toString());
- ProcessBuilder pb = new ProcessBuilder(m_DesignerExec, "-RPARAMS", "-console", "-vmargs", sbDesignerArgs.toString());
+ // This uses Runtime instead of ProcessBuilder because the latter doesn't properly launch
+ // Designer on all systems, and a String instead of an array because Designer is quite
+ // finicky about that as well
+ String cmd = m_DesignerExec + " -RPARAMS -console -vmargs " + sbDesignerArgs;
try {
- Process process = pb.start();
+ Process process = Runtime.getRuntime().exec(cmd);
int result = process.waitFor();
+ if(result != 0) {
+ throw new MojoExecutionException("Designer task ended with non-zero exit code: " + result);
+ }
getLog().debug("DDE HeadlessDesigner ended with: " + result);
boolean finished = false;
int nCounter = 0;
@@ -175,6 +201,8 @@ private void executeDesigner(String fileName) throws MojoExecutionException {
throw new MojoExecutionException("DDE HeadlessDesignerPlugin not finished in 120 sec timeout");
}
+ } catch (MojoExecutionException ex) {
+ throw ex;
} catch (Exception ex) {
throw new MojoExecutionException("DDE HeadlessDesignerPlugin reports an error: ", ex);
}
@@ -224,6 +252,115 @@ private void configureTemplateBuild(File odpPath) throws MojoExecutionException
IOUtils.closeQuietly(templateXmlStream);
}
}
+
+ private void configureAcl(File odpPath) throws MojoExecutionException {
+ File databaseProperties = new File(odpPath, "AppProperties" + File.separator + "database.properties");
+ if(!databaseProperties.exists()) {
+ throw new MojoExecutionException("Could not locate database properties file: " + databaseProperties.getAbsolutePath());
+ }
+
+ try {
+ FileInputStream fis = new FileInputStream(databaseProperties);
+ String xmlString;
+ try {
+ xmlString = StreamUtil.readString(fis);
+ } finally {
+ StreamUtil.close(fis);
+ }
+ getLog().debug("Read XML of length " + xmlString.length() + " from database.properties file " + databaseProperties.getAbsolutePath());
+
+ Document xml = DOMUtil.createDocument(xmlString);
+ Element aclNode = (Element)DOMUtil.evaluateXPath(xml, "/database/acl").getSingleNode();
+ if(aclNode == null) {
+ // Then add a new one under the document element
+ aclNode = xml.createElement("acl");
+ xml.getDocumentElement().appendChild(aclNode);
+ }
+ // Clear out existing children in favor of our new ACL
+ DOMUtil.removeChildren(aclNode);
+
+ aclNode.setAttribute("adminserver", StringUtil.toString(acl.getAdminServer()));
+ aclNode.setAttribute("consistentacl", String.valueOf(acl.isConsistentAcl()));
+ aclNode.setAttribute("maxinternetaccess", StringUtil.toString(acl.getMaxInternetAccess()));
+ List roles = acl.getRoles();
+ if(roles != null) {
+ for(String role : roles) {
+ String name = HDUtils.getBracketedName(role);
+ if(StringUtil.isNotEmpty(name)) {
+ Element roleElement = xml.createElement("role");
+ roleElement.setTextContent(name);
+ aclNode.appendChild(roleElement);
+ }
+ }
+ }
+ List entries = acl.getEntries();
+ if(entries != null) {
+ for(ACLEntry entry : entries) {
+ String name = entry.getName();
+ if(StringUtil.isNotEmpty(name)) {
+ Element entryElement = xml.createElement("aclentry");
+ entryElement.setAttribute("name", name);
+ if("-Default-".equals(name)) {
+ entryElement.setAttribute("default", "true");
+ }
+ if(entry.getType() != null) {
+ entryElement.setAttribute("type", entry.getType().name());
+ }
+ entryElement.setAttribute("level", entry.getLevel().name());
+ if(!entry.isCreateDocs()) {
+ entryElement.setAttribute("createdocs", "false");
+ }
+ if(entry.isDeleteDocs()) {
+ entryElement.setAttribute("deletedocs", "true");
+ }
+ if(!entry.isCreateLsJavaAgents()) {
+ entryElement.setAttribute("createlsjavaagents", "false");
+ }
+ if(!entry.isCreatePersonalAgents()) {
+ entryElement.setAttribute("createpersonalagents", "false");
+ }
+ if(!entry.isCreatePersonalViews()) {
+ entryElement.setAttribute("createpersonalviews", "false");
+ }
+ if(!entry.isCreateSharedViews()) {
+ entryElement.setAttribute("createsharedviews", "false");
+ }
+ if(!entry.isReadPublicDocs()) {
+ entryElement.setAttribute("readpublicdocs", "false");
+ }
+ if(!entry.isWritePublicDocs()) {
+ entryElement.setAttribute("writepublicdocs", "false");
+ }
+ List entryRoles = entry.getRoles();
+ if(entryRoles != null) {
+ for(String role : entryRoles) {
+ String roleName = HDUtils.getBracketedName(role);
+ if(StringUtil.isNotEmpty(roleName)) {
+ Element roleElement = xml.createElement("role");
+ roleElement.setTextContent(roleName);
+ entryElement.appendChild(roleElement);
+ }
+ }
+ }
+
+ aclNode.appendChild(entryElement);
+ }
+ }
+ }
+
+ String resultXml = DOMUtil.getXMLString(xml, false);
+ getLog().debug("Writing result XML of length " + resultXml.length());
+ FileOutputStream fos = new FileOutputStream(databaseProperties);
+ try {
+ IOUtils.write(resultXml, fos);
+ } finally {
+ IOUtils.closeQuietly(fos);
+ }
+
+ } catch(Throwable ex) {
+ throw new MojoExecutionException("Failed to configure ACL", ex);
+ }
+ }
private void installFeature() throws MojoExecutionException {
if (m_Features != null && m_Features.size() > 0) {
@@ -236,9 +373,8 @@ private void installFeature() throws MojoExecutionException {
StringBuilder sb = new StringBuilder();
sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command install -from ");
sb.append(site.getUrl());
- sb.append(" -to file:/");
- sb.append(m_NotesData);
- sb.append("/workspace/applications");
+ sb.append(" -to ");
+ sb.append(HDUtils.fileUri(m_NotesData, "workspace", "applications"));
sb.append(" -featureId ");
sb.append(site.getFeatureId());
sb.append(" -version ");
@@ -277,9 +413,8 @@ private void enableFeatures() throws MojoExecutionException {
pw.println("config,true,true");
for (Feature site : m_Features) {
StringBuilder sb = new StringBuilder();
- sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command enable -to file:/");
- sb.append(m_NotesData);
- sb.append("/workspace/applications");
+ sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command enable -to ");
+ sb.append(HDUtils.fileUri(m_NotesData, "workspace", "applications"));
sb.append(" -featureId ");
sb.append(site.getFeatureId());
sb.append(" -version ");
@@ -330,9 +465,8 @@ private void disableFeatures() throws MojoExecutionException {
pw.println("config,true,true");
for (Feature site : m_Features) {
StringBuilder sb = new StringBuilder();
- sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command disable -to file:/");
- sb.append(m_NotesData);
- sb.append("/workspace/applications");
+ sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command disable -to ");
+ sb.append(HDUtils.fileUri(m_NotesData, "workspace", "applications"));
sb.append(" -featureId ");
sb.append(site.getFeatureId());
sb.append(" -version ");
@@ -364,9 +498,8 @@ private void uninstallFeatures() throws MojoExecutionException {
pw.println("config,true,true");
for (Feature site : m_Features) {
StringBuilder sb = new StringBuilder();
- sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command uninstall -to file:/");
- sb.append(m_NotesData);
- sb.append("/workspace/applications");
+ sb.append("com.ibm.designer.domino.tools.userlessbuild.jobs.UpdateManagerJob,-command uninstall -to ");
+ sb.append(HDUtils.fileUri(m_NotesData, "workspace", "applications"));
sb.append(" -featureId ");
sb.append(site.getFeatureId());
sb.append(" -version ");
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACL.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACL.java
new file mode 100644
index 0000000..99f0073
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACL.java
@@ -0,0 +1,97 @@
+package org.openntf.maven.design;
+
+import java.util.List;
+
+import org.apache.maven.plugins.annotations.Parameter;
+
+/**
+ * Represents an ACL specification to be added to the database.properties file during building.
+ *
+ * @author Jesse Gallagher
+ * @since 1.4.0
+ */
+public class ACL {
+ /**
+ * A List of ACL entries to populate the ACL
+ */
+ @Parameter
+ private List entries;
+ /**
+ * The administration server to specify for the ACL
+ */
+ @Parameter
+ private String adminServer;
+ /**
+ * Whether the database should be set to maintain a consistent ACL across replicas
+ */
+ @Parameter
+ private boolean consistentAcl;
+ /**
+ * The maximum effective access level for Internet sessions
+ */
+ @Parameter(defaultValue="editor")
+ private ACLAccessLevel maxInternetAccess = ACLAccessLevel.editor;
+ /**
+ * A List of roles available in the database
+ */
+ @Parameter
+ private List roles;
+
+ /**
+ * @return the entries
+ */
+ public List getEntries() {
+ return entries;
+ }
+ /**
+ * @param entries the entries to set
+ */
+ public void setEntries(List entries) {
+ this.entries = entries;
+ }
+ /**
+ * @return the adminServer
+ */
+ public String getAdminServer() {
+ return adminServer;
+ }
+ /**
+ * @param adminServer the adminServer to set
+ */
+ public void setAdminServer(String adminServer) {
+ this.adminServer = adminServer;
+ }
+ /**
+ * @return the consistentAcl
+ */
+ public boolean isConsistentAcl() {
+ return consistentAcl;
+ }
+ /**
+ * @param consistentAcl the consistentAcl to set
+ */
+ public void setConsistentAcl(boolean consistentAcl) {
+ this.consistentAcl = consistentAcl;
+ }
+ /**
+ * @return the maxInternetAccess
+ */
+ public ACLAccessLevel getMaxInternetAccess() {
+ return maxInternetAccess;
+ }
+ /**
+ * @param maxInternetAccess the maxInternetAccess to set
+ */
+ public void setMaxInternetAccess(ACLAccessLevel maxInternetAccess) {
+ this.maxInternetAccess = maxInternetAccess;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLAccessLevel.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLAccessLevel.java
new file mode 100644
index 0000000..42d2aa2
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLAccessLevel.java
@@ -0,0 +1,11 @@
+package org.openntf.maven.design;
+
+/**
+ * This enumeration specifies the available levels for an ACL entry.
+ *
+ * @author Jesse Gallagher
+ * @since 1.4.0
+ */
+public enum ACLAccessLevel {
+ noaccess, depositor, reader, author, editor, designer, manager
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntry.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntry.java
new file mode 100644
index 0000000..6f6d940
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntry.java
@@ -0,0 +1,197 @@
+package org.openntf.maven.design;
+
+import java.util.List;
+
+import org.apache.maven.plugins.annotations.Parameter;
+
+public class ACLEntry {
+ /**
+ * A List of roles to apply to this entry
+ */
+ @Parameter
+ private List roles;
+ /**
+ * The name of the entry, in canonical format
+ */
+ @Parameter(required=true)
+ private String name;
+ /**
+ * The access level for the entry
+ */
+ @Parameter(required=true)
+ private ACLAccessLevel level;
+ /**
+ * Whether the entry is allowed to delete documents
+ */
+ @Parameter(defaultValue="false")
+ private boolean deleteDocs = false;
+ /**
+ * Whether the entry is allowed to write public documents
+ */
+ @Parameter(defaultValue="true")
+ private boolean writePublicDocs = true;
+ /**
+ * Whether the entry is allowed to read public documents
+ */
+ @Parameter(defaultValue="true")
+ private boolean readPublicDocs = true;
+ /**
+ * Whether the entry is allowed to create shared agents
+ */
+ @Parameter(defaultValue="true")
+ private boolean createLsJavaAgents = true;
+ /**
+ * Whether the entry is allowed to create personal views
+ */
+ @Parameter(defaultValue="true")
+ private boolean createPersonalViews = true;
+ /**
+ * Whether the entry is allowed to create personal agents
+ */
+ @Parameter(defaultValue="true")
+ private boolean createPersonalAgents = true;
+ /**
+ * Whether the entry is allowed to create shared agents
+ */
+ @Parameter(defaultValue="true")
+ private boolean createSharedViews = true;
+ /**
+ * Whether the entry is allowed to create documents
+ */
+ @Parameter(defaultValue="true")
+ private boolean createDocs = true;
+ /**
+ * The type of the named entity
+ */
+ @Parameter
+ private ACLEntryType type;
+
+ /**
+ * @return the roles
+ */
+ public List getRoles() {
+ return roles;
+ }
+ /**
+ * @param roles the roles to set
+ */
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+ /**
+ * @return the level
+ */
+ public ACLAccessLevel getLevel() {
+ return level;
+ }
+ /**
+ * @param level the level to set
+ */
+ public void setLevel(ACLAccessLevel level) {
+ this.level = level;
+ }
+ /**
+ * @return the deleteDocs
+ */
+ public boolean isDeleteDocs() {
+ return deleteDocs;
+ }
+ /**
+ * @param deleteDocs the deleteDocs to set
+ */
+ public void setDeleteDocs(boolean deleteDocs) {
+ this.deleteDocs = deleteDocs;
+ }
+ /**
+ * @return the writePublicDocs
+ */
+ public boolean isWritePublicDocs() {
+ return writePublicDocs;
+ }
+ /**
+ * @param writePublicDocs the writePublicDocs to set
+ */
+ public void setWritePublicDocs(boolean writePublicDocs) {
+ this.writePublicDocs = writePublicDocs;
+ }
+ /**
+ * @return the readPublicDocs
+ */
+ public boolean isReadPublicDocs() {
+ return readPublicDocs;
+ }
+ /**
+ * @param readPublicDocs the readPublicDocs to set
+ */
+ public void setReadPublicDocs(boolean readPublicDocs) {
+ this.readPublicDocs = readPublicDocs;
+ }
+ /**
+ * @return the createLsJavaAgents
+ */
+ public boolean isCreateLsJavaAgents() {
+ return createLsJavaAgents;
+ }
+ /**
+ * @param createLsJavaAgents the createLsJavaAgents to set
+ */
+ public void setCreateLsJavaAgents(boolean createLsJavaAgents) {
+ this.createLsJavaAgents = createLsJavaAgents;
+ }
+ /**
+ * @return the createPersonalViews
+ */
+ public boolean isCreatePersonalViews() {
+ return createPersonalViews;
+ }
+ /**
+ * @param createPersonalViews the createPersonalViews to set
+ */
+ public void setCreatePersonalViews(boolean createPersonalViews) {
+ this.createPersonalViews = createPersonalViews;
+ }
+ /**
+ * @return the createPersonalAgents
+ */
+ public boolean isCreatePersonalAgents() {
+ return createPersonalAgents;
+ }
+ /**
+ * @param createPersonalAgents the createPersonalAgents to set
+ */
+ public void setCreatePersonalAgents(boolean createPersonalAgents) {
+ this.createPersonalAgents = createPersonalAgents;
+ }
+ /**
+ * @return the createSharedViews
+ */
+ public boolean isCreateSharedViews() {
+ return createSharedViews;
+ }
+ /**
+ * @param createSharedViews the createSharedViews to set
+ */
+ public void setCreateSharedViews(boolean createSharedViews) {
+ this.createSharedViews = createSharedViews;
+ }
+
+ public ACLEntryType getType() {
+ return type;
+ }
+
+ public boolean isCreateDocs() {
+ return createDocs;
+ }
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntryType.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntryType.java
new file mode 100644
index 0000000..e39ab0d
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/design/ACLEntryType.java
@@ -0,0 +1,5 @@
+package org.openntf.maven.design;
+
+public enum ACLEntryType {
+ person, server, servergroup, persongroup, mixedgroup
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/util/HDUtils.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/util/HDUtils.java
new file mode 100644
index 0000000..1dd3ac7
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/main/java/org/openntf/maven/util/HDUtils.java
@@ -0,0 +1,49 @@
+package org.openntf.maven.util;
+
+import java.io.File;
+
+public enum HDUtils {
+ ;
+
+ /**
+ * Constructs a file:// URI from the provided system file path and subfolders.
+ *
+ * @param filePath the base file path in system format
+ * @param subfolders any subfolders to append
+ * @return a file:// URI for the provided path
+ */
+ public static String fileUri(String filePath, String... subfolders) {
+ StringBuilder sub = new StringBuilder();
+ if(subfolders != null) {
+ for(String subfolder : subfolders) {
+ if(sub.length() > 0) {
+ sub.append('/');
+ }
+ sub.append(subfolder);
+ }
+ }
+ File baseFile = new File(filePath);
+ if(sub.length() > 0) {
+ File result = new File(baseFile, sub.toString());
+ return result.toURI().toString();
+ } else {
+ return baseFile.toURI().toString();
+ }
+
+ }
+
+ /**
+ * @return the Domino role name in bracketed form, in case it was provided without
+ */
+ public static String getBracketedName(String name) {
+ if(name == null || name.isEmpty()) {
+ return name;
+ } else {
+ if(name.startsWith("[") && name.endsWith("]")) {
+ return name;
+ } else {
+ return "[" + name + "]";
+ }
+ }
+ }
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/AllTests.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/AllTests.java
new file mode 100644
index 0000000..78dd3dc
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/AllTests.java
@@ -0,0 +1,13 @@
+package org.openntf.maven.test;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.openntf.maven.test.util.TestHDUtils;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ TestHDUtils.class
+})
+public class AllTests {
+
+}
diff --git a/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/util/TestHDUtils.java b/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/util/TestHDUtils.java
new file mode 100644
index 0000000..09b83f9
--- /dev/null
+++ b/buildpatterns/maven/headlessdesigner-maven-plugin/src/test/java/org/openntf/maven/test/util/TestHDUtils.java
@@ -0,0 +1,22 @@
+package org.openntf.maven.test.util;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import org.junit.Test;
+import org.openntf.maven.util.HDUtils;
+
+public class TestHDUtils {
+ @Test
+ public void testFileUri() {
+ String filePath = System.getProperty("java.io.tmpdir");
+ String result = HDUtils.fileUri(filePath, "workspace", "applications");
+
+ File file = new File(filePath);
+ File concat = new File(file, "workspace/applications");
+ String expected = concat.toURI().toString();
+
+ assertEquals("file URI should match expected", expected, result);
+ }
+}
diff --git a/testpatterns/org.openntf.junit.xsp.parent/pom.xml b/testpatterns/org.openntf.junit.xsp.parent/pom.xml
index 77e3381..44dbe93 100644
--- a/testpatterns/org.openntf.junit.xsp.parent/pom.xml
+++ b/testpatterns/org.openntf.junit.xsp.parent/pom.xml
@@ -114,12 +114,12 @@
eclipse-plugin
com.ibm.notes.java.api.win32.linux
- [9.0.1,9.0.2)
+ 9.0.1
eclipse-plugin
com.ibm.designer.lib.jsf
- [9.0.1,9.0.2)
+ 9.0.1
@@ -322,18 +322,13 @@
-
-
- com.mycila
- license-maven-plugin
- 2.6
- maven-plugin
-
-
- org.codehaus.plexus
- plexus-utils
- 3.0.15
-
-
+
+
+ artifactory.openntf.org
+ artifactory.openntf.org
+ https://artifactory.openntf.org/openntf
+
+
+
JUnit4XPages Plugin
\ No newline at end of file