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