Java tutorial
package org.ops4j.pax.construct.clone; /* * Copyright 2007 Stuart McCulloch * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.maven.model.Dependency; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.archiver.Archiver; import org.codehaus.plexus.archiver.manager.ArchiverManager; import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.StringUtils; import org.ops4j.pax.construct.util.DirUtils; import org.ops4j.pax.construct.util.PomUtils; import org.ops4j.pax.construct.util.PomUtils.Pom; /** * Clones an existing project and produces a script (plus archetypes) to mimic its structure using Pax-Construct * * <code><pre> * mvn pax:clone * </pre></code> * * @goal clone * @aggregator true */ public class CloneMojo extends AbstractMojo { /** * Component factory for various archivers * * @component */ private ArchiverManager m_archiverManager; /** * Initiating groupId. * * @parameter expression="${project.groupId}" * @required * @readonly */ private String m_rootGroupId; /** * Initiating artifactId. * * @parameter expression="${project.artifactId}" * @required * @readonly */ private String m_rootArtifactId; /** * Initiating base directory. * * @parameter expression="${project.basedir}" * @required * @readonly */ private File m_basedir; /** * Temporary directory, where scripts and templates will be saved. * * @parameter expression="${project.build.directory}/clone" * @required * @readonly */ private File m_tempdir; /** * The current Maven reactor. * * @parameter expression="${reactorProjects}" * @required * @readonly */ private List m_reactorProjects; /** * When true, replace any local bundle dependencies with pax-import-bundle commands. * * @parameter expression="${repair}" */ private boolean repair; /** * When true, unify multiple projects under one single pax-create-project command. Warning: this doesn't merge * settings from sub-projects, such as <dependencyManagement>, so some manually editing will be necessary. * * @parameter expression="${unify}" */ private boolean unify; /** * List of directories that have already been processed */ private List m_handledDirs; /** * Maps Maven POMs to pax-create-project commands */ private Map m_majorProjectMap; /** * Maps groupId:artifactId to local bundle names */ private Map m_bundleNameMap; /** * Sequence of archetypes with project/bundle content */ private List m_installCommands; /** * {@inheritDoc} */ public void execute() throws MojoExecutionException { // general purpose Pax-Construct script PaxScript buildScript = new PaxScriptImpl(); m_bundleNameMap = new HashMap(); m_majorProjectMap = new HashMap(); m_handledDirs = new ArrayList(); m_installCommands = new ArrayList(); getFragmentDir().mkdirs(); for (Iterator i = m_reactorProjects.iterator(); i.hasNext();) { // potential project to be converted / captured MavenProject project = (MavenProject) i.next(); String packaging = project.getPackaging(); // fixup standalone maven project if (m_reactorProjects.size() == 1) { // always repair repair = true; // provide basic jar conversion if ("jar".equals(packaging)) { packaging = "bundle"; } } if ("bundle".equals(packaging)) { handleBundleProject(buildScript, project); } else if ("pom".equals(packaging)) { if (isMajorProject(project)) { handleMajorProject(buildScript, project); } else { handleBundleImport(buildScript, project); } } // else handled by the major project(s) } // grab everything else archiveMajorProjects(); writePlatformScripts(buildScript); } /** * Write out various platform-specific scripts based on the abstract build script * * @param script build script */ private void writePlatformScripts(PaxScript script) { String cloneId = PomUtils.getCompoundId(m_rootGroupId, m_rootArtifactId); String scriptName = "create-" + cloneId; File winScript = new File(m_tempdir, scriptName + ".bat"); File nixScript = new File(m_tempdir, scriptName + ".sh"); getLog().info(""); getLog().info("SUCCESSFULLY CLONED " + cloneId); getLog().info(""); String title = m_rootGroupId + ':' + m_rootArtifactId; try { getLog().info("Saving UNIX shell script " + nixScript); script.write(title, nixScript, m_installCommands); } catch (IOException e) { getLog().warn("Unable to write " + nixScript); } try { getLog().info("Saving Windows batch file " + winScript); script.write(title, winScript, m_installCommands); } catch (IOException e) { getLog().warn("Unable to write " + winScript); } getLog().info(""); getLog().info("CLONE DIRECTORY " + m_tempdir); getLog().info(""); getLog().info("(this directory can be zipped and shared with other team members)"); } /** * Analyze major project and build the right pax-create-project call * * @param script build script * @param project major Maven project */ private void handleMajorProject(PaxScript script, MavenProject project) { if (unify && !project.isExecutionRoot()) { // exclude the local poms settings from the unified project m_handledDirs.add(new File(project.getBasedir(), "poms")); return; } PaxCommandBuilder command = script.call(PaxScript.CREATE_PROJECT); command.option('g', project.getGroupId()); command.option('a', project.getArtifactId()); command.option('v', project.getVersion()); setTargetDirectory(command, project.getBasedir().getParentFile()); registerProject(project); m_majorProjectMap.put(project, command); } /** * Analyze bundle project and determine if any pax-create-bundle or pax-wrap-jar calls are needed * * @param script build script * @param project Maven bundle project * @throws MojoExecutionException */ private void handleBundleProject(PaxScript script, MavenProject project) throws MojoExecutionException { PaxCommandBuilder command; String bundleName; String namespace = findBundleNamespace(project); if (null != namespace) { bundleName = project.getArtifactId(); command = script.call(PaxScript.CREATE_BUNDLE); command.option('p', namespace); if (!bundleName.equals(namespace)) { command.option('n', bundleName); } command.option('v', project.getVersion()); command.maven().flag("noDeps"); } else { Dependency wrappee = findWrappee(project); if (wrappee != null) { command = script.call(PaxScript.WRAP_JAR); command.option('g', wrappee.getGroupId()); command.option('a', wrappee.getArtifactId()); command.option('v', wrappee.getVersion()); if (repair) { // this is expected to be the generated bundle name bundleName = PomUtils.getCompoundId(wrappee.getGroupId(), wrappee.getArtifactId()); // detect if we need to add the version back later on... if (project.getArtifactId().endsWith(wrappee.getVersion())) { command.maven().flag("addVersion"); } } else { bundleName = project.getArtifactId(); // need to retain the old name and version settings command.maven().option("bundleName", bundleName); command.maven().option("bundleVersion", project.getVersion()); } } else { getLog().warn("Unable to clone bundle project " + project.getId()); return; } } Pom customizedPom = null; if (repair) { // fix references to local bundles by re-importing customizedPom = repairBundleImports(script, project); } else { // need to keep old groupId intact (name is already retained) command.maven().option("bundleGroupId", project.getGroupId()); } addFragmentToCommand(command, createBundleArchetype(project, namespace, customizedPom)); setTargetDirectory(command, project.getBasedir().getParentFile()); registerProject(project); registerBundleName(project, bundleName); } /** * Analyze POM project and determine if any pax-import-bundle calls are needed * * @param script build script * @param project Maven POM project */ private void handleBundleImport(PaxScript script, MavenProject project) { Dependency importee = findImportee(project); if (importee != null) { PaxCommandBuilder command; command = script.call(PaxScript.IMPORT_BUNDLE); command.option('g', importee.getGroupId()); command.option('a', importee.getArtifactId()); command.option('v', importee.getVersion()); // enable overwrite command.flag('o'); // imported bundles now in provision POM setTargetDirectory(command, m_basedir); registerProject(project); } // else handled by the major project } /** * Analyze the position of this project in the tree, as not all projects need their own distinct set of "poms" * * @param project Maven POM project * @return true if this project requires a pax-create-project call */ private boolean isMajorProject(MavenProject project) { if (project.isExecutionRoot()) { // top-most project return true; } // disconnected project, or project with its own set of "poms" where settings can be customized return (null == project.getParent() || new File(project.getBasedir(), "poms").isDirectory()); } /** * Analyze bundle project to see if it actually just wraps another artifact * * @param project Maven bundle project * @return wrapped artifact, null if it isn't a wrapper project */ private Dependency findWrappee(MavenProject project) { Properties properties = project.getProperties(); Dependency wrappee = new Dependency(); // Pax-Construct v2 wrappee.setGroupId(properties.getProperty("wrapped.groupId")); wrappee.setArtifactId(properties.getProperty("wrapped.artifactId")); wrappee.setVersion(properties.getProperty("wrapped.version")); if (null == wrappee.getArtifactId()) { // original Pax-Construct wrappee.setGroupId(properties.getProperty("jar.groupId")); wrappee.setArtifactId(properties.getProperty("jar.artifactId")); wrappee.setVersion(properties.getProperty("jar.version")); if (null == wrappee.getArtifactId()) { // has someone customized their wrapper? return findCustomizedWrappee(project); } } return wrappee; } /** * Analyze project structure to try to deduce if this really is a wrapper * * @param project Maven bundle project * @return wrapped artifact, null if it isn't a wrapper project */ private Dependency findCustomizedWrappee(MavenProject project) { List dependencies = project.getDependencies(); String sourcePath = project.getBuild().getSourceDirectory(); // try to find a dependency that relates to the wrapper project if (dependencies.size() > 0 && !new File(sourcePath).exists()) { for (Iterator i = dependencies.iterator(); i.hasNext();) { Dependency dependency = (Dependency) i.next(); if (project.getArtifactId().indexOf(dependency.getArtifactId()) >= 0) { return dependency; // closest match } } return (Dependency) dependencies.get(0); } return null; } /** * Analyze POM project to see if it actually just imports an existing bundle * * @param project Maven POM project * @return imported bundle, null if it isn't a import project */ private Dependency findImportee(MavenProject project) { Properties properties = project.getProperties(); Dependency importee = new Dependency(); // original Pax-Construct importee.setGroupId(properties.getProperty("bundle.groupId")); importee.setArtifactId(properties.getProperty("bundle.artifactId")); importee.setVersion(properties.getProperty("bundle.version")); if (importee.getArtifactId() != null) { return importee; } return null; } /** * Analyze bundle project to find the primary namespace it provides * * @param project Maven project * @return primary Java namespace */ private String findBundleNamespace(MavenProject project) { Properties properties = project.getProperties(); // Pax-Construct v2 String namespace = properties.getProperty("bundle.namespace"); if (null == namespace) { // original Pax-Construct namespace = properties.getProperty("bundle.package"); String sourcePath = project.getBuild().getSourceDirectory(); if (null == namespace && new File(sourcePath).exists()) { namespace = findPrimaryPackage(sourcePath); } } return namespace; } /** * Find the most likely candidate for the primary Java package * * @param dir source directory * @return primary Java package */ private String findPrimaryPackage(String dir) { String[] pathInclude = new String[] { "**/*.java" // consider all Java sources }; DirectoryScanner scanner = new DirectoryScanner(); scanner.setIncludes(pathInclude); scanner.setFollowSymlinks(false); scanner.addDefaultExcludes(); scanner.setBasedir(dir); scanner.scan(); String path = null; String[] candidates = scanner.getIncludedFiles(); for (int i = 0; i < scanner.getIncludedFiles().length; i++) { String newPath = candidates[i]; if (null == path || (validCandidate(path, newPath) && path.length() > newPath.length())) { path = newPath; } } return getJavaNamespace(path); } /** * @param current current primary java path * @param candidate candidate java path * @return true if the candidate might be a better primary java path, otherwise false */ private boolean validCandidate(String current, String candidate) { // internal packages are good candidates to find the primary package String candidateFolder = new File(candidate).getParentFile().getName(); if ("internal".equalsIgnoreCase(candidateFolder) || "impl".equalsIgnoreCase(candidateFolder)) { return true; } // but if we already have an internal package, ignore non-internal packages String currentFolder = new File(current).getParentFile().getName(); if ("internal".equalsIgnoreCase(currentFolder) || "impl".equalsIgnoreCase(currentFolder)) { return false; } // neither is an internal package, so candidate package may be better return true; } /** * Convert source code location into dotted Java namespace * * @param javaFile source location * @return Java namespace */ private String getJavaNamespace(String javaFile) { if (null == javaFile) { return null; } // strip the classname and any internal package File packageDir = new File(javaFile).getParentFile(); if ("internal".equals(packageDir.getName()) || "impl".equals(packageDir.getName())) { packageDir = packageDir.getParentFile(); } // standard slashes to dots conversion return packageDir.getPath().replaceAll("[/\\\\]+", "."); } /** * Set the directory where the Pax-Construct command should be run * * @param command Pax-Construct command * @param targetDir target directory */ private void setTargetDirectory(PaxCommandBuilder command, File targetDir) { String[] pivot = DirUtils.calculateRelativePath(m_basedir.getParentFile(), targetDir); if (pivot != null && pivot[0].length() == 0 && pivot[2].length() > 0) { // fix path to use the correct artifactId, in case directory tree has been renamed String relativePath = StringUtils.replaceOnce(pivot[2], m_basedir.getName(), m_rootArtifactId); command.maven().option("targetDirectory", relativePath); } } /** * Create a new archetype for a bundle project, with potentially customized POM and Bnd settings * * @param project Maven project * @param namespace Java namespace, may be null * @param customizedPom customized Maven project model, may be null * @return clause identifying the archetype fragment * @throws MojoExecutionException */ private String createBundleArchetype(MavenProject project, String namespace, Pom customizedPom) throws MojoExecutionException { File baseDir = project.getBasedir(); getLog().info("Cloning bundle project " + project.getArtifactId()); ArchetypeFragment fragment = new ArchetypeFragment(getFragmentDir(), namespace, false); fragment.addPom(baseDir, customizedPom); if (null != namespace) { fragment.addSources(baseDir, project.getBuild().getSourceDirectory(), false); fragment.addSources(baseDir, project.getBuild().getTestSourceDirectory(), true); } for (Iterator i = project.getTestResources().iterator(); i.hasNext();) { Resource r = (Resource) i.next(); fragment.addResources(baseDir, r.getDirectory(), r.getIncludes(), r.getExcludes(), true); } List excludes = new ArrayList(); excludes.addAll(fragment.getIncludedFiles()); excludes.add("target/"); excludes.add("runner/"); excludes.add("pom.xml"); // consider everything else in the bundle directory to be a resource fragment.addResources(baseDir, baseDir.getPath(), null, excludes, false); // archetype must use different id String groupId = project.getGroupId(); String artifactId = project.getArtifactId() + "-archetype"; String version = project.getVersion(); // archive customized bundle sources, POM and Bnd instructions String fragmentId = groupId + ':' + artifactId + ':' + version; fragment.createArchive(fragmentId.replace(':', '_'), newJarArchiver()); return fragmentId; } /** * Attempt to repair bundle imports by replacing them with pax-import-bundle commands * * @param script build script * @param project Maven project * @return customized Maven project model */ private Pom repairBundleImports(PaxScript script, MavenProject project) { Pom pom; try { File tempFile = File.createTempFile("pom", ".xml", m_tempdir); FileUtils.copyFile(project.getFile(), tempFile); pom = PomUtils.readPom(tempFile); tempFile.deleteOnExit(); } catch (IOException e) { pom = null; } for (Iterator i = project.getDependencies().iterator(); i.hasNext();) { Dependency dependency = (Dependency) i.next(); String bundleId = dependency.getGroupId() + ':' + dependency.getArtifactId(); String bundleName = (String) m_bundleNameMap.get(bundleId); // is this a local bundle project? if (null != bundleName) { PaxCommandBuilder command; command = script.call(PaxScript.IMPORT_BUNDLE); command.option('a', bundleName); // enable overwrite command.flag('o'); // only apply to the importing bundle project setTargetDirectory(command, project.getBasedir()); if (null != pom) { pom.removeDependency(dependency); } } } try { if (null != pom) { pom.write(); } return pom; } catch (IOException e) { return null; } } /** * Create archetype fragments for all recorded major projects (as well as any non-bundle modules) * * @throws MojoExecutionException */ private void archiveMajorProjects() throws MojoExecutionException { for (Iterator i = m_majorProjectMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); MavenProject project = (MavenProject) entry.getKey(); addFragmentToCommand((PaxCommandBuilder) entry.getValue(), createProjectArchetype(project)); } } /** * Archive all the selected resources under a single Maven archetype * * @param project containing Maven project * @return clause identifying the archetype fragment * @throws MojoExecutionException */ private String createProjectArchetype(MavenProject project) throws MojoExecutionException { File baseDir = project.getBasedir(); getLog().info("Cloning primary project " + project.getArtifactId()); ArchetypeFragment fragment = new ArchetypeFragment(getFragmentDir(), null, unify); fragment.addPom(baseDir, null); List excludes = new ArrayList(); excludes.addAll(getExcludedPaths(project)); excludes.add("**/target/"); excludes.add("runner/"); excludes.add("pom.xml"); // consider everything else that's not been handled to be a resource fragment.addResources(baseDir, baseDir.getPath(), null, excludes, false); // archetype must use different id String groupId = project.getGroupId(); String artifactId = project.getArtifactId() + "-archetype"; String version = project.getVersion(); // archive all the customized non-bundle POMs and projects String fragmentId = groupId + ':' + artifactId + ':' + version; fragment.createArchive(fragmentId.replace(':', '_'), newJarArchiver()); return fragmentId; } /** * Find which paths in this Maven project have already been collected, and should therefore be excluded * * @param project major Maven project * @return list of excluded paths */ private List getExcludedPaths(MavenProject project) { List excludes = new ArrayList(); File baseDir = project.getBasedir(); for (Iterator i = m_handledDirs.iterator(); i.hasNext();) { File dir = (File) i.next(); String[] pivot = DirUtils.calculateRelativePath(baseDir, dir); if (pivot != null && pivot[0].length() == 0 && pivot[2].length() > 0) { excludes.add(pivot[2]); } } return excludes; } /** * @return new Jar archiver * @throws MojoExecutionException */ private Archiver newJarArchiver() throws MojoExecutionException { try { return m_archiverManager.getArchiver("jar"); } catch (NoSuchArchiverException e) { throw new MojoExecutionException("Unable to find Jar archiver", e); } } /** * @return temporary fragment directory */ private File getFragmentDir() { return new File(m_tempdir, "fragments"); } /** * @param project Maven project */ private void registerProject(MavenProject project) { m_handledDirs.add(project.getBasedir()); } /** * @param project Maven bundle project * @param bundleName expected symbolic name of the bundle (null if imported) */ private void registerBundleName(MavenProject project, String bundleName) { m_bundleNameMap.put(project.getGroupId() + ':' + project.getArtifactId(), bundleName); } /** * @param command one of the create commands * @param fragmentId archetype fragment id */ private void addFragmentToCommand(PaxCommandBuilder command, String fragmentId) { if (null == fragmentId) { return; } command.maven().option("contents", fragmentId); StringBuffer buffer = new StringBuffer(); String[] ids = fragmentId.split(":"); // add Maven command to install the archetype fragment before using it buffer.append("mvn -N install:install-file \"-Dpackaging=jar\" \"-DgroupId="); buffer.append(ids[0]); buffer.append("\" \"-DartifactId="); buffer.append(ids[1]); buffer.append("\" \"-Dversion="); buffer.append(ids[2]); buffer.append("\" \"-Dfile=${_SCRIPTDIR_}/fragments/"); buffer.append(fragmentId.replace(':', '_')); buffer.append(".jar\""); m_installCommands.add(buffer); } }