blob: d0365e201ff16c07458ef23a353c3a4d011ab39a [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.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.BytecodeWriter;
import org.apache.openjpa.lib.util.JavaVersions;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.util.GeneratedClasses;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.UserException;
import serp.bytecode.BCClass;
/**
* Redefines the method bodies of existing unenhanced classes to make them
* notify state managers of mutations.
*
* @since 1.0.0
*/
public class ManagedClassSubclasser {
private static final Localizer _loc = Localizer.forPackage(
ManagedClassSubclasser.class);
/**
* For each element in <code>classes</code>, creates and registers a
* new subclass that implements {@link PersistenceCapable}, and prepares
* OpenJPA to handle new instances of the unenhanced type. If this is
* invoked in a Java 6 environment, this method will redefine the methods
* for each class in the argument list such that field accesses are
* intercepted in-line. If invoked in a Java 5 environment, this
* redefinition is not possible; in these contexts, when using field
* access, OpenJPA will need to do state comparisons to detect any change
* to any instance at any time, and when using property access, OpenJPA
* will need to do state comparisons to detect changes to newly inserted
* instances after a flush has been called.
*
* @return the new subclasses, or <code>null</code> if <code>classes</code>
* is <code>null</code>.
* @throws UserException if <code>conf</code> requires build-time
* enhancement and <code>classes</code> includes unenhanced types.
*
* @since 1.0.0
*/
public static List<Class> prepareUnenhancedClasses(
final OpenJPAConfiguration conf,
final Collection<? extends Class> classes,
final ClassLoader envLoader) {
if (classes == null)
return null;
if (classes.size() == 0)
return Collections.EMPTY_LIST;
Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
if (conf.getRuntimeUnenhancedClassesConstant()
!= RuntimeUnenhancedClasssesModes.SUPPORTED) {
Collection unenhanced = new ArrayList();
for (Class cls : classes)
if (!PersistenceCapable.class.isAssignableFrom(cls))
unenhanced.add(cls);
if (unenhanced.size() > 0) {
Message msg = _loc.get("runtime-optimization-disabled",
unenhanced);
if (conf.getRuntimeUnenhancedClassesConstant()
== RuntimeUnenhancedClasssesModes.WARN)
log.warn(msg);
else
throw new UserException(msg);
}
return null;
}
boolean redefine = ClassRedefiner.canRedefineClasses();
if (redefine)
log.info(_loc.get("enhance-and-subclass-and-redef-start",
classes));
else
log.info(_loc.get("enhance-and-subclass-no-redef-start",
classes));
final Map<Class, byte[]> map = new HashMap<Class, byte[]>();
final List subs = new ArrayList(classes.size());
final List ints = new ArrayList(classes.size());
Set<Class> unspecified = null;
for (Iterator iter = classes.iterator(); iter.hasNext(); ) {
final Class cls = (Class) iter.next();
final PCEnhancer enhancer = new PCEnhancer(conf, cls);
enhancer.setBytecodeWriter(new BytecodeWriter() {
public void write(BCClass bc) throws IOException {
ManagedClassSubclasser.write(bc, enhancer, map,
cls, subs, ints);
}
});
if (redefine)
enhancer.setRedefine(true);
enhancer.setCreateSubclass(true);
enhancer.setAddDefaultConstructor(true);
// set this before enhancement as well as after since enhancement
// uses a different metadata repository, and the metadata config
// matters in the enhancement contract. Don't do any warning here,
// since we'll issue warnings when we do the final metadata
// reconfiguration at the end of this method.
configureMetaData(enhancer.getMetaData(), conf, redefine, false);
unspecified = collectRelatedUnspecifiedTypes(enhancer.getMetaData(),
classes, unspecified);
int runResult = enhancer.run();
if (runResult == PCEnhancer.ENHANCE_PC) {
try {
enhancer.record();
} catch (IOException e) {
// our impl of BytecodeWriter doesn't throw IOException
throw new InternalException(e);
}
}
}
if (unspecified != null && !unspecified.isEmpty())
throw new UserException(_loc.get("unspecified-unenhanced-types",
classes, unspecified));
ClassRedefiner.redefineClasses(conf, map);
for (Class cls : map.keySet()) {
setIntercepting(conf, envLoader, cls);
configureMetaData(conf, envLoader, cls, redefine);
}
for (Class cls : (Collection<Class>) subs)
configureMetaData(conf, envLoader, cls, redefine);
for (Class cls : (Collection<Class>) ints)
setIntercepting(conf, envLoader, cls);
return subs;
}
private static Set<Class> collectRelatedUnspecifiedTypes(ClassMetaData meta,
Collection<? extends Class> classes, Set<Class> unspecified) {
unspecified = collectUnspecifiedType(meta.getPCSuperclass(), classes,
unspecified);
for (FieldMetaData fmd : meta.getFields()) {
if (fmd.isTransient())
continue;
if (fmd.isTypePC())
unspecified = collectUnspecifiedType(fmd.getType(), classes,
unspecified);
if (fmd.getElement() != null && fmd.getElement().isTypePC())
unspecified = collectUnspecifiedType(fmd.getElement().getType(),
classes, unspecified);
if (fmd.getKey() != null && fmd.getKey().isTypePC())
unspecified = collectUnspecifiedType(fmd.getKey().getType(),
classes, unspecified);
if (fmd.getValue() != null && fmd.getValue().isTypePC())
unspecified = collectUnspecifiedType(fmd.getValue().getType(),
classes, unspecified);
}
return unspecified;
}
private static Set<Class> collectUnspecifiedType(Class cls,
Collection<? extends Class> classes, Set<Class> unspecified) {
if (cls != null && !classes.contains(cls)
&& !ImplHelper.isManagedType(null, cls)) {
if (unspecified == null)
unspecified = new HashSet<Class>();
unspecified.add(cls);
}
return unspecified;
}
private static void configureMetaData(OpenJPAConfiguration conf,
ClassLoader envLoader, Class cls, boolean redefineAvailable) {
ClassMetaData meta = conf.getMetaDataRepositoryInstance()
.getMetaData(cls, envLoader, true);
configureMetaData(meta, conf, redefineAvailable, true);
}
private static void configureMetaData(ClassMetaData meta,
OpenJPAConfiguration conf, boolean redefineAvailable, boolean warn) {
setDetachedState(meta);
if (warn && meta.getAccessType() == ClassMetaData.ACCESS_FIELD
&& !redefineAvailable) {
// only warn about declared fields; superclass fields will be
// warned about when the superclass is handled
for (FieldMetaData fmd : meta.getDeclaredFields()) {
switch (fmd.getTypeCode()) {
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
// we can lazily load these, since we own the
// relationship container
break;
default:
if (!fmd.isInDefaultFetchGroup()
&& !(fmd.isVersion() || fmd.isPrimaryKey())) {
Log log = conf.getLog(
OpenJPAConfiguration.LOG_ENHANCE);
log.warn(_loc.get("subclasser-fetch-group-override",
meta.getDescribedType().getName(),
fmd.getName()));
fmd.setInDefaultFetchGroup(true);
}
}
}
}
}
private static void write(BCClass bc, PCEnhancer enhancer,
Map<Class, byte[]> map, Class cls, List subs, List ints)
throws IOException {
if (bc == enhancer.getManagedTypeBytecode()) {
// if it was already defined, don't put it in the map,
// but do set the metadata accordingly.
if (enhancer.isAlreadyRedefined())
ints.add(bc.getType());
else if (JavaVersions.VERSION >= 5)
map.put(bc.getType(), bc.toByteArray());
} else {
if (!enhancer.isAlreadySubclassed()) {
// this is the new subclass
ClassLoader loader = GeneratedClasses.getMostDerivedLoader(
cls, PersistenceCapable.class);
subs.add(GeneratedClasses.loadBCClass(bc, loader));
}
}
}
private static void setIntercepting(OpenJPAConfiguration conf,
ClassLoader envLoader, Class cls) {
ClassMetaData meta = conf.getMetaDataRepositoryInstance()
.getMetaData(cls, envLoader, true);
meta.setIntercepting(true);
}
/**
* If the metadata is configured to use a synthetic
* detached state, reset it to not use a detached
* state field, since we can't add fields when redefining.
*/
private static void setDetachedState(ClassMetaData meta) {
if (ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()))
meta.setDetachedState(null);
}
}