/* ==========================================================================
* Copyright 2003-2004 Mevenide Team
*
* Licensed 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.codehaus.mojo.nbm;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.artifact.Artifact;
//import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.codehaus.mojo.nbm.model.NbmResource;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.filtering.MavenResourcesExecution;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Jar;
import org.apache.tools.ant.taskdefs.LoadProperties;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.util.FileUtils;
import org.netbeans.nbbuild.CreateModuleXML;
import org.netbeans.nbbuild.MakeListOfNBM;
import org.codehaus.mojo.nbm.model.NetbeansModule;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.netbeans.nbbuild.JHIndexer;
/**
* Create the Netbeans module directory structure, a prerequisite for nbm creation and cluster creation.
* <p/>
*
* @author <a href="mailto:mkleint@codehaus.org">Milos Kleint</a>
*
*/
public abstract class CreateNetbeansFileStructure
extends AbstractNbmMojo
{
/**
* Netbeans module assembly build directory.
* directory where the the netbeans jar and nbm file get constructed.
* @parameter default-value="${project.build.directory}/nbm" expression="${maven.nbm.buildDir}"
*/
protected File nbmBuildDir;
/**
* Build directory
* @parameter expression="${project.build.directory}"
* @required
* @readonly
*/
protected File buildDir;
/**
* Name of the jar packaged by the jar:jar plugin
* @parameter alias="jarName" expression="${project.build.finalName}"
*/
protected String finalName;
/**
* a netbeans module descriptor containing dependency information and more..
*
* @parameter default-value="${basedir}/src/main/nbm/module.xml"
*/
protected File descriptor;
/**
* Netbeans module's cluster. Replaces the cluster element in module descriptor.
*
* @parameter default-value="extra"
* @required
*/
protected String cluster;
/**
* The location of JavaHelp sources for the project. The documentation
* itself is expected to be in the directory structure based on codenamebase of the module.
* eg. if your codenamebase is "org.netbeans.modules.apisupport", then the actual docs
* files shall go to ${basedir}/src/main/javahelp/org/netbeans/modules/apisupport/docs.
* Obsolete as of NetBeans 7.0 with @HelpSetRegistration.
* @parameter default-value="${basedir}/src/main/javahelp"
* @since 2.7
*/
protected File nbmJavahelpSource;
/**
* @parameter expression="${project}"
* @required
* @readonly
*/
protected MavenProject project;
/**
* Distribution base URL for the NBM at runtime deployment time.
* Note: Uselfulness of the parameter is questionable, it doesn't allow for mirrors and
* usually when downloading the nbm, one alreayd knows the location anyway.
* Please note that the netbeans.org Ant scripts put a dummy url here.
* The actual correct value used when constructing update site is
* explicitly set there. The general assuption there is that all modules from one update
* center come from one base URL.
* <p/>
* The value is either a direct http protocol based URL that points to
* the location under which nbm file will be located, or
* <p/>
* it allows to create an update site based on maven repository content.
* The later created autoupdate site document can use this information and
* compose the application from one or multiple maven repositories.
* <br/>
* Format: id::layout::url same as in maven-deploy-plugin
* <br/>
* with the 'default' and 'legacy' layouts. (maven2 vs maven1 layout)
* <br/>
* If the value doesn't contain :: characters,
* it's assumed to be the flat structure and the value is just the URL.
*
* @parameter expression="${maven.nbm.distributionURL}"
*/
private String distributionUrl;
/**
* The list of nbmResources we want to include in the nbm file (not in module jar,
* but as external content within the nbm. Replaces the same configuration in the module
* descriptor file. For example to include external dll files in the nbm:
*
<code>
<nbmResource><br/>
<directory>src/main/libs</directory><br/>
<targetPath>lib</targetPath><br/>
<includes><br/>
<include>*.dll</include><br/>
<include>*.so</include><br/>
</includes><br/>
</nbmResource><br/>
</code>
*
* @parameter
* @since 3.2
*/
protected Resource[] nbmResources;
/**
* The character encoding scheme to be applied when filtering nbm resources.
*
* @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
* @since 3.2
*/
protected String encoding;
/**
*
* @component role="org.apache.maven.shared.filtering.MavenResourcesFiltering" role-hint="default"
* @required
*/
protected MavenResourcesFiltering mavenResourcesFiltering;
/**
* @parameter default-value="${session}"
* @readonly
* @required
*/
protected MavenSession session;
//items used by the CreateNBMMojo.
protected Project antProject;
protected NetbeansModule module;
protected File clusterDir;
protected String moduleJarName;
public void execute()
throws MojoExecutionException, MojoFailureException
{
antProject = registerNbmAntTasks();
if ( descriptor != null && descriptor.exists() )
{
module = readModuleDescriptor( descriptor );
} else
{
module = createDefaultDescriptor( project, false );
}
if ( distributionUrl != null )
{
module.setDistributionUrl( distributionUrl );
}
String type = module.getModuleType();
boolean autoload = "autoload".equals( type );
boolean eager = "eager".equals( type );
// 1. initialization
if ( autoload && eager )
{
getLog().error( "Module cannot be both eager and autoload" );
throw new MojoExecutionException(
"Module cannot be both eager and autoload" );
}
String moduleName = module.getCodeNameBase();
if ( moduleName == null )
{
moduleName = project.getGroupId() + "." + project.getArtifactId();
moduleName = moduleName.replaceAll( "-", "." );
}
moduleJarName = moduleName.replace( '.', '-' );
if ( "extra".equals( cluster ) && module.getCluster() != null )
{
getLog().warn(
"Parameter cluster in module descriptor is deprecated, use the plugin configuration element." );
cluster = module.getCluster();
}
// it can happen the moduleName is in format org.milos/1
int index = moduleJarName.indexOf( '/' );
if ( index > -1 )
{
moduleJarName = moduleJarName.substring( 0, index ).trim();
}
File jarFile = new File( buildDir, finalName + ".jar" );
clusterDir = new File( nbmBuildDir,
"netbeans" + File.separator + cluster );
File moduleJarLocation = new File( clusterDir, "modules" );
moduleJarLocation.mkdirs();
//2. create nbm resources
File moduleFile = new File( moduleJarLocation, moduleJarName + ".jar" );
getLog().info( "Copying module jar to " + moduleJarLocation );
try
{
FileUtils.getFileUtils().copyFile( jarFile, moduleFile, null, true,
false );
} catch ( IOException ex )
{
getLog().error( "Cannot copy module jar" );
throw new MojoExecutionException( "Cannot copy module jar", ex );
}
ExamineManifest modExaminator = new ExamineManifest( getLog() );
modExaminator.setJarFile( jarFile );
modExaminator.checkFile();
String classpathValue = modExaminator.getClasspath();
if ( module != null )
{
// copy libraries to the designated place..
List<String> librList = new ArrayList<String>();
if ( module.getLibraries() != null )
{
librList.addAll( module.getLibraries() );
}
@SuppressWarnings("unchecked")
List<Artifact> artifacts = project.getRuntimeArtifacts();
for ( Artifact artifact : artifacts )
{
File source = artifact.getFile();
if ( classpathValue.contains( "ext/" + artifact.getGroupId() + "/" + source.getName() ) )
{
File targetDir = new File( moduleJarLocation, "ext/" + artifact.getGroupId() );
targetDir.mkdirs();
File target = new File( targetDir, source.getName() );
try
{
FileUtils.getFileUtils().copyFile( source, target, null,
true, false );
} catch ( IOException ex )
{
getLog().error( "Cannot copy library jar" );
throw new MojoExecutionException(
"Cannot copy library jar", ex );
}
}
}
if (nbmResources != null) {
copyNbmResources();
}
copyDeprecatedNbmResources();
}
//javahelp stuff.
if ( nbmJavahelpSource.exists() )
{
File javahelp_target = new File( buildDir, "javahelp" );
String javahelpbase = moduleJarName.replace( '-', File.separatorChar ) + File.separator + "docs";
String javahelpSearch = "JavaHelpSearch";
File b = new File( javahelp_target, javahelpbase );
File p = new File( b, javahelpSearch );
p.mkdirs();
Copy cp = (Copy) antProject.createTask( "copy" );
cp.setTodir( javahelp_target );
FileSet set = new FileSet();
set.setDir( nbmJavahelpSource );
cp.addFileset( set );
cp.execute();
getLog().info( "Generating JavaHelp Index..." );
JHIndexer jhTask = (JHIndexer) antProject.createTask( "jhindexer" );
jhTask.setBasedir( b );
jhTask.setDb( p );
jhTask.setIncludes( "**/*.html" );
jhTask.setExcludes( javahelpSearch );
Path path = new Path( antProject );
jhTask.setClassPath( path );
MNMMODULE51hackClearStaticFieldsInJavaHelpIndexer();
try
{
jhTask.execute();
} catch ( BuildException e )
{
getLog().error( "Cannot generate JavaHelp index." );
throw new MojoExecutionException( e.getMessage(), e );
}
File helpJarLocation = new File( clusterDir, "modules/docs" );
helpJarLocation.mkdirs();
Jar jar = (Jar) antProject.createTask( "jar" );
jar.setDestFile( new File( helpJarLocation, moduleJarName + ".jar" ) );
set = new FileSet();
set.setDir( javahelp_target );
jar.addFileset( set );
jar.execute();
}
File configDir = new File( clusterDir,
"config" + File.separator + "Modules" );
configDir.mkdirs();
CreateModuleXML moduleXmlTask = (CreateModuleXML) antProject.createTask(
"createmodulexml" );
moduleXmlTask.setXmldir( configDir );
FileSet fs = new FileSet();
fs.setDir( clusterDir );
fs.setIncludes( "modules" + File.separator + moduleJarName + ".jar" );
if ( autoload )
{
moduleXmlTask.addAutoload( fs );
} else if ( eager )
{
moduleXmlTask.addEager( fs );
} else
{
moduleXmlTask.addEnabled( fs );
}
try
{
moduleXmlTask.execute();
} catch ( BuildException e )
{
getLog().error( "Cannot generate config file." );
throw new MojoExecutionException( e.getMessage(), e );
}
MakeListOfNBM makeTask = (MakeListOfNBM) antProject.createTask(
"genlist" );
antProject.setNewProperty( "module.name", finalName );
antProject.setProperty( "cluster.dir", cluster );
FileSet set = makeTask.createFileSet();
set.setDir( clusterDir );
PatternSet pattern = set.createPatternSet();
pattern.setIncludes( "**" );
makeTask.setModule(
"modules" + File.separator + moduleJarName + ".jar" );
makeTask.setOutputfiledir( clusterDir );
try
{
makeTask.execute();
} catch ( BuildException e )
{
getLog().error( "Cannot Generate nbm list" );
throw new MojoExecutionException( e.getMessage(), e );
}
}
private void copyDeprecatedNbmResources() throws BuildException, MojoExecutionException {
// copy additional resources..
List<NbmResource> ress = module.getNbmResources();
if (ress.size() > 0) {
getLog().warn("NBM resources defined in module descriptor are deprecated. Please configure NBM resources in plugin configuration.");
Copy cp = (Copy) antProject.createTask( "copy" );
cp.setTodir(clusterDir);
HashMap<File, Collection<FileSet>> customPaths = new HashMap<File, Collection<FileSet>>();
boolean hasStandard = false;
for (NbmResource res : ress) {
if (res.getBaseDirectory() != null) {
File base = new File(project.getBasedir(), res.getBaseDirectory());
FileSet set = new FileSet();
set.setDir(base);
for (String inc : res.getIncludes()) {
set.createInclude().setName(inc);
}
for (String exc : res.getExcludes()) {
set.createExclude().setName(exc);
}
if (res.getRelativeClusterPath() != null) {
File path = new File(clusterDir, res.getRelativeClusterPath());
Collection<FileSet> col = customPaths.get( path );
if (col == null) {
col = new ArrayList<FileSet>();
customPaths.put(path, col);
}
col.add(set);
} else {
cp.addFileset(set);
hasStandard = true;
}
}
}
try {
if (hasStandard) {
cp.execute();
}
if (customPaths.size() > 0) {
for (Map.Entry<File, Collection<FileSet>> ent : customPaths.entrySet()) {
cp = (Copy) antProject.createTask( "copy" );
cp.setTodir(ent.getKey());
for (FileSet set : ent.getValue()) {
cp.addFileset(set);
}
cp.execute();
}
}
} catch (BuildException e) {
getLog().error("Cannot copy additional resources into the nbm file");
throw new MojoExecutionException(e.getMessage(), e);
}
}
}
// repeated invokation of the javahelp indexer (possibly via multiple classloaders)
// is causing trouble, residue from previous invokations seems to cause errors
// this is a nasty workaround for the problem.
// alternatively we could try invoking the indexer from a separate jvm i guess,
// ut that's more work.
private void MNMMODULE51hackClearStaticFieldsInJavaHelpIndexer()
{
try
{
Class clazz = Class.forName( "com.sun.java.help.search.Indexer" );
Field fld = clazz.getDeclaredField( "kitRegistry" );
fld.setAccessible( true );
Hashtable hash = (Hashtable) fld.get( null );
hash.clear();
clazz = Class.forName( "com.sun.java.help.search.HTMLIndexerKit" );
fld = clazz.getDeclaredField( "defaultParser" );
fld.setAccessible( true );
fld.set( null, null);
fld = clazz.getDeclaredField( "defaultCallback" );
fld.setAccessible( true );
fld.set( null, null);
}
catch ( IllegalArgumentException ex )
{
Logger.getLogger( CreateNetbeansFileStructure.class.getName() ).log( Level.SEVERE, null, ex );
}
catch ( IllegalAccessException ex )
{
Logger.getLogger( CreateNetbeansFileStructure.class.getName() ).log( Level.SEVERE, null, ex );
} catch ( NoSuchFieldException ex )
{
Logger.getLogger( CreateNetbeansFileStructure.class.getName() ).log( Level.SEVERE, null, ex );
}
catch ( SecurityException ex )
{
Logger.getLogger( CreateNetbeansFileStructure.class.getName() ).log( Level.SEVERE, null, ex );
} catch ( ClassNotFoundException ex )
{
Logger.getLogger( CreateNetbeansFileStructure.class.getName() ).log( Level.SEVERE, null, ex );
}
}
private void copyNbmResources() throws MojoExecutionException {
try {
if (StringUtils.isEmpty(encoding) && isFilteringEnabled(nbmResources)) {
getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!");
}
MavenResourcesExecution mavenResourcesExecution = new MavenResourcesExecution(Arrays.asList(nbmResources), clusterDir, project, encoding, Collections.EMPTY_LIST, Collections.EMPTY_LIST, session);
mavenResourcesExecution.setEscapeWindowsPaths(true);
mavenResourcesFiltering.filterResources(mavenResourcesExecution);
} catch (MavenFilteringException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
}
/**
* Determines whether filtering has been enabled for any resource.
*
* @param resources The set of resources to check for filtering.
* @return <code>true</code> if at least one resource uses filtering, <code>false</code> otherwise.
*/
private boolean isFilteringEnabled( Resource[] resources )
{
for ( Resource resource : resources )
{
if ( resource.isFiltering() )
{
return true;
}
}
return false;
}
}