| /* |
| * 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. |
| */ |
| package org.apache.maven.plugins.surefire.report; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| |
| import org.apache.maven.model.ReportPlugin; |
| import org.apache.maven.plugin.surefire.log.api.ConsoleLogger; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.reporting.AbstractMavenReport; |
| import org.apache.maven.reporting.MavenReportException; |
| import org.apache.maven.settings.Settings; |
| import org.apache.maven.shared.utils.PathTool; |
| import org.codehaus.plexus.i18n.I18N; |
| import org.codehaus.plexus.interpolation.EnvarBasedValueSource; |
| import org.codehaus.plexus.interpolation.InterpolationException; |
| import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; |
| import org.codehaus.plexus.interpolation.PropertiesBasedValueSource; |
| import org.codehaus.plexus.interpolation.RegexBasedInterpolator; |
| |
| import static java.util.Collections.addAll; |
| import static org.apache.maven.plugins.surefire.report.SurefireReportParser.hasReportFiles; |
| |
| /** |
| * Abstract base class for reporting test results using Surefire. |
| * |
| * @author Stephen Connolly |
| */ |
| public abstract class AbstractSurefireReport extends AbstractMavenReport { |
| |
| /** |
| * If set to false, only failures are shown. |
| */ |
| @Parameter(defaultValue = "true", required = true, property = "showSuccess") |
| private boolean showSuccess; |
| |
| /** |
| * Directories containing the XML Report files that will be parsed and rendered to HTML format. |
| */ |
| @Parameter |
| private File[] reportsDirectories; |
| |
| /** |
| * (Deprecated, use reportsDirectories) This directory contains the XML Report files that will be parsed and |
| * rendered to HTML format. |
| */ |
| @Deprecated |
| @Parameter |
| private File reportsDirectory; |
| |
| /** |
| * The projects in the reactor for aggregation report. |
| */ |
| @Parameter(defaultValue = "${reactorProjects}", readonly = true) |
| private List<MavenProject> reactorProjects; |
| |
| /** |
| * Location of the Xrefs to link. |
| */ |
| @Parameter(defaultValue = "${project.reporting.outputDirectory}/xref-test") |
| private File xrefLocation; |
| |
| /** |
| * Whether to link the XRef if found. |
| */ |
| @Parameter(defaultValue = "true", property = "linkXRef") |
| private boolean linkXRef; |
| |
| /** |
| * Whether to build an aggregated report at the root, or build individual reports. |
| */ |
| @Parameter(defaultValue = "false", property = "aggregate") |
| private boolean aggregate; |
| |
| /** |
| * The current user system settings for use in Maven. |
| */ |
| @Parameter(defaultValue = "${settings}", readonly = true, required = true) |
| private Settings settings; |
| |
| /** |
| * Path for a custom bundle instead of using the default one. <br> |
| * Using this field, you could change the texts in the generated reports. |
| * |
| * @since 3.1.0 |
| */ |
| @Parameter(defaultValue = "${basedir}/src/site/custom/surefire-report.properties") |
| private String customBundle; |
| |
| /** |
| * Internationalization component |
| */ |
| @Component |
| private I18N i18n; |
| |
| private List<File> resolvedReportsDirectories; |
| |
| /** |
| * Whether the report should be generated or not. |
| * |
| * @return {@code true} if and only if the report should be generated. |
| * @since 2.11 |
| */ |
| protected boolean isSkipped() { |
| return false; |
| } |
| |
| /** |
| * Whether the report should be generated when there are no test results. |
| * |
| * @return {@code true} if and only if the report should be generated when there are no result files at all. |
| * @since 2.11 |
| */ |
| protected boolean isGeneratedWhenNoResults() { |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void executeReport(Locale locale) throws MavenReportException { |
| SurefireReportRenderer r = new SurefireReportRenderer( |
| getSink(), |
| getI18N(locale), |
| getI18Nsection(), |
| locale, |
| getConsoleLogger(), |
| showSuccess, |
| getReportsDirectories(), |
| determineXrefLocation()); |
| r.render(); |
| } |
| |
| @Override |
| public boolean canGenerateReport() { |
| if (isSkipped()) { |
| return false; |
| } |
| |
| final List<File> reportsDirectories = getReportsDirectories(); |
| |
| if (reportsDirectories == null) { |
| return false; |
| } |
| |
| if (!isGeneratedWhenNoResults()) { |
| boolean atLeastOneDirectoryExists = false; |
| for (Iterator<File> i = reportsDirectories.iterator(); i.hasNext() && !atLeastOneDirectoryExists; ) { |
| atLeastOneDirectoryExists = hasReportFiles(i.next()); |
| } |
| if (!atLeastOneDirectoryExists) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private List<File> getReportsDirectories() { |
| if (resolvedReportsDirectories != null) { |
| return resolvedReportsDirectories; |
| } |
| |
| resolvedReportsDirectories = new ArrayList<>(); |
| |
| if (this.reportsDirectories != null) { |
| addAll(resolvedReportsDirectories, this.reportsDirectories); |
| } |
| //noinspection deprecation |
| if (reportsDirectory != null) { |
| //noinspection deprecation |
| resolvedReportsDirectories.add(reportsDirectory); |
| } |
| if (aggregate) { |
| if (!project.isExecutionRoot()) { |
| return null; |
| } |
| if (this.reportsDirectories == null) { |
| if (reactorProjects.size() > 1) { |
| for (MavenProject mavenProject : getProjectsWithoutRoot()) { |
| resolvedReportsDirectories.add(getSurefireReportsDirectory(mavenProject)); |
| } |
| } else { |
| resolvedReportsDirectories.add(getSurefireReportsDirectory(project)); |
| } |
| } else { |
| // Multiple report directories are configured. |
| // Let's see if those directories exist in each sub-module to fix SUREFIRE-570 |
| String parentBaseDir = getProject().getBasedir().getAbsolutePath(); |
| for (MavenProject subProject : getProjectsWithoutRoot()) { |
| String moduleBaseDir = subProject.getBasedir().getAbsolutePath(); |
| for (File reportsDirectory1 : this.reportsDirectories) { |
| String reportDir = reportsDirectory1.getPath(); |
| if (reportDir.startsWith(parentBaseDir)) { |
| reportDir = reportDir.substring(parentBaseDir.length()); |
| } |
| File reportsDirectory = new File(moduleBaseDir, reportDir); |
| if (reportsDirectory.exists() && reportsDirectory.isDirectory()) { |
| getConsoleLogger().debug("Adding report dir: " + moduleBaseDir + reportDir); |
| resolvedReportsDirectories.add(reportsDirectory); |
| } |
| } |
| } |
| } |
| } else { |
| if (resolvedReportsDirectories.isEmpty()) { |
| |
| resolvedReportsDirectories.add(getSurefireReportsDirectory(project)); |
| } |
| } |
| return resolvedReportsDirectories; |
| } |
| |
| /** |
| * Gets the default surefire reports directory for the specified project. |
| * |
| * @param subProject the project to query. |
| * @return the default surefire reports directory for the specified project. |
| */ |
| protected abstract File getSurefireReportsDirectory(MavenProject subProject); |
| |
| private List<MavenProject> getProjectsWithoutRoot() { |
| List<MavenProject> result = new ArrayList<>(); |
| for (MavenProject subProject : reactorProjects) { |
| if (!project.equals(subProject)) { |
| result.add(subProject); |
| } |
| } |
| return result; |
| } |
| |
| private String determineXrefLocation() { |
| String location = null; |
| |
| if (linkXRef) { |
| String relativePath = PathTool.getRelativePath(getOutputDirectory(), xrefLocation.getAbsolutePath()); |
| if (relativePath == null || relativePath.isEmpty()) { |
| relativePath = "."; |
| } |
| relativePath = relativePath + "/" + xrefLocation.getName(); |
| if (xrefLocation.exists()) { |
| // XRef was already generated by manual execution of a lifecycle binding |
| location = relativePath; |
| } else { |
| // Not yet generated - check if the report is on its way |
| for (Object o : project.getReportPlugins()) { |
| ReportPlugin report = (ReportPlugin) o; |
| |
| String artifactId = report.getArtifactId(); |
| if ("maven-jxr-plugin".equals(artifactId) || "jxr-maven-plugin".equals(artifactId)) { |
| location = relativePath; |
| } |
| } |
| } |
| |
| if (location == null) { |
| getConsoleLogger().warning("Unable to locate Test Source XRef to link to - DISABLED"); |
| } |
| } |
| return location; |
| } |
| |
| /** |
| * @param locale The locale |
| * @param key The key to search for |
| * @return The text appropriate for the locale. |
| */ |
| protected String getI18nString(Locale locale, String key) { |
| return getI18N(locale).getString("surefire-report", locale, "report." + getI18Nsection() + '.' + key); |
| } |
| /** |
| * @param locale The local. |
| * @return I18N for the locale |
| */ |
| protected I18N getI18N(Locale locale) { |
| if (customBundle != null) { |
| File customBundleFile = new File(customBundle); |
| if (customBundleFile.isFile() && customBundleFile.getName().endsWith(".properties")) { |
| if (!i18n.getClass().isAssignableFrom(CustomI18N.class) |
| || !i18n.getDefaultLanguage().equals(locale.getLanguage())) { |
| // first load |
| i18n = new CustomI18N(project, settings, customBundleFile, locale, i18n); |
| } |
| } |
| } |
| |
| return i18n; |
| } |
| /** |
| * @return The according string for the section. |
| */ |
| protected abstract String getI18Nsection(); |
| |
| /** {@inheritDoc} */ |
| public String getName(Locale locale) { |
| return getI18nString(locale, "name"); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getDescription(Locale locale) { |
| return getI18nString(locale, "description"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public abstract String getOutputName(); |
| |
| protected final ConsoleLogger getConsoleLogger() { |
| return new PluginConsoleLogger(getLog()); |
| } |
| |
| @Override |
| protected MavenProject getProject() { |
| return project; |
| } |
| |
| // TODO Review, especially Locale.getDefault() |
| private static class CustomI18N implements I18N { |
| private final MavenProject project; |
| |
| private final Settings settings; |
| |
| private final String bundleName; |
| |
| private final Locale locale; |
| |
| private final I18N i18nOriginal; |
| |
| private ResourceBundle bundle; |
| |
| private static final Object[] NO_ARGS = new Object[0]; |
| |
| CustomI18N(MavenProject project, Settings settings, File customBundleFile, Locale locale, I18N i18nOriginal) { |
| super(); |
| this.project = project; |
| this.settings = settings; |
| this.locale = locale; |
| this.i18nOriginal = i18nOriginal; |
| this.bundleName = customBundleFile |
| .getName() |
| .substring(0, customBundleFile.getName().indexOf(".properties")); |
| |
| URLClassLoader classLoader = null; |
| try { |
| classLoader = new URLClassLoader( |
| new URL[] {customBundleFile.getParentFile().toURI().toURL()}, null); |
| } catch (MalformedURLException e) { |
| // could not happen. |
| } |
| |
| this.bundle = ResourceBundle.getBundle(this.bundleName, locale, classLoader); |
| if (!this.bundle.getLocale().getLanguage().equals(locale.getLanguage())) { |
| this.bundle = ResourceBundle.getBundle(this.bundleName, Locale.getDefault(), classLoader); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public String getDefaultLanguage() { |
| return locale.getLanguage(); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getDefaultCountry() { |
| return locale.getCountry(); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getDefaultBundleName() { |
| return bundleName; |
| } |
| |
| /** {@inheritDoc} */ |
| public String[] getBundleNames() { |
| return new String[] {bundleName}; |
| } |
| |
| /** {@inheritDoc} */ |
| public ResourceBundle getBundle() { |
| return bundle; |
| } |
| |
| /** {@inheritDoc} */ |
| public ResourceBundle getBundle(String bundleName) { |
| return bundle; |
| } |
| |
| /** {@inheritDoc} */ |
| public ResourceBundle getBundle(String bundleName, String languageHeader) { |
| return bundle; |
| } |
| |
| /** {@inheritDoc} */ |
| public ResourceBundle getBundle(String bundleName, Locale locale) { |
| return bundle; |
| } |
| |
| /** {@inheritDoc} */ |
| public Locale getLocale(String languageHeader) { |
| return new Locale(languageHeader); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getString(String key) { |
| return getString(bundleName, locale, key); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getString(String key, Locale locale) { |
| return getString(bundleName, locale, key); |
| } |
| |
| /** {@inheritDoc} */ |
| public String getString(String bundleName, Locale locale, String key) { |
| String value; |
| |
| if (locale == null) { |
| locale = getLocale(null); |
| } |
| |
| ResourceBundle rb = getBundle(bundleName, locale); |
| value = getStringOrNull(rb, key); |
| |
| if (value == null) { |
| // try to load default |
| value = i18nOriginal.getString(bundleName, locale, key); |
| } |
| |
| if (!value.contains("${")) { |
| return value; |
| } |
| |
| final RegexBasedInterpolator interpolator = new RegexBasedInterpolator(); |
| try { |
| interpolator.addValueSource(new EnvarBasedValueSource()); |
| } catch (final IOException e) { |
| // In which cases could this happen? And what should we do? |
| } |
| |
| interpolator.addValueSource(new PropertiesBasedValueSource(System.getProperties())); |
| interpolator.addValueSource(new PropertiesBasedValueSource(project.getProperties())); |
| interpolator.addValueSource(new PrefixedObjectValueSource("project", project)); |
| interpolator.addValueSource(new PrefixedObjectValueSource("pom", project)); |
| interpolator.addValueSource(new PrefixedObjectValueSource("settings", settings)); |
| |
| try { |
| value = interpolator.interpolate(value); |
| } catch (final InterpolationException e) { |
| // What does this exception mean? |
| } |
| |
| return value; |
| } |
| |
| /** {@inheritDoc} */ |
| public String format(String key, Object arg1) { |
| return format(bundleName, locale, key, new Object[] {arg1}); |
| } |
| |
| /** {@inheritDoc} */ |
| public String format(String key, Object arg1, Object arg2) { |
| return format(bundleName, locale, key, new Object[] {arg1, arg2}); |
| } |
| |
| /** {@inheritDoc} */ |
| public String format(String bundleName, Locale locale, String key, Object arg1) { |
| return format(bundleName, locale, key, new Object[] {arg1}); |
| } |
| |
| /** {@inheritDoc} */ |
| public String format(String bundleName, Locale locale, String key, Object arg1, Object arg2) { |
| return format(bundleName, locale, key, new Object[] {arg1, arg2}); |
| } |
| |
| /** {@inheritDoc} */ |
| public String format(String bundleName, Locale locale, String key, Object[] args) { |
| if (locale == null) { |
| locale = getLocale(null); |
| } |
| |
| String value = getString(bundleName, locale, key); |
| if (args == null) { |
| args = NO_ARGS; |
| } |
| |
| MessageFormat messageFormat = new MessageFormat(""); |
| messageFormat.setLocale(locale); |
| messageFormat.applyPattern(value); |
| |
| return messageFormat.format(args); |
| } |
| |
| private String getStringOrNull(ResourceBundle rb, String key) { |
| if (rb != null) { |
| try { |
| return rb.getString(key); |
| } catch (MissingResourceException ignored) { |
| // intentional |
| } |
| } |
| return null; |
| } |
| } |
| } |