blob: e8d10a8e0c3eb403b06a89c96a69d29c651833eb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.tools.maven;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.openjpa.lib.util.Options;
import org.codehaus.plexus.util.FileUtils;
/**
* Base class for OpenJPA maven tasks.
*
* @version $Id$
*/
public abstract class AbstractOpenJpaMojo extends AbstractMojo
{
/**
* The working directory for putting persistence.xml and
* other stuff into if we need to.
*/
@Parameter(property="openjpa.workdir", defaultValue="${project.build.directory}/openjpa-work", required=true)
protected File workDir;
/**
* Location where <code>persistence-enabled</code> classes are located.
*/
@Parameter(property="openjpa.classes", defaultValue="${project.build.outputDirectory}", required=true)
protected File classes;
/**
* Comma separated list of includes to scan searchDir to pass to the jobs.
* This may be used to restrict the OpenJPA tasks to e.g. a single package which
* contains all the entities.
*/
@Parameter(defaultValue="**/*.class")
private String includes;
/**
* Comma separated list of excludes to scan searchDir to pass to the jobs.
* This option may be used to stop OpenJPA tasks from scanning non-JPA classes
* (which usually leads to warnings such as "Type xxx has no metadata")
*/
@Parameter(defaultValue="")
private String excludes;
/**
* Additional properties passed to the OpenJPA tools.
*/
@Parameter
private Properties toolProperties;
/**
* Used if a non-default file location for the persistence.xml should be used
* If not specified, the default one in META-INF/persistence.xml will be used.
* Since openjpa-2.3.0 this can also be a resource location. In prior releases
* it was only possible to specify a file location.
*/
@Parameter
private String persistenceXmlFile;
/**
* An optional PersistenceUnit name.
* If not specified then OpenJPA will run against 'all anchors'.
* Means it will use all persistenceunits of all persistence.xml files it finds.
*/
@Parameter(defaultValue="${openjpa.persistenceUnitName}")
private String persistenceUnitName;
/**
* <p>This setting can be used to override any openjpa.ConnectionDriverName set in the
* persistence.xml. It can also be used if the persistence.xml contains no connection
* information at all.<P>
*
* Sample:
* <pre>
* &lt;connectionDriverName&gt;com.mchange.v2.c3p0.ComboPooledDataSource&lt;/connectionDriverName&gt;
* </pre>
*
* This is most times used in conjunction with {@link #connectionProperties}.
*/
@Parameter
private String connectionDriverName;
/** the string used for passing information about the connectionDriverName */
protected static final String OPTION_CONNECTION_DRIVER_NAME = "ConnectionDriverName";
/**
* <p>Used to define the credentials or any other connection properties.</p>
*
* Sample:
* <pre>
* &lt;connectionProperties&gt;
* driverClass=com.mysql.cj.jdbc.Driver,
* jdbcUrl=jdbc:mysql://localhost/mydatabase,
* user=root,
* password=,
* minPoolSize=5,
* acquireRetryAttempts=3,
* maxPoolSize=20
* &lt;/connectionProperties&gt;
* </pre>
*
* This is most times used in conjunction with {@link #connectionDriverName}.
*/
@Parameter
private String connectionProperties;
/** the string used for passing information about the connectionProperties */
protected static final String OPTION_CONNECTION_PROPERTIES = "ConnectionProperties";
/**
* List of all class path elements that will be searched for the
* <code>persistence-enabled</code> classes and resources expected by
* PCEnhancer.
*/
@Parameter(defaultValue="${project.compileClasspathElements}", required=true, readonly=true)
protected List<String> compileClasspathElements;
/**
* Setting this parameter to <code>true</code> will force
* the execution of this mojo, even if it would get skipped usually.
*/
@Parameter(property="forceOpenJpaExecution", defaultValue="false", required=true)
private boolean forceMojoExecution;
/**
* The Maven Project Object
*/
@Parameter(defaultValue="${project}", required=true, readonly=true)
protected MavenProject project;
/** the properties option is used for passing information about the persistence.xml file location */
protected static final String OPTION_PROPERTIES_FILE = "propertiesFile";
/**
* The properties option is used for passing information about the persistence.xml
* classpath resource and the default unit
*/
protected static final String OPTION_PROPERTIES = "properties";
/**
* When <code>true</code>, skip the execution.
* @since 1.0
*/
@Parameter(defaultValue="false")
private boolean skip;
/**
* default ct
*/
public AbstractOpenJpaMojo()
{
super();
}
/**
* The File where the class files of the entities to enhance reside
* @return normaly the entity classes are located in target/classes
*/
protected File getEntityClasses()
{
return classes;
}
/**
* This function retrieves the injected classpath elements for the current mojo.
* @return List of classpath elements for the compile phase
*/
protected List<String> getClasspathElements()
{
return compileClasspathElements;
}
/**
* Get the options for the various OpenJPA tools.
* @return populated Options
*/
protected abstract Options getOptions() throws MojoExecutionException;
/**
* <p>Determine if the mojo execution should get skipped.</p>
* This is the case if:
* <ul>
* <li>{@link #skip} is <code>true</code></li>
* <li>if the mojo gets executed on a project with packaging type 'pom' and
* {@link #forceMojoExecution} is <code>false</code></li>
* </ul>
*
* @return <code>true</code> if the mojo execution should be skipped.
*/
protected boolean skipMojo()
{
if ( skip )
{
getLog().info( "Skip sql execution" );
return true;
}
if ( !forceMojoExecution && project != null && "pom".equals( project.getPackaging() ) )
{
getLog().info( "Skipping sql execution for project with packaging type 'pom'" );
return true;
}
return false;
}
/**
* This function will usually get called by {@link #getOptions()}
* @return the Options filled with the initial values
*/
protected Options createOptions() throws MojoExecutionException
{
Options opts = new Options();
if ( toolProperties != null )
{
opts.putAll( toolProperties );
}
String persistenceXmlResource = "META-INF/persistence.xml";
if ( persistenceXmlFile != null && persistenceXmlFile.length() > 0)
{
fixPersistenceXmlIfNeeded(Thread.currentThread().getContextClassLoader());
opts.put( OPTION_PROPERTIES_FILE, persistenceXmlFile );
getLog().debug("using special persistence XML file: " + persistenceXmlFile);
persistenceXmlResource = persistenceXmlFile;
}
if (persistenceUnitName != null && persistenceUnitName.length() > 0) {
opts.put(OPTION_PROPERTIES, persistenceXmlResource + "#" + persistenceUnitName);
}
else if (!new File(classes, "META-INF/persistence.xml").exists())
{ // use default but try from classpath
persistenceXmlFile = "META-INF/persistence.xml";
if (!fixPersistenceXmlIfNeeded(Thread.currentThread().getContextClassLoader())) {
persistenceXmlFile = null;
} else {
opts.put( OPTION_PROPERTIES_FILE, persistenceXmlFile );
}
}
if ( connectionDriverName != null )
{
opts.put( OPTION_CONNECTION_DRIVER_NAME, connectionDriverName );
}
if ( connectionProperties != null )
{
opts.put( OPTION_CONNECTION_PROPERTIES, connectionProperties );
}
return opts;
}
/**
*
*
* @param loader
* @throws MojoExecutionException
*/
private boolean fixPersistenceXmlIfNeeded(final ClassLoader loader) throws MojoExecutionException
{
return !new File(persistenceXmlFile).exists() &&
(findPersistenceXmlFromLoader(loader)
|| findPersistenceXmlInArtifacts(project.getCompileArtifacts())
|| findPersistenceXmlInArtifacts(project.getRuntimeArtifacts()));
}
private boolean findPersistenceXmlFromLoader(final ClassLoader loader) throws MojoExecutionException {
final URL url = loader.getResource(persistenceXmlFile);
if (url != null) // copy file to be sure to set persistenceXmlFile to a file
{
final File tmpPersistenceXml = new File(workDir,
"persistence" + System.currentTimeMillis() + ".xml");
if (!tmpPersistenceXml.getParentFile().exists() && !tmpPersistenceXml.getParentFile().mkdirs())
{
throw new MojoExecutionException("Can't create "
+ tmpPersistenceXml.getParentFile().getAbsolutePath());
}
try
{
FileUtils.copyURLToFile(url, tmpPersistenceXml);
}
catch (final IOException e)
{
throw new MojoExecutionException(e.getMessage(), e);
}
persistenceXmlFile = tmpPersistenceXml.getAbsolutePath();
return true;
}
return false;
}
private boolean findPersistenceXmlInArtifacts(final List<Artifact> artifacts) throws MojoExecutionException {
for (final Artifact artifact : artifacts) {
final File file = artifact.getFile();
if (file != null && file.exists())
{
try // find the persistence.xml using a fake classloader to not need to play with URLs
{
if (findPersistenceXmlFromLoader(new URLClassLoader(new URL[] { file.toURI().toURL() },
ClassLoader.getSystemClassLoader())))
{
return true;
}
}
catch (final MalformedURLException e)
{
// no-op
}
}
}
return false;
}
/**
* This will prepare the current ClassLoader and add all jars and local
* classpaths (e.g. target/classes) needed by the OpenJPA task.
*
* @throws MojoExecutionException on any error inside the mojo
*/
protected void extendRealmClasspath()
throws MojoExecutionException
{
List<URL> urls = new ArrayList<>();
for(String fileName: getClasspathElements()) {
File pathElem = new File(fileName);
try
{
URL url = pathElem.toURI().toURL();
urls.add( url );
getLog().debug( "Added classpathElement URL " + url );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "Error in adding the classpath " + pathElem, e );
}
}
ClassLoader jpaRealm =
new URLClassLoader( (URL[]) urls.toArray( new URL[urls.size()] ), getClass().getClassLoader() );
// set the new ClassLoader as default for this Thread
Thread.currentThread().setContextClassLoader( jpaRealm );
}
/**
* Locates and returns a list of class files found under specified class
* directory.
*
* @return list of class files.
* @throws MojoExecutionException if there was an error scanning class file
* resources.
*/
protected List<File> findEntityClassFiles() throws MojoExecutionException
{
List<File> files = new ArrayList<>();
try
{
files = (List<File>) FileUtils.getFiles( getEntityClasses(), includes, excludes );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error while scanning for '" + includes + "' in " + "'"
+ getEntityClasses().getAbsolutePath() + "'.", e );
}
return files;
}
/**
* @param files List of files
* @return the paths of the given files as String[]
*/
protected String[] getFilePaths( List<File> files )
{
String[] args = new String[ files.size() ];
for ( int i = 0; i < files.size(); i++ )
{
File file = files.get( i );
args[ i ] = file.getAbsolutePath();
}
return args;
}
}