1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.enhance;
20
21 import java.io.ByteArrayInputStream;
22 import java.lang.instrument.ClassFileTransformer;
23 import java.lang.instrument.IllegalClassFormatException;
24 import java.security.ProtectionDomain;
25 import java.util.Set;
26
27 import org.apache.openjpa.conf.OpenJPAConfiguration;
28 import org.apache.openjpa.lib.log.Log;
29 import org.apache.openjpa.lib.util.Localizer;
30 import org.apache.openjpa.lib.util.Options;
31 import org.apache.openjpa.meta.MetaDataRepository;
32 import org.apache.openjpa.util.GeneralException;
33 import serp.bytecode.Project;
34 import serp.bytecode.lowlevel.ConstantPoolTable;
35
36 /**
37 * Transformer that makes persistent classes implement the
38 * {@link PersistenceCapable} interface at runtime.
39 *
40 * @author Abe White
41 * @nojavadoc
42 */
43 public class PCClassFileTransformer
44 implements ClassFileTransformer {
45
46 private static final Localizer _loc = Localizer.forPackage
47 (PCClassFileTransformer.class);
48
49 private final MetaDataRepository _repos;
50 private final PCEnhancer.Flags _flags;
51 private final ClassLoader _tmpLoader;
52 private final Log _log;
53 private final Set _names;
54 private boolean _transforming = false;
55
56 /**
57 * Constructor.
58 *
59 * @param repos metadata repository to use internally
60 * @param opts enhancer configuration options
61 * @param loader temporary class loader for loading intermediate classes
62 */
63 public PCClassFileTransformer(MetaDataRepository repos, Options opts,
64 ClassLoader loader) {
65 this(repos, toFlags(opts), loader, opts.removeBooleanProperty
66 ("scanDevPath", "ScanDevPath", false));
67 }
68
69 /**
70 * Create enhancer flags from the given options.
71 */
72 private static PCEnhancer.Flags toFlags(Options opts) {
73 PCEnhancer.Flags flags = new PCEnhancer.Flags();
74 flags.addDefaultConstructor = opts.removeBooleanProperty
75 ("addDefaultConstructor", "AddDefaultConstructor",
76 flags.addDefaultConstructor);
77 flags.enforcePropertyRestrictions = opts.removeBooleanProperty
78 ("enforcePropertyRestrictions", "EnforcePropertyRestrictions",
79 flags.enforcePropertyRestrictions);
80 return flags;
81 }
82
83 /**
84 * Constructor.
85 *
86 * @param repos metadata repository to use internally
87 * @param flags enhancer configuration
88 * @param loader temporary class loader for loading intermediate classes
89 * @param devscan whether to scan the dev classpath for persistent types
90 * if none are configured
91 */
92 public PCClassFileTransformer(MetaDataRepository repos,
93 PCEnhancer.Flags flags, ClassLoader tmpLoader, boolean devscan) {
94 _repos = repos;
95 _tmpLoader = tmpLoader;
96
97 _log = repos.getConfiguration().
98 getLog(OpenJPAConfiguration.LOG_ENHANCE);
99 _flags = flags;
100
101 _names = repos.getPersistentTypeNames(devscan, tmpLoader);
102 if (_names == null && _log.isInfoEnabled())
103 _log.info(_loc.get("runtime-enhance-pcclasses"));
104 }
105
106 public byte[] transform(ClassLoader loader, String className,
107 Class redef, ProtectionDomain domain, byte[] bytes)
108 throws IllegalClassFormatException {
109 if (loader == _tmpLoader)
110 return null;
111
112 // prevent re-entrant calls, which can occur if the enhanceing
113 // loader is used to also load OpenJPA libraries; this is to prevent
114 // recursive enhancement attempts for internal openjpa libraries
115 if (_transforming)
116 return null;
117
118 _transforming = true;
119
120 return transform0(className, redef, bytes);
121 }
122
123 /**
124 * We have to split the transform method into two methods to avoid
125 * ClassCircularityError when executing method using pure-JIT JVMs
126 * such as JRockit.
127 */
128 private byte[] transform0(String className, Class redef, byte[] bytes)
129 throws IllegalClassFormatException {
130
131 try {
132 Boolean enhance = needsEnhance(className, redef, bytes);
133 if (enhance != null && _log.isTraceEnabled())
134 _log.trace(_loc.get("needs-runtime-enhance", className,
135 enhance));
136 if (enhance != Boolean.TRUE)
137 return null;
138
139 PCEnhancer enhancer = new PCEnhancer(_repos.getConfiguration(),
140 new Project().loadClass(new ByteArrayInputStream(bytes),
141 _tmpLoader), _repos);
142 enhancer.setAddDefaultConstructor(_flags.addDefaultConstructor);
143 enhancer.setEnforcePropertyRestrictions
144 (_flags.enforcePropertyRestrictions);
145
146 if (enhancer.run() == PCEnhancer.ENHANCE_NONE)
147 return null;
148 return enhancer.getPCBytecode().toByteArray();
149 } catch (Throwable t) {
150 _log.warn(_loc.get("cft-exception-thrown", className), t);
151 if (t instanceof RuntimeException)
152 throw (RuntimeException) t;
153 if (t instanceof IllegalClassFormatException)
154 throw (IllegalClassFormatException) t;
155 throw new GeneralException(t);
156 } finally {
157 _transforming = false;
158 }
159 }
160
161 /**
162 * Return whether the given class needs enhancement.
163 */
164 private Boolean needsEnhance(String clsName, Class redef, byte[] bytes) {
165 if (redef != null) {
166 Class[] intfs = redef.getInterfaces();
167 for (int i = 0; i < intfs.length; i++)
168 if (PersistenceCapable.class.getName().
169 equals(intfs[i].getName()))
170 return Boolean.valueOf(!isEnhanced(bytes));
171 return null;
172 }
173
174 if (_names != null) {
175 if (_names.contains(clsName.replace('/', '.')))
176 return Boolean.valueOf(!isEnhanced(bytes));
177 return null;
178 }
179
180 if (clsName.startsWith("java/") || clsName.startsWith("javax/"))
181 return null;
182 if (isEnhanced(bytes))
183 return Boolean.FALSE;
184
185 try {
186 Class c = Class.forName(clsName.replace('/', '.'), false,
187 _tmpLoader);
188 if (_repos.getMetaData(c, null, false) != null)
189 return Boolean.TRUE;
190 return null;
191 } catch (ClassNotFoundException cnfe) {
192 // cannot load the class: this might mean that it is a proxy
193 // or otherwise inaccessible class which can't be an entity
194 return Boolean.FALSE;
195 } catch (LinkageError cce) {
196 // this can happen if we are loading classes that this
197 // class depends on; these will never be enhanced anyway
198 return Boolean.FALSE;
199 } catch (RuntimeException re) {
200 throw re;
201 } catch (Throwable t) {
202 throw new GeneralException(t);
203 }
204 }
205
206 /**
207 * Analyze the bytecode to see if the given class definition implements
208 * {@link PersistenceCapable}.
209 */
210 private static boolean isEnhanced(byte[] b) {
211 ConstantPoolTable table = new ConstantPoolTable(b);
212 int idx = table.getEndIndex();
213
214 idx += 6; // skip access, cls, super
215 int ifaces = table.readUnsignedShort(idx);
216 int clsEntry, utfEntry;
217 String name;
218 for (int i = 0; i < ifaces; i++) {
219 idx += 2;
220 clsEntry = table.readUnsignedShort(idx);
221 utfEntry = table.readUnsignedShort(table.get(clsEntry));
222 name = table.readString(table.get(utfEntry));
223 if ("org/apache/openjpa/enhance/PersistenceCapable".equals(name))
224 return true;
225 }
226 return false;
227 }
228 }