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.File;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32
33 import org.apache.openjpa.conf.OpenJPAConfiguration;
34 import org.apache.openjpa.lib.log.Log;
35 import org.apache.openjpa.lib.util.BytecodeWriter;
36 import org.apache.openjpa.lib.util.JavaVersions;
37 import org.apache.openjpa.lib.util.Localizer;
38 import org.apache.openjpa.lib.util.Files;
39 import org.apache.openjpa.lib.util.Localizer.Message;
40 import org.apache.openjpa.meta.ClassMetaData;
41 import org.apache.openjpa.meta.FieldMetaData;
42 import org.apache.openjpa.meta.JavaTypes;
43 import org.apache.openjpa.util.GeneratedClasses;
44 import org.apache.openjpa.util.ImplHelper;
45 import org.apache.openjpa.util.InternalException;
46 import org.apache.openjpa.util.UserException;
47 import serp.bytecode.BCClass;
48
49 /**
50 * Redefines the method bodies of existing unenhanced classes to make them
51 * notify state managers of mutations.
52 *
53 * @since 1.0.0
54 */
55 public class ManagedClassSubclasser {
56 private static final Localizer _loc = Localizer.forPackage(
57 ManagedClassSubclasser.class);
58
59 /**
60 * For each element in <code>classes</code>, creates and registers a
61 * new subclass that implements {@link PersistenceCapable}, and prepares
62 * OpenJPA to handle new instances of the unenhanced type. If this is
63 * invoked in a Java 6 environment, this method will redefine the methods
64 * for each class in the argument list such that field accesses are
65 * intercepted in-line. If invoked in a Java 5 environment, this
66 * redefinition is not possible; in these contexts, when using field
67 * access, OpenJPA will need to do state comparisons to detect any change
68 * to any instance at any time, and when using property access, OpenJPA
69 * will need to do state comparisons to detect changes to newly inserted
70 * instances after a flush has been called.
71 *
72 * @return the new subclasses, or <code>null</code> if <code>classes</code>
73 * is <code>null</code>.
74 * @throws UserException if <code>conf</code> requires build-time
75 * enhancement and <code>classes</code> includes unenhanced types.
76 *
77 * @since 1.0.0
78 */
79 public static List<Class> prepareUnenhancedClasses(
80 final OpenJPAConfiguration conf,
81 final Collection<? extends Class> classes,
82 final ClassLoader envLoader) {
83 if (classes == null)
84 return null;
85 if (classes.size() == 0)
86 return Collections.EMPTY_LIST;
87
88 Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
89 if (conf.getRuntimeUnenhancedClassesConstant()
90 != RuntimeUnenhancedClasssesModes.SUPPORTED) {
91 Collection unenhanced = new ArrayList();
92 for (Class cls : classes)
93 if (!PersistenceCapable.class.isAssignableFrom(cls))
94 unenhanced.add(cls);
95 if (unenhanced.size() > 0) {
96 Message msg = _loc.get("runtime-optimization-disabled",
97 unenhanced);
98 if (conf.getRuntimeUnenhancedClassesConstant()
99 == RuntimeUnenhancedClasssesModes.WARN)
100 log.warn(msg);
101 else
102 throw new UserException(msg);
103 }
104 return null;
105 }
106
107 boolean redefine = ClassRedefiner.canRedefineClasses();
108 if (redefine)
109 log.info(_loc.get("enhance-and-subclass-and-redef-start",
110 classes));
111 else
112 log.info(_loc.get("enhance-and-subclass-no-redef-start",
113 classes));
114
115 final Map<Class, byte[]> map = new HashMap<Class, byte[]>();
116 final List subs = new ArrayList(classes.size());
117 final List ints = new ArrayList(classes.size());
118 Set<Class> unspecified = null;
119 for (Iterator iter = classes.iterator(); iter.hasNext(); ) {
120 final Class cls = (Class) iter.next();
121 final PCEnhancer enhancer = new PCEnhancer(conf, cls);
122
123 enhancer.setBytecodeWriter(new BytecodeWriter() {
124 public void write(BCClass bc) throws IOException {
125 ManagedClassSubclasser.write(bc, enhancer, map,
126 cls, subs, ints);
127 }
128 });
129 if (redefine)
130 enhancer.setRedefine(true);
131 enhancer.setCreateSubclass(true);
132 enhancer.setAddDefaultConstructor(true);
133
134 // set this before enhancement as well as after since enhancement
135 // uses a different metadata repository, and the metadata config
136 // matters in the enhancement contract. Don't do any warning here,
137 // since we'll issue warnings when we do the final metadata
138 // reconfiguration at the end of this method.
139 configureMetaData(enhancer.getMetaData(), conf, redefine, false);
140
141 unspecified = collectRelatedUnspecifiedTypes(enhancer.getMetaData(),
142 classes, unspecified);
143
144 int runResult = enhancer.run();
145 if (runResult == PCEnhancer.ENHANCE_PC) {
146 try {
147 enhancer.record();
148 } catch (IOException e) {
149 // our impl of BytecodeWriter doesn't throw IOException
150 throw new InternalException(e);
151 }
152 }
153 }
154
155 if (unspecified != null && !unspecified.isEmpty())
156 throw new UserException(_loc.get("unspecified-unenhanced-types",
157 classes, unspecified));
158
159 ClassRedefiner.redefineClasses(conf, map);
160 for (Class cls : map.keySet()) {
161 setIntercepting(conf, envLoader, cls);
162 configureMetaData(conf, envLoader, cls, redefine);
163 }
164 for (Class cls : (Collection<Class>) subs)
165 configureMetaData(conf, envLoader, cls, redefine);
166 for (Class cls : (Collection<Class>) ints)
167 setIntercepting(conf, envLoader, cls);
168
169 return subs;
170 }
171
172 private static Set<Class> collectRelatedUnspecifiedTypes(ClassMetaData meta,
173 Collection<? extends Class> classes, Set<Class> unspecified) {
174 unspecified = collectUnspecifiedType(meta.getPCSuperclass(), classes,
175 unspecified);
176
177 for (FieldMetaData fmd : meta.getFields()) {
178 if (fmd.isTransient())
179 continue;
180 if (fmd.isTypePC())
181 unspecified = collectUnspecifiedType(fmd.getType(), classes,
182 unspecified);
183 if (fmd.getElement() != null && fmd.getElement().isTypePC())
184 unspecified = collectUnspecifiedType(fmd.getElement().getType(),
185 classes, unspecified);
186 if (fmd.getKey() != null && fmd.getKey().isTypePC())
187 unspecified = collectUnspecifiedType(fmd.getKey().getType(),
188 classes, unspecified);
189 if (fmd.getValue() != null && fmd.getValue().isTypePC())
190 unspecified = collectUnspecifiedType(fmd.getValue().getType(),
191 classes, unspecified);
192 }
193 return unspecified;
194 }
195
196 private static Set<Class> collectUnspecifiedType(Class cls,
197 Collection<? extends Class> classes, Set<Class> unspecified) {
198 if (cls != null && !classes.contains(cls)
199 && !ImplHelper.isManagedType(null, cls)) {
200 if (unspecified == null)
201 unspecified = new HashSet<Class>();
202 unspecified.add(cls);
203 }
204 return unspecified;
205 }
206
207 private static void configureMetaData(OpenJPAConfiguration conf,
208 ClassLoader envLoader, Class cls, boolean redefineAvailable) {
209 ClassMetaData meta = conf.getMetaDataRepositoryInstance()
210 .getMetaData(cls, envLoader, true);
211 configureMetaData(meta, conf, redefineAvailable, true);
212 }
213
214 private static void configureMetaData(ClassMetaData meta,
215 OpenJPAConfiguration conf, boolean redefineAvailable, boolean warn) {
216
217 setDetachedState(meta);
218
219 if (warn && meta.getAccessType() == ClassMetaData.ACCESS_FIELD
220 && !redefineAvailable) {
221 // only warn about declared fields; superclass fields will be
222 // warned about when the superclass is handled
223 for (FieldMetaData fmd : meta.getDeclaredFields()) {
224 switch (fmd.getTypeCode()) {
225 case JavaTypes.COLLECTION:
226 case JavaTypes.MAP:
227 // we can lazily load these, since we own the
228 // relationship container
229 break;
230 default:
231 if (!fmd.isInDefaultFetchGroup()
232 && !(fmd.isVersion() || fmd.isPrimaryKey())) {
233 Log log = conf.getLog(
234 OpenJPAConfiguration.LOG_ENHANCE);
235 log.warn(_loc.get("subclasser-fetch-group-override",
236 meta.getDescribedType().getName(),
237 fmd.getName()));
238 fmd.setInDefaultFetchGroup(true);
239 }
240 }
241 }
242 }
243 }
244
245 private static void write(BCClass bc, PCEnhancer enhancer,
246 Map<Class, byte[]> map, Class cls, List subs, List ints)
247 throws IOException {
248
249 if (bc == enhancer.getManagedTypeBytecode()) {
250 // if it was already defined, don't put it in the map,
251 // but do set the metadata accordingly.
252 if (enhancer.isAlreadyRedefined())
253 ints.add(bc.getType());
254 else if (JavaVersions.VERSION >= 5) {
255 map.put(bc.getType(), bc.toByteArray());
256 debugBytecodes(bc);
257 }
258 } else {
259 if (!enhancer.isAlreadySubclassed()) {
260 debugBytecodes(bc);
261
262 // this is the new subclass
263 ClassLoader loader = GeneratedClasses.getMostDerivedLoader(
264 cls, PersistenceCapable.class);
265 subs.add(GeneratedClasses.loadBCClass(bc, loader));
266 }
267 }
268 }
269
270 private static void debugBytecodes(BCClass bc) throws IOException {
271 // Write the bytecodes to disk for debugging purposes.
272 if ("true".equals(System.getProperty(
273 ManagedClassSubclasser.class.getName() + ".dumpBytecodes")))
274 {
275 File tmp = new File(System.getProperty("java.io.tmpdir"));
276 File dir = new File(tmp, "openjpa");
277 dir = new File(dir, "pcsubclasses");
278 dir.mkdirs();
279 dir = Files.getPackageFile(dir, bc.getPackageName(), true);
280 File f = new File(dir, bc.getClassName() + ".class");
281 System.err.println("Writing to " + f);
282 bc.write(f);
283 }
284 }
285
286 private static void setIntercepting(OpenJPAConfiguration conf,
287 ClassLoader envLoader, Class cls) {
288 ClassMetaData meta = conf.getMetaDataRepositoryInstance()
289 .getMetaData(cls, envLoader, true);
290 meta.setIntercepting(true);
291 }
292
293 /**
294 * If the metadata is configured to use a synthetic
295 * detached state, reset it to not use a detached
296 * state field, since we can't add fields when redefining.
297 */
298 private static void setDetachedState(ClassMetaData meta) {
299 if (ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()))
300 meta.setDetachedState(null);
301 }
302 }