blob: d67fb48a1159225802d528e8df1827194d93f4ad [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.enhance;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.AccessController;
import java.security.ProtectionDomain;
import java.util.Set;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.util.GeneralException;
import serp.bytecode.BCClass;
import serp.bytecode.Project;
import serp.bytecode.lowlevel.ConstantPoolTable;
/**
* Transformer that makes persistent classes implement the
* {@link PersistenceCapable} interface at runtime.
*
* @author Abe White
*/
public class PCClassFileTransformer
implements ClassFileTransformer {
private static final Localizer _loc = Localizer.forPackage
(PCClassFileTransformer.class);
private final MetaDataRepository _repos;
private final PCEnhancer.Flags _flags;
private final ClassLoader _tmpLoader;
private final Log _log;
private final Set _names;
private boolean _transforming = false;
/**
* Constructor.
*
* @param repos metadata repository to use internally
* @param opts enhancer configuration options
* @param loader temporary class loader for loading intermediate classes
*/
public PCClassFileTransformer(MetaDataRepository repos, Options opts, ClassLoader loader) {
this(repos, toFlags(opts), loader, opts.removeBooleanProperty
("scanDevPath", "ScanDevPath", false));
}
/**
* Create enhancer flags from the given options.
*/
private static PCEnhancer.Flags toFlags(Options opts) {
PCEnhancer.Flags flags = new PCEnhancer.Flags();
flags.addDefaultConstructor = opts.removeBooleanProperty
("addDefaultConstructor", "AddDefaultConstructor",
flags.addDefaultConstructor);
flags.enforcePropertyRestrictions = opts.removeBooleanProperty
("enforcePropertyRestrictions", "EnforcePropertyRestrictions",
flags.enforcePropertyRestrictions);
return flags;
}
/**
* Constructor.
*
* @param repos metadata repository to use internally
* @param flags enhancer configuration
* @param tmpLoader temporary class loader for loading intermediate classes
* @param devscan whether to scan the dev classpath for persistent types
* if none are configured
*/
public PCClassFileTransformer(MetaDataRepository repos, PCEnhancer.Flags flags, ClassLoader tmpLoader, boolean devscan) {
_repos = repos;
_tmpLoader = tmpLoader;
_log = repos.getConfiguration().
getLog(OpenJPAConfiguration.LOG_ENHANCE);
_flags = flags;
_names = repos.getPersistentTypeNames(devscan, tmpLoader);
if (_names == null && _log.isInfoEnabled())
_log.info(_loc.get("runtime-enhance-pcclasses"));
}
@Override
public byte[] transform(ClassLoader loader, String className, Class redef, ProtectionDomain domain, byte[] bytes)
throws IllegalClassFormatException {
if (loader == _tmpLoader)
return null;
// JDK bug -- OPENJPA-1676
if (className == null) {
return null;
}
// prevent re-entrant calls, which can occur if the enhancing
// loader is used to also load OpenJPA libraries; this is to prevent
// recursive enhancement attempts for internal openjpa libraries
if (_transforming)
return null;
_transforming = true;
return transform0(className, redef, bytes);
}
/**
* We have to split the transform method into two methods to avoid
* ClassCircularityError when executing method using pure-JIT JVMs
* such as JRockit.
*/
private byte[] transform0(String className, Class redef, byte[] bytes)
throws IllegalClassFormatException {
byte[] returnBytes = null;
try {
Boolean enhance = needsEnhance(className, redef, bytes);
if (enhance != null && _log.isTraceEnabled())
_log.trace(_loc.get("needs-runtime-enhance", className,
enhance));
if (enhance != Boolean.TRUE)
return null;
ClassLoader oldLoader = AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
AccessController.doPrivileged(J2DoPrivHelper.setContextClassLoaderAction(_tmpLoader));
try {
PCEnhancer enhancer = new PCEnhancer(_repos.getConfiguration(),
new Project().loadClass(new ByteArrayInputStream(bytes),
_tmpLoader), _repos);
enhancer.setAddDefaultConstructor(_flags.addDefaultConstructor);
enhancer.setEnforcePropertyRestrictions
(_flags.enforcePropertyRestrictions);
if (enhancer.run() == PCEnhancer.ENHANCE_NONE)
return null;
BCClass pcb = enhancer.getPCBytecode();
returnBytes = AsmAdaptor.toByteArray(pcb, pcb.toByteArray());
return returnBytes;
} finally {
AccessController.doPrivileged(J2DoPrivHelper.setContextClassLoaderAction(oldLoader));
}
} catch (Throwable t) {
_log.warn(_loc.get("cft-exception-thrown", className), t);
if (t instanceof RuntimeException)
throw (RuntimeException) t;
if (t instanceof IllegalClassFormatException)
throw (IllegalClassFormatException) t;
throw new GeneralException(t);
} finally {
_transforming = false;
if (returnBytes != null && _log.isTraceEnabled())
_log.trace(_loc.get("runtime-enhance-complete", className,
bytes.length, returnBytes.length));
}
}
/**
* Return whether the given class needs enhancement.
*/
private Boolean needsEnhance(String clsName, Class redef, byte[] bytes) {
if (redef != null && PersistenceCapable.class.isAssignableFrom(redef)) {
// if the original class is already enhanced (implements PersistenceCapable)
// then we don't need to do any further processing.
return null;
}
if (_names != null) {
if (_names.contains(clsName.replace('/', '.')))
return Boolean.valueOf(!isEnhanced(bytes));
return null;
}
if (clsName.startsWith("java/") || clsName.startsWith("javax/") || clsName.startsWith("jakarta/")) {
return null;
}
if (isEnhanced(bytes)) {
return Boolean.FALSE;
}
try {
Class c = Class.forName(clsName.replace('/', '.'), false,
_tmpLoader);
if (_repos.getMetaData(c, null, false) != null)
return Boolean.TRUE;
return null;
} catch (ClassNotFoundException cnfe) {
// cannot load the class: this might mean that it is a proxy
// or otherwise inaccessible class which can't be an entity
return Boolean.FALSE;
} catch (LinkageError cce) {
// this can happen if we are loading classes that this
// class depends on; these will never be enhanced anyway
return Boolean.FALSE;
} catch (RuntimeException re) {
throw re;
} catch (Throwable t) {
throw new GeneralException(t);
}
}
/**
* Analyze the bytecode to see if the given class definition implements
* {@link PersistenceCapable}.
*/
private static boolean isEnhanced(byte[] b) {
if (AsmAdaptor.use())
{
return AsmAdaptor.isEnhanced(b);
}
ConstantPoolTable table = new ConstantPoolTable(b);
int idx = table.getEndIndex();
idx += 6; // skip access, cls, super
int ifaces = table.readUnsignedShort(idx);
int clsEntry, utfEntry;
String name;
for (int i = 0; i < ifaces; i++) {
idx += 2;
clsEntry = table.readUnsignedShort(idx);
utfEntry = table.readUnsignedShort(table.get(clsEntry));
name = table.readString(table.get(utfEntry));
if ("org/apache/openjpa/enhance/PersistenceCapable".equals(name))
return true;
}
return false;
}
}