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.MalformedURLException;
24 import java.net.URL;
25 import java.security.AccessController;
26 import java.security.PrivilegedActionException;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Enumeration;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.MissingResourceException;
33 import javax.persistence.spi.PersistenceUnitInfo;
34 import javax.persistence.spi.PersistenceUnitTransactionType;
35
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.openjpa.conf.OpenJPAConfiguration;
38 import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
39 import org.apache.openjpa.conf.OpenJPAProductDerivation;
40 import org.apache.openjpa.lib.conf.AbstractProductDerivation;
41 import org.apache.openjpa.lib.conf.Configuration;
42 import org.apache.openjpa.lib.conf.ConfigurationProvider;
43 import org.apache.openjpa.lib.conf.Configurations;
44 import org.apache.openjpa.lib.conf.MapConfigurationProvider;
45 import org.apache.openjpa.lib.conf.ProductDerivations;
46 import org.apache.openjpa.lib.log.Log;
47 import org.apache.openjpa.lib.meta.XMLMetaDataParser;
48 import org.apache.openjpa.lib.util.J2DoPrivHelper;
49 import org.apache.openjpa.lib.util.Localizer;
50 import org.xml.sax.Attributes;
51 import org.xml.sax.SAXException;
52
53 /**
54 * Sets JPA specification defaults and parses JPA specification XML files.
55 *
56 * For globals, looks in <code>openjpa.properties</code> system property for
57 * the location of a file to parse. If no system property is defined, the
58 * default resource location of <code>META-INF/openjpa.xml</code> is used.
59 *
60 * For defaults, looks for <code>META-INF/persistence.xml</code>.
61 * Within <code>persistence.xml</code>, look for the named persistence unit, or
62 * if no name given, an OpenJPA unit (preferring an unnamed OpenJPA unit to
63 * a named one).
64 *
65 * @author Abe White
66 * @nojavadoc
67 */
68 public class PersistenceProductDerivation
69 extends AbstractProductDerivation
70 implements OpenJPAProductDerivation {
71
72 public static final String SPEC_JPA = "jpa";
73 public static final String ALIAS_EJB = "ejb";
74 public static final String RSRC_GLOBAL = "META-INF/openjpa.xml";
75 public static final String RSRC_DEFAULT = "META-INF/persistence.xml";
76
77 private static final Localizer _loc = Localizer.forPackage
78 (PersistenceProductDerivation.class);
79
80 public void putBrokerFactoryAliases(Map m) {
81 }
82
83 public int getType() {
84 return TYPE_SPEC;
85 }
86
87 @Override
88 public void validate()
89 throws Exception {
90 // make sure JPA is available
91 AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(
92 javax.persistence.EntityManagerFactory.class));
93 }
94
95 @Override
96 public boolean beforeConfigurationLoad(Configuration c) {
97 if (!(c instanceof OpenJPAConfigurationImpl))
98 return false;
99
100 OpenJPAConfigurationImpl conf = (OpenJPAConfigurationImpl) c;
101 conf.metaFactoryPlugin.setAlias(ALIAS_EJB,
102 PersistenceMetaDataFactory.class.getName());
103 conf.metaFactoryPlugin.setAlias(SPEC_JPA,
104 PersistenceMetaDataFactory.class.getName());
105
106 conf.addValue(new EntityManagerFactoryValue());
107 return true;
108 }
109
110 @Override
111 public boolean afterSpecificationSet(Configuration c) {
112 if (!(c instanceof OpenJPAConfigurationImpl)
113 || !SPEC_JPA.equals(((OpenJPAConfiguration) c).getSpecification()))
114 return false;
115
116 OpenJPAConfigurationImpl conf = (OpenJPAConfigurationImpl) c;
117 conf.metaFactoryPlugin.setDefault(SPEC_JPA);
118 conf.metaFactoryPlugin.setString(SPEC_JPA);
119 conf.lockManagerPlugin.setDefault("version");
120 conf.lockManagerPlugin.setString("version");
121 conf.nontransactionalWrite.setDefault("true");
122 conf.nontransactionalWrite.set(true);
123 return true;
124 }
125
126 /**
127 * Load configuration from the given persistence unit with the specified
128 * user properties.
129 */
130 public ConfigurationProvider load(PersistenceUnitInfo pinfo, Map m)
131 throws IOException {
132 if (pinfo == null)
133 return null;
134 if (!isOpenJPAPersistenceProvider(pinfo, null)) {
135 warnUnknownProvider(pinfo);
136 return null;
137 }
138
139 ConfigurationProviderImpl cp = new ConfigurationProviderImpl();
140 cp.addProperties(PersistenceUnitInfoImpl.toOpenJPAProperties(pinfo));
141 cp.addProperties(m);
142 if (pinfo instanceof PersistenceUnitInfoImpl) {
143 PersistenceUnitInfoImpl impl = (PersistenceUnitInfoImpl) pinfo;
144 if (impl.getPersistenceXmlFileUrl() != null)
145 cp.setSource(impl.getPersistenceXmlFileUrl().toString());
146 }
147 return cp;
148 }
149
150 /**
151 * Load configuration from the given resource and unit names, which may
152 * be null.
153 */
154 public ConfigurationProvider load(String rsrc, String name, Map m)
155 throws IOException {
156 boolean explicit = !StringUtils.isEmpty(rsrc);
157 if (!explicit)
158 rsrc = RSRC_DEFAULT;
159
160 ConfigurationProviderImpl cp = new ConfigurationProviderImpl();
161 Boolean ret = load(cp, rsrc, name, m, null, explicit);
162 if (ret != null)
163 return (ret.booleanValue()) ? cp : null;
164 if (explicit)
165 return null;
166
167 // persistence.xml does not exist; just load map
168 PersistenceUnitInfoImpl pinfo = new PersistenceUnitInfoImpl();
169 pinfo.fromUserProperties(m);
170 if (!isOpenJPAPersistenceProvider(pinfo, null)) {
171 warnUnknownProvider(pinfo);
172 return null;
173 }
174 cp.addProperties(pinfo.toOpenJPAProperties());
175 return cp;
176 }
177
178 @Override
179 public ConfigurationProvider load(String rsrc, String anchor,
180 ClassLoader loader)
181 throws IOException {
182 if (rsrc != null && !rsrc.endsWith(".xml"))
183 return null;
184 ConfigurationProviderImpl cp = new ConfigurationProviderImpl();
185 if (load(cp, rsrc, anchor, null, loader, true) == Boolean.TRUE)
186 return cp;
187 return null;
188 }
189
190 @Override
191 public ConfigurationProvider load(File file, String anchor)
192 throws IOException {
193 if (!file.getName().endsWith(".xml"))
194 return null;
195
196 ConfigurationParser parser = new ConfigurationParser(null);
197 parser.parse(file);
198 return load(findUnit((List<PersistenceUnitInfoImpl>)
199 parser.getResults(), anchor, null), null);
200 }
201
202 @Override
203 public String getDefaultResourceLocation() {
204 return RSRC_DEFAULT;
205 }
206
207 @Override
208 public List getAnchorsInFile(File file) throws IOException {
209 ConfigurationParser parser = new ConfigurationParser(null);
210 try {
211 parser.parse(file);
212 return getUnitNames(parser);
213 } catch (IOException e) {
214 // not all configuration files are XML; return null if unparsable
215 return null;
216 }
217 }
218
219 private List<String> getUnitNames(ConfigurationParser parser) {
220 List<PersistenceUnitInfoImpl> units = parser.getResults();
221 List<String> names = new ArrayList<String>();
222 for (PersistenceUnitInfoImpl unit : units)
223 names.add(unit.getPersistenceUnitName());
224 return names;
225 }
226
227 @Override
228 public List getAnchorsInResource(String resource) throws Exception {
229 ConfigurationParser parser = new ConfigurationParser(null);
230 try {
231 ClassLoader loader = (ClassLoader) AccessController.doPrivileged(
232 J2DoPrivHelper.getContextClassLoaderAction());
233 List<URL> urls = getResourceURLs(resource, loader);
234 if (urls != null) {
235 for (URL url : urls) {
236 parser.parse(url);
237 }
238 }
239 return getUnitNames(parser);
240 } catch (IOException e) {
241 // not all configuration files are XML; return null if unparsable
242 return null;
243 }
244 }
245
246 @Override
247 public ConfigurationProvider loadGlobals(ClassLoader loader)
248 throws IOException {
249 String[] prefixes = ProductDerivations.getConfigurationPrefixes();
250 String rsrc = null;
251 for (int i = 0; i < prefixes.length && StringUtils.isEmpty(rsrc); i++)
252 rsrc = (String) AccessController.doPrivileged(J2DoPrivHelper
253 .getPropertyAction(prefixes[i] + ".properties"));
254 boolean explicit = !StringUtils.isEmpty(rsrc);
255 String anchor = null;
256 int idx = (!explicit) ? -1 : rsrc.lastIndexOf('#');
257 if (idx != -1) {
258 // separate name from <resrouce>#<name> string
259 if (idx < rsrc.length() - 1)
260 anchor = rsrc.substring(idx + 1);
261 rsrc = rsrc.substring(0, idx);
262 }
263 if (StringUtils.isEmpty(rsrc))
264 rsrc = RSRC_GLOBAL;
265 else if (!rsrc.endsWith(".xml"))
266 return null;
267
268 ConfigurationProviderImpl cp = new ConfigurationProviderImpl();
269 if (load(cp, rsrc, anchor, null, loader, explicit) == Boolean.TRUE)
270 return cp;
271 return null;
272 }
273
274 @Override
275 public ConfigurationProvider loadDefaults(ClassLoader loader)
276 throws IOException {
277 ConfigurationProviderImpl cp = new ConfigurationProviderImpl();
278 if (load(cp, RSRC_DEFAULT, null, null, loader, false) == Boolean.TRUE)
279 return cp;
280 return null;
281 }
282
283 private static List<URL> getResourceURLs(String rsrc, ClassLoader loader)
284 throws IOException {
285 Enumeration<URL> urls = null;
286 try {
287 urls = (Enumeration) AccessController.doPrivileged(
288 J2DoPrivHelper.getResourcesAction(loader, rsrc));
289 if (!urls.hasMoreElements()) {
290 if (!rsrc.startsWith("META-INF"))
291 urls = (Enumeration) AccessController.doPrivileged(
292 J2DoPrivHelper.getResourcesAction(
293 loader, "META-INF/" + rsrc));
294 if (!urls.hasMoreElements())
295 return null;
296 }
297 } catch (PrivilegedActionException pae) {
298 throw (IOException) pae.getException();
299 }
300
301 return Collections.list(urls);
302 }
303
304 /**
305 * Looks through the resources at <code>rsrc</code> for a configuration
306 * file that matches <code>name</code> (or an unnamed one if
307 * <code>name</code> is <code>null</code>), and loads the XML in the
308 * resource into a new {@link PersistenceUnitInfo}. Then, applies the
309 * overrides in <code>m</code>.
310 *
311 * @return {@link Boolean#TRUE} if the resource was loaded, null if it
312 * does not exist, or {@link Boolean#FALSE} if it is not for OpenJPA
313 */
314 private Boolean load(ConfigurationProviderImpl cp, String rsrc,
315 String name, Map m, ClassLoader loader, boolean explicit)
316 throws IOException {
317 if (loader == null)
318 loader = (ClassLoader) AccessController.doPrivileged(
319 J2DoPrivHelper.getContextClassLoaderAction());
320
321 List<URL> urls = getResourceURLs(rsrc, loader);
322 if (urls == null || urls.size() == 0)
323 return null;
324
325 ConfigurationParser parser = new ConfigurationParser(m);
326 PersistenceUnitInfoImpl pinfo = parseResources(parser, urls, name,
327 loader);
328 if (pinfo == null) {
329 if (!explicit)
330 return Boolean.FALSE;
331 throw new MissingResourceException(_loc.get("missing-xml-config",
332 rsrc, String.valueOf(name)).getMessage(), getClass().getName(),
333 rsrc);
334 } else if (!isOpenJPAPersistenceProvider(pinfo, loader)) {
335 if (!explicit) {
336 warnUnknownProvider(pinfo);
337 return Boolean.FALSE;
338 }
339 throw new MissingResourceException(_loc.get("unknown-provider",
340 rsrc, name, pinfo.getPersistenceProviderClassName()).
341 getMessage(), getClass().getName(), rsrc);
342 }
343 cp.addProperties(pinfo.toOpenJPAProperties());
344 cp.setSource(pinfo.getPersistenceXmlFileUrl().toString());
345 return Boolean.TRUE;
346 }
347
348 /**
349 * Parse resources at the given location. Searches for a
350 * PersistenceUnitInfo with the requested name, or an OpenJPA unit if
351 * no name given (preferring an unnamed OpenJPA unit to a named one).
352 */
353 private PersistenceUnitInfoImpl parseResources(ConfigurationParser parser,
354 List<URL> urls, String name, ClassLoader loader)
355 throws IOException {
356 List<PersistenceUnitInfoImpl> pinfos =
357 new ArrayList<PersistenceUnitInfoImpl>();
358 for (URL url : urls) {
359 parser.parse(url);
360 pinfos.addAll((List<PersistenceUnitInfoImpl>) parser.getResults());
361 }
362 return findUnit(pinfos, name, loader);
363 }
364
365 /**
366 * Find the unit with the given name, or an OpenJPA unit if no name is
367 * given (preferring an unnamed OpenJPA unit to a named one).
368 */
369 private PersistenceUnitInfoImpl findUnit(List<PersistenceUnitInfoImpl>
370 pinfos, String name, ClassLoader loader) {
371 PersistenceUnitInfoImpl ojpa = null;
372 for (PersistenceUnitInfoImpl pinfo : pinfos) {
373 // found named unit?
374 if (name != null) {
375 if (name.equals(pinfo.getPersistenceUnitName()))
376 return pinfo;
377 continue;
378 }
379
380 if (isOpenJPAPersistenceProvider(pinfo, loader)) {
381 // if no name given and found unnamed unit, return it.
382 // otherwise record as default unit unless we find a
383 // better match later
384 if (StringUtils.isEmpty(pinfo.getPersistenceUnitName()))
385 return pinfo;
386 if (ojpa == null)
387 ojpa = pinfo;
388 }
389 }
390 return ojpa;
391 }
392
393 /**
394 * Return whether the given persistence unit uses an OpenJPA provider.
395 */
396 private static boolean isOpenJPAPersistenceProvider
397 (PersistenceUnitInfo pinfo, ClassLoader loader) {
398 String provider = pinfo.getPersistenceProviderClassName();
399 if (StringUtils.isEmpty(provider)
400 || PersistenceProviderImpl.class.getName().equals(provider))
401 return true;
402
403 if (loader == null)
404 loader = (ClassLoader) AccessController.doPrivileged(
405 J2DoPrivHelper.getContextClassLoaderAction());
406 try {
407 if (PersistenceProviderImpl.class.isAssignableFrom
408 (Class.forName(provider, false, loader)))
409 return true;
410 } catch (Throwable t) {
411 log(_loc.get("unloadable-provider", provider, t).getMessage());
412 return false;
413 }
414 return false;
415 }
416
417 /**
418 * Warn the user that we could only find an unrecognized persistence
419 * provider.
420 */
421 private static void warnUnknownProvider(PersistenceUnitInfo pinfo) {
422 log(_loc.get("unrecognized-provider",
423 pinfo.getPersistenceProviderClassName()).getMessage());
424 }
425
426 /**
427 * Log a message.
428 */
429 private static void log(String msg) {
430 // at this point logging isn't configured yet
431 System.err.println(msg);
432 }
433
434 /**
435 * Custom configuration provider.
436 */
437 public static class ConfigurationProviderImpl
438 extends MapConfigurationProvider {
439
440 private String _source;
441
442 public ConfigurationProviderImpl() {
443 }
444
445 public ConfigurationProviderImpl(Map props) {
446 super(props);
447 }
448
449 /**
450 * Set the source of information in this provider.
451 */
452 public void setSource(String source) {
453 _source = source;
454 }
455
456 @Override
457 public void setInto(Configuration conf) {
458 if (conf instanceof OpenJPAConfiguration) {
459 OpenJPAConfiguration oconf = (OpenJPAConfiguration) conf;
460 oconf.setSpecification(SPEC_JPA);
461
462 // we merge several persistence.xml elements into the
463 // MetaDataFactory property implicitly. if the user has a
464 // global openjpa.xml with this property set, its value will
465 // get overwritten by our implicit setting. so instead, combine
466 // the global value with our settings
467 String orig = oconf.getMetaDataFactory();
468 if (!StringUtils.isEmpty(orig)) {
469 String key = ProductDerivations.getConfigurationKey
470 ("MetaDataFactory", getProperties());
471 Object override = getProperties().get(key);
472 if (override instanceof String)
473 addProperty(key, Configurations.combinePlugins(orig,
474 (String) override));
475 }
476 }
477
478 super.setInto(conf, null);
479 Log log = conf.getConfigurationLog();
480 if (log.isTraceEnabled()) {
481 String src = (_source == null) ? "?" : _source;
482 log.trace(_loc.get("conf-load", src, getProperties()));
483 }
484 }
485 }
486
487 /**
488 * SAX handler capable of parsing an JPA persistence.xml file.
489 * Package-protected for testing.
490 */
491 public static class ConfigurationParser
492 extends XMLMetaDataParser {
493
494 private final Map _map;
495 private PersistenceUnitInfoImpl _info = null;
496 private URL _source = null;
497
498 public ConfigurationParser(Map map) {
499 _map = map;
500 setCaching(false);
501 setValidating(true);
502 setParseText(true);
503 }
504
505 @Override
506 public void parse(URL url)
507 throws IOException {
508 _source = url;
509 super.parse(url);
510 }
511
512 @Override
513 public void parse(File file)
514 throws IOException {
515 try {
516 _source = (URL) AccessController.doPrivileged(J2DoPrivHelper
517 .toURLAction(file));
518 } catch (PrivilegedActionException pae) {
519 throw (MalformedURLException) pae.getException();
520 }
521 super.parse(file);
522 }
523
524 @Override
525 protected Object getSchemaSource() {
526 return getClass().getResourceAsStream("persistence-xsd.rsrc");
527 }
528
529 @Override
530 protected void reset() {
531 super.reset();
532 _info = null;
533 _source = null;
534 }
535
536 protected boolean startElement(String name, Attributes attrs)
537 throws SAXException {
538 if (currentDepth() == 1)
539 startPersistenceUnit(attrs);
540 else if (currentDepth() == 3 && "property".equals(name))
541 _info.setProperty(attrs.getValue("name"),
542 attrs.getValue("value"));
543 return true;
544 }
545
546 protected void endElement(String name)
547 throws SAXException {
548 if (currentDepth() == 1) {
549 _info.fromUserProperties(_map);
550 addResult(_info);
551 }
552 if (currentDepth() != 2)
553 return;
554
555 switch (name.charAt(0)) {
556 case 'c': // class
557 _info.addManagedClassName(currentText());
558 case 'e': // exclude-unlisted-classes
559 _info.setExcludeUnlistedClasses("true".equalsIgnoreCase
560 (currentText()));
561 break;
562 case 'j':
563 if ("jta-data-source".equals(name))
564 _info.setJtaDataSourceName(currentText());
565 else // jar-file
566 {
567 try {
568 _info.addJarFileName(currentText());
569 } catch (IllegalArgumentException iae) {
570 throw getException(iae.getMessage());
571 }
572 }
573 break;
574 case 'm': // mapping-file
575 _info.addMappingFileName(currentText());
576 break;
577 case 'n': // non-jta-data-source
578 _info.setNonJtaDataSourceName(currentText());
579 break;
580 case 'p':
581 if ("provider".equals(name))
582 _info.setPersistenceProviderClassName(currentText());
583 break;
584 }
585 }
586
587 /**
588 * Parse persistence-unit element.
589 */
590 private void startPersistenceUnit(Attributes attrs)
591 throws SAXException {
592 _info = new PersistenceUnitInfoImpl();
593 _info.setPersistenceUnitName(attrs.getValue("name"));
594
595 // we only parse this ourselves outside a container, so default
596 // transaction type to local
597 String val = attrs.getValue("transaction-type");
598 if (val == null)
599 _info.setTransactionType
600 (PersistenceUnitTransactionType.RESOURCE_LOCAL);
601 else
602 _info.setTransactionType(Enum.valueOf
603 (PersistenceUnitTransactionType.class, val));
604
605 if (_source != null)
606 _info.setPersistenceXmlFileUrl(_source);
607 }
608 }
609 }