Java tutorial
package org.apache.maven.doxia.tools; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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. */ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.apache.commons.io.FilenameUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.doxia.site.decoration.Banner; import org.apache.maven.doxia.site.decoration.DecorationModel; import org.apache.maven.doxia.site.decoration.Menu; import org.apache.maven.doxia.site.decoration.MenuItem; import org.apache.maven.doxia.site.decoration.Skin; import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler; import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader; import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer; import org.apache.maven.model.DistributionManagement; import org.apache.maven.model.Site; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.apache.maven.reporting.MavenReport; import org.codehaus.plexus.component.annotations.Component; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.i18n.I18N; import org.codehaus.plexus.logging.AbstractLogEnabled; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.interpolation.EnvarBasedValueSource; import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.MapBasedValueSource; import org.codehaus.plexus.interpolation.ObjectBasedValueSource; import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource; import org.codehaus.plexus.interpolation.RegexBasedInterpolator; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; /** * Default implementation of the site tool. * * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> */ @Component(role = SiteTool.class) public class DefaultSiteTool extends AbstractLogEnabled implements SiteTool { // ---------------------------------------------------------------------- // Components // ---------------------------------------------------------------------- /** * The component that is used to resolve additional artifacts required. */ @Requirement private ArtifactResolver artifactResolver; /** * The component used for creating artifact instances. */ @Requirement private ArtifactFactory artifactFactory; /** * Internationalization. */ @Requirement protected I18N i18n; /** * The component for assembling inheritance. */ @Requirement protected DecorationModelInheritanceAssembler assembler; /** * Project builder (deprecated in Maven 3: should use ProjectBuilder, which will avoid * issues like DOXIASITETOOLS-166) */ @Requirement protected MavenProjectBuilder mavenProjectBuilder; // ---------------------------------------------------------------------- // Public methods // ---------------------------------------------------------------------- /** {@inheritDoc} */ public Artifact getSkinArtifactFromRepository(ArtifactRepository localRepository, List<ArtifactRepository> remoteArtifactRepositories, DecorationModel decoration) throws SiteToolException { checkNotNull("localRepository", localRepository); checkNotNull("remoteArtifactRepositories", remoteArtifactRepositories); checkNotNull("decoration", decoration); Skin skin = decoration.getSkin(); if (skin == null) { skin = Skin.getDefaultSkin(); } String version = skin.getVersion(); Artifact artifact; try { if (version == null) { version = Artifact.RELEASE_VERSION; } VersionRange versionSpec = VersionRange.createFromVersionSpec(version); artifact = artifactFactory.createDependencyArtifact(skin.getGroupId(), skin.getArtifactId(), versionSpec, "jar", null, null); artifactResolver.resolve(artifact, remoteArtifactRepositories, localRepository); } catch (InvalidVersionSpecificationException e) { throw new SiteToolException("InvalidVersionSpecificationException: The skin version '" + version + "' is not valid: " + e.getMessage(), e); } catch (ArtifactResolutionException e) { throw new SiteToolException("ArtifactResolutionException: Unable to find skin", e); } catch (ArtifactNotFoundException e) { throw new SiteToolException("ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e); } return artifact; } /** {@inheritDoc} */ public Artifact getDefaultSkinArtifact(ArtifactRepository localRepository, List<ArtifactRepository> remoteArtifactRepositories) throws SiteToolException { return getSkinArtifactFromRepository(localRepository, remoteArtifactRepositories, new DecorationModel()); } /** {@inheritDoc} */ public String getRelativePath(String to, String from) { checkNotNull("to", to); checkNotNull("from", from); URL toUrl = null; URL fromUrl = null; String toPath = to; String fromPath = from; try { toUrl = new URL(to); } catch (MalformedURLException e) { try { toUrl = new File(getNormalizedPath(to)).toURI().toURL(); } catch (MalformedURLException e1) { getLogger().warn("Unable to load a URL for '" + to + "': " + e.getMessage()); } } try { fromUrl = new URL(from); } catch (MalformedURLException e) { try { fromUrl = new File(getNormalizedPath(from)).toURI().toURL(); } catch (MalformedURLException e1) { getLogger().warn("Unable to load a URL for '" + from + "': " + e.getMessage()); } } if (toUrl != null && fromUrl != null) { // URLs, determine if they share protocol and domain info if ((toUrl.getProtocol().equalsIgnoreCase(fromUrl.getProtocol())) && (toUrl.getHost().equalsIgnoreCase(fromUrl.getHost())) && (toUrl.getPort() == fromUrl.getPort())) { // shared URL domain details, use URI to determine relative path toPath = toUrl.getFile(); fromPath = fromUrl.getFile(); } else { // don't share basic URL information, no relative available return to; } } else if ((toUrl != null && fromUrl == null) || (toUrl == null && fromUrl != null)) { // one is a URL and the other isn't, no relative available. return to; } // either the two locations are not URLs or if they are they // share the common protocol and domain info and we are left // with their URI information String relativePath = getRelativeFilePath(fromPath, toPath); if (relativePath == null) { relativePath = to; } if (getLogger().isDebugEnabled() && !relativePath.toString().equals(to)) { getLogger().debug("Mapped url: " + to + " to relative path: " + relativePath); } return relativePath; } private static String getRelativeFilePath(final String oldPath, final String newPath) { // normalize the path delimiters String fromPath = new File(oldPath).getPath(); String toPath = new File(newPath).getPath(); // strip any leading slashes if its a windows path if (toPath.matches("^\\[a-zA-Z]:")) { toPath = toPath.substring(1); } if (fromPath.matches("^\\[a-zA-Z]:")) { fromPath = fromPath.substring(1); } // lowercase windows drive letters. if (fromPath.startsWith(":", 1)) { fromPath = Character.toLowerCase(fromPath.charAt(0)) + fromPath.substring(1); } if (toPath.startsWith(":", 1)) { toPath = Character.toLowerCase(toPath.charAt(0)) + toPath.substring(1); } // check for the presence of windows drives. No relative way of // traversing from one to the other. if ((toPath.startsWith(":", 1) && fromPath.startsWith(":", 1)) && (!toPath.substring(0, 1).equals(fromPath.substring(0, 1)))) { // they both have drive path element but they don't match, no // relative path return null; } if ((toPath.startsWith(":", 1) && !fromPath.startsWith(":", 1)) || (!toPath.startsWith(":", 1) && fromPath.startsWith(":", 1))) { // one has a drive path element and the other doesn't, no relative // path. return null; } final String relativePath = buildRelativePath(toPath, fromPath, File.separatorChar); return relativePath.toString(); } /** {@inheritDoc} */ public File getSiteDescriptor(File siteDirectory, Locale locale) { checkNotNull("siteDirectory", siteDirectory); final Locale llocale = (locale == null) ? new Locale("") : locale; File siteDescriptor = new File(siteDirectory, "site_" + llocale.getLanguage() + ".xml"); if (!siteDescriptor.isFile()) { siteDescriptor = new File(siteDirectory, "site.xml"); } return siteDescriptor; } /** * Get a site descriptor from one of the repositories. * * @param project the Maven project, not null. * @param localRepository the Maven local repository, not null. * @param repositories the Maven remote repositories, not null. * @param locale the locale wanted for the site descriptor. If not null, searching for * <code>site_<i>localeLanguage</i>.xml</code>, otherwise searching for <code>site.xml</code>. * @return the site descriptor into the local repository after download of it from repositories or null if not * found in repositories. * @throws SiteToolException if any */ File getSiteDescriptorFromRepository(MavenProject project, ArtifactRepository localRepository, List<ArtifactRepository> repositories, Locale locale) throws SiteToolException { checkNotNull("project", project); checkNotNull("localRepository", localRepository); checkNotNull("repositories", repositories); final Locale llocale = (locale == null) ? new Locale("") : locale; try { return resolveSiteDescriptor(project, localRepository, repositories, llocale); } catch (ArtifactNotFoundException e) { getLogger().debug("ArtifactNotFoundException: Unable to locate site descriptor: " + e); return null; } catch (ArtifactResolutionException e) { throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: " + e.getMessage(), e); } catch (IOException e) { throw new SiteToolException("IOException: Unable to locate site descriptor: " + e.getMessage(), e); } } /** * Read site descriptor content from Reader, adding support for deprecated <code>${reports}</code>, * <code>${parentProject}</code> and <code>${modules}</code> tags. * * @param reader * @return the input content interpolated with deprecated tags * @throws IOException */ private String readSiteDescriptor(Reader reader, String projectId) throws IOException { String siteDescriptorContent = IOUtil.toString(reader); // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags. Properties props = new Properties(); props.put("reports", "<menu ref=\"reports\"/>"); props.put("modules", "<menu ref=\"modules\"/>"); props.put("parentProject", "<menu ref=\"parent\"/>"); // warn if interpolation required for (Object prop : props.keySet()) { if (siteDescriptorContent.contains("$" + prop)) { getLogger().warn("Site descriptor for " + projectId + " contains $" + prop + ": should be replaced with " + props.getProperty((String) prop)); } if (siteDescriptorContent.contains("${" + prop + "}")) { getLogger().warn("Site descriptor for " + projectId + " contains ${" + prop + "}: should be replaced with " + props.getProperty((String) prop)); } } return StringUtils.interpolate(siteDescriptorContent, props); } /** {@inheritDoc} */ public DecorationModel getDecorationModel(File siteDirectory, Locale locale, MavenProject project, List<MavenProject> reactorProjects, ArtifactRepository localRepository, List<ArtifactRepository> repositories) throws SiteToolException { checkNotNull("project", project); checkNotNull("reactorProjects", reactorProjects); checkNotNull("localRepository", localRepository); checkNotNull("repositories", repositories); final Locale llocale = (locale == null) ? Locale.getDefault() : locale; getLogger().debug("Computing decoration model of " + project.getId() + " for locale " + llocale); Map.Entry<DecorationModel, MavenProject> result = getDecorationModel(0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories); DecorationModel decorationModel = result.getKey(); MavenProject parentProject = result.getValue(); if (decorationModel == null) { getLogger().debug("Using default site descriptor"); String siteDescriptorContent; Reader reader = null; try { // Note the default is not a super class - it is used when nothing else is found reader = ReaderFactory.newXmlReader(getClass().getResourceAsStream("/default-site.xml")); siteDescriptorContent = readSiteDescriptor(reader, "default-site.xml"); } catch (IOException e) { throw new SiteToolException("Error reading default site descriptor: " + e.getMessage(), e); } finally { IOUtil.close(reader); } decorationModel = readDecorationModel(siteDescriptorContent); } // DecorationModel back to String to interpolate, then go back to DecorationModel String siteDescriptorContent = decorationModelToString(decorationModel); // "classical" late interpolation, after full inheritance siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, false); decorationModel = readDecorationModel(siteDescriptorContent); if (parentProject != null) { populateParentMenu(decorationModel, llocale, project, parentProject, true); } try { populateModulesMenu(decorationModel, llocale, project, reactorProjects, localRepository, true); } catch (IOException e) { throw new SiteToolException("Error while populating modules menu: " + e.getMessage(), e); } if (decorationModel.getBannerLeft() == null) { // extra default to set Banner banner = new Banner(); banner.setName(project.getName()); decorationModel.setBannerLeft(banner); } return decorationModel; } /** {@inheritDoc} */ public String getInterpolatedSiteDescriptorContent(Map<String, String> props, MavenProject aProject, String siteDescriptorContent) throws SiteToolException { checkNotNull("props", props); // "classical" late interpolation return getInterpolatedSiteDescriptorContent(aProject, siteDescriptorContent, false); } private String getInterpolatedSiteDescriptorContent(MavenProject aProject, String siteDescriptorContent, boolean isEarly) throws SiteToolException { checkNotNull("aProject", aProject); checkNotNull("siteDescriptorContent", siteDescriptorContent); RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); if (isEarly) { interpolator.addValueSource(new PrefixedObjectValueSource("this.", aProject)); interpolator.addValueSource(new PrefixedPropertiesValueSource("this.", aProject.getProperties())); } else { interpolator.addValueSource(new ObjectBasedValueSource(aProject)); interpolator.addValueSource(new MapBasedValueSource(aProject.getProperties())); try { interpolator.addValueSource(new EnvarBasedValueSource()); } catch (IOException e) { // Prefer logging? throw new SiteToolException( "IOException: cannot interpolate environment properties: " + e.getMessage(), e); } } try { // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118 return interpolator.interpolate(siteDescriptorContent, isEarly ? null : "project"); } catch (InterpolationException e) { throw new SiteToolException("Cannot interpolate site descriptor: " + e.getMessage(), e); } } /** {@inheritDoc} */ public MavenProject getParentProject(MavenProject aProject, List<MavenProject> reactorProjects, ArtifactRepository localRepository) { checkNotNull("aProject", aProject); checkNotNull("reactorProjects", reactorProjects); checkNotNull("localRepository", localRepository); if (isMaven3OrMore()) { // no need to make voodoo with Maven 3: job already done return aProject.getParent(); } MavenProject parentProject = null; MavenProject origParent = aProject.getParent(); if (origParent != null) { for (MavenProject reactorProject : reactorProjects) { if (reactorProject.getGroupId().equals(origParent.getGroupId()) && reactorProject.getArtifactId().equals(origParent.getArtifactId()) && reactorProject.getVersion().equals(origParent.getVersion())) { parentProject = reactorProject; getLogger().debug("Parent project " + origParent.getId() + " picked from reactor"); break; } } if (parentProject == null && aProject.getBasedir() != null && StringUtils.isNotEmpty(aProject.getModel().getParent().getRelativePath())) { try { String relativePath = aProject.getModel().getParent().getRelativePath(); File pomFile = new File(aProject.getBasedir(), relativePath); if (pomFile.isDirectory()) { pomFile = new File(pomFile, "pom.xml"); } pomFile = new File(getNormalizedPath(pomFile.getPath())); if (pomFile.isFile()) { MavenProject mavenProject = mavenProjectBuilder.build(pomFile, localRepository, null); if (mavenProject.getGroupId().equals(origParent.getGroupId()) && mavenProject.getArtifactId().equals(origParent.getArtifactId()) && mavenProject.getVersion().equals(origParent.getVersion())) { parentProject = mavenProject; getLogger().debug("Parent project " + origParent.getId() + " loaded from a relative path: " + relativePath); } } } catch (ProjectBuildingException e) { getLogger().info("Unable to load parent project " + origParent.getId() + " from a relative path: " + e.getMessage()); } } if (parentProject == null) { try { parentProject = mavenProjectBuilder.buildFromRepository(aProject.getParentArtifact(), aProject.getRemoteArtifactRepositories(), localRepository); getLogger().debug("Parent project " + origParent.getId() + " loaded from repository"); } catch (ProjectBuildingException e) { getLogger().warn("Unable to load parent project " + origParent.getId() + " from repository: " + e.getMessage()); } } if (parentProject == null) { // fallback to original parent, which may contain uninterpolated value (still need a unit test) parentProject = origParent; getLogger().debug("Parent project " + origParent.getId() + " picked from original value"); } } return parentProject; } /** * Populate the pre-defined <code>parent</code> menu of the decoration model, * if used through <code><menu ref="parent"/></code>. * * @param decorationModel the Doxia Sitetools DecorationModel, not null. * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. * @param project a Maven project, not null. * @param parentProject a Maven parent project, not null. * @param keepInheritedRefs used for inherited references. */ private void populateParentMenu(DecorationModel decorationModel, Locale locale, MavenProject project, MavenProject parentProject, boolean keepInheritedRefs) { checkNotNull("decorationModel", decorationModel); checkNotNull("project", project); checkNotNull("parentProject", parentProject); Menu menu = decorationModel.getMenuRef("parent"); if (menu == null) { return; } if (keepInheritedRefs && menu.isInheritAsRef()) { return; } final Locale llocale = (locale == null) ? Locale.getDefault() : locale; String parentUrl = getDistMgmntSiteUrl(parentProject); if (parentUrl != null) { if (parentUrl.endsWith("/")) { parentUrl += "index.html"; } else { parentUrl += "/index.html"; } parentUrl = getRelativePath(parentUrl, getDistMgmntSiteUrl(project)); } else { // parent has no url, assume relative path is given by site structure File parentBasedir = parentProject.getBasedir(); // First make sure that the parent is available on the file system if (parentBasedir != null) { // Try to find the relative path to the parent via the file system String parentPath = parentBasedir.getAbsolutePath(); String projectPath = project.getBasedir().getAbsolutePath(); parentUrl = getRelativePath(parentPath, projectPath) + "/index.html"; } } // Only add the parent menu if we were able to find a URL for it if (parentUrl == null) { getLogger().warn("Unable to find a URL to the parent project. The parent menu will NOT be added."); } else { if (menu.getName() == null) { menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.parentproject")); } MenuItem item = new MenuItem(); item.setName(parentProject.getName()); item.setHref(parentUrl); menu.addItem(item); } } /** * Populate the pre-defined <code>modules</code> menu of the decoration model, * if used through <code><menu ref="modules"/></code>. * * @param decorationModel the Doxia Sitetools DecorationModel, not null. * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm. * @param project a Maven project, not null. * @param reactorProjects the Maven reactor projects, not null. * @param localRepository the Maven local repository, not null. * @param keepInheritedRefs used for inherited references. * @throws SiteToolException if any * @throws IOException */ private void populateModulesMenu(DecorationModel decorationModel, Locale locale, MavenProject project, List<MavenProject> reactorProjects, ArtifactRepository localRepository, boolean keepInheritedRefs) throws SiteToolException, IOException { checkNotNull("project", project); checkNotNull("reactorProjects", reactorProjects); checkNotNull("localRepository", localRepository); checkNotNull("decorationModel", decorationModel); Menu menu = decorationModel.getMenuRef("modules"); if (menu == null) { return; } if (keepInheritedRefs && menu.isInheritAsRef()) { return; } final Locale llocale = (locale == null) ? Locale.getDefault() : locale; // we require child modules and reactors to process module menu if (project.getModules().size() > 0) { if (menu.getName() == null) { menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.projectmodules")); } for (String module : (List<String>) project.getModules()) { MavenProject moduleProject = getModuleFromReactor(project, reactorProjects, module); if (moduleProject == null) { getLogger().warn("Module " + module + " not found in reactor: loading locally"); File f = new File(project.getBasedir(), module + "/pom.xml"); if (f.exists()) { try { moduleProject = mavenProjectBuilder.build(f, localRepository, null); } catch (ProjectBuildingException e) { throw new SiteToolException("Unable to read local module-POM", e); } } else { getLogger().warn("No filesystem module-POM available"); moduleProject = new MavenProject(); moduleProject.setName(module); moduleProject.setDistributionManagement(new DistributionManagement()); moduleProject.getDistributionManagement().setSite(new Site()); moduleProject.getDistributionManagement().getSite().setUrl(module); } } String siteUrl = getDistMgmntSiteUrl(moduleProject); String itemName = (moduleProject.getName() == null) ? moduleProject.getArtifactId() : moduleProject.getName(); appendMenuItem(project, menu, itemName, siteUrl, moduleProject.getArtifactId()); } } else if (decorationModel.getMenuRef("modules").getInherit() == null) { // only remove if project has no modules AND menu is not inherited, see MSHARED-174 decorationModel.removeMenuRef("modules"); } } private static MavenProject getModuleFromReactor(MavenProject project, List<MavenProject> reactorProjects, String module) throws IOException { File moduleBasedir = new File(project.getBasedir(), module).getCanonicalFile(); for (MavenProject reactorProject : reactorProjects) { if (moduleBasedir.equals(reactorProject.getBasedir())) { return reactorProject; } } // module not found in reactor return null; } /** {@inheritDoc} */ public void populateReportsMenu(DecorationModel decorationModel, Locale locale, Map<String, List<MavenReport>> categories) { checkNotNull("decorationModel", decorationModel); checkNotNull("categories", categories); Menu menu = decorationModel.getMenuRef("reports"); if (menu == null) { return; } final Locale llocale = (locale == null) ? Locale.getDefault() : locale; if (menu.getName() == null) { menu.setName(i18n.getString("site-tool", llocale, "decorationModel.menu.projectdocumentation")); } boolean found = false; if (menu.getItems().isEmpty()) { List<MavenReport> categoryReports = categories.get(MavenReport.CATEGORY_PROJECT_INFORMATION); if (!isEmptyList(categoryReports)) { MenuItem item = createCategoryMenu( i18n.getString("site-tool", llocale, "decorationModel.menu.projectinformation"), "/project-info.html", categoryReports, llocale); menu.getItems().add(item); found = true; } categoryReports = categories.get(MavenReport.CATEGORY_PROJECT_REPORTS); if (!isEmptyList(categoryReports)) { MenuItem item = createCategoryMenu( i18n.getString("site-tool", llocale, "decorationModel.menu.projectreports"), "/project-reports.html", categoryReports, llocale); menu.getItems().add(item); found = true; } } if (!found) { decorationModel.removeMenuRef("reports"); } } /** {@inheritDoc} */ public List<Locale> getSiteLocales(String locales) { if (locales == null) { return Collections.singletonList(DEFAULT_LOCALE); } String[] localesArray = StringUtils.split(locales, ","); List<Locale> localesList = new ArrayList<Locale>(localesArray.length); for (String localeString : localesArray) { Locale locale = codeToLocale(localeString); if (locale == null) { continue; } if (!Arrays.asList(Locale.getAvailableLocales()).contains(locale)) { if (getLogger().isWarnEnabled()) { getLogger().warn( "The locale defined by '" + locale + "' is not available in this Java Virtual Machine (" + System.getProperty("java.version") + " from " + System.getProperty("java.vendor") + ") - IGNORING"); } continue; } // Default bundles are in English if ((!locale.getLanguage().equals(DEFAULT_LOCALE.getLanguage())) && (!i18n .getBundle("site-tool", locale).getLocale().getLanguage().equals(locale.getLanguage()))) { if (getLogger().isWarnEnabled()) { getLogger().warn("The locale '" + locale + "' (" + locale.getDisplayName(Locale.ENGLISH) + ") is not currently supported by Maven Site - IGNORING." + "\nContributions are welcome and greatly appreciated!" + "\nIf you want to contribute a new translation, please visit " + "http://maven.apache.org/plugins/localization.html for detailed instructions."); } continue; } localesList.add(locale); } if (localesList.isEmpty()) { localesList = Collections.singletonList(DEFAULT_LOCALE); } return localesList; } /** * Converts a locale code like "en", "en_US" or "en_US_win" to a <code>java.util.Locale</code> * object. * <p>If localeCode = <code>default</code>, return the current value of the default locale for this instance * of the Java Virtual Machine.</p> * * @param localeCode the locale code string. * @return a java.util.Locale object instanced or null if errors occurred * @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Locale.html">java.util.Locale#getDefault()</a> */ private Locale codeToLocale(String localeCode) { if (localeCode == null) { return null; } if ("default".equalsIgnoreCase(localeCode)) { return Locale.getDefault(); } String language = ""; String country = ""; String variant = ""; StringTokenizer tokenizer = new StringTokenizer(localeCode, "_"); final int maxTokens = 3; if (tokenizer.countTokens() > maxTokens) { if (getLogger().isWarnEnabled()) { getLogger().warn("Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING"); } return null; } if (tokenizer.hasMoreTokens()) { language = tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) { country = tokenizer.nextToken(); if (tokenizer.hasMoreTokens()) { variant = tokenizer.nextToken(); } } } return new Locale(language, country, variant); } // ---------------------------------------------------------------------- // Protected methods // ---------------------------------------------------------------------- /** * @param path could be null. * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path. * @see FilenameUtils#normalize(String) */ protected static String getNormalizedPath(String path) { String normalized = FilenameUtils.normalize(path); if (normalized == null) { normalized = path; } return (normalized == null) ? null : normalized.replace('\\', '/'); } // ---------------------------------------------------------------------- // Private methods // ---------------------------------------------------------------------- /** * @param project not null * @param localRepository not null * @param repositories not null * @param locale not null * @return the resolved site descriptor * @throws IOException if any * @throws ArtifactResolutionException if any * @throws ArtifactNotFoundException if any */ private File resolveSiteDescriptor(MavenProject project, ArtifactRepository localRepository, List<ArtifactRepository> repositories, Locale locale) throws IOException, ArtifactResolutionException, ArtifactNotFoundException { File result; // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1? Artifact artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), project.getVersion(), "xml", "site_" + locale.getLanguage()); boolean found = false; try { artifactResolver.resolve(artifact, repositories, localRepository); result = artifact.getFile(); // we use zero length files to avoid re-resolution (see below) if (result.length() > 0) { found = true; } else { getLogger().debug("No site descriptor found for " + project.getId() + " for locale " + locale.getLanguage() + ", trying without locale..."); } } catch (ArtifactNotFoundException e) { getLogger().debug("Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e); // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote // repository, because the parent was already released (and snapshots are updated automatically if changed) result = new File(localRepository.getBasedir(), localRepository.pathOf(artifact)); result.getParentFile().mkdirs(); result.createNewFile(); } if (!found) { artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), project.getVersion(), "xml", "site"); try { artifactResolver.resolve(artifact, repositories, localRepository); } catch (ArtifactNotFoundException e) { // see above regarding this zero length file result = new File(localRepository.getBasedir(), localRepository.pathOf(artifact)); result.getParentFile().mkdirs(); result.createNewFile(); throw e; } result = artifact.getFile(); // we use zero length files to avoid re-resolution (see below) if (result.length() == 0) { getLogger().debug("No site descriptor found for " + project.getId() + " without locale."); result = null; } } return result; } /** * @param depth depth of project * @param siteDirectory, can be null if project.basedir is null, ie POM from repository * @param locale not null * @param project not null * @param reactorProjects not null * @param localRepository not null * @param repositories not null * @param origProps not null * @return the decoration model depending the locale and the parent project * @throws SiteToolException if any */ private Map.Entry<DecorationModel, MavenProject> getDecorationModel(int depth, File siteDirectory, Locale locale, MavenProject project, List<MavenProject> reactorProjects, ArtifactRepository localRepository, List<ArtifactRepository> repositories) throws SiteToolException { // 1. get site descriptor File File siteDescriptor; if (project.getBasedir() == null) { // POM is in the repository: look into the repository for site descriptor try { siteDescriptor = getSiteDescriptorFromRepository(project, localRepository, repositories, locale); } catch (SiteToolException e) { throw new SiteToolException( "The site descriptor cannot be resolved from the repository: " + e.getMessage(), e); } } else { // POM is in build directory: look for site descriptor as local file siteDescriptor = getSiteDescriptor(siteDirectory, locale); } // 2. read DecorationModel from site descriptor File and do early interpolation (${this.*}) DecorationModel decoration = null; Reader siteDescriptorReader = null; try { if (siteDescriptor != null && siteDescriptor.exists()) { getLogger().debug("Reading" + (depth == 0 ? "" : (" parent level " + depth)) + " site descriptor from " + siteDescriptor); siteDescriptorReader = ReaderFactory.newXmlReader(siteDescriptor); String siteDescriptorContent = readSiteDescriptor(siteDescriptorReader, project.getId()); // interpolate ${this.*} = early interpolation siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, true); decoration = readDecorationModel(siteDescriptorContent); decoration.setLastModified(siteDescriptor.lastModified()); } else { getLogger().debug("No" + (depth == 0 ? "" : (" parent level " + depth)) + " site descriptor."); } } catch (IOException e) { throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from " + siteDescriptor, e); } finally { IOUtil.close(siteDescriptorReader); } // 3. look for parent project MavenProject parentProject = getParentProject(project, reactorProjects, localRepository); // 4. merge with parent project DecorationModel if (parentProject != null && (decoration == null || decoration.isMergeParent())) { depth++; getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: " + parentProject.getId()); File parentSiteDirectory = null; if (parentProject.getBasedir() != null) { // extrapolate parent project site directory String siteRelativePath = getRelativeFilePath(project.getBasedir().getAbsolutePath(), siteDescriptor.getParentFile().getAbsolutePath()); parentSiteDirectory = new File(parentProject.getBasedir(), siteRelativePath); // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin // has different configuration. But this is a rare case (this only has impact if parent is from reactor) } DecorationModel parentDecoration = getDecorationModel(depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository, repositories).getKey(); // MSHARED-116 requires an empty decoration model (instead of a null one) // MSHARED-145 requires us to do this only if there is a parent to merge it with if (decoration == null && parentDecoration != null) { // we have no site descriptor: merge the parent into an empty one decoration = new DecorationModel(); } String name = project.getName(); if (decoration != null && StringUtils.isNotEmpty(decoration.getName())) { name = decoration.getName(); } // Merge the parent and child DecorationModels String projectDistMgmnt = getDistMgmntSiteUrl(project); String parentDistMgmnt = getDistMgmntSiteUrl(parentProject); if (getLogger().isDebugEnabled()) { getLogger().debug("Site decoration model inheritance: assembling child with level " + depth + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = " + parentDistMgmnt); } assembler.assembleModelInheritance(name, decoration, parentDecoration, projectDistMgmnt, parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt); } return new AbstractMap.SimpleEntry<DecorationModel, MavenProject>(decoration, parentProject); } /** * @param siteDescriptorContent not null * @return the decoration model object * @throws SiteToolException if any */ private DecorationModel readDecorationModel(String siteDescriptorContent) throws SiteToolException { try { return new DecorationXpp3Reader().read(new StringReader(siteDescriptorContent)); } catch (XmlPullParserException e) { throw new SiteToolException("Error parsing site descriptor", e); } catch (IOException e) { throw new SiteToolException("Error reading site descriptor", e); } } private String decorationModelToString(DecorationModel decoration) throws SiteToolException { StringWriter writer = new StringWriter(); try { new DecorationXpp3Writer().write(writer, decoration); return writer.toString(); } catch (IOException e) { throw new SiteToolException("Error reading site descriptor", e); } finally { IOUtil.close(writer); } } private static String buildRelativePath(final String toPath, final String fromPath, final char separatorChar) { // use tokenizer to traverse paths and for lazy checking StringTokenizer toTokeniser = new StringTokenizer(toPath, String.valueOf(separatorChar)); StringTokenizer fromTokeniser = new StringTokenizer(fromPath, String.valueOf(separatorChar)); int count = 0; // walk along the to path looking for divergence from the from path while (toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens()) { if (separatorChar == '\\') { if (!fromTokeniser.nextToken().equalsIgnoreCase(toTokeniser.nextToken())) { break; } } else { if (!fromTokeniser.nextToken().equals(toTokeniser.nextToken())) { break; } } count++; } // reinitialize the tokenizers to count positions to retrieve the // gobbled token toTokeniser = new StringTokenizer(toPath, String.valueOf(separatorChar)); fromTokeniser = new StringTokenizer(fromPath, String.valueOf(separatorChar)); while (count-- > 0) { fromTokeniser.nextToken(); toTokeniser.nextToken(); } StringBuilder relativePath = new StringBuilder(); // add back refs for the rest of from location. while (fromTokeniser.hasMoreTokens()) { fromTokeniser.nextToken(); relativePath.append(".."); if (fromTokeniser.hasMoreTokens()) { relativePath.append(separatorChar); } } if (relativePath.length() != 0 && toTokeniser.hasMoreTokens()) { relativePath.append(separatorChar); } // add fwd fills for whatever's left of to. while (toTokeniser.hasMoreTokens()) { relativePath.append(toTokeniser.nextToken()); if (toTokeniser.hasMoreTokens()) { relativePath.append(separatorChar); } } return relativePath.toString(); } /** * @param project not null * @param menu not null * @param name not null * @param href could be null * @param defaultHref not null */ private void appendMenuItem(MavenProject project, Menu menu, String name, String href, String defaultHref) { String selectedHref = href; if (selectedHref == null) { selectedHref = defaultHref; } MenuItem item = new MenuItem(); item.setName(name); String baseUrl = getDistMgmntSiteUrl(project); if (baseUrl != null) { selectedHref = getRelativePath(selectedHref, baseUrl); } if (selectedHref.endsWith("/")) { item.setHref(selectedHref + "index.html"); } else { item.setHref(selectedHref + "/index.html"); } menu.addItem(item); } /** * @param name not null * @param href not null * @param categoryReports not null * @param locale not null * @return the menu item object */ private MenuItem createCategoryMenu(String name, String href, List<MavenReport> categoryReports, Locale locale) { MenuItem item = new MenuItem(); item.setName(name); item.setCollapse(true); item.setHref(href); // MSHARED-172, allow reports to define their order in some other way? //Collections.sort( categoryReports, new ReportComparator( locale ) ); for (MavenReport report : categoryReports) { MenuItem subitem = new MenuItem(); subitem.setName(report.getName(locale)); subitem.setHref(report.getOutputName() + ".html"); item.getItems().add(subitem); } return item; } // ---------------------------------------------------------------------- // static methods // ---------------------------------------------------------------------- /** * Convenience method. * * @param list could be null * @return true if the list is <code>null</code> or empty */ private static boolean isEmptyList(List<?> list) { return list == null || list.isEmpty(); } /** * Return distributionManagement.site.url if defined, null otherwise. * * @param project not null * @return could be null */ private static String getDistMgmntSiteUrl(MavenProject project) { return getDistMgmntSiteUrl(project.getDistributionManagement()); } private static String getDistMgmntSiteUrl(DistributionManagement distMgmnt) { if (distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null) { return urlEncode(distMgmnt.getSite().getUrl()); } return null; } private static String urlEncode(final String url) { if (url == null) { return null; } try { return new File(url).toURI().toURL().toExternalForm(); } catch (MalformedURLException ex) { return url; // this will then throw somewhere else } } private void checkNotNull(String name, Object value) { if (value == null) { throw new IllegalArgumentException("The parameter '" + name + "' cannot be null."); } } /** * Check the current Maven version to see if it's Maven 3.0 or newer. */ private static boolean isMaven3OrMore() { return new DefaultArtifactVersion(getMavenVersion()).getMajorVersion() >= 3; } private static String getMavenVersion() { // This relies on the fact that MavenProject is the in core classloader // and that the core classloader is for the maven-core artifact // and that should have a pom.properties file // if this ever changes, we will have to revisit this code. final Properties properties = new Properties(); final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties"; final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream(corePomProperties); try { properties.load(in); } catch (IOException ioe) { return ""; } finally { IOUtil.close(in); } return properties.getProperty("version").trim(); } }