org.datanucleus.enhancer.DataNucleusEnhancer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datanucleus-core Show documentation
Show all versions of datanucleus-core Show documentation
DataNucleus Core provides the primary components of a heterogenous Java persistence solution.
It supports persistence API's being layered on top of the core functionality.
/**********************************************************************
Copyright (c) 2004 Andy Jefferson and others. All rights reserved.
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.
Contributors:
2004 Marco Schulze (NightLabs.de) - added verbose output of classpath
2004 Andy Jefferson - updated formatting of user output. Added version, vendor
2006 Andy Jefferson - restructured to have modular ClassEnhancer
2007 Andy Jefferson - swap across to using ASM
2008 Andy Jefferson - rewrite to match JDOEnhancer API
2008 Andy Jefferson - drop BCEL option
...
**********************************************************************/
package org.datanucleus.enhancer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.NucleusContext;
import org.datanucleus.PropertyNames;
import org.datanucleus.exceptions.ClassNotResolvedException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.FileMetaData;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.MetaDataUtils;
import org.datanucleus.metadata.PackageMetaData;
import org.datanucleus.metadata.PersistenceUnitMetaData;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
/**
* DataNucleus Byte-Code Enhancer.
* This provides the entry point for enhancement. Enhancement is performed using a ClassEnhancer.
* Currently provides a ClassEnhancer using ASM.
*
* You can use the DataNucleusEnhancer in two ways :-
*
* - Via the command line, entering via the main method. This creates a DataNucleusEnhancer object,
* sets the options necessary and calls the execute method.
* - Programmatically, creating the DataNucleusEnhancer object settings options and running etc.
*
*
* The programmatic way would be something like this :-
*
* DataNucleusEnhancer enhancer = new DataNucleusEnhancer();
* enhancer.setVerbose().addPersistenceUnit("myPersistenceUnit").enhance();
*
* enhancing all classes specified by the persistence unit.
*/
public class DataNucleusEnhancer
{
/** Logger for enhancing. */
public static final NucleusLogger LOGGER = NucleusLogger.getLoggerInstance("DataNucleus.Enhancer");
private MetaDataManager metadataMgr;
/** ClassLoader resolver for use during the enhancement process. */
private ClassLoaderResolver clr;
/** Name of the API to use for enhancement (default is JDO). */
private String apiName = "JDO";
private String enhancerVersion = null;
/** Optional output directory where any writing of enhanced classes goes. */
private String outputDirectory = null;
/** Whether we should provide verbose output. */
private boolean verbose = false;
/** Whether to output to System.out (as well as logging). */
private boolean systemOut = false;
/** Whether to allow generation of the PK when needed. */
private boolean generatePK = true;
/** Whether to allow generation of the default constructor when needed. */
private boolean generateConstructor = true;
/** Whether to use Detach Listener */
private boolean detachListener = false;
/** User-provided class loader. */
protected ClassLoader userClassLoader = null;
/** Set of components registered for enhancing. */
private Collection componentsToEnhance = new ArrayList();
/** Map storing input bytes of any classes to enhance, keyed by the class name (if specified using bytes). */
private Map bytesForClassesToEnhanceByClassName = null;
/**
* Map of the enhanced bytes of all classes just enhanced, keyed by the class name.
* Only populated after call of enhance().
*/
private Map enhancedBytesByClassName = null;
/**
* Map of the bytes of the PK classes created, keyed by the class name.
* Only populated after call of enhance().
*/
private Map pkClassBytesByClassName = null;
static class EnhanceComponent
{
public final static int CLASS = 0;
public final static int CLASS_FILE = 1;
public final static int MAPPING_FILE = 2;
public final static int JAR_FILE = 3;
public final static int PERSISTENCE_UNIT = 4;
int type;
Object value;
public EnhanceComponent(int type, Object value)
{
this.type = type;
this.value = value;
}
public Object getValue()
{
return value;
}
public int getType()
{
return type;
}
}
/**
* Constructor for an enhancer specifying the API and class enhancer and optional properties.
* @param apiName Name of the API to use; defines which MetaDataManager to utilise.
* @param props properties controlling enhancement
*/
public DataNucleusEnhancer(String apiName, Properties props)
{
this.apiName = apiName;
// Create NucleusContext for enhancement
// TODO Aim to separate MetaDataManager from NucleusContext so we can just have MetaDataManager here
NucleusContext nucleusContext = new EnhancementNucleusContextImpl(apiName, props);
if (props != null)
{
// Superimpose any user-provided properties
Map enhanceProps = new HashMap<>();
for (String key : props.stringPropertyNames())
{
props.put(key, props.getProperty(key));
}
nucleusContext.getConfiguration().setPersistenceProperties(enhanceProps);
}
this.metadataMgr = nucleusContext.getMetaDataManager();
this.clr = nucleusContext.getClassLoaderResolver(null);
this.enhancerVersion = nucleusContext.getPluginManager().getVersionForBundle("org.datanucleus");
}
/**
* Accessor for the MetaDataManager.
* Allows users to register their own MetaData if defined dynamically.
* @return MetaData Manager
*/
public MetaDataManager getMetaDataManager()
{
return metadataMgr;
}
/**
* Acessor for the output directory.
* @return the output directory
*/
public String getOutputDirectory()
{
return outputDirectory;
}
/**
* Mutator for the output directory where any classes will be written.
* The default is to use any current class file location and overwrite it
* @param dir the output directory
* @return The enhancer
*/
public DataNucleusEnhancer setOutputDirectory(String dir)
{
resetEnhancement();
this.outputDirectory = dir;
return this;
}
/**
* Accessor for the user-defined class loader for enhancement (if any).
* @return The class loader
*/
public ClassLoader getClassLoader()
{
return userClassLoader;
}
/**
* Method to set the class loader to use for loading the class(es) to be enhanced.
* @param loader The loader
* @return The enhancer
*/
public DataNucleusEnhancer setClassLoader(ClassLoader loader)
{
resetEnhancement();
this.userClassLoader = loader;
if (userClassLoader != null)
{
// Set it now in case the user wants to make use of MetaDataManager or something
clr.registerUserClassLoader(userClassLoader);
}
return this;
}
/**
* Accessor for the verbose setting
* @return the verbose
*/
public boolean isVerbose()
{
return verbose;
}
/**
* Mutator for the verbose flag
* @param verbose the verbose to set
* @return The enhancer
*/
public DataNucleusEnhancer setVerbose(boolean verbose)
{
resetEnhancement();
this.verbose = verbose;
return this;
}
/**
* Mutator for whether to output to system out.
* @param sysout Whether to use sysout
* @return The enhancer
*/
public DataNucleusEnhancer setSystemOut(boolean sysout)
{
resetEnhancement();
systemOut = sysout;
return this;
}
/**
* Mutator for whether to allow generation of PKs where needed.
* @param flag Whether to enable this
* @return The enhancer
*/
public DataNucleusEnhancer setGeneratePK(boolean flag)
{
resetEnhancement();
generatePK = flag;
return this;
}
/**
* Mutator for whether to allow generation of default constructor where needed.
* @param flag Whether to enable this
* @return The enhancer
*/
public DataNucleusEnhancer setGenerateConstructor(boolean flag)
{
resetEnhancement();
generateConstructor = flag;
return this;
}
/**
* Mutator for whether to allow generation of default constructor where needed.
* @param flag Whether to enable this
* @return The enhancer
*/
public DataNucleusEnhancer setDetachListener(boolean flag)
{
resetEnhancement();
detachListener = flag;
return this;
}
/**
* Method to add the specified class (and its input bytes) to the list of classes to enhance.
* @param className Name of the class (in the format "mydomain.MyClass")
* @param bytes Bytes of the class
* @return The enhancer
*/
public DataNucleusEnhancer addClass(String className, byte[] bytes)
{
if (className == null)
{
return this;
}
if (bytesForClassesToEnhanceByClassName == null)
{
bytesForClassesToEnhanceByClassName = new HashMap();
}
bytesForClassesToEnhanceByClassName.put(className, bytes);
componentsToEnhance.add(new EnhanceComponent(EnhanceComponent.CLASS, className));
return this;
}
/**
* Method to add the specified classes to the list of classes to enhance.
* @param classNames Names of the classes
* @return The enhancer
*/
public DataNucleusEnhancer addClasses(String... classNames)
{
if (classNames == null)
{
return this;
}
Collection names = new HashSet<>();
for (int i=0;i classFiles = new ArrayList(); // List to keep ordering
Collection mappingFiles = new ArrayList(); // List to keep ordering
Collection jarFiles = new HashSet();
for (int i=0;i fileMetaData = getFileMetaDataForInput();
// Enhance the classes implied by the FileMetaData
long inputTime = System.currentTimeMillis();
Set classNames = new HashSet();
boolean success = true;
for (FileMetaData filemd : fileMetaData)
{
for (int packagenum = 0; packagenum < filemd.getNoOfPackages(); packagenum++)
{
PackageMetaData pmd = filemd.getPackage(packagenum);
for (int classnum = 0; classnum < pmd.getNoOfClasses(); classnum++)
{
ClassMetaData cmd = pmd.getClass(classnum);
if (classNames.contains(cmd.getFullClassName()))
{
// Already processed, maybe via annotations and this is MetaData
continue;
}
classNames.add(cmd.getFullClassName());
byte[] bytes = bytesForClassesToEnhanceByClassName != null ?
bytesForClassesToEnhanceByClassName.get(cmd.getFullClassName()) : null;
ClassEnhancer classEnhancer = getClassEnhancer(cmd, bytes);
// Enhance, but don't store if based on input bytes
boolean clsSuccess = enhanceClass(cmd, classEnhancer, bytes == null);
if (!clsSuccess)
{
success = false;
}
}
}
}
if (!success)
{
throw new NucleusException("Failure during enhancement of classes - see the log for details");
}
// Log info about timings
long enhanceTime = System.currentTimeMillis();
String msg = null;
if (verbose)
{
msg = Localiser.msg("005004", classNames.size(), "" + (inputTime-startTime), "" + (enhanceTime-inputTime), "" + (enhanceTime-startTime));
}
else
{
msg = Localiser.msg("005005", classNames.size());
}
addMessage(msg, false);
// Remove the input specification
if (bytesForClassesToEnhanceByClassName != null)
{
bytesForClassesToEnhanceByClassName.clear();
bytesForClassesToEnhanceByClassName = null;
}
componentsToEnhance.clear();
return classNames.size();
}
/**
* Method to validate all classes defined by addClass, addClasses, addJar, addPersistenceUnit, addFiles.
* @return Number of classes validated
*/
public int validate()
{
if (componentsToEnhance.isEmpty())
{
return 0; // Nothing to validate
}
// Load the meta-data for the registered components to enhance.
long startTime = System.currentTimeMillis();
Collection fileMetaData = getFileMetaDataForInput();
// Validate the classes implied by the FileMetaData
long inputTime = System.currentTimeMillis();
Set classNames = new HashSet<>();
for (FileMetaData filemd : fileMetaData)
{
for (int packagenum = 0; packagenum < filemd.getNoOfPackages(); packagenum++)
{
PackageMetaData pmd = filemd.getPackage(packagenum);
for (int classnum = 0; classnum < pmd.getNoOfClasses(); classnum++)
{
ClassMetaData cmd = pmd.getClass(classnum);
if (classNames.contains(cmd.getFullClassName()))
{
// Already processed, maybe via annotations and this is MetaData
continue;
}
classNames.add(cmd.getFullClassName());
byte[] bytes = bytesForClassesToEnhanceByClassName != null ? bytesForClassesToEnhanceByClassName.get(cmd.getFullClassName()) : null;
ClassEnhancer classEnhancer = getClassEnhancer(cmd, bytes);
validateClass(cmd, classEnhancer);
}
}
}
// Log info about timings
long enhanceTime = System.currentTimeMillis();
String msg = null;
if (verbose)
{
msg = Localiser.msg("005004", classNames.size(), "" + (inputTime-startTime), "" + (enhanceTime-inputTime), "" + (enhanceTime-startTime));
}
else
{
msg = Localiser.msg("005005", classNames.size());
}
addMessage(msg, false);
// Remove the input specification
if (bytesForClassesToEnhanceByClassName != null)
{
bytesForClassesToEnhanceByClassName.clear();
bytesForClassesToEnhanceByClassName = null;
}
componentsToEnhance.clear();
return classNames.size();
}
/**
* Method that processes the registered components to enhance, and loads the metadata for
* them into the MetaDataManager, returning the associated FileMetaData.
* @return The FileMetaData for the registered components.
*/
protected Collection getFileMetaDataForInput()
{
Collection fileMetaData = new ArrayList();
for (EnhanceComponent comp : componentsToEnhance)
{
FileMetaData[] filemds = null;
switch (comp.getType())
{
case EnhanceComponent.CLASS : // Of the form "mydomain.MyClass"
if (comp.getValue() instanceof String)
{
// Single class
String className = (String)comp.getValue();
if (bytesForClassesToEnhanceByClassName != null && bytesForClassesToEnhanceByClassName.get(className) != null)
{
// Retrieve the meta-data "file"
AbstractClassMetaData cmd = metadataMgr.getMetaDataForClass(className, clr);
if (cmd != null)
{
filemds = new FileMetaData[] {cmd.getPackageMetaData().getFileMetaData()};
}
else
{
// No meta-data has been registered for this byte-defined class!
}
}
else
{
filemds = metadataMgr.loadClasses(new String[] {(String)comp.getValue()}, userClassLoader);
}
}
else
{
// Multiple classes
filemds = metadataMgr.loadClasses((String[])comp.getValue(), userClassLoader);
}
break;
case EnhanceComponent.CLASS_FILE : // Absolute/relative filename(s)
if (comp.getValue() instanceof String)
{
// Single class file
String className = null;
String classFilename = (String)comp.getValue();
if (!StringUtils.getFileForFilename(classFilename).exists())
{
String msg = Localiser.msg("005003", classFilename);
addMessage(msg, true);
}
else
{
className = ClassEnhancerImpl.getClassNameForFileName(classFilename);
}
if (className != null)
{
filemds = metadataMgr.loadClasses(new String[] {className}, userClassLoader);
}
}
else
{
// Multiple class files
Collection classNames = new ArrayList();
String[] classFilenames = (String[])comp.getValue();
for (int i=0;i filemdsColl = new HashSet<>();
for (int i=0;i options = new HashSet<>();
if (generatePK)
{
options.add(ClassEnhancer.OPTION_GENERATE_PK);
}
if (generateConstructor)
{
options.add(ClassEnhancer.OPTION_GENERATE_DEFAULT_CONSTRUCTOR);
}
if (detachListener)
{
options.add(ClassEnhancer.OPTION_GENERATE_DETACH_LISTENER);
}
classEnhancer.setOptions(options);
return classEnhancer;
}
/**
* Method to add a message at the required output level.
* @param msg The message
* @param error Whether the message is an error, so log at error level (otherwise info)
*/
protected void addMessage(String msg, boolean error)
{
if (error)
{
LOGGER.error(msg);
}
else
{
LOGGER.info(msg);
}
if (systemOut)
{
System.out.println(msg);
}
}
/**
* Method to enhance the class defined by the MetaData.
* @param cmd MetaData for the class
* @param enhancer ClassEnhancer to use
* @param store Whether to store the class after enhancing
* @return Whether the operation performed without error
*/
protected boolean enhanceClass(ClassMetaData cmd, ClassEnhancer enhancer, boolean store)
{
boolean success = true;
try
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(Localiser.msg("005010", cmd.getFullClassName()));
}
boolean enhanced = enhancer.enhance();
if (enhanced)
{
// Store the enhanced bytes
if (enhancedBytesByClassName == null)
{
enhancedBytesByClassName = new HashMap();
}
enhancedBytesByClassName.put(cmd.getFullClassName(), enhancer.getClassBytes());
byte[] pkClassBytes = enhancer.getPrimaryKeyClassBytes();
if (pkClassBytes != null)
{
if (pkClassBytesByClassName == null)
{
pkClassBytesByClassName = new HashMap();
}
pkClassBytesByClassName.put(cmd.getFullClassName(), pkClassBytes);
}
if (store)
{
enhancer.save(outputDirectory);
}
if (isVerbose())
{
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
addMessage(Localiser.msg("005036", cmd.getFullClassName()), false);
}
else if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_AWARE)
{
addMessage(Localiser.msg("005037", cmd.getFullClassName()), false);
}
else
{
addMessage(Localiser.msg("005038", cmd.getFullClassName()), false);
}
}
}
else
{
if (isVerbose())
{
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
addMessage(Localiser.msg("005039", cmd.getFullClassName()), false);
}
else if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_AWARE)
{
addMessage(Localiser.msg("005040", cmd.getFullClassName()), false);
}
else
{
addMessage(Localiser.msg("005038", cmd.getFullClassName()), false);
}
}
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE || cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_AWARE)
{
// Error in enhancement
success = false;
}
}
}
catch (IOException ioe)
{
// Exception thrown in saving the enhanced file
if (isVerbose())
{
addMessage(Localiser.msg("005041", cmd.getFullClassName()), false);
}
String msg = Localiser.msg("005018", cmd.getFullClassName(), ioe.getMessage());
LOGGER.error(msg, ioe);
System.out.println(msg);
success = false;
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(Localiser.msg("005011", cmd.getFullClassName()));
}
return success;
}
/**
* Method to validate the enhancement state of the class defined by the MetaData.
* @param cmd MetaData for the class
* @param enhancer ClassEnhancer to use
* @return Always returns true since there is nothing that can go wrong
*/
protected boolean validateClass(ClassMetaData cmd, ClassEnhancer enhancer)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(Localiser.msg("005012", cmd.getFullClassName()));
}
boolean enhanced = enhancer.validate();
if (enhanced)
{
if (isVerbose())
{
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
addMessage(Localiser.msg("005036", cmd.getFullClassName()), false);
}
else if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_AWARE)
{
addMessage(Localiser.msg("005037", cmd.getFullClassName()), false);
}
else
{
addMessage(Localiser.msg("005038", cmd.getFullClassName()), false);
}
}
}
else
{
if (isVerbose())
{
if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
{
addMessage(Localiser.msg("005039", cmd.getFullClassName()), false);
}
else if (cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_AWARE)
{
addMessage(Localiser.msg("005040", cmd.getFullClassName()), false);
}
else
{
addMessage(Localiser.msg("005038", cmd.getFullClassName()), false);
}
}
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug(Localiser.msg("005013", cmd.getFullClassName()));
}
return true;
}
/**
* Accessor for global properties defining this enhancer.
* Provides "VersionNumber", "VendorName" as the minimum, but typically also returns
* "API", and "EnhancerName"
* @return The properties.
*/
public Properties getProperties()
{
Properties props = new Properties();
props.setProperty("VendorName", "DataNucleus");
props.setProperty("VersionNumber", enhancerVersion);
props.setProperty("API", apiName);
return props;
}
public String getEnhancerVersion()
{
return enhancerVersion;
}
/**
* Entry point for command line enhancer.
* @param args Command line arguments
* @throws Exception Thrown if an error occurs
*/
public static void main(String args[])
throws Exception
{
// Create the enhancer, and set the various options
final CommandLineHelper clh = new CommandLineHelper(args);
final boolean quiet = clh.isQuiet();
final DataNucleusEnhancer enhancer = clh.createDataNucleusEnhancer();
// Perform the enhancement/validation using the specified input
final String persistenceUnitName = clh.getPersistenceUnitName();
final String directoryName = clh.getDirectory();
final String[] filenames = clh.getFiles();
int numClasses = 0;
try
{
if (persistenceUnitName != null)
{
// Process persistence-unit
enhancer.addPersistenceUnit(persistenceUnitName);
}
else if (directoryName != null)
{
File dir = new File(directoryName);
if (!dir.exists())
{
System.out.println(directoryName + " is not a directory. please set this as a directory");
System.exit(1);
}
Collection files = ClassUtils.getFilesForDirectory(dir);
int i = 0;
String[] fileNames = new String[files.size()];
for (File file : files)
{
fileNames[i++] = file.getPath();
}
enhancer.addFiles(fileNames);
}
else
{
// Process class/mapping-files
enhancer.addFiles(filenames);
}
if (clh.isValidating())
{
numClasses = enhancer.validate();
}
else
{
numClasses = enhancer.enhance();
}
}
catch (NucleusException ne)
{
System.out.println(ne.getMessage());
String msg = Localiser.msg("005006");
LOGGER.error(msg, ne);
if (!quiet)
{
System.out.println(msg);
}
System.exit(1);
}
if (numClasses == 0)
{
String msg = Localiser.msg("005007");
LOGGER.info(msg);
if (!quiet)
{
System.out.println(msg);
}
}
}
}