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.persistence;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.net.URL;
24 import java.security.AccessController;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 import javax.persistence.Embeddable;
33 import javax.persistence.Entity;
34 import javax.persistence.MappedSuperclass;
35 import javax.persistence.NamedNativeQueries;
36 import javax.persistence.NamedNativeQuery;
37 import javax.persistence.NamedQueries;
38 import javax.persistence.NamedQuery;
39 import javax.persistence.SqlResultSetMapping;
40 import javax.persistence.SqlResultSetMappings;
41
42 import org.apache.openjpa.lib.conf.Configurable;
43 import org.apache.openjpa.lib.conf.Configuration;
44 import org.apache.openjpa.lib.conf.GenericConfigurable;
45 import org.apache.openjpa.lib.meta.ClassAnnotationMetaDataFilter;
46 import org.apache.openjpa.lib.meta.ClassArgParser;
47 import org.apache.openjpa.lib.meta.MetaDataFilter;
48 import org.apache.openjpa.lib.meta.MetaDataParser;
49 import org.apache.openjpa.lib.util.J2DoPriv5Helper;
50 import org.apache.openjpa.lib.util.Localizer;
51 import org.apache.openjpa.lib.util.Options;
52 import org.apache.openjpa.meta.AbstractCFMetaDataFactory;
53 import org.apache.openjpa.meta.ClassMetaData;
54 import org.apache.openjpa.meta.FieldMetaData;
55 import org.apache.openjpa.meta.MetaDataDefaults;
56 import org.apache.openjpa.meta.MetaDataFactory;
57 import org.apache.openjpa.meta.QueryMetaData;
58 import org.apache.openjpa.meta.SequenceMetaData;
59 import org.apache.openjpa.util.GeneralException;
60 import org.apache.openjpa.util.MetaDataException;
61
62 /**
63 * {@link MetaDataFactory} for JPA metadata.
64 *
65 * @author Steve Kim
66 * @since 0.4.0
67 * @nojavadoc
68 */
69 public class PersistenceMetaDataFactory
70 extends AbstractCFMetaDataFactory
71 implements Configurable, GenericConfigurable {
72
73 private static final Localizer _loc = Localizer.forPackage
74 (PersistenceMetaDataFactory.class);
75
76 private final PersistenceMetaDataDefaults _def =
77 new PersistenceMetaDataDefaults();
78 private AnnotationPersistenceMetaDataParser _annoParser = null;
79 private AnnotationPersistenceXMLMetaDataParser _annoXMLParser = null;
80 private XMLPersistenceMetaDataParser _xmlParser = null;
81 private Map<URL, Set> _xml = null; // xml rsrc -> class names
82 private Set<URL> _unparsed = null; // xml rsrc
83 private boolean _fieldOverride = true;
84
85 /**
86 * Whether to use field-level override or class-level override.
87 * Defaults to true.
88 */
89 public void setFieldOverride(boolean field) {
90 _fieldOverride = field;
91 }
92
93 /**
94 * Whether to use field-level override or class-level override.
95 * Defaults to true.
96 */
97 public boolean getFieldOverride() {
98 return _fieldOverride;
99 }
100
101 /**
102 * Return metadata parser, creating it if it does not already exist.
103 */
104 public AnnotationPersistenceMetaDataParser getAnnotationParser() {
105 if (_annoParser == null) {
106 _annoParser = newAnnotationParser();
107 _annoParser.setRepository(repos);
108 }
109 return _annoParser;
110 }
111
112 /**
113 * Set the metadata parser.
114 */
115 public void setAnnotationParser(
116 AnnotationPersistenceMetaDataParser parser) {
117 if (_annoParser != null)
118 _annoParser.setRepository(null);
119 if (parser != null)
120 parser.setRepository(repos);
121 _annoParser = parser;
122 }
123
124 /**
125 * Create a new metadata parser.
126 */
127 protected AnnotationPersistenceMetaDataParser newAnnotationParser() {
128 return new AnnotationPersistenceMetaDataParser
129 (repos.getConfiguration());
130 }
131
132 /**
133 * Create a new annotation serializer.
134 */
135 protected AnnotationPersistenceMetaDataSerializer
136 newAnnotationSerializer() {
137 return new AnnotationPersistenceMetaDataSerializer
138 (repos.getConfiguration());
139 }
140
141 /**
142 * Return XML metadata parser, creating it if it does not already exist.
143 */
144 public XMLPersistenceMetaDataParser getXMLParser() {
145 if (_xmlParser == null) {
146 _xmlParser = newXMLParser(true);
147 _xmlParser.setRepository(repos);
148 if (_fieldOverride)
149 _xmlParser.setAnnotationParser(getAnnotationParser());
150 }
151 return _xmlParser;
152 }
153
154 /**
155 * Set the metadata parser.
156 */
157 public void setXMLParser(XMLPersistenceMetaDataParser parser) {
158 if (_xmlParser != null)
159 _xmlParser.setRepository(null);
160 if (parser != null)
161 parser.setRepository(repos);
162 _xmlParser = parser;
163 }
164
165 /**
166 * Create a new metadata parser.
167 */
168 protected XMLPersistenceMetaDataParser newXMLParser(boolean loading) {
169 return new XMLPersistenceMetaDataParser(repos.getConfiguration());
170 }
171
172 /**
173 * Create a new serializer
174 */
175 protected XMLPersistenceMetaDataSerializer newXMLSerializer() {
176 return new XMLPersistenceMetaDataSerializer(repos.getConfiguration());
177 }
178
179 public void load(Class cls, int mode, ClassLoader envLoader) {
180 if (mode == MODE_NONE)
181 return;
182 if (!strict && (mode & MODE_META) != 0)
183 mode |= MODE_MAPPING;
184
185 // getting the list of persistent types runs callbacks to
186 // mapPersistentTypeNames if it hasn't been called already, which
187 // caches XML resources
188 getPersistentTypeNames(false, envLoader);
189 URL xml = findXML(cls);
190
191 // we have to parse metadata up-front to register persistence unit
192 // defaults and system callbacks
193 ClassMetaData meta;
194 boolean parsedXML = false;
195 if (_unparsed != null && !_unparsed.isEmpty()
196 && (mode & MODE_META) != 0) {
197 for (URL url : _unparsed)
198 parseXML(url, cls, mode, envLoader);
199 parsedXML = _unparsed.contains(xml);
200 _unparsed.clear();
201
202 // XML process check
203 meta = repos.getCachedMetaData(cls);
204 if (meta != null && (meta.getSourceMode() & mode) == mode) {
205 validateStrategies(meta);
206 return;
207 }
208 }
209
210 // might have been looking for system-level query
211 if (cls == null)
212 return;
213
214 // we may still need to parse XML if this is a redeploy of a class, or
215 // if we're in strict query-only mode
216 if (!parsedXML && xml != null) {
217 parseXML(xml, cls, mode, envLoader);
218 // XML process check
219 meta = repos.getCachedMetaData(cls);
220 if (meta != null && (meta.getSourceMode() & mode) == mode) {
221 validateStrategies(meta);
222 return;
223 }
224 }
225
226 AnnotationPersistenceMetaDataParser parser = getAnnotationParser();
227 parser.setEnvClassLoader(envLoader);
228 parser.setMode(mode);
229 parser.parse(cls);
230
231 meta = repos.getCachedMetaData(cls);
232 if (meta != null && (meta.getSourceMode() & mode) == mode)
233 validateStrategies(meta);
234 }
235
236 /**
237 * Parse the given XML resource.
238 */
239 private void parseXML(URL xml, Class cls, int mode, ClassLoader envLoader) {
240 ClassLoader loader = repos.getConfiguration().
241 getClassResolverInstance().getClassLoader(cls, envLoader);
242 XMLPersistenceMetaDataParser xmlParser = getXMLParser();
243 xmlParser.setClassLoader(envLoader != null ? envLoader : loader);
244 xmlParser.setEnvClassLoader(envLoader);
245 xmlParser.setMode(mode);
246 try {
247 xmlParser.parse(xml);
248 } catch (IOException ioe) {
249 throw new GeneralException(ioe);
250 }
251 }
252
253 /**
254 * Locate the XML resource for the given class.
255 */
256 private URL findXML(Class cls) {
257 if (_xml != null && cls != null)
258 for (Map.Entry<URL, Set> entry : _xml.entrySet())
259 if (entry.getValue().contains(cls.getName()))
260 return entry.getKey();
261 return null;
262 }
263
264 @Override
265 protected void mapPersistentTypeNames(Object rsrc, String[] names) {
266 if (rsrc.toString().endsWith(".class")) {
267 if (log.isTraceEnabled())
268 log.trace(
269 _loc.get("map-persistent-types-skipping-class", rsrc));
270 return;
271 } else if (!(rsrc instanceof URL)) {
272 if (log.isTraceEnabled())
273 log.trace(
274 _loc.get("map-persistent-types-skipping-non-url", rsrc));
275 return;
276 }
277
278 if (log.isTraceEnabled())
279 log.trace(_loc.get(
280 "map-persistent-type-names", rsrc, Arrays.asList(names)));
281
282 if (_xml == null)
283 _xml = new HashMap<URL, Set>();
284 _xml.put((URL) rsrc, new HashSet(Arrays.asList(names)));
285 if (_unparsed == null)
286 _unparsed = new HashSet<URL>();
287 _unparsed.add((URL) rsrc);
288 }
289
290 @Override
291 public Class getQueryScope(String queryName, ClassLoader loader) {
292 if (queryName == null)
293 return null;
294 Collection classes = repos.loadPersistentTypes(false, loader);
295 for (Class cls : (Collection<Class>) classes) {
296 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
297 .isAnnotationPresentAction(cls, NamedQuery.class)))
298 .booleanValue() && hasNamedQuery
299 (queryName, (NamedQuery) cls.getAnnotation(NamedQuery.class)))
300 return cls;
301 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
302 .isAnnotationPresentAction(cls, NamedQueries.class)))
303 .booleanValue() &&
304 hasNamedQuery(queryName, ((NamedQueries) cls.
305 getAnnotation(NamedQueries.class)).value()))
306 return cls;
307 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
308 .isAnnotationPresentAction(cls, NamedNativeQuery.class)))
309 .booleanValue() &&
310 hasNamedNativeQuery(queryName, (NamedNativeQuery) cls.
311 getAnnotation(NamedNativeQuery.class)))
312 return cls;
313 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
314 .isAnnotationPresentAction(cls, NamedNativeQueries.class)))
315 .booleanValue() &&
316 hasNamedNativeQuery(queryName, ((NamedNativeQueries) cls.
317 getAnnotation(NamedNativeQueries.class)).value()))
318 return cls;
319 }
320 return null;
321 }
322
323 @Override
324 public Class getResultSetMappingScope(String rsMappingName,
325 ClassLoader loader) {
326 if (rsMappingName == null)
327 return null;
328
329 Collection classes = repos.loadPersistentTypes(false, loader);
330 for (Class cls : (Collection<Class>) classes) {
331
332 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
333 .isAnnotationPresentAction(cls, SqlResultSetMapping.class)))
334 .booleanValue() &&
335 hasRSMapping(rsMappingName, (SqlResultSetMapping) cls.
336 getAnnotation(SqlResultSetMapping.class)))
337 return cls;
338
339 if (((Boolean) AccessController.doPrivileged(J2DoPriv5Helper
340 .isAnnotationPresentAction(cls, SqlResultSetMappings.class)))
341 .booleanValue() &&
342 hasRSMapping(rsMappingName, ((SqlResultSetMappings) cls.
343 getAnnotation(SqlResultSetMappings.class)).value()))
344 return cls;
345 }
346 return null;
347 }
348
349 private boolean hasNamedQuery(String query, NamedQuery... queries) {
350 for (NamedQuery q : queries) {
351 if (query.equals(q.name()))
352 return true;
353 }
354 return false;
355 }
356
357 private boolean hasRSMapping(String rsMapping,
358 SqlResultSetMapping... mappings) {
359 for (SqlResultSetMapping m : mappings) {
360 if (rsMapping.equals(m.name()))
361 return true;
362 }
363 return false;
364 }
365
366 private boolean hasNamedNativeQuery(String query,
367 NamedNativeQuery... queries) {
368 for (NamedNativeQuery q : queries) {
369 if (query.equals(q.name()))
370 return true;
371 }
372 return false;
373 }
374
375 @Override
376 protected MetaDataFilter newMetaDataFilter() {
377 ClassAnnotationMetaDataFilter camdf = new ClassAnnotationMetaDataFilter(
378 new Class[] { Entity.class, Embeddable.class,
379 MappedSuperclass.class });
380 camdf.setLog(log);
381 return camdf;
382 }
383
384 /**
385 * Ensure all fields have declared a strategy.
386 */
387 private void validateStrategies(ClassMetaData meta) {
388 StringBuffer buf = null;
389 for (FieldMetaData fmd : meta.getDeclaredFields()) {
390 if (!fmd.isExplicit()) {
391 if (buf == null)
392 buf = new StringBuffer();
393 else
394 buf.append(", ");
395 buf.append(fmd);
396 }
397 }
398 if (buf != null)
399 throw new MetaDataException(_loc.get("no-pers-strat", buf));
400 }
401
402 public MetaDataDefaults getDefaults() {
403 return _def;
404 }
405
406 @Override
407 public ClassArgParser newClassArgParser() {
408 ClassArgParser parser = new ClassArgParser();
409 parser.setMetaDataStructure("package", null, new String[]{
410 "entity", "embeddable", "mapped-superclass" }, "class");
411 return parser;
412 }
413
414 @Override
415 public void clear() {
416 super.clear();
417 if (_annoParser != null)
418 _annoParser.clear();
419 if (_xmlParser != null)
420 _xmlParser.clear();
421 if (_xml != null)
422 _xml.clear();
423 }
424
425 protected Parser newParser(boolean loading) {
426 return newXMLParser(loading);
427 }
428
429 protected Serializer newSerializer() {
430 return newXMLSerializer();
431 }
432
433 @Override
434 protected void parse(MetaDataParser parser, Class[] cls) {
435 parse(parser, Collections.singleton(defaultXMLFile()));
436 }
437
438 protected File defaultSourceFile(ClassMetaData meta) {
439 return defaultXMLFile();
440 }
441
442 protected File defaultSourceFile(QueryMetaData query, Map clsNames) {
443 ClassMetaData meta = getDefiningMetaData(query, clsNames);
444 File file = (meta == null) ? null : meta.getSourceFile();
445 if (file != null)
446 return file;
447 return defaultXMLFile();
448 }
449
450 protected File defaultSourceFile(SequenceMetaData seq, Map clsNames) {
451 return defaultXMLFile();
452 }
453
454 /**
455 * Look for META-INF/orm.xml, and if it doesn't exist, choose a default.
456 */
457 private File defaultXMLFile() {
458 ClassLoader loader = repos.getConfiguration().
459 getClassResolverInstance().getClassLoader(getClass(), null);
460 URL rsrc = (URL) AccessController.doPrivileged(
461 J2DoPriv5Helper.getResourceAction(loader, "META-INF/orm.xml"));
462 if (rsrc != null) {
463 File file = new File(rsrc.getFile());
464 if (((Boolean) AccessController.doPrivileged(
465 J2DoPriv5Helper.existsAction(file))).booleanValue())
466 return file;
467 }
468 return new File("orm.xml");
469 }
470
471 public void setConfiguration(Configuration conf) {
472 }
473
474 public void startConfiguration() {
475 }
476
477 public void endConfiguration() {
478 if (rsrcs == null)
479 rsrcs = Collections.singleton("META-INF/orm.xml");
480 else
481 rsrcs.add("META-INF/orm.xml");
482 }
483
484 public void setInto(Options opts) {
485 opts.keySet().retainAll(opts.setInto(_def).keySet());
486 }
487
488 /**
489 * Return JAXB XML annotation parser,
490 * creating it if it does not already exist.
491 */
492 public AnnotationPersistenceXMLMetaDataParser getXMLAnnotationParser() {
493 if (_annoXMLParser == null) {
494 _annoXMLParser = newXMLAnnotationParser();
495 _annoXMLParser.setRepository(repos);
496 }
497 return _annoXMLParser;
498 }
499
500 /**
501 * Set the JAXB XML annotation parser.
502 */
503 public void setXMLAnnotationParser(
504 AnnotationPersistenceXMLMetaDataParser parser) {
505 if (_annoXMLParser != null)
506 _annoXMLParser.setRepository(null);
507 if (parser != null)
508 parser.setRepository(repos);
509 _annoXMLParser = parser;
510 }
511
512 /**
513 * Create a new JAXB XML annotation parser.
514 */
515 protected AnnotationPersistenceXMLMetaDataParser newXMLAnnotationParser() {
516 return new AnnotationPersistenceXMLMetaDataParser
517 (repos.getConfiguration());
518 }
519
520 public void loadXMLMetaData(FieldMetaData fmd) {
521 AnnotationPersistenceXMLMetaDataParser parser
522 = getXMLAnnotationParser();
523 parser.parse(fmd);
524 }
525 }