blob: 1ae8f1adc9e9d7959adf7c13e18e072f381863de [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.meta;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.apache.openjpa.enhance.PCEnhancer;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.InternalException;
import serp.bytecode.BCClass;
import serp.bytecode.BCClassLoader;
import serp.bytecode.BCField;
import serp.bytecode.BCMethod;
import serp.bytecode.Code;
import serp.bytecode.Constants;
import serp.bytecode.Project;
/**
* Creates implementations of managed interfaces. Will throw exceptions
* on unknown properties.
*
* @author Steve Kim
*/
class InterfaceImplGenerator {
private static final Localizer _loc = Localizer.forPackage
(InterfaceImplGenerator.class);
private static final String POSTFIX = "openjpaimpl";
private final MetaDataRepository _repos;
private final Map<Class<?>,Class<?>> _impls = new WeakHashMap<>();
private final Project _project = new Project();
// distinct project / loader for enhanced version of class
private final Project _enhProject = new Project();
/**
* Constructor. Supply repository.
*/
public InterfaceImplGenerator(MetaDataRepository repos) {
_repos = repos;
}
/**
* Create a concrete implementation of the given type, possibly
* returning a cached version of the class.
*/
public synchronized Class<?> createImpl(ClassMetaData meta) {
Class<?> iface = meta.getDescribedType();
// check cache.
Class<?> impl = _impls.get(iface);
if (impl != null)
return impl;
ClassLoader parentLoader = AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(iface));
BCClassLoader loader = AccessController
.doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(_project,
parentLoader));
BCClassLoader enhLoader = AccessController
.doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(_enhProject,
parentLoader));
BCClass bc = _project.loadClass(getClassName(meta));
bc.declareInterface(iface);
ClassMetaData sup = meta.getPCSuperclassMetaData();
if (sup != null) {
bc.setSuperclass(sup.getInterfaceImpl());
enhLoader = AccessController
.doPrivileged(J2DoPrivHelper.newBCClassLoaderAction(
_enhProject, AccessController
.doPrivileged(J2DoPrivHelper.getClassLoaderAction(sup
.getInterfaceImpl()))));
}
FieldMetaData[] fields = meta.getDeclaredFields();
Set<Method> methods = new HashSet<>();
for (FieldMetaData field : fields) {
addField(bc, iface, field, methods);
}
invalidateNonBeanMethods(bc, iface, methods);
// first load the base Class<?> as the enhancer requires the class
// to be available
try {
meta.setInterfaceImpl(Class.forName(bc.getName(), true, loader));
} catch (Throwable t) {
throw new InternalException(_loc.get("interface-load", iface,
loader), t).setFatal(true);
}
// copy the BCClass<?> into the enhancer project.
bc = _enhProject.loadClass(new ByteArrayInputStream(bc.toByteArray()),
loader);
PCEnhancer enhancer = new PCEnhancer(_repos, bc, meta);
int result = enhancer.run();
if (result != PCEnhancer.ENHANCE_PC)
throw new InternalException(_loc.get("interface-badenhance",
iface)).setFatal(true);
try {
// load the Class<?> for real.
impl = Class.forName(bc.getName(), true, enhLoader);
} catch (Throwable t) {
throw new InternalException(_loc.get("interface-load2", iface,
enhLoader), t).setFatal(true);
}
// cache the generated impl.
_impls.put(iface, impl);
return impl;
}
/**
* Add bean getters and setters, also recording seen methods
* into the given set.
*/
private void addField (BCClass bc, Class<?> iface, FieldMetaData fmd,
Set<Method> methods) {
String name = fmd.getName();
Class<?> type = fmd.getDeclaredType();
BCField field = bc.declareField(name, type);
field.setAccessFlags(Constants.ACCESS_PRIVATE);
// getter
name = StringUtil.capitalize(name);
String prefix = isGetter(iface, fmd) ? "get" : "is";
BCMethod meth = bc.declareMethod(prefix + name, type, null);
meth.makePublic();
Code code = meth.getCode(true);
code.aload().setThis();
code.getfield().setField(field);
code.xreturn().setType(type);
code.calculateMaxStack();
code.calculateMaxLocals();
methods.add(getMethodSafe(iface, meth.getName(), null));
// setter
meth = bc.declareMethod("set" + name, void.class, new Class[]{type});
meth.makePublic();
code = meth.getCode(true);
code.aload().setThis();
code.xload().setParam(0).setType(type);
code.putfield().setField(field);
code.vreturn();
code.calculateMaxStack();
code.calculateMaxLocals();
methods.add(getMethodSafe(iface, meth.getName(), type));
}
/**
* Invalidate methods on the interface which are not managed.
*/
private void invalidateNonBeanMethods(BCClass bc, Class<?> iface,
Set<Method> methods) {
Method[] meths = (Method[]) AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredMethodsAction(iface));
BCMethod meth;
Code code;
Class<?> type = _repos.getMetaDataFactory().getDefaults().
getUnimplementedExceptionType();
for (Method method : meths) {
if (methods.contains(method))
continue;
meth = bc.declareMethod(method.getName(),
method.getReturnType(), method.getParameterTypes());
meth.makePublic();
code = meth.getCode(true);
code.anew().setType(type);
code.dup();
code.invokespecial().setMethod(type, "<init>", void.class, null);
code.athrow();
code.calculateMaxLocals();
code.calculateMaxStack();
}
}
/**
* Return a unique Class<?> name.
*/
protected final String getClassName(ClassMetaData meta) {
Class<?> iface = meta.getDescribedType();
return iface.getName() + "$" + System.identityHashCode(iface) + POSTFIX;
}
/**
* Convenience method to return the given method / arg.
*/
private static Method getMethodSafe(Class<?> iface, String name, Class<?> arg) {
try {
return AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredMethodAction(
iface, name, arg == null ? null : new Class[]{arg}));
} catch (PrivilegedActionException pae) {
throw new InternalException (_loc.get ("interface-mismatch", name));
}
}
private static boolean isGetter(Class<?> iface, FieldMetaData fmd) {
if (fmd.getType() != boolean.class && fmd.getType() != Boolean.class)
return true;
try {
Method meth = AccessController.doPrivileged(
J2DoPrivHelper.getDeclaredMethodAction(iface, "is" +
StringUtil.capitalize(fmd.getName()), (Class[]) null));
return meth == null;
} catch (PrivilegedActionException pae) {}
return true;
}
boolean isImplType(Class<?> cls) {
return (cls.getName().endsWith(POSTFIX)
&& cls.getName().indexOf('$') != -1);
}
public Class<?> toManagedInterface(Class<?> cls) {
Class<?>[] ifaces = cls.getInterfaces();
for (Class<?> iface : ifaces) {
if (_impls.get(iface) == cls)
return iface;
}
throw new IllegalArgumentException(cls.getName());
}
}