Java tutorial
/** * Copyright 2005-2014 Red Hat, Inc. * * Red Hat licenses this file to you 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. */ package io.fabric8.maven; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import com.fasterxml.jackson.databind.ObjectMapper; import io.fabric8.api.Constants; import io.fabric8.utils.Files; import io.fabric8.utils.Strings; import io.fabric8.deployer.dto.DependencyDTO; import io.fabric8.deployer.dto.DtoHelper; import io.fabric8.deployer.dto.ProjectRequirements; import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; import org.apache.maven.shared.invoker.DefaultInvocationRequest; import org.apache.maven.shared.invoker.DefaultInvoker; import org.apache.maven.shared.invoker.InvocationRequest; import org.apache.maven.shared.invoker.InvocationResult; import org.apache.maven.shared.invoker.Invoker; import org.apache.maven.shared.invoker.MavenInvocationException; /** * Generates a ZIP file of the profile configuration */ @Mojo(name = "zip", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class CreateProfileZipMojo extends AbstractProfileMojo { /** * Name of the directory used to create the profile configuration zip */ @Parameter(property = "fabric8.zip.buildDir", defaultValue = "${project.build.directory}/generated-profiles") private File buildDir; /** * Name of the created profile zip file */ @Parameter(property = "fabric8.zip.outFile", defaultValue = "${project.build.directory}/profile.zip") private File outputFile; @Component private MavenProjectHelper projectHelper; /** * The artifact type for attaching the generated profile zip file to the project */ @Parameter(property = "fabric8.zip.artifactType", defaultValue = "zip") private String artifactType = "zip"; /** * The artifact classifier for attaching the generated profile zip file to the project */ @Parameter(property = "fabric8.zip.artifactClassifier", defaultValue = "profile") private String artifactClassifier = "profile"; /** * Files to be excluded */ @Parameter(property = "fabric8.excludedFiles", defaultValue = "io.fabric8.agent.properties") private String[] filesToBeExcluded; /** * The projects in the reactor. */ @Parameter(defaultValue = "${reactorProjects}") private List<MavenProject> reactorProjects; /** * Name of the directory used to create the profile zip files in each reactor project when creating an aggregated zip * for all the {@link #reactorProjects} */ @Parameter(property = "fabric8.fullzip.reactorProjectOutputPath", defaultValue = "target/generated-profiles") private String reactorProjectOutputPath; /** * The Maven Session. * * @parameter expression="${session}" * @required * @readonly */ protected MavenSession session; @Override public void execute() throws MojoExecutionException, MojoFailureException { try { if (isIgnoreProject()) return; generateZip(); if (reactorProjects != null) { List<MavenProject> pomZipProjects = new ArrayList<>(); List<MavenProject> fabricZipGoalProjects = new ArrayList<>(); List<MavenProject> fabricHasParentZipGoalProject = new ArrayList<MavenProject>(); for (MavenProject reactorProject : reactorProjects) { if ("pom".equals(reactorProject.getPackaging())) { pomZipProjects.add(reactorProject); } List<Plugin> buildPlugins = reactorProject.getBuildPlugins(); for (Plugin buildPlugin : buildPlugins) { String artifactId = buildPlugin.getArtifactId(); // TODO I guess we could try find if the "zip" goal is being invoked? if ("fabric8-maven-plugin".equals(artifactId)) { // TODO should we only consider reactorProjects which have a fabric8:zip goal? Object goals = buildPlugin.getGoals(); boolean hasZipGoal = goals != null && goals.toString().contains("zip"); List<PluginExecution> executions = buildPlugin.getExecutions(); for (PluginExecution execution : executions) { List<String> execGoals = execution.getGoals(); if (execGoals.contains("zip")) { hasZipGoal = true; } } getLog().debug( "project " + reactorProject.getArtifactId() + " has zip goal: " + hasZipGoal); fabricZipGoalProjects.add(reactorProject); } } } // we want a list of projects which has a parent that has a zip goal too // as that helps us detect the 'last' project when we do a full build from the entire project for (MavenProject project : fabricZipGoalProjects) { if (fabricZipGoalProjects.contains(project.getParent())) { fabricHasParentZipGoalProject.add(project); } } // are we the last project? boolean last = reactorProjects.size() > 1 && project == reactorProjects.get(reactorProjects.size() - 1); if (!last) { // are we the last project with the zip goal, part of a group as they have a parent? // TODO: there can be multiple groups, so when we switch to a new group we should aggregate last = fabricHasParentZipGoalProject.size() > 1 && project == fabricHasParentZipGoalProject .get(fabricHasParentZipGoalProject.size() - 1); } if (!last) { // are we the last project with the zip goal? last = fabricZipGoalProjects.size() > 1 && project == fabricZipGoalProjects.get(fabricZipGoalProjects.size() - 1); } // we need to generate the aggregated zip last, so we have all the generated profiles in the other modules // which we can aggregate if (last) { getLog().info(""); getLog().info("Creating aggregated profile zip"); getLog().info("built the last fabric8:zip project so generating a combined zip for all " + fabricZipGoalProjects.size() + " projects with a fabric8:zip goal"); // favor root project as the 1st project with fabric8:zip goal MavenProject rootProject = fabricZipGoalProjects.size() > 0 ? fabricZipGoalProjects.get(0) : reactorProjects.get(0); // we got the root project, now filter out pom projects which has the rootProject as one of their parents List<MavenProject> ourPomZipProjects = new ArrayList<MavenProject>(); // include the root project if its a zip as well if (pomZipProjects.contains(rootProject)) { ourPomZipProjects.add(rootProject); } ourPomZipProjects.add(rootProject); for (MavenProject zip : pomZipProjects) { if (hasParent(zip, rootProject, true)) { ourPomZipProjects.add(zip); } } getLog().info("Choosing root project " + rootProject.getArtifactId() + " for generation of aggregated zip"); generateAggregatedZip(rootProject, fabricZipGoalProjects, ourPomZipProjects); } } } catch (MojoFailureException e) { throw e; } catch (MojoExecutionException e) { throw e; } catch (Exception e) { throw new MojoExecutionException("Error executing", e); } } protected boolean hasParent(MavenProject me, MavenProject parent, boolean recusive) { if (me == null) { return false; } else if (me.getParent() == parent) { return true; } else if (recusive) { return hasParent(me.getParent(), parent, recusive); } else { return false; } } protected void generateAggregatedZip(MavenProject rootProject, List<MavenProject> reactorProjects, List<MavenProject> pomZipProjects) throws IOException, MojoExecutionException { File projectBaseDir = rootProject.getBasedir(); File projectOutputFile = new File(projectBaseDir, "target/profile.zip"); getLog().info("Generating " + projectOutputFile.getAbsolutePath() + " from root project " + rootProject.getArtifactId()); File projectBuildDir = new File(projectBaseDir, reactorProjectOutputPath); createAggregatedZip(reactorProjects, projectBaseDir, projectBuildDir, reactorProjectOutputPath, projectOutputFile, includeReadMe, pomZipProjects); if (rootProject.getAttachedArtifacts() != null) { // need to remove existing as otherwise we get a WARN Artifact found = null; for (Artifact artifact : rootProject.getAttachedArtifacts()) { if (artifactClassifier != null && artifact.hasClassifier() && artifact.getClassifier().equals(artifactClassifier)) { found = artifact; break; } } if (found != null) { rootProject.getAttachedArtifacts().remove(found); } } getLog().info("Attaching aggregated zip " + projectOutputFile + " to root project " + rootProject.getArtifactId()); projectHelper.attachArtifact(rootProject, artifactType, artifactClassifier, projectOutputFile); // if we are doing an install goal, then also install the aggregated zip manually // as maven will install the root project first, and then build the reactor projects, and at this point // it does not help to attach artifact to root project, as those artifacts will not be installed // so we need to install manually if (rootProject.hasLifecyclePhase("install")) { getLog().info("Installing aggregated zip " + projectOutputFile); InvocationRequest request = new DefaultInvocationRequest(); request.setBaseDirectory(rootProject.getBasedir()); request.setPomFile(new File("./pom.xml")); request.setGoals(Collections.singletonList("install:install-file")); request.setRecursive(false); request.setInteractive(false); Properties props = new Properties(); props.setProperty("file", "target/profile.zip"); props.setProperty("groupId", rootProject.getGroupId()); props.setProperty("artifactId", rootProject.getArtifactId()); props.setProperty("version", rootProject.getVersion()); props.setProperty("classifier", "profile"); props.setProperty("packaging", "zip"); request.setProperties(props); getLog().info( "Installing aggregated zip using: mvn install:install-file" + serializeMvnProperties(props)); Invoker invoker = new DefaultInvoker(); try { InvocationResult result = invoker.execute(request); if (result.getExitCode() != 0) { throw new IllegalStateException("Error invoking Maven goal install:install-file"); } } catch (MavenInvocationException e) { throw new MojoExecutionException("Error invoking Maven goal install:install-file", e); } } } protected void generateZip() throws DependencyTreeBuilderException, MojoExecutionException, IOException, MojoFailureException { ProjectRequirements requirements = new ProjectRequirements(); DependencyDTO rootDependency = null; if (isIncludeArtifact()) { rootDependency = loadRootDependency(); requirements.setRootDependency(rootDependency); } configureRequirements(requirements); if (isIncludeArtifact()) { addProjectArtifactBundle(requirements); } File profileBuildDir = createProfileBuildDir(requirements.getProfileId()); boolean hasConfigDir = profileConfigDir.isDirectory(); if (hasConfigDir) { copyProfileConfigFiles(profileBuildDir, profileConfigDir); } else { getLog().info("The profile configuration files directory " + profileConfigDir + " doesn't exist, so not copying any additional project documentation or configuration files"); } // lets only generate a profile zip if we have a requirement (e.g. we're not a parent pom packaging project) and // we have defined some configuration files or dependencies // to avoid generating dummy profiles for parent poms if (hasConfigDir || rootDependency != null || notEmpty(requirements.getBundles()) || notEmpty(requirements.getFeatures()) || notEmpty(requirements.getFeatureRepositories())) { if (includeReadMe) { copyReadMe(project.getFile().getParentFile(), profileBuildDir); } if (generateSummaryFile) { String description = project.getDescription(); if (Strings.isNotBlank(description)) { File summaryMd = new File(profileBuildDir, "Summary.md"); summaryMd.getParentFile().mkdirs(); if (!summaryMd.exists()) { byte[] bytes = description.getBytes(); Files.copy(new ByteArrayInputStream(bytes), new FileOutputStream(summaryMd)); } } } if (isIncludeArtifact()) { writeProfileRequirements(requirements, profileBuildDir); } generateFabricAgentProperties(requirements, new File(profileBuildDir, "io.fabric8.agent.properties")); // only generate if its a WAR project if ("war".equals(project.getPackaging())) { generateFabricContextPathProperties(requirements, new File(profileBuildDir, Constants.WEB_CONTEXT_PATHS_PID + ".properties")); } Zips.createZipFile(getLog(), buildDir, outputFile); projectHelper.attachArtifact(project, artifactType, artifactClassifier, outputFile); getLog().info("Created profile zip file: " + outputFile); } } public static boolean notEmpty(List<?> list) { return list != null && !list.isEmpty(); } /** * Copies any local configuration files into the profile directory */ protected void copyProfileConfigFiles(File profileBuildDir, File profileConfigDir) throws IOException { File[] files = profileConfigDir.listFiles(); if (files != null) { profileBuildDir.mkdirs(); for (File file : files) { if (!toBeExclude(file.getName())) { File outFile = new File(profileBuildDir, file.getName()); if (file.isDirectory()) { copyProfileConfigFiles(outFile, file); } else { Files.copy(file, outFile); } } } } } protected ArrayList<String> removePath(List<String> filesToBeExcluded) { ArrayList<String> fileName = new ArrayList<String>(); for (String name : filesToBeExcluded) { int pos = name.lastIndexOf("/"); if (pos > 0) { String fname = name.substring(0, pos); fileName.add(fname); } } return fileName; } protected boolean toBeExclude(String fileName) { List excludedFilesList = Arrays.asList(filesToBeExcluded); Boolean result = excludedFilesList.contains(fileName); return result; } /** * Returns the directory within the {@link #buildDir} to generate data for the profile */ protected File createProfileBuildDir(String profileId) { String profilePath = profileId.replace('-', '/') + ".profile"; return new File(buildDir, profilePath); } protected void writeProfileRequirements(ProjectRequirements requirements, File profileBuildDir) throws IOException { ObjectMapper mapper = DtoHelper.getMapper(); String name = DtoHelper.getRequirementsConfigFileName(requirements); File outFile = new File(profileBuildDir, name); outFile.getParentFile().mkdirs(); mapper.writeValue(outFile, requirements); getLog().info("Writing " + outFile); } protected void generateFabricAgentProperties(ProjectRequirements requirements, File file) throws MojoExecutionException, IOException { file.getParentFile().mkdirs(); PrintWriter writer = new PrintWriter(new FileWriter(file)); try { String profileId = requirements.getProfileId(); writer.println("# Profile: " + profileId); writer.println("# generated by the fabric8 maven plugin at " + new Date()); writer.println("# see: http://fabric8.io/gitbook/mavenPlugin.html"); writer.println(); List<String> parentProfiles = Zips.notNullList(requirements.getParentProfiles()); if (!parentProfiles.isEmpty()) { writer.write("attribute.parents ="); for (String parentProfile : parentProfiles) { writer.write(" "); writer.write(parentProfile); } writer.println(); writer.println(); } List<String> bundles = Zips.notNullList(requirements.getBundles()); List<String> features = Zips.notNullList(requirements.getFeatures()); List<String> repos = Zips.notNullList(requirements.getFeatureRepositories()); for (String bundle : bundles) { if (Strings.isNotBlank(bundle)) { writer.println("bundle." + escapeAgentPropertiesKey(bundle) + " = " + escapeAgentPropertiesValue(bundle)); } } if (!bundles.isEmpty()) { writer.println(); } for (String feature : features) { if (Strings.isNotBlank(feature)) { writer.println("feature." + escapeAgentPropertiesKey(feature) + " = " + escapeAgentPropertiesValue(feature)); } } if (!features.isEmpty()) { writer.println(); } for (String repo : repos) { if (Strings.isNotBlank(repo)) { writer.println("repository." + escapeAgentPropertiesKey(repo) + " = " + escapeAgentPropertiesValue(repo)); } } } finally { try { writer.close(); } catch (Exception e) { // ignore } } } protected void generateFabricContextPathProperties(ProjectRequirements requirements, File file) throws MojoExecutionException, IOException { String webContextPath = requirements.getWebContextPath(); if (Strings.isNullOrBlank(webContextPath)) { // no file need to be generated return; } file.getParentFile().mkdirs(); PrintWriter writer = new PrintWriter(new FileWriter(file)); try { // the path must start with a leading slash String path = leadingSlash(webContextPath); String key = project.getGroupId() + "/" + project.getArtifactId(); writer.println(escapeAgentPropertiesKey(key) + " = " + escapeAgentPropertiesValue(path)); } finally { try { writer.close(); } catch (Exception e) { // ignore } } } protected String escapeAgentPropertiesKey(String text) { return text.replaceAll("\\:", "\\\\:"); } protected String escapeAgentPropertiesValue(String text) { return escapeAgentPropertiesKey(text); } protected static String leadingSlash(String path) { if (path.startsWith("/")) { return path; } else { return "/" + path; } } private String serializeMvnProperties(Properties properties) { StringBuilder sb = new StringBuilder(); if (properties != null) { for (Iterator it = properties.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); sb.append(" -D").append(key).append('=').append(value); } } return sb.toString(); } }