/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2013-2015 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * http://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.jersey.tools.plugins; import org.apache.maven.execution.MavenSession; import org.apache.maven.execution.ProjectDependencyGraph; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * The plugins main MOJO class. * Walks through the maven dependency tree and creates the docbook output file. * * @goal generate * @phase process-sources * @aggregator */ public class GenerateJerseyModuleListMojo extends AbstractMojo { /** * Placeholder in a table row template, will be replaced by the module name */ static final String MODULE_NAME_PLACEHOLDER = "%NAME"; /** * Placeholder in a table row template, will be replaced by the module description */ static final String MODULE_DESCRIPTION_PLACEHOLDER = "%DESCRIPTION"; /** * Placeholder in a header template, will be replaced by the category name. */ static final String CATEGORY_CAPTION_PLACEHOLDER = "%CAPTION"; /** * Placeholder in a header template, will be replaced by the groupId to create a unique element id. */ static final String CATEGORY_GROUP_ID_PLACEHOLDER = "%GROUP_ID"; /** * Placeholder in a template file, will be replaced by the generated table. */ static final String CONTENT_PLACEHOLDER = "%CONTENT"; /** * Placeholder in a table row template, will be replaced by the relative path to the link of project-info on java.net */ static final String MODULE_LINK_PATH_PLACEHOLDER = "%LINK_PATH"; /** * @parameter default-value="${project} * @required * @readonly */ private MavenProject mavenProject; /** * @component * @required * @readonly */ private MavenSession mavenSession; /** * @parameter default-value="modules.xml" */ private String outputFileName; /** * Name of a "template" file. * The file should contain all the static content from the docbook section related to modules. * The file should contain a placeholder {@see CONTENT_PLACEHOLDER}, which will be replaced by the generated table. * * @parameter */ private String templateFileName; /** * Template for a header part of each category. * Written to the output once per category. * Supported placeholders are {@see CATEGORY_CAPTION_PLACEHOLDER} and {@see CATEGORY_GROUP_ID_PLACEHOLDER}. * * @parameter */ private String tableHeaderFileName; /** * Template for a footer part of each category. * Written to the output once per category. No placeholders supported. * * @parameter */ private String tableFooterFileName; /** * Template for a table row in the module listing. * Written to the output once per module. * Supported placeholders are {@see MODULE_NAME_PLACEHOLDER} and {@see MODULE_DESCRIPTION_PLACEHOLDER}. * * @parameter */ private String tableRowFileName; /** * @parameter default-value="false" */ private boolean outputUnmatched; private Configuration configuration; private Log log; @Override public void execute() throws MojoExecutionException { try { configuration = prepareParameters(); } catch (IOException e) { throw new MojoExecutionException("Plugin initialization failed. Problem reading input files.", e); } ProjectDependencyGraph graph = mavenSession.getProjectDependencyGraph(); List<MavenProject> projects = graph.getDownstreamProjects(mavenProject, true); // categorize modules based on predefined categories Map<String, List<MavenProject>> categorizedProjects = categorizeModules(projects); // list of already "used" categories (maintained in order to identified unmatched modules later) Set<String> allGroups = new HashSet<>(); // The entire module list table content - will replace the placeholder in the template StringBuilder content = new StringBuilder(); // iterate over known categories for (PredefinedCategories category : PredefinedCategories.values()) { String groupId = category.getGroupId(); allGroups.add(groupId); // ignore tests, but still keep them among the used categories (so that they do not appear in the unmatched list) if (groupId.contains("tests")) { continue; } List<MavenProject> projectsInCategory = new LinkedList<>(); for (final Map.Entry<String, List<MavenProject>> entry : categorizedProjects.entrySet()) { final String key = entry.getKey(); if (key.startsWith(groupId)) { allGroups.add(key); projectsInCategory.addAll(entry.getValue()); } } content.append(processCategory(category, projectsInCategory)); } // get the list of unmatched modules List<MavenProject> unmatched = new LinkedList<>(); for (String groupId : categorizedProjects.keySet()) { if (!allGroups.contains(groupId)) { unmatched.addAll(categorizedProjects.get(groupId)); } } if (unmatched.size() > 0) { log.warn("There are unmatched modules (" + unmatched.size() + ")."); if (!outputUnmatched) { log.warn("You can configure the plugin to output unmatched modules by adding " + "<outputUnmatched>true</outputUnmatched> in the configuration."); } } if (outputUnmatched) { content.append(processUnmatched(unmatched)); } PrintWriter writer = null; try { writer = new PrintWriter(outputFileName); writer.println(configuration.getSectionTemplate().replace(CONTENT_PLACEHOLDER, content.toString())); } catch (FileNotFoundException e) { throw new MojoExecutionException("File not found exception"); } finally { if (writer != null) { writer.close(); } } log.info("Output written to: " + outputFileName); } private Map<String, List<MavenProject>> categorizeModules(List<MavenProject> projects) { Map<String, List<MavenProject>> categorizedProjects = new HashMap<>(); for (MavenProject project : projects) { String groupId = project.getGroupId(); if (categorizedProjects.containsKey(groupId)) { categorizedProjects.get(groupId).add(project); } else { List<MavenProject> actualList = new LinkedList<>(); actualList.add(project); categorizedProjects.put(groupId, actualList); } } return categorizedProjects; } private StringBuilder processUnmatched(List<MavenProject> unmatchedModules) { return processModuleList("Other", "other", unmatchedModules); } private StringBuilder processCategory(PredefinedCategories category, List<MavenProject> projectsInCategory) { return processModuleList(category.getCaption(), category.getGroupId(), projectsInCategory); } private StringBuilder processModuleList(String categoryCaption, String categoryId, List<MavenProject> projectsInCategory) { StringBuilder categoryContent = new StringBuilder(); // Output table header for the category categoryContent.append(configuration.getTableHeader() .replace(CATEGORY_CAPTION_PLACEHOLDER, categoryCaption) .replace(CATEGORY_GROUP_ID_PLACEHOLDER, categoryId)); // Sort projects in each category alphabetically Collections.sort(projectsInCategory, new Comparator<MavenProject>() { @Override public int compare(MavenProject o1, MavenProject o2) { return o1.getArtifactId().compareTo(o2.getArtifactId()); } }); // Output projects in a category for (MavenProject project : projectsInCategory) { // skip the "parent" type projects if (project.getArtifactId().equals("project")) { continue; } String linkPrefix = getLinkPath(project); categoryContent.append(configuration.getTableRow() .replace(MODULE_NAME_PLACEHOLDER, project.getArtifactId()) .replace(MODULE_DESCRIPTION_PLACEHOLDER, project.getDescription()) .replace(MODULE_LINK_PATH_PLACEHOLDER, linkPrefix + project.getArtifactId())); } categoryContent.append(configuration.getTableFooter()); return categoryContent; } /** * Build the project-info link path by including all the artifactId up to (excluding) the root parent * @param project project for which the path should be determined. * @return path consisting of hierarchically nested maven artifact IDs. Used for referencing to the project-info on java.net */ private String getLinkPath(MavenProject project) { String path = ""; MavenProject parent = project.getParent(); while (parent != null && !(parent.getArtifactId().equals("project") && parent.getGroupId().equals("org.glassfish.jersey"))) { path = parent.getArtifactId() + "/" + path; parent = parent.getParent(); } return path; } @Override public void setLog(org.apache.maven.plugin.logging.Log log) { this.log = log; } public String readFile(String fileName) throws IOException { BufferedReader reader = new BufferedReader(new FileReader(fileName)); String s; StringBuilder sb = new StringBuilder(); while ((s = reader.readLine()) != null) { sb.append(s); sb.append("\n"); } reader.close(); return sb.toString(); } public Configuration prepareParameters() throws IOException { Configuration configuration = new Configuration(); configuration.setSectionTemplate(readFile(templateFileName)); configuration.setTableHeader(readFile(tableHeaderFileName)); configuration.setTableRow(readFile(tableRowFileName)); configuration.setTableFooter(readFile(tableFooterFileName)); return configuration; } }