001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.enhance;
020:
021: import java.lang.instrument.Instrumentation;
022: import java.lang.instrument.ClassFileTransformer;
023: import java.lang.instrument.ClassDefinition;
024: import java.lang.instrument.UnmodifiableClassException;
025: import java.lang.reflect.InvocationTargetException;
026: import java.lang.reflect.Method;
027: import java.security.ProtectionDomain;
028: import java.util.Map;
029: import java.util.Collection;
030: import java.util.Collections;
031: import java.util.ArrayList;
032: import java.io.IOException;
033:
034: import org.apache.openjpa.conf.OpenJPAConfiguration;
035: import org.apache.openjpa.lib.util.JavaVersions;
036: import org.apache.openjpa.lib.util.Localizer;
037: import org.apache.openjpa.lib.log.Log;
038: import org.apache.openjpa.util.InternalException;
039: import org.apache.openjpa.util.UserException;
040:
041: /**
042: * Redefines the method bodies of existing classes. Supports Java 5 VMs that
043: * have a javaagent installed on the command line as well as newer VMs without
044: * any javaagent flag.
045: *
046: * @since 1.0.0
047: */
048: public class ClassRedefiner {
049:
050: private static final Localizer _loc = Localizer
051: .forPackage(ClassRedefiner.class);
052:
053: private static Boolean _canRedefine = null;
054:
055: /**
056: * For each element in <code>classes</code>, this method will redefine
057: * all the element's methods such that field accesses are intercepted
058: * in-line. If {@link #canRedefineClasses()} returns <code>false</code>,
059: * this method is a no-op.
060: */
061: public static void redefineClasses(OpenJPAConfiguration conf,
062: final Map<Class, byte[]> classes) {
063: if (classes == null || classes.size() == 0
064: || !canRedefineClasses())
065: return;
066:
067: Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
068: Instrumentation inst = null;
069: ClassFileTransformer t = null;
070: try {
071: inst = InstrumentationFactory.getInstrumentation();
072:
073: Class[] array = classes.keySet().toArray(
074: new Class[classes.size()]);
075: if (JavaVersions.VERSION >= 6) {
076: log.trace(_loc.get("retransform-types", classes
077: .keySet()));
078:
079: t = new ClassFileTransformer() {
080: public byte[] transform(ClassLoader loader,
081: String clsName,
082: Class<?> classBeingRedefined,
083: ProtectionDomain pd, byte[] classfileBuffer) {
084: return classes.get(classBeingRedefined);
085: }
086: };
087:
088: // these are Java 6 methods, and we don't have a Java 6 build
089: // module yet. The cost of reflection here is negligible
090: // compared to the redefinition / enhancement costs in total,
091: // so this should not be a big problem.
092: Method meth = inst.getClass().getMethod(
093: "addTransformer",
094: new Class[] { ClassFileTransformer.class,
095: boolean.class });
096: meth.invoke(inst, new Object[] { t, true });
097: meth = inst.getClass().getMethod("retransformClasses",
098: new Class[] { array.getClass() });
099: meth.invoke(inst, new Object[] { array });
100: } else {
101: log.trace(_loc.get("redefine-types", classes.keySet()));
102: // in a Java 5 context, we can use class redefinition instead
103: ClassDefinition[] defs = new ClassDefinition[array.length];
104: for (int i = 0; i < defs.length; i++)
105: defs[i] = new ClassDefinition(array[i], classes
106: .get(array[i]));
107: inst.redefineClasses(defs);
108: }
109: } catch (NoSuchMethodException e) {
110: throw new InternalException(e);
111: } catch (IllegalAccessException e) {
112: throw new InternalException(e);
113: } catch (InvocationTargetException e) {
114: throw new UserException(e.getCause());
115: } catch (IOException e) {
116: throw new InternalException(e);
117: } catch (ClassNotFoundException e) {
118: throw new InternalException(e);
119: } catch (UnmodifiableClassException e) {
120: throw new InternalException(e);
121: } finally {
122: if (inst != null && t != null)
123: inst.removeTransformer(t);
124: }
125: }
126:
127: /**
128: * @return whether or not this VM has an instrumentation installed that
129: * permits redefinition of classes. This assumes that all the arguments
130: * will be modifiable classes according to
131: * {@link java.lang.instrument.Instrumentation#isModifiableClass}, and
132: * only checks whether or not an instrumentation is available and
133: * if retransformation is possible.
134: */
135: public static boolean canRedefineClasses() {
136: if (_canRedefine == null) {
137: try {
138: Instrumentation inst = InstrumentationFactory
139: .getInstrumentation();
140: if (inst == null) {
141: _canRedefine = Boolean.FALSE;
142: } else if (JavaVersions.VERSION == 5) {
143: // if inst is non-null and we're using Java 5,
144: // isRetransformClassesSupported isn't available,
145: // so we use the more basic class redefinition
146: // instead.
147: _canRedefine = Boolean.TRUE;
148: } else {
149: _canRedefine = (Boolean) Instrumentation.class
150: .getMethod("isRetransformClassesSupported")
151: .invoke(inst);
152: }
153: } catch (Exception e) {
154: _canRedefine = Boolean.FALSE;
155: }
156: }
157: return _canRedefine.booleanValue();
158: }
159: }
|