| /* |
| * 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()); |
| } |
| } |