Jurrie closed pull request #1: Feature/mrresources 102 URL: https://github.com/apache/maven-remote-resources-plugin/pull/1
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/ProcessRemoteResourcesMojo.java b/src/main/java/org/apache/maven/plugin/resources/remote/ProcessRemoteResourcesMojo.java index 1edfc3f..fb9e096 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/ProcessRemoteResourcesMojo.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/ProcessRemoteResourcesMojo.java @@ -96,6 +96,7 @@ import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.RuntimeServices; import org.apache.velocity.runtime.log.LogChute; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; @@ -127,1444 +128,1486 @@ // (remember, this runs in the generate-resources phase) will cause the build to fail. // // @requiresDependencyResolution test -@Mojo( name = "process", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true ) +@Mojo(name = "process", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) public class ProcessRemoteResourcesMojo - extends AbstractMojo - implements LogChute + extends AbstractMojo + implements LogChute { - private static final String TEMPLATE_SUFFIX = ".vm"; - - /** - * <p> - * In cases where a local resource overrides one from a remote resource bundle, that resource should be filtered if - * the resource set specifies it. In those cases, this parameter defines the list of delimiters for filterable - * expressions. These delimiters are specified in the form 'beginToken*endToken'. If no '*' is given, the delimiter - * is assumed to be the same for start and end. - * </p> - * <p> - * So, the default filtering delimiters might be specified as: - * </p> - * - * <pre> - * <delimiters> - * <delimiter>${*}</delimiter> - * <delimiter>@</delimiter> - * </delimiters> - * </pre> - * <p/> - * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can). - * - * @since 1.1 - */ - @Parameter - protected List<String> filterDelimiters; - - /** - * @since 1.1 - */ - @Parameter( defaultValue = "true" ) - protected boolean useDefaultFilterDelimiters; - - /** - * If true, only generate resources in the directory of the root project in a multimodule build. - * Dependencies from all modules will be aggregated before resource-generation takes place. - * - * @since 1.1 - */ - @Parameter( defaultValue = "false" ) - protected boolean runOnlyAtExecutionRoot; - - /** - * Used for calculation of execution-root for {@link ProcessRemoteResourcesMojo#runOnlyAtExecutionRoot}. - */ - @Parameter( defaultValue = "${basedir}", readonly = true, required = true ) - protected File basedir; - - /** - * The character encoding scheme to be applied when filtering resources. - */ - @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" ) - protected String encoding; - - /** - * The local repository taken from Maven's runtime. Typically <code>$HOME/.m2/repository</code>. - */ - @Parameter( defaultValue = "${localRepository}", readonly = true, required = true ) - private ArtifactRepository localRepository; - - /** - * List of Remote Repositories used by the resolver. - */ - @Parameter( defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true ) - private List<ArtifactRepository> remoteArtifactRepositories; - - /** - * The current Maven project. - */ - @Parameter( defaultValue = "${project}", readonly = true, required = true ) - private MavenProject project; - - /** - * The directory where processed resources will be placed for packaging. - */ - @Parameter( defaultValue = "${project.build.directory}/maven-shared-archive-resources" ) - private File outputDirectory; - - /** - * The directory containing extra information appended to the generated resources. - */ - @Parameter( defaultValue = "${basedir}/src/main/appended-resources" ) - private File appendedResourcesDirectory; - - /** - * Supplemental model data. Useful when processing - * artifacts with incomplete POM metadata. - * <p/> - * By default, this Mojo looks for supplemental model data in the file - * "<code>${appendedResourcesDirectory}/supplemental-models.xml</code>". - * - * @since 1.0-alpha-5 - */ - @Parameter - private String[] supplementalModels; - - /** - * List of artifacts that are added to the search path when looking - * for supplementalModels, expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format. - * - * @since 1.1 - */ - @Parameter - private List<String> supplementalModelArtifacts; - - /** - * Map of artifacts to supplemental project object models. - */ - private Map<String, Model> supplementModels; - - /** - * Merges supplemental data model with artifact metadata. Useful when processing artifacts with - * incomplete POM metadata. - */ - @Component - private ModelInheritanceAssembler inheritanceAssembler; - - /** - * The resource bundles that will be retrieved and processed, - * expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format. - */ - @Parameter( required = true ) - private List<String> resourceBundles; - - /** - * Skip remote-resource processing - * - * @since 1.0-alpha-5 - */ - @Parameter( property = "remoteresources.skip", defaultValue = "false" ) - private boolean skip; - - /** - * Attaches the resources to the main build of the project as a resource directory. - * - * @since 1.5 - */ - @Parameter( defaultValue = "true", property = "attachToMain" ) - private boolean attachToMain; - - /** - * Attaches the resources to the test build of the project as a resource directory. - * - * @since 1.5 - */ - @Parameter( defaultValue = "true", property = "attachToTest" ) - private boolean attachToTest; - - /** - * Additional properties to be passed to Velocity. - * <p/> - * Several properties are automatically added:<ul> - * <li><code>project</code> - the current MavenProject </li> - * <li><code>projects</code> - the list of dependency projects</li> - * <li><code>projectsSortedByOrganization</code> - the list of dependency projects sorted by organization</li> - * <li><code>projectTimespan</code> - the timespan of the current project (requires inceptionYear in pom)</li> - * <li><code>locator</code> - the ResourceManager that can be used to retrieve additional resources</li> - * </ul> - * See <a - * href="https://maven.apache.org/ref/current/maven-project/apidocs/org/apache/maven/project/MavenProject.html"> the - * javadoc for MavenProject</a> for information about the properties on the MavenProject. - */ - @Parameter - private Map<String, Object> properties = new HashMap<String, Object>(); - - /** - * Whether to include properties defined in the project when filtering resources. - * - * @since 1.2 - */ - @Parameter( defaultValue = "false" ) - protected boolean includeProjectProperties = false; - - /** - * When the result of velocity transformation fits in memory, it is compared with the actual contents on disk - * to eliminate unnecessary destination file overwrite. This improves build times since further build steps - * typically rely on the modification date. - * - * @since 1.6 - */ - @Parameter( defaultValue = "5242880" ) - protected int velocityFilterInMemoryThreshold = 5 * 1024 * 1024; - - /** - * The list of resources defined for the project. - */ - @Parameter( defaultValue = "${project.resources}", readonly = true, required = true ) - private List<Resource> resources; - - /** - * Artifact Resolver, needed to resolve and download the {@code resourceBundles}. - */ - @Component - private ArtifactResolver artifactResolver; - - /** - * Filtering support, for local resources that override those in the remote bundle. - */ - @Component - private MavenFileFilter fileFilter; - - /** - * Artifact factory, needed to create artifacts. - */ - @Component - private ArtifactFactory artifactFactory; - - /** - * The Maven session. - */ - @Parameter( defaultValue = "${session}", readonly = true, required = true ) - private MavenSession mavenSession; - - /** - * ProjectBuilder, needed to create projects from the artifacts. - */ - @Component( role = MavenProjectBuilder.class ) - private MavenProjectBuilder mavenProjectBuilder; - - /** - */ - @Component - private ResourceManager locator; - - /** - * Scope to include. An Empty string indicates all scopes (default is "runtime"). - * - * @since 1.0 - */ - @Parameter( property = "includeScope", defaultValue = "runtime" ) - protected String includeScope; - - /** - * Scope to exclude. An Empty string indicates no scopes (default). - * - * @since 1.0 - */ - @Parameter( property = "excludeScope", defaultValue = "" ) - protected String excludeScope; - - /** - * When resolving project dependencies, specify the scopes to include. - * The default is the same as "includeScope" if there are no exclude scopes set. - * Otherwise, it defaults to "test" to grab all the dependencies so the - * exclude filters can filter out what is not needed. - * - * @since 1.5 - */ - @Parameter - private String[] resolveScopes; - - /** - * Comma separated list of Artifact names too exclude. - * - * @since 1.0 - */ - @Parameter( property = "excludeArtifactIds", defaultValue = "" ) - protected String excludeArtifactIds; - - /** - * Comma separated list of Artifact names to include. - * - * @since 1.0 - */ - @Parameter( property = "includeArtifactIds", defaultValue = "" ) - protected String includeArtifactIds; - - /** - * Comma separated list of GroupId Names to exclude. - * - * @since 1.0 - */ - @Parameter( property = "excludeGroupIds", defaultValue = "" ) - protected String excludeGroupIds; - - /** - * Comma separated list of GroupIds to include. - * - * @since 1.0 - */ - @Parameter( property = "includeGroupIds", defaultValue = "" ) - protected String includeGroupIds; - - /** - * If we should exclude transitive dependencies - * - * @since 1.0 - */ - @Parameter( property = "excludeTransitive", defaultValue = "false" ) - protected boolean excludeTransitive; - - /** - */ - @Component( hint = "default" ) - protected ProjectDependenciesResolver dependencyResolver; - - private VelocityEngine velocity; - - @SuppressWarnings( "unchecked" ) - public void execute() - throws MojoExecutionException - { - if ( skip ) - { - getLog().info( "Skipping remote resources execution." ); - return; - } - - if ( StringUtils.isEmpty( encoding ) ) - { - getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING - + ", i.e. build is platform dependent!" ); - } - - if ( runOnlyAtExecutionRoot && !project.isExecutionRoot() ) - { - getLog().info( "Skipping remote-resource generation in this project because it's not the Execution Root" ); - return; - } - - if ( resolveScopes == null ) - { - resolveScopes = - new String[] { StringUtils.isEmpty( excludeScope ) ? this.includeScope : Artifact.SCOPE_TEST }; - } - - if ( supplementalModels == null ) - { - File sups = new File( appendedResourcesDirectory, "supplemental-models.xml" ); - if ( sups.exists() ) - { - try - { - supplementalModels = new String[] { sups.toURI().toURL().toString() }; - } - catch ( MalformedURLException e ) - { - // ignore - getLog().debug( "URL issue with supplemental-models.xml: " + e.toString() ); - } - } - } - - configureLocator(); - - if ( includeProjectProperties ) - { - final Properties projectProperties = project.getProperties(); - for ( Object key : projectProperties.keySet() ) - { - properties.put( key.toString(), projectProperties.get( key ).toString() ); - } - } - - ClassLoader origLoader = Thread.currentThread().getContextClassLoader(); - try - { - validate(); - - List<File> resourceBundleArtifacts = downloadBundles( resourceBundles ); - supplementModels = loadSupplements( supplementalModels ); - - ClassLoader classLoader = initalizeClassloader( resourceBundleArtifacts ); - - Thread.currentThread().setContextClassLoader( classLoader ); - - velocity = new VelocityEngine(); - velocity.setProperty( VelocityEngine.RUNTIME_LOG_LOGSYSTEM, this ); - velocity.setProperty( "resource.loader", "classpath" ); - velocity.setProperty( "classpath.resource.loader.class", ClasspathResourceLoader.class.getName() ); - velocity.init(); - - VelocityContext context = buildVelocityContext( properties ); - - processResourceBundles( classLoader, context ); - - if ( outputDirectory.exists() ) - { - // ---------------------------------------------------------------------------- - // Push our newly generated resources directory into the MavenProject so that - // these resources can be picked up by the process-resources phase. - // ---------------------------------------------------------------------------- - Resource resource = new Resource(); - resource.setDirectory( outputDirectory.getAbsolutePath() ); - // MRRESOURCES-61 handle main and test resources separately - if ( attachToMain ) - { - project.getResources().add( resource ); - } - if ( attachToTest ) - { - project.getTestResources().add( resource ); - } - - // ---------------------------------------------------------------------------- - // Write out archiver dot file - // ---------------------------------------------------------------------------- - try - { - File dotFile = new File( project.getBuild().getDirectory(), ".plxarc" ); - FileUtils.mkdir( dotFile.getParentFile().getAbsolutePath() ); - FileUtils.fileWrite( dotFile.getAbsolutePath(), outputDirectory.getName() ); - } - catch ( IOException e ) - { - throw new MojoExecutionException( "Error creating dot file for archiving instructions.", e ); - } - } - } - finally - { - Thread.currentThread().setContextClassLoader( origLoader ); - } - } - - private void configureLocator() - throws MojoExecutionException - { - if ( supplementalModelArtifacts != null && !supplementalModelArtifacts.isEmpty() ) - { - List<File> artifacts = downloadBundles( supplementalModelArtifacts ); - - for ( File artifact : artifacts ) - { - if ( artifact.isDirectory() ) - { - locator.addSearchPath( FileResourceLoader.ID, artifact.getAbsolutePath() ); - } - else - { - try - { - locator.addSearchPath( "jar", "jar:" + artifact.toURI().toURL().toExternalForm() ); - } - catch ( MalformedURLException e ) - { - throw new MojoExecutionException( "Could not use jar " + artifact.getAbsolutePath(), e ); - } - } - } - - } - - locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() ); - if ( appendedResourcesDirectory != null ) - { - locator.addSearchPath( FileResourceLoader.ID, appendedResourcesDirectory.getAbsolutePath() ); - } - locator.addSearchPath( "url", "" ); - locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) ); - } - - @SuppressWarnings( "unchecked" ) - protected List<MavenProject> getProjects() - { - List<MavenProject> projects = new ArrayList<MavenProject>(); - - // add filters in well known order, least specific to most specific - FilterArtifacts filter = new FilterArtifacts(); - - Set<Artifact> artifacts = resolveProjectArtifacts(); - if ( this.excludeTransitive ) - { - Set<Artifact> depArtifacts; - if ( runOnlyAtExecutionRoot ) - { - depArtifacts = aggregateProjectDependencyArtifacts(); - } - else - { - depArtifacts = project.getDependencyArtifacts(); - } - filter.addFilter( new ProjectTransitivityFilter( depArtifacts, true ) ); - } - - filter.addFilter( new ScopeFilter( this.includeScope, this.excludeScope ) ); - filter.addFilter( new GroupIdFilter( this.includeGroupIds, this.excludeGroupIds ) ); - filter.addFilter( new ArtifactIdFilter( this.includeArtifactIds, this.excludeArtifactIds ) ); - - // perform filtering - try - { - artifacts = filter.filter( artifacts ); - } - catch ( ArtifactFilterException e ) - { - throw new IllegalStateException( e.getMessage(), e ); - } - - for ( Artifact artifact : artifacts ) - { - try - { - List<ArtifactRepository> remoteRepo = remoteArtifactRepositories; - if ( artifact.isSnapshot() ) - { - VersionRange rng = VersionRange.createFromVersion( artifact.getBaseVersion() ); - artifact = - artifactFactory.createDependencyArtifact( artifact.getGroupId(), artifact.getArtifactId(), rng, - artifact.getType(), artifact.getClassifier(), - artifact.getScope(), null, artifact.isOptional() ); - } - - getLog().debug( "Building project for " + artifact ); - MavenProject p; - try - { - p = mavenProjectBuilder.buildFromRepository( artifact, remoteRepo, localRepository ); - } - catch ( InvalidProjectModelException e ) - { - getLog().warn( "Invalid project model for artifact [" + artifact.getArtifactId() + ":" - + artifact.getGroupId() + ":" + artifact.getVersion() + "]. " - + "It will be ignored by the remote resources Mojo." ); - continue; - } - - String supplementKey = - generateSupplementMapKey( p.getModel().getGroupId(), p.getModel().getArtifactId() ); - - if ( supplementModels.containsKey( supplementKey ) ) - { - Model mergedModel = mergeModels( p.getModel(), supplementModels.get( supplementKey ) ); - MavenProject mergedProject = new MavenProject( mergedModel ); - projects.add( mergedProject ); - mergedProject.setArtifact( artifact ); - mergedProject.setVersion( artifact.getVersion() ); - getLog().debug( "Adding project with groupId [" + mergedProject.getGroupId() + "] (supplemented)" ); - } - else - { - projects.add( p ); - getLog().debug( "Adding project with groupId [" + p.getGroupId() + "]" ); - } - } - catch ( ProjectBuildingException e ) - { - throw new IllegalStateException( e.getMessage(), e ); - } - } - Collections.sort( projects, new ProjectComparator() ); - return projects; - } - - private Set<Artifact> resolveProjectArtifacts() - { - try - { - if ( runOnlyAtExecutionRoot ) - { - List<MavenProject> projects = mavenSession.getSortedProjects(); - return dependencyResolver.resolve( projects, Arrays.asList( resolveScopes ), mavenSession ); - } - else - { - return dependencyResolver.resolve( project, Arrays.asList( resolveScopes ), mavenSession ); - } - } - catch ( ArtifactResolutionException e ) - { - throw new IllegalStateException( "Failed to resolve dependencies for one or more projects in the reactor. " - + "Reason: " + e.getMessage(), e ); - } - catch ( ArtifactNotFoundException e ) - { - throw new IllegalStateException( "Failed to resolve dependencies for one or more projects in the reactor. " - + "Reason: " + e.getMessage(), e ); - } - } - - @SuppressWarnings( "unchecked" ) - private Set<Artifact> aggregateProjectDependencyArtifacts() - { - Set<Artifact> artifacts = new LinkedHashSet<Artifact>(); - - List<MavenProject> projects = mavenSession.getSortedProjects(); - for ( MavenProject p : projects ) - { - if ( p.getDependencyArtifacts() == null ) - { - try - { - Set<Artifact> depArtifacts = p.createArtifacts( artifactFactory, null, null ); - - if ( depArtifacts != null && !depArtifacts.isEmpty() ) - { - for ( Artifact artifact : depArtifacts ) - { - if ( artifact.getVersion() == null && artifact.getVersionRange() != null ) - { - // Version is required for equality comparison between artifacts, - // but it is not needed for our purposes of filtering out - // transitive dependencies (which requires only groupId/artifactId). - // Therefore set an arbitrary version if missing. - artifact.setResolvedVersion( Artifact.LATEST_VERSION ); - } - artifacts.add( artifact ); - } - } - } - catch ( InvalidDependencyVersionException e ) - { - throw new IllegalStateException( "Failed to create dependency artifacts for: " + p.getId() - + ". Reason: " + e.getMessage(), e ); - } - } - } - - return artifacts; - } - - protected Map<Organization, List<MavenProject>> getProjectsSortedByOrganization( List<MavenProject> projects ) - { - Map<Organization, List<MavenProject>> organizations = - new TreeMap<Organization, List<MavenProject>>( new OrganizationComparator() ); - List<MavenProject> unknownOrganization = new ArrayList<MavenProject>(); - - for ( MavenProject p : projects ) - { - if ( p.getOrganization() != null && StringUtils.isNotEmpty( p.getOrganization().getName() ) ) - { - List<MavenProject> sortedProjects = organizations.get( p.getOrganization() ); - if ( sortedProjects == null ) - { - sortedProjects = new ArrayList<MavenProject>(); - } - sortedProjects.add( p ); - - organizations.put( p.getOrganization(), sortedProjects ); - } - else - { - unknownOrganization.add( p ); - } - } - if ( !unknownOrganization.isEmpty() ) - { - Organization unknownOrg = new Organization(); - unknownOrg.setName( "an unknown organization" ); - organizations.put( unknownOrg, unknownOrganization ); - } - - return organizations; - } - - protected boolean copyResourceIfExists( File file, String relFileName, VelocityContext context ) - throws IOException, MojoExecutionException - { - for ( Resource resource : resources ) - { - File resourceDirectory = new File( resource.getDirectory() ); - - if ( !resourceDirectory.exists() ) - { - continue; - } - - // TODO - really should use the resource includes/excludes and name mapping - File source = new File( resourceDirectory, relFileName ); - File templateSource = new File( resourceDirectory, relFileName + TEMPLATE_SUFFIX ); - - if ( !source.exists() && templateSource.exists() ) - { - source = templateSource; - } - - if ( source.exists() && !source.equals( file ) ) - { - if ( source == templateSource ) - { - Reader reader = null; - Writer writer = null; - DeferredFileOutputStream os = new DeferredFileOutputStream( velocityFilterInMemoryThreshold, file ); - try - { - - if ( encoding != null ) - { - reader = new InputStreamReader( new FileInputStream( source ), encoding ); - writer = new OutputStreamWriter( os, encoding ); - } - else - { - reader = ReaderFactory.newPlatformReader( source ); - writer = WriterFactory.newPlatformWriter( os ); - } - - velocity.evaluate( context, writer, "", reader ); - writer.close(); - writer = null; - reader.close(); - reader = null; - } - catch ( ParseErrorException e ) - { - throw new MojoExecutionException( "Error rendering velocity resource: " + source, e ); - } - catch ( MethodInvocationException e ) - { - throw new MojoExecutionException( "Error rendering velocity resource: " + source, e ); - } - catch ( ResourceNotFoundException e ) - { - throw new MojoExecutionException( "Error rendering velocity resource: " + source, e ); - } - finally - { - IOUtil.close( writer ); - IOUtil.close( reader ); - } - fileWriteIfDiffers( os ); - } - else if ( resource.isFiltering() ) - { - - MavenFileFilterRequest req = setupRequest( resource, source, file ); - - try - { - fileFilter.copyFile( req ); - } - catch ( MavenFilteringException e ) - { - throw new MojoExecutionException( "Error filtering resource: " + source, e ); - } - } - else - { - FileUtils.copyFile( source, file ); - } - - // exclude the original (so eclipse doesn't complain about duplicate resources) - resource.addExclude( relFileName ); - - return true; - } - - } - return false; - } - - /** - * If the transformation result fits in memory and the destination file already exists - * then both are compared. - * <p>If destination file is byte-by-byte equal, then it is not overwritten. - * This improves subsequent compilation times since upstream plugins property see that - * the resource was not modified. - * <p>Note: the method should be called after {@link org.apache.commons.io.output.DeferredFileOutputStream#close} - * - * @param outStream Deferred stream - * @throws IOException - */ - private void fileWriteIfDiffers( DeferredFileOutputStream outStream ) - throws IOException - { - File file = outStream.getFile(); - if ( outStream.isThresholdExceeded() ) - { - getLog().info( "File " + file + " was overwritten due to content limit threshold " - + outStream.getThreshold() + " reached" ); - return; - } - boolean needOverwrite = true; - - if ( file.exists() ) - { - InputStream is = null; - try - { - is = new FileInputStream( file ); - final InputStream newContents = new ByteArrayInputStream( outStream.getData() ); - needOverwrite = !IOUtil.contentEquals( is, newContents ); - if ( getLog().isDebugEnabled() ) - { - getLog().debug( "File " + file + " contents " - + ( needOverwrite ? "differs" : "does not differ" ) ); - } - - is.close(); - is = null; - } - finally - { - IOUtil.close( is ); - } - } - - if ( !needOverwrite ) - { - getLog().debug( "File " + file + " is up to date" ); - return; - } - getLog().debug( "Writing " + file ); - OutputStream os = new FileOutputStream( file ); - try - { - outStream.writeTo( os ); - os.close(); - os = null; - } - finally - { - IOUtil.close( os ); - } - } - - private MavenFileFilterRequest setupRequest( Resource resource, File source, File file ) - { - MavenFileFilterRequest req = new MavenFileFilterRequest(); - req.setFrom( source ); - req.setTo( file ); - req.setFiltering( resource.isFiltering() ); - - req.setMavenProject( project ); - req.setMavenSession( mavenSession ); - req.setInjectProjectBuildFilters( true ); - - if ( encoding != null ) - { - req.setEncoding( encoding ); - } - - if ( filterDelimiters != null && !filterDelimiters.isEmpty() ) - { - LinkedHashSet<String> delims = new LinkedHashSet<String>(); - if ( useDefaultFilterDelimiters ) - { - delims.addAll( req.getDelimiters() ); - } - - for ( String delim : filterDelimiters ) - { - if ( delim == null ) - { - delims.add( "${*}" ); - } - else - { - delims.add( delim ); - } - } - - req.setDelimiters( delims ); - } - - return req; - } - - protected void validate() - throws MojoExecutionException - { - int bundleCount = 1; - - for ( String artifactDescriptor : resourceBundles ) - { - // groupId:artifactId:version, groupId:artifactId:version:type - // or groupId:artifactId:version:type:classifier - String[] s = StringUtils.split( artifactDescriptor, ":" ); - - if ( s.length < 3 || s.length > 5 ) - { - String position; - - if ( bundleCount == 1 ) - { - position = "1st"; - } - else if ( bundleCount == 2 ) - { - position = "2nd"; - } - else if ( bundleCount == 3 ) - { - position = "3rd"; - } - else - { - position = bundleCount + "th"; - } - - throw new MojoExecutionException( "The " + position - + " resource bundle configured must specify a groupId, artifactId, " - + " version and, optionally, type and classifier for a remote resource bundle. " - + "Must be of the form <resourceBundle>groupId:artifactId:version</resourceBundle>, " - + "<resourceBundle>groupId:artifactId:version:type</resourceBundle> or " - + "<resourceBundle>groupId:artifactId:version:type:classifier</resourceBundle>" ); - } - - bundleCount++; - } - - } - - private static final String KEY_PROJECTS = "projects"; - private static final String KEY_PROJECTS_ORGS = "projectsSortedByOrganization"; - - protected VelocityContext buildVelocityContext( Map<String, Object> properties ) - throws MojoExecutionException - { - // the following properties are expensive to calculate, so we provide them lazily - VelocityContext context = new VelocityContext( properties ) - { - @Override - public Object internalGet( String key ) - { - Object result = super.internalGet( key ); - if ( result == null && key != null && key.startsWith( KEY_PROJECTS ) && containsKey( key ) ) - { - // calculate and put projects* properties - List<MavenProject> projects = getProjects(); - put( KEY_PROJECTS, projects ); - put( KEY_PROJECTS_ORGS, getProjectsSortedByOrganization( projects ) ); - return super.internalGet( key ); - } - return result; - } - }; - // to have a consistent getKeys()/containsKey() behaviour, keys must be present from the start - context.put( KEY_PROJECTS, null ); - context.put( KEY_PROJECTS_ORGS, null ); - // the following properties are cheap to calculate, so we provide them eagerly - - String inceptionYear = project.getInceptionYear(); - String year = new SimpleDateFormat( "yyyy" ).format( new Date() ); - - if ( StringUtils.isEmpty( inceptionYear ) ) - { - if ( getLog().isDebugEnabled() ) - { - getLog().debug( "inceptionYear not specified, defaulting to " + year ); - } - - inceptionYear = year; - } - context.put( "project", project ); - context.put( "presentYear", year ); - context.put( "locator", locator ); - - if ( inceptionYear.equals( year ) ) - { - context.put( "projectTimespan", year ); - } - else - { - context.put( "projectTimespan", inceptionYear + "-" + year ); - } - return context; - } - - private List<File> downloadBundles( List<String> bundles ) - throws MojoExecutionException - { - List<File> bundleArtifacts = new ArrayList<File>(); - - try - { - for ( String artifactDescriptor : bundles ) - { - getLog().info( "Preparing remote bundle " + artifactDescriptor ); - // groupId:artifactId:version[:type[:classifier]] - String[] s = artifactDescriptor.split( ":" ); - - File artifactFile = null; - // check if the artifact is part of the reactor - if ( mavenSession != null ) - { - List<MavenProject> list = mavenSession.getSortedProjects(); - for ( MavenProject p : list ) - { - if ( s[0].equals( p.getGroupId() ) && s[1].equals( p.getArtifactId() ) - && s[2].equals( p.getVersion() ) ) - { - if ( s.length >= 4 && "test-jar".equals( s[3] ) ) - { - artifactFile = new File( p.getBuild().getTestOutputDirectory() ); - } - else - { - artifactFile = new File( p.getBuild().getOutputDirectory() ); - } - } - } - } - if ( artifactFile == null || !artifactFile.exists() ) - { - String type = ( s.length >= 4 ? s[3] : "jar" ); - String classifier = ( s.length == 5 ? s[4] : null ); - Artifact artifact = - artifactFactory.createDependencyArtifact( s[0], s[1], VersionRange.createFromVersion( s[2] ), - type, classifier, Artifact.SCOPE_RUNTIME ); - - artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository ); - - artifactFile = artifact.getFile(); - } - bundleArtifacts.add( artifactFile ); - } - } - catch ( ArtifactResolutionException e ) - { - throw new MojoExecutionException( "Error downloading resources archive.", e ); - } - catch ( ArtifactNotFoundException e ) - { - throw new MojoExecutionException( "Resources archive cannot be found.", e ); - } - - return bundleArtifacts; - } - - private ClassLoader initalizeClassloader( List<File> artifacts ) - throws MojoExecutionException - { - RemoteResourcesClassLoader cl = new RemoteResourcesClassLoader( null ); - try - { - for ( File artifact : artifacts ) - { - cl.addURL( artifact.toURI().toURL() ); - } - return cl; - } - catch ( MalformedURLException e ) - { - throw new MojoExecutionException( "Unable to configure resources classloader: " + e.getMessage(), e ); - } - } - - protected void processResourceBundles( ClassLoader classLoader, VelocityContext context ) - throws MojoExecutionException - { - List<Map.Entry<String, RemoteResourcesBundle>> remoteResources = - new ArrayList<Map.Entry<String, RemoteResourcesBundle>>(); - int bundleCount = 0; - int resourceCount = 0; - - // list remote resources form bundles - InputStream in = null; - try - { - RemoteResourcesBundleXpp3Reader bundleReader = new RemoteResourcesBundleXpp3Reader(); - - for ( Enumeration<URL> e = - classLoader.getResources( BundleRemoteResourcesMojo.RESOURCES_MANIFEST ); e.hasMoreElements(); ) - { - URL url = e.nextElement(); - bundleCount++; - getLog().debug( "processResourceBundle on bundle#" + bundleCount + " " + url ); - - in = url.openStream(); - - RemoteResourcesBundle bundle = bundleReader.read( in ); - - in.close(); - in = null; - - int n = 0; - for ( String bundleResource : bundle.getRemoteResources() ) - { - n++; - resourceCount++; - getLog().debug( "bundle#" + bundleCount + " resource#" + n + " " + bundleResource ); - remoteResources.add( new AbstractMap.SimpleEntry<String, RemoteResourcesBundle>( bundleResource, - bundle ) ); - } - } - } - catch ( IOException ioe ) - { - throw new MojoExecutionException( "Error finding remote resources manifests", ioe ); - } - catch ( XmlPullParserException xppe ) - { - throw new MojoExecutionException( "Error parsing remote resource bundle descriptor.", xppe ); - } - finally - { - IOUtil.close( in ); - } - - getLog().info( "Copying " + resourceCount + " resource" + ( ( resourceCount > 1 ) ? "s" : "" ) + " from " - + bundleCount + " bundle" + ( ( bundleCount > 1 ) ? "s" : "" ) + "." ); - - String velocityResource = null; - OutputStream out = null; - Writer writer = null; - Reader reader = null; - - try - { - - for ( Map.Entry<String, RemoteResourcesBundle> entry : remoteResources ) - { - String bundleResource = entry.getKey(); - RemoteResourcesBundle bundle = entry.getValue(); - - String projectResource = bundleResource; - - boolean doVelocity = false; - if ( projectResource.endsWith( TEMPLATE_SUFFIX ) ) - { - projectResource = projectResource.substring( 0, projectResource.length() - 3 ); - velocityResource = bundleResource; - doVelocity = true; - } - - // Don't overwrite resource that are already being provided. - - File f = new File( outputDirectory, projectResource ); - - FileUtils.mkdir( f.getParentFile().getAbsolutePath() ); - - if ( !copyResourceIfExists( f, projectResource, context ) ) - { - if ( doVelocity ) - { - DeferredFileOutputStream os = - new DeferredFileOutputStream( velocityFilterInMemoryThreshold, f ); - - writer = bundle.getSourceEncoding() == null ? new OutputStreamWriter( os ) - : new OutputStreamWriter( os, bundle.getSourceEncoding() ); - - if ( bundle.getSourceEncoding() == null ) - { - // TODO: Is this correct? Shouldn't we behave like the rest of maven and fail - // down to JVM default instead ISO-8859-1 ? - velocity.mergeTemplate( bundleResource, "ISO-8859-1", context, writer ); - } - else - { - velocity.mergeTemplate( bundleResource, bundle.getSourceEncoding(), context, writer ); - - } - - writer.close(); - writer = null; - fileWriteIfDiffers( os ); - } - else - { - URL resUrl = classLoader.getResource( bundleResource ); - if ( resUrl != null ) - { - FileUtils.copyURLToFile( resUrl, f ); - } - } - - File appendedResourceFile = new File( appendedResourcesDirectory, projectResource ); - File appendedVmResourceFile = new File( appendedResourcesDirectory, projectResource + ".vm" ); - - if ( appendedResourceFile.exists() ) - { - getLog().info( "Copying appended resource: " + projectResource ); - in = new FileInputStream( appendedResourceFile ); - out = new FileOutputStream( f, true ); - IOUtil.copy( in, out ); - out.close(); - out = null; - in.close(); - in = null; - } - else if ( appendedVmResourceFile.exists() ) - { - getLog().info( "Filtering appended resource: " + projectResource + ".vm" ); - reader = new FileReader( appendedVmResourceFile ); - - if ( bundle.getSourceEncoding() == null ) - { - writer = new PrintWriter( new FileWriter( f, true ) ); - } - else - { - writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( f, true ), - bundle.getSourceEncoding() ) ); - } - - Velocity.init(); - Velocity.evaluate( context, writer, "remote-resources", reader ); - writer.close(); - writer = null; - reader.close(); - reader = null; - } - } - } - } - catch ( IOException ioe ) - { - throw new MojoExecutionException( "Error reading remote resource", ioe ); - } - catch ( VelocityException e ) - { - throw new MojoExecutionException( "Error rendering Velocity resource '" + velocityResource + "'", e ); - } - finally - { - IOUtil.close( out ); - IOUtil.close( in ); - IOUtil.close( writer ); - IOUtil.close( reader ); - } - } - - protected Model getSupplement( Xpp3Dom supplementModelXml ) - throws MojoExecutionException - { - MavenXpp3Reader modelReader = new MavenXpp3Reader(); - Model model = null; - - try - { - model = modelReader.read( new StringReader( supplementModelXml.toString() ) ); - String groupId = model.getGroupId(); - String artifactId = model.getArtifactId(); - - if ( groupId == null || groupId.trim().equals( "" ) ) - { - throw new MojoExecutionException( "Supplemental project XML " - + "requires that a <groupId> element be present." ); - } - - if ( artifactId == null || artifactId.trim().equals( "" ) ) - { - throw new MojoExecutionException( "Supplemental project XML " - + "requires that a <artifactId> element be present." ); - } - } - catch ( IOException e ) - { - getLog().warn( "Unable to read supplemental XML: " + e.getMessage(), e ); - } - catch ( XmlPullParserException e ) - { - getLog().warn( "Unable to parse supplemental XML: " + e.getMessage(), e ); - } - - return model; - } - - protected Model mergeModels( Model parent, Model child ) - { - inheritanceAssembler.assembleModelInheritance( child, parent ); - return child; - } - - private static String generateSupplementMapKey( String groupId, String artifactId ) - { - return groupId.trim() + ":" + artifactId.trim(); - } - - private Map<String, Model> loadSupplements( String models[] ) - throws MojoExecutionException - { - if ( models == null ) - { - getLog().debug( "Supplemental data models won't be loaded. " + "No models specified." ); - return Collections.emptyMap(); - } - - List<Supplement> supplements = new ArrayList<Supplement>(); - for ( String set : models ) - { - getLog().debug( "Preparing ruleset: " + set ); - try - { - File f = locator.getResourceAsFile( set, getLocationTemp( set ) ); - - if ( null == f || !f.exists() ) - { - throw new MojoExecutionException( "Cold not resolve " + set ); - } - if ( !f.canRead() ) - { - throw new MojoExecutionException( "Supplemental data models won't be loaded. " + "File " - + f.getAbsolutePath() + " cannot be read, check permissions on the file." ); - } - - getLog().debug( "Loading supplemental models from " + f.getAbsolutePath() ); - - SupplementalDataModelXpp3Reader reader = new SupplementalDataModelXpp3Reader(); - SupplementalDataModel supplementalModel = reader.read( new FileReader( f ) ); - supplements.addAll( supplementalModel.getSupplement() ); - } - catch ( Exception e ) - { - String msg = "Error loading supplemental data models: " + e.getMessage(); - getLog().error( msg, e ); - throw new MojoExecutionException( msg, e ); - } - } - - getLog().debug( "Loading supplements complete." ); - - Map<String, Model> supplementMap = new HashMap<String, Model>(); - for ( Supplement sd : supplements ) - { - Xpp3Dom dom = (Xpp3Dom) sd.getProject(); - - Model m = getSupplement( dom ); - supplementMap.put( generateSupplementMapKey( m.getGroupId(), m.getArtifactId() ), m ); - } - - return supplementMap; - } - - /** - * Convenience method to get the location of the specified file name. - * - * @param name the name of the file whose location is to be resolved - * @return a String that contains the absolute file name of the file - */ - private String getLocationTemp( String name ) - { - String loc = name; - if ( loc.indexOf( '/' ) != -1 ) - { - loc = loc.substring( loc.lastIndexOf( '/' ) + 1 ); - } - if ( loc.indexOf( '\\' ) != -1 ) - { - loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 ); - } - getLog().debug( "Before: " + name + " After: " + loc ); - return loc; - } - - class OrganizationComparator - implements Comparator<Organization> - { - public int compare( Organization org1, Organization org2 ) - { - int i = compareStrings( org1.getName(), org2.getName() ); - if ( i == 0 ) - { - i = compareStrings( org1.getUrl(), org2.getUrl() ); - } - return i; - } - - public boolean equals( Organization o1, Organization o2 ) - { - return compare( o1, o2 ) == 0; - } - - private int compareStrings( String s1, String s2 ) - { - if ( s1 == null && s2 == null ) - { - return 0; - } - else if ( s1 == null && s2 != null ) - { - return 1; - } - else if ( s1 != null && s2 == null ) - { - return -1; - } - - return s1.compareToIgnoreCase( s2 ); - } - } - - class ProjectComparator - implements Comparator<MavenProject> - { - public int compare( MavenProject p1, MavenProject p2 ) - { - return p1.getArtifact().compareTo( p2.getArtifact() ); - } - - public boolean equals( MavenProject p1, MavenProject p2 ) - { - return p1.getArtifact().equals( p2.getArtifact() ); - } - } - - /* LogChute methods */ - public void init( RuntimeServices rs ) - throws Exception - { - } - - public void log( int level, String message ) - { - switch ( level ) - { - case LogChute.WARN_ID: - getLog().warn( message ); - break; - case LogChute.INFO_ID: - // velocity info messages are too verbose, just consider them as debug messages... - getLog().debug( message ); - break; - case LogChute.DEBUG_ID: - getLog().debug( message ); - break; - case LogChute.ERROR_ID: - getLog().error( message ); - break; - default: - getLog().debug( message ); - break; - } - } - - public void log( int level, String message, Throwable t ) - { - switch ( level ) - { - case LogChute.WARN_ID: - getLog().warn( message, t ); - break; - case LogChute.INFO_ID: - // velocity info messages are too verbose, just consider them as debug messages... - getLog().debug( message, t ); - break; - case LogChute.DEBUG_ID: - getLog().debug( message, t ); - break; - case LogChute.ERROR_ID: - getLog().error( message, t ); - break; - default: - getLog().debug( message, t ); - break; - } - } - - public boolean isLevelEnabled( int level ) - { - return false; - } + private static final String TEMPLATE_SUFFIX = ".vm"; + + /** + * <p> + * In cases where a local resource overrides one from a remote resource bundle, that resource should be filtered if + * the resource set specifies it. In those cases, this parameter defines the list of delimiters for filterable + * expressions. These delimiters are specified in the form 'beginToken*endToken'. If no '*' is given, the delimiter + * is assumed to be the same for start and end. + * </p> + * <p> + * So, the default filtering delimiters might be specified as: + * </p> + * + * <pre> + * <delimiters> + * <delimiter>${*}</delimiter> + * <delimiter>@</delimiter> + * </delimiters> + * </pre> + * <p/> + * Since the '@' delimiter is the same on both ends, we don't need to specify '@*@' (though we can). + * + * @since 1.1 + */ + @Parameter + protected List<String> filterDelimiters; + + /** + * @since 1.1 + */ + @Parameter(defaultValue = "true") + protected boolean useDefaultFilterDelimiters; + + /** + * If true, only generate resources in the directory of the root project in a multimodule build. + * Dependencies from all modules will be aggregated before resource-generation takes place. + * + * @since 1.1 + */ + @Parameter(defaultValue = "false") + protected boolean runOnlyAtExecutionRoot; + + /** + * Used for calculation of execution-root for {@link ProcessRemoteResourcesMojo#runOnlyAtExecutionRoot}. + */ + @Parameter(defaultValue = "${basedir}", readonly = true, required = true) + protected File basedir; + + /** + * The character encoding scheme to be applied when filtering resources. + */ + @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}") + protected String encoding; + + /** + * The local repository taken from Maven's runtime. Typically <code>$HOME/.m2/repository</code>. + */ + @Parameter(defaultValue = "${localRepository}", readonly = true, required = true) + private ArtifactRepository localRepository; + + /** + * List of Remote Repositories used by the resolver. + */ + @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true, required = true) + private List<ArtifactRepository> remoteArtifactRepositories; + + /** + * The current Maven project. + */ + @Parameter(defaultValue = "${project}", readonly = true, required = true) + private MavenProject project; + + /** + * The directory where processed resources will be placed for packaging. + */ + @Parameter(defaultValue = "${project.build.directory}/maven-shared-archive-resources") + private File outputDirectory; + + /** + * The directory containing extra information appended to the generated resources. + */ + @Parameter(defaultValue = "${basedir}/src/main/appended-resources") + private File appendedResourcesDirectory; + + /** + * Supplemental model data. Useful when processing + * artifacts with incomplete POM metadata. + * <p/> + * By default, this Mojo looks for supplemental model data in the file + * "<code>${appendedResourcesDirectory}/supplemental-models.xml</code>". + * + * @since 1.0-alpha-5 + */ + @Parameter + private String[] supplementalModels; + + /** + * List of artifacts that are added to the search path when looking + * for supplementalModels, expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format. + * + * @since 1.1 + */ + @Parameter + private List<String> supplementalModelArtifacts; + + /** + * Map of artifacts to supplemental project object models. + */ + private Map<String, Model> supplementModels; + + /** + * Merges supplemental data model with artifact metadata. Useful when processing artifacts with + * incomplete POM metadata. + */ + @Component + private ModelInheritanceAssembler inheritanceAssembler; + + /** + * The resource bundles that will be retrieved and processed, + * expressed with <code>groupId:artifactId:version[:type[:classifier]]</code> format. + */ + @Parameter(required = true) + private List<String> resourceBundles; + + /** + * Skip remote-resource processing + * + * @since 1.0-alpha-5 + */ + @Parameter(property = "remoteresources.skip", defaultValue = "false") + private boolean skip; + + /** + * Attaches the resources to the main build of the project as a resource directory. + * + * @since 1.5 + */ + @Parameter(defaultValue = "true", property = "attachToMain") + private boolean attachToMain; + + /** + * Attaches the resources to the test build of the project as a resource directory. + * + * @since 1.5 + */ + @Parameter(defaultValue = "true", property = "attachToTest") + private boolean attachToTest; + + /** + * Additional properties to be passed to Velocity. + * <p/> + * Several properties are automatically added: + * <ul> + * <li><code>project</code> - the current MavenProject</li> + * <li><code>projects</code> - the list of dependency projects</li> + * <li><code>projectsSortedByOrganization</code> - the list of dependency projects sorted by organization</li> + * <li><code>projectTimespan</code> - the timespan of the current project (requires inceptionYear in pom)</li> + * <li><code>locator</code> - the ResourceManager that can be used to retrieve additional resources</li> + * </ul> + * See <a + * href="https://maven.apache.org/ref/current/maven-project/apidocs/org/apache/maven/project/MavenProject.html"> the + * javadoc for MavenProject</a> for information about the properties on the MavenProject. + */ + @Parameter + private Map<String, Object> properties = new HashMap<String, Object>(); + + /** + * Whether to include properties defined in the project when filtering resources. + * + * @since 1.2 + */ + @Parameter(defaultValue = "false") + protected boolean includeProjectProperties = false; + + /** + * When the result of velocity transformation fits in memory, it is compared with the actual contents on disk + * to eliminate unnecessary destination file overwrite. This improves build times since further build steps + * typically rely on the modification date. + * + * @since 1.6 + */ + @Parameter(defaultValue = "5242880") + protected int velocityFilterInMemoryThreshold = 5 * 1024 * 1024; + + /** + * The list of resources defined for the project. + */ + @Parameter(defaultValue = "${project.resources}", readonly = true, required = true) + private List<Resource> resources; + + /** + * Artifact Resolver, needed to resolve and download the {@code resourceBundles}. + */ + @Component + private ArtifactResolver artifactResolver; + + /** + * Filtering support, for local resources that override those in the remote bundle. + */ + @Component + private MavenFileFilter fileFilter; + + /** + * Artifact factory, needed to create artifacts. + */ + @Component + private ArtifactFactory artifactFactory; + + /** + * The Maven session. + */ + @Parameter(defaultValue = "${session}", readonly = true, required = true) + private MavenSession mavenSession; + + /** + * ProjectBuilder, needed to create projects from the artifacts. + */ + @Component(role = MavenProjectBuilder.class) + private MavenProjectBuilder mavenProjectBuilder; + + /** + */ + @Component + private ResourceManager locator; + + /** + * Scope to include. An Empty string indicates all scopes (default is "runtime"). + * + * @since 1.0 + */ + @Parameter(property = "includeScope", defaultValue = "runtime") + protected String includeScope; + + /** + * Scope to exclude. An Empty string indicates no scopes (default). + * + * @since 1.0 + */ + @Parameter(property = "excludeScope", defaultValue = "") + protected String excludeScope; + + /** + * When resolving project dependencies, specify the scopes to include. + * The default is the same as "includeScope" if there are no exclude scopes set. + * Otherwise, it defaults to "test" to grab all the dependencies so the + * exclude filters can filter out what is not needed. + * + * @since 1.5 + */ + @Parameter + private String[] resolveScopes; + + /** + * Comma separated list of Artifact names to exclude. + * + * @since 1.0 + */ + @Parameter(property = "excludeArtifactIds", defaultValue = "") + protected String excludeArtifactIds; + + /** + * Comma separated list of Artifact names to include. + * + * @since 1.0 + */ + @Parameter(property = "includeArtifactIds", defaultValue = "") + protected String includeArtifactIds; + + /** + * Comma separated list of GroupId Names to exclude. + * + * @since 1.0 + */ + @Parameter(property = "excludeGroupIds", defaultValue = "") + protected String excludeGroupIds; + + /** + * Comma separated list of GroupIds to include. + * + * @since 1.0 + */ + @Parameter(property = "includeGroupIds", defaultValue = "") + protected String includeGroupIds; + + /** + * If we should exclude transitive dependencies + * + * @since 1.0 + */ + @Parameter(property = "excludeTransitive", defaultValue = "false") + protected boolean excludeTransitive; + + /** + * Should we use resource filtering when copying the remote resources? + */ + @Parameter(property = "useResourceFiltering", defaultValue = "false") + protected boolean useResourceFiltering; + + /** + */ + @Component(hint = "default") + protected ProjectDependenciesResolver dependencyResolver; + + private VelocityEngine velocity; + + @Override + @SuppressWarnings("unchecked") + public void execute() + throws MojoExecutionException + { + if (skip) + { + getLog().info("Skipping remote resources execution."); + return; + } + + if (StringUtils.isEmpty(encoding)) + { + getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING + + ", i.e. build is platform dependent!"); + } + + if (runOnlyAtExecutionRoot && !project.isExecutionRoot()) + { + getLog().info("Skipping remote-resource generation in this project because it's not the Execution Root"); + return; + } + + if (resolveScopes == null) + { + resolveScopes = new String[] { StringUtils.isEmpty(excludeScope) ? includeScope : Artifact.SCOPE_TEST }; + } + + if (supplementalModels == null) + { + File sups = new File(appendedResourcesDirectory, "supplemental-models.xml"); + if (sups.exists()) + { + try + { + supplementalModels = new String[] { sups.toURI().toURL().toString() }; + } + catch (MalformedURLException e) + { + // ignore + getLog().debug("URL issue with supplemental-models.xml: " + e.toString()); + } + } + } + + configureLocator(); + + if (includeProjectProperties) + { + final Properties projectProperties = project.getProperties(); + for (Object key : projectProperties.keySet()) + { + properties.put(key.toString(), projectProperties.get(key).toString()); + } + } + + ClassLoader origLoader = Thread.currentThread().getContextClassLoader(); + try + { + validate(); + + List<File> resourceBundleArtifacts = downloadBundles(resourceBundles); + supplementModels = loadSupplements(supplementalModels); + + ClassLoader classLoader = initalizeClassloader(resourceBundleArtifacts); + + Thread.currentThread().setContextClassLoader(classLoader); + + velocity = new VelocityEngine(); + velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, this); + velocity.setProperty("resource.loader", "classpath"); + velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); + velocity.init(); + + VelocityContext context = buildVelocityContext(properties); + + processResourceBundles(classLoader, context); + + if (outputDirectory.exists()) + { + // ---------------------------------------------------------------------------- + // Push our newly generated resources directory into the MavenProject so that + // these resources can be picked up by the process-resources phase. + // ---------------------------------------------------------------------------- + Resource resource = new Resource(); + resource.setDirectory(outputDirectory.getAbsolutePath()); + // MRRESOURCES-61 handle main and test resources separately + if (attachToMain) + { + project.getResources().add(resource); + } + if (attachToTest) + { + project.getTestResources().add(resource); + } + + // ---------------------------------------------------------------------------- + // Write out archiver dot file + // ---------------------------------------------------------------------------- + try + { + File dotFile = new File(project.getBuild().getDirectory(), ".plxarc"); + FileUtils.mkdir(dotFile.getParentFile().getAbsolutePath()); + FileUtils.fileWrite(dotFile.getAbsolutePath(), outputDirectory.getName()); + } + catch (IOException e) + { + throw new MojoExecutionException("Error creating dot file for archiving instructions.", e); + } + } + } + finally + { + Thread.currentThread().setContextClassLoader(origLoader); + } + } + + private void configureLocator() + throws MojoExecutionException + { + if (supplementalModelArtifacts != null && !supplementalModelArtifacts.isEmpty()) + { + List<File> artifacts = downloadBundles(supplementalModelArtifacts); + + for (File artifact : artifacts) + { + if (artifact.isDirectory()) + { + locator.addSearchPath(FileResourceLoader.ID, artifact.getAbsolutePath()); + } + else + { + try + { + locator.addSearchPath("jar", "jar:" + artifact.toURI().toURL().toExternalForm()); + } + catch (MalformedURLException e) + { + throw new MojoExecutionException("Could not use jar " + artifact.getAbsolutePath(), e); + } + } + } + + } + + locator.addSearchPath(FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath()); + if (appendedResourcesDirectory != null) + { + locator.addSearchPath(FileResourceLoader.ID, appendedResourcesDirectory.getAbsolutePath()); + } + locator.addSearchPath("url", ""); + locator.setOutputDirectory(new File(project.getBuild().getDirectory())); + } + + @SuppressWarnings("unchecked") + protected List<MavenProject> getProjects() + { + List<MavenProject> projects = new ArrayList<MavenProject>(); + + // add filters in well known order, least specific to most specific + FilterArtifacts filter = new FilterArtifacts(); + + Set<Artifact> artifacts = resolveProjectArtifacts(); + if (excludeTransitive) + { + Set<Artifact> depArtifacts; + if (runOnlyAtExecutionRoot) + { + depArtifacts = aggregateProjectDependencyArtifacts(); + } + else + { + depArtifacts = project.getDependencyArtifacts(); + } + filter.addFilter(new ProjectTransitivityFilter(depArtifacts, true)); + } + + filter.addFilter(new ScopeFilter(includeScope, excludeScope)); + filter.addFilter(new GroupIdFilter(includeGroupIds, excludeGroupIds)); + filter.addFilter(new ArtifactIdFilter(includeArtifactIds, excludeArtifactIds)); + + // perform filtering + try + { + artifacts = filter.filter(artifacts); + } + catch (ArtifactFilterException e) + { + throw new IllegalStateException(e.getMessage(), e); + } + + for (Artifact artifact : artifacts) + { + try + { + List<ArtifactRepository> remoteRepo = remoteArtifactRepositories; + if (artifact.isSnapshot()) + { + VersionRange rng = VersionRange.createFromVersion(artifact.getBaseVersion()); + artifact = artifactFactory.createDependencyArtifact(artifact.getGroupId(), artifact.getArtifactId(), rng, + artifact.getType(), artifact.getClassifier(), + artifact.getScope(), null, artifact.isOptional()); + } + + getLog().debug("Building project for " + artifact); + MavenProject p; + try + { + p = mavenProjectBuilder.buildFromRepository(artifact, remoteRepo, localRepository); + } + catch (InvalidProjectModelException e) + { + getLog().warn("Invalid project model for artifact [" + artifact.getArtifactId() + ":" + + artifact.getGroupId() + ":" + artifact.getVersion() + "]. " + + "It will be ignored by the remote resources Mojo."); + continue; + } + + String supplementKey = generateSupplementMapKey(p.getModel().getGroupId(), p.getModel().getArtifactId()); + + if (supplementModels.containsKey(supplementKey)) + { + Model mergedModel = mergeModels(p.getModel(), supplementModels.get(supplementKey)); + MavenProject mergedProject = new MavenProject(mergedModel); + projects.add(mergedProject); + mergedProject.setArtifact(artifact); + mergedProject.setVersion(artifact.getVersion()); + getLog().debug("Adding project with groupId [" + mergedProject.getGroupId() + "] (supplemented)"); + } + else + { + projects.add(p); + getLog().debug("Adding project with groupId [" + p.getGroupId() + "]"); + } + } + catch (ProjectBuildingException e) + { + throw new IllegalStateException(e.getMessage(), e); + } + } + Collections.sort(projects, new ProjectComparator()); + return projects; + } + + private Set<Artifact> resolveProjectArtifacts() + { + try + { + if (runOnlyAtExecutionRoot) + { + List<MavenProject> projects = mavenSession.getSortedProjects(); + return dependencyResolver.resolve(projects, Arrays.asList(resolveScopes), mavenSession); + } + else + { + return dependencyResolver.resolve(project, Arrays.asList(resolveScopes), mavenSession); + } + } + catch (ArtifactResolutionException e) + { + throw new IllegalStateException("Failed to resolve dependencies for one or more projects in the reactor. " + + "Reason: " + e.getMessage(), e); + } + catch (ArtifactNotFoundException e) + { + throw new IllegalStateException("Failed to resolve dependencies for one or more projects in the reactor. " + + "Reason: " + e.getMessage(), e); + } + } + + @SuppressWarnings("unchecked") + private Set<Artifact> aggregateProjectDependencyArtifacts() + { + Set<Artifact> artifacts = new LinkedHashSet<Artifact>(); + + List<MavenProject> projects = mavenSession.getSortedProjects(); + for (MavenProject p : projects) + { + if (p.getDependencyArtifacts() == null) + { + try + { + Set<Artifact> depArtifacts = p.createArtifacts(artifactFactory, null, null); + + if (depArtifacts != null && !depArtifacts.isEmpty()) + { + for (Artifact artifact : depArtifacts) + { + if (artifact.getVersion() == null && artifact.getVersionRange() != null) + { + // Version is required for equality comparison between artifacts, + // but it is not needed for our purposes of filtering out + // transitive dependencies (which requires only groupId/artifactId). + // Therefore set an arbitrary version if missing. + artifact.setResolvedVersion(Artifact.LATEST_VERSION); + } + artifacts.add(artifact); + } + } + } + catch (InvalidDependencyVersionException e) + { + throw new IllegalStateException("Failed to create dependency artifacts for: " + p.getId() + + ". Reason: " + e.getMessage(), e); + } + } + } + + return artifacts; + } + + protected Map<Organization, List<MavenProject>> getProjectsSortedByOrganization(List<MavenProject> projects) + { + Map<Organization, List<MavenProject>> organizations = new TreeMap<Organization, List<MavenProject>>(new OrganizationComparator()); + List<MavenProject> unknownOrganization = new ArrayList<MavenProject>(); + + for (MavenProject p : projects) + { + if (p.getOrganization() != null && StringUtils.isNotEmpty(p.getOrganization().getName())) + { + List<MavenProject> sortedProjects = organizations.get(p.getOrganization()); + if (sortedProjects == null) + { + sortedProjects = new ArrayList<MavenProject>(); + } + sortedProjects.add(p); + + organizations.put(p.getOrganization(), sortedProjects); + } + else + { + unknownOrganization.add(p); + } + } + if (!unknownOrganization.isEmpty()) + { + Organization unknownOrg = new Organization(); + unknownOrg.setName("an unknown organization"); + organizations.put(unknownOrg, unknownOrganization); + } + + return organizations; + } + + protected boolean copyResourceIfExists(File file, String relFileName, VelocityContext context) + throws IOException, MojoExecutionException + { + for (Resource resource : resources) + { + File resourceDirectory = new File(resource.getDirectory()); + + if (!resourceDirectory.exists()) + { + continue; + } + + // TODO - really should use the resource includes/excludes and name mapping + File source = new File(resourceDirectory, relFileName); + File templateSource = new File(resourceDirectory, relFileName + TEMPLATE_SUFFIX); + + if (!source.exists() && templateSource.exists()) + { + source = templateSource; + } + + if (source.exists() && !source.equals(file)) + { + if (source == templateSource) + { + Reader reader = null; + Writer writer = null; + DeferredFileOutputStream os = new DeferredFileOutputStream(velocityFilterInMemoryThreshold, file); + try + { + + if (encoding != null) + { + reader = new InputStreamReader(new FileInputStream(source), encoding); + writer = new OutputStreamWriter(os, encoding); + } + else + { + reader = ReaderFactory.newPlatformReader(source); + writer = WriterFactory.newPlatformWriter(os); + } + + velocity.evaluate(context, writer, "", reader); + writer.close(); + writer = null; + reader.close(); + reader = null; + } + catch (ParseErrorException e) + { + throw new MojoExecutionException("Error rendering velocity resource: " + source, e); + } + catch (MethodInvocationException e) + { + throw new MojoExecutionException("Error rendering velocity resource: " + source, e); + } + catch (ResourceNotFoundException e) + { + throw new MojoExecutionException("Error rendering velocity resource: " + source, e); + } + finally + { + IOUtil.close(writer); + IOUtil.close(reader); + } + fileWriteIfDiffers(os); + } + else if (resource.isFiltering()) + { + MavenFileFilterRequest req = setupRequest(source, file, resource.isFiltering()); + + try + { + fileFilter.copyFile(req); + } + catch (MavenFilteringException e) + { + throw new MojoExecutionException("Error filtering resource: " + source, e); + } + } + else + { + FileUtils.copyFile(source, file); + } + + // exclude the original (so eclipse doesn't complain about duplicate resources) + resource.addExclude(relFileName); + + return true; + } + + } + return false; + } + + /** + * If the transformation result fits in memory and the destination file already exists + * then both are compared. + * <p> + * If destination file is byte-by-byte equal, then it is not overwritten. + * This improves subsequent compilation times since upstream plugins property see that + * the resource was not modified. + * <p> + * Note: the method should be called after {@link org.apache.commons.io.output.DeferredFileOutputStream#close} + * + * @param outStream Deferred stream + * @throws IOException + */ + private void fileWriteIfDiffers(DeferredFileOutputStream outStream) + throws IOException + { + File file = outStream.getFile(); + if (outStream.isThresholdExceeded()) + { + getLog().info("File " + file + " was overwritten due to content limit threshold " + + outStream.getThreshold() + " reached"); + return; + } + boolean needOverwrite = true; + + if (file.exists()) + { + InputStream is = null; + try + { + is = new FileInputStream(file); + final InputStream newContents = new ByteArrayInputStream(outStream.getData()); + needOverwrite = !IOUtil.contentEquals(is, newContents); + if (getLog().isDebugEnabled()) + { + getLog().debug("File " + file + " contents " + + (needOverwrite ? "differs" : "does not differ")); + } + + is.close(); + is = null; + } + finally + { + IOUtil.close(is); + } + } + + if (!needOverwrite) + { + getLog().debug("File " + file + " is up to date"); + return; + } + getLog().debug("Writing " + file); + OutputStream os = new FileOutputStream(file); + try + { + outStream.writeTo(os); + os.close(); + os = null; + } + finally + { + IOUtil.close(os); + } + } + + private MavenFileFilterRequest setupRequest(File source, File file, boolean isFiltering) + { + MavenFileFilterRequest req = new MavenFileFilterRequest(); + req.setFrom(source); + req.setTo(file); + req.setFiltering(isFiltering); + + req.setMavenProject(project); + req.setMavenSession(mavenSession); + req.setInjectProjectBuildFilters(true); + + if (encoding != null) + { + req.setEncoding(encoding); + } + + if (filterDelimiters != null && !filterDelimiters.isEmpty()) + { + LinkedHashSet<String> delims = new LinkedHashSet<String>(); + if (useDefaultFilterDelimiters) + { + delims.addAll(req.getDelimiters()); + } + + for (String delim : filterDelimiters) + { + if (delim == null) + { + delims.add("${*}"); + } + else + { + delims.add(delim); + } + } + + req.setDelimiters(delims); + } + + return req; + } + + protected void validate() + throws MojoExecutionException + { + int bundleCount = 1; + + for (String artifactDescriptor : resourceBundles) + { + // groupId:artifactId:version, groupId:artifactId:version:type + // or groupId:artifactId:version:type:classifier + String[] s = StringUtils.split(artifactDescriptor, ":"); + + if (s.length < 3 || s.length > 5) + { + String position; + + if (bundleCount == 1) + { + position = "1st"; + } + else if (bundleCount == 2) + { + position = "2nd"; + } + else if (bundleCount == 3) + { + position = "3rd"; + } + else + { + position = bundleCount + "th"; + } + + throw new MojoExecutionException("The " + position + + " resource bundle configured must specify a groupId, artifactId, " + + " version and, optionally, type and classifier for a remote resource bundle. " + + "Must be of the form <resourceBundle>groupId:artifactId:version</resourceBundle>, " + + "<resourceBundle>groupId:artifactId:version:type</resourceBundle> or " + + "<resourceBundle>groupId:artifactId:version:type:classifier</resourceBundle>"); + } + + bundleCount++; + } + + } + + private static final String KEY_PROJECTS = "projects"; + private static final String KEY_PROJECTS_ORGS = "projectsSortedByOrganization"; + + protected VelocityContext buildVelocityContext(Map<String, Object> properties) + throws MojoExecutionException + { + // the following properties are expensive to calculate, so we provide them lazily + VelocityContext context = new VelocityContext(properties) + { + @Override + public Object internalGet(String key) + { + Object result = super.internalGet(key); + if (result == null && key != null && key.startsWith(KEY_PROJECTS) && containsKey(key)) + { + // calculate and put projects* properties + List<MavenProject> projects = getProjects(); + put(KEY_PROJECTS, projects); + put(KEY_PROJECTS_ORGS, getProjectsSortedByOrganization(projects)); + return super.internalGet(key); + } + return result; + } + }; + // to have a consistent getKeys()/containsKey() behaviour, keys must be present from the start + context.put(KEY_PROJECTS, null); + context.put(KEY_PROJECTS_ORGS, null); + // the following properties are cheap to calculate, so we provide them eagerly + + String inceptionYear = project.getInceptionYear(); + String year = new SimpleDateFormat("yyyy").format(new Date()); + + if (StringUtils.isEmpty(inceptionYear)) + { + if (getLog().isDebugEnabled()) + { + getLog().debug("inceptionYear not specified, defaulting to " + year); + } + + inceptionYear = year; + } + context.put("project", project); + context.put("presentYear", year); + context.put("locator", locator); + + if (inceptionYear.equals(year)) + { + context.put("projectTimespan", year); + } + else + { + context.put("projectTimespan", inceptionYear + "-" + year); + } + return context; + } + + private List<File> downloadBundles(List<String> bundles) + throws MojoExecutionException + { + List<File> bundleArtifacts = new ArrayList<File>(); + + try + { + for (String artifactDescriptor : bundles) + { + getLog().info("Preparing remote bundle " + artifactDescriptor); + // groupId:artifactId:version[:type[:classifier]] + String[] s = artifactDescriptor.split(":"); + + File artifactFile = null; + // check if the artifact is part of the reactor + if (mavenSession != null) + { + List<MavenProject> list = mavenSession.getSortedProjects(); + for (MavenProject p : list) + { + if (s[0].equals(p.getGroupId()) && s[1].equals(p.getArtifactId()) + && s[2].equals(p.getVersion())) + { + if (s.length >= 4 && "test-jar".equals(s[3])) + { + artifactFile = new File(p.getBuild().getTestOutputDirectory()); + } + else + { + artifactFile = new File(p.getBuild().getOutputDirectory()); + } + } + } + } + if (artifactFile == null || !artifactFile.exists()) + { + String type = s.length >= 4 ? s[3] : "jar"; + String classifier = s.length == 5 ? s[4] : null; + Artifact artifact = artifactFactory.createDependencyArtifact(s[0], s[1], VersionRange.createFromVersion(s[2]), + type, classifier, Artifact.SCOPE_RUNTIME); + + artifactResolver.resolve(artifact, remoteArtifactRepositories, localRepository); + + artifactFile = artifact.getFile(); + } + bundleArtifacts.add(artifactFile); + } + } + catch (ArtifactResolutionException e) + { + throw new MojoExecutionException("Error downloading resources archive.", e); + } + catch (ArtifactNotFoundException e) + { + throw new MojoExecutionException("Resources archive cannot be found.", e); + } + + return bundleArtifacts; + } + + private ClassLoader initalizeClassloader(List<File> artifacts) + throws MojoExecutionException + { + RemoteResourcesClassLoader cl = new RemoteResourcesClassLoader(null); + try + { + for (File artifact : artifacts) + { + cl.addURL(artifact.toURI().toURL()); + } + return cl; + } + catch (MalformedURLException e) + { + throw new MojoExecutionException("Unable to configure resources classloader: " + e.getMessage(), e); + } + } + + protected void processResourceBundles(ClassLoader classLoader, VelocityContext context) + throws MojoExecutionException + { + List<Map.Entry<String, RemoteResourcesBundle>> remoteResources = new ArrayList<Map.Entry<String, RemoteResourcesBundle>>(); + int bundleCount = 0; + int resourceCount = 0; + + // list remote resources form bundles + InputStream in = null; + try + { + RemoteResourcesBundleXpp3Reader bundleReader = new RemoteResourcesBundleXpp3Reader(); + + for (Enumeration<URL> e = classLoader.getResources(BundleRemoteResourcesMojo.RESOURCES_MANIFEST); e.hasMoreElements();) + { + URL url = e.nextElement(); + bundleCount++; + getLog().debug("processResourceBundle on bundle#" + bundleCount + " " + url); + + in = url.openStream(); + + RemoteResourcesBundle bundle = bundleReader.read(in); + + in.close(); + in = null; + + int n = 0; + for (String bundleResource : bundle.getRemoteResources()) + { + n++; + resourceCount++; + getLog().debug("bundle#" + bundleCount + " resource#" + n + " " + bundleResource); + remoteResources.add(new AbstractMap.SimpleEntry<String, RemoteResourcesBundle>(bundleResource, + bundle)); + } + } + } + catch (IOException ioe) + { + throw new MojoExecutionException("Error finding remote resources manifests", ioe); + } + catch (XmlPullParserException xppe) + { + throw new MojoExecutionException("Error parsing remote resource bundle descriptor.", xppe); + } + finally + { + IOUtil.close(in); + } + + getLog().info("Copying " + resourceCount + " resource" + (resourceCount > 1 ? "s" : "") + " from " + + bundleCount + " bundle" + (bundleCount > 1 ? "s" : "") + "."); + + String velocityResource = null; + OutputStream out = null; + Writer writer = null; + Reader reader = null; + + try + { + + for (Map.Entry<String, RemoteResourcesBundle> entry : remoteResources) + { + String bundleResource = entry.getKey(); + RemoteResourcesBundle bundle = entry.getValue(); + + String projectResource = bundleResource; + + boolean doVelocity = false; + if (projectResource.endsWith(TEMPLATE_SUFFIX)) + { + projectResource = projectResource.substring(0, projectResource.length() - 3); + velocityResource = bundleResource; + doVelocity = true; + } + + // Don't overwrite resource that are already being provided. + + File f = new File(outputDirectory, projectResource); + + FileUtils.mkdir(f.getParentFile().getAbsolutePath()); + + if (!copyResourceIfExists(f, projectResource, context)) + { + if (doVelocity) + { + DeferredFileOutputStream os = new DeferredFileOutputStream(velocityFilterInMemoryThreshold, f); + + writer = bundle.getSourceEncoding() == null ? new OutputStreamWriter(os) + : new OutputStreamWriter(os, bundle.getSourceEncoding()); + + if (bundle.getSourceEncoding() == null) + { + // TODO: Is this correct? Shouldn't we behave like the rest of maven and fail + // down to JVM default instead ISO-8859-1 ? + velocity.mergeTemplate(bundleResource, "ISO-8859-1", context, writer); + } + else + { + velocity.mergeTemplate(bundleResource, bundle.getSourceEncoding(), context, writer); + + } + + writer.close(); + writer = null; + fileWriteIfDiffers(os); + } + else if (useResourceFiltering) + { + filterBundleResource(classLoader, bundleResource, f); + } + else + { + copyBundleResource(classLoader, bundleResource, f); + } + + File appendedResourceFile = new File(appendedResourcesDirectory, projectResource); + File appendedVmResourceFile = new File(appendedResourcesDirectory, projectResource + ".vm"); + + if (appendedResourceFile.exists()) + { + getLog().info("Copying appended resource: " + projectResource); + in = new FileInputStream(appendedResourceFile); + out = new FileOutputStream(f, true); + IOUtil.copy(in, out); + out.close(); + out = null; + in.close(); + in = null; + } + else if (appendedVmResourceFile.exists()) + { + getLog().info("Filtering appended resource: " + projectResource + ".vm"); + reader = new FileReader(appendedVmResourceFile); + + if (bundle.getSourceEncoding() == null) + { + writer = new PrintWriter(new FileWriter(f, true)); + } + else + { + writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f, true), + bundle.getSourceEncoding())); + } + + Velocity.init(); + Velocity.evaluate(context, writer, "remote-resources", reader); + writer.close(); + writer = null; + reader.close(); + reader = null; + } + } + } + } + catch (IOException ioe) + { + throw new MojoExecutionException("Error reading remote resource", ioe); + } + catch (VelocityException e) + { + throw new MojoExecutionException("Error rendering Velocity resource '" + velocityResource + "'", e); + } + finally + { + IOUtil.close(out); + IOUtil.close(in); + IOUtil.close(writer); + IOUtil.close(reader); + } + } + + private void copyBundleResource(ClassLoader classLoader, String bundleResource, File to) + throws IOException + { + URL resUrl = classLoader.getResource(bundleResource); + if (resUrl != null) + { + FileUtils.copyURLToFile(resUrl, to); + } + } + + private void filterBundleResource(ClassLoader classLoader, String bundleResource, File to) + throws IOException, MojoExecutionException + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("maven-remote-resources-plugin", null); + tmpFile.deleteOnExit(); + copyBundleResource(classLoader, bundleResource, tmpFile); + MavenFileFilterRequest req = setupRequest(tmpFile, to, true); + fileFilter.copyFile(req); + } + catch (MavenFilteringException e) + { + throw new MojoExecutionException("Error filtering remote resource: " + bundleResource, e); + } + finally + { + if (tmpFile != null && !tmpFile.delete()) + { + getLog().warn("Unable to delete temporary file: " + tmpFile); + } + } + } + + protected Model getSupplement(Xpp3Dom supplementModelXml) + throws MojoExecutionException + { + MavenXpp3Reader modelReader = new MavenXpp3Reader(); + Model model = null; + + try + { + model = modelReader.read(new StringReader(supplementModelXml.toString())); + String groupId = model.getGroupId(); + String artifactId = model.getArtifactId(); + + if (groupId == null || groupId.trim().equals("")) + { + throw new MojoExecutionException("Supplemental project XML " + + "requires that a <groupId> element be present."); + } + + if (artifactId == null || artifactId.trim().equals("")) + { + throw new MojoExecutionException("Supplemental project XML " + + "requires that a <artifactId> element be present."); + } + } + catch (IOException e) + { + getLog().warn("Unable to read supplemental XML: " + e.getMessage(), e); + } + catch (XmlPullParserException e) + { + getLog().warn("Unable to parse supplemental XML: " + e.getMessage(), e); + } + + return model; + } + + protected Model mergeModels(Model parent, Model child) + { + inheritanceAssembler.assembleModelInheritance(child, parent); + return child; + } + + private static String generateSupplementMapKey(String groupId, String artifactId) + { + return groupId.trim() + ":" + artifactId.trim(); + } + + private Map<String, Model> loadSupplements(String models[]) + throws MojoExecutionException + { + if (models == null) + { + getLog().debug("Supplemental data models won't be loaded. " + "No models specified."); + return Collections.emptyMap(); + } + + List<Supplement> supplements = new ArrayList<Supplement>(); + for (String set : models) + { + getLog().debug("Preparing ruleset: " + set); + try + { + File f = locator.getResourceAsFile(set, getLocationTemp(set)); + + if (null == f || !f.exists()) + { + throw new MojoExecutionException("Cold not resolve " + set); + } + if (!f.canRead()) + { + throw new MojoExecutionException("Supplemental data models won't be loaded. " + "File " + + f.getAbsolutePath() + " cannot be read, check permissions on the file."); + } + + getLog().debug("Loading supplemental models from " + f.getAbsolutePath()); + + SupplementalDataModelXpp3Reader reader = new SupplementalDataModelXpp3Reader(); + SupplementalDataModel supplementalModel = reader.read(new FileReader(f)); + supplements.addAll(supplementalModel.getSupplement()); + } + catch (Exception e) + { + String msg = "Error loading supplemental data models: " + e.getMessage(); + getLog().error(msg, e); + throw new MojoExecutionException(msg, e); + } + } + + getLog().debug("Loading supplements complete."); + + Map<String, Model> supplementMap = new HashMap<String, Model>(); + for (Supplement sd : supplements) + { + Xpp3Dom dom = (Xpp3Dom) sd.getProject(); + + Model m = getSupplement(dom); + supplementMap.put(generateSupplementMapKey(m.getGroupId(), m.getArtifactId()), m); + } + + return supplementMap; + } + + /** + * Convenience method to get the location of the specified file name. + * + * @param name the name of the file whose location is to be resolved + * @return a String that contains the absolute file name of the file + */ + private String getLocationTemp(String name) + { + String loc = name; + if (loc.indexOf('/') != -1) + { + loc = loc.substring(loc.lastIndexOf('/') + 1); + } + if (loc.indexOf('\\') != -1) + { + loc = loc.substring(loc.lastIndexOf('\\') + 1); + } + getLog().debug("Before: " + name + " After: " + loc); + return loc; + } + + class OrganizationComparator + implements Comparator<Organization> + { + @Override + public int compare(Organization org1, Organization org2) + { + int i = compareStrings(org1.getName(), org2.getName()); + if (i == 0) + { + i = compareStrings(org1.getUrl(), org2.getUrl()); + } + return i; + } + + public boolean equals(Organization o1, Organization o2) + { + return compare(o1, o2) == 0; + } + + private int compareStrings(String s1, String s2) + { + if (s1 == null && s2 == null) + { + return 0; + } + else if (s1 == null && s2 != null) + { + return 1; + } + else if (s1 != null && s2 == null) + { + return -1; + } + + return s1.compareToIgnoreCase(s2); + } + } + + class ProjectComparator + implements Comparator<MavenProject> + { + @Override + public int compare(MavenProject p1, MavenProject p2) + { + return p1.getArtifact().compareTo(p2.getArtifact()); + } + + public boolean equals(MavenProject p1, MavenProject p2) + { + return p1.getArtifact().equals(p2.getArtifact()); + } + } + + /* LogChute methods */ + @Override + public void init(RuntimeServices rs) + throws Exception + { + } + + @Override + public void log(int level, String message) + { + switch (level) + { + case LogChute.WARN_ID: + getLog().warn(message); + break; + case LogChute.INFO_ID: + // velocity info messages are too verbose, just consider them as debug messages... + getLog().debug(message); + break; + case LogChute.DEBUG_ID: + getLog().debug(message); + break; + case LogChute.ERROR_ID: + getLog().error(message); + break; + default: + getLog().debug(message); + break; + } + } + + @Override + public void log(int level, String message, Throwable t) + { + switch (level) + { + case LogChute.WARN_ID: + getLog().warn(message, t); + break; + case LogChute.INFO_ID: + // velocity info messages are too verbose, just consider them as debug messages... + getLog().debug(message, t); + break; + case LogChute.DEBUG_ID: + getLog().debug(message, t); + break; + case LogChute.ERROR_ID: + getLog().error(message, t); + break; + default: + getLog().debug(message, t); + break; + } + } + + @Override + public boolean isLevelEnabled(int level) + { + return false; + } } ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services