| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.persistence; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.security.AccessController; |
| import java.security.PrivilegedActionException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.MissingResourceException; |
| import javax.persistence.spi.PersistenceUnitInfo; |
| import javax.persistence.spi.PersistenceUnitTransactionType; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.conf.OpenJPAConfigurationImpl; |
| import org.apache.openjpa.conf.OpenJPAProductDerivation; |
| import org.apache.openjpa.lib.conf.AbstractProductDerivation; |
| import org.apache.openjpa.lib.conf.Configuration; |
| import org.apache.openjpa.lib.conf.ConfigurationProvider; |
| import org.apache.openjpa.lib.conf.Configurations; |
| import org.apache.openjpa.lib.conf.MapConfigurationProvider; |
| import org.apache.openjpa.lib.conf.ProductDerivations; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.meta.XMLMetaDataParser; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * Sets JPA specification defaults and parses JPA specification XML files. |
| * |
| * For globals, looks in <code>openjpa.properties</code> system property for |
| * the location of a file to parse. If no system property is defined, the |
| * default resource location of <code>META-INF/openjpa.xml</code> is used. |
| * |
| * For defaults, looks for <code>META-INF/persistence.xml</code>. |
| * Within <code>persistence.xml</code>, look for the named persistence unit, or |
| * if no name given, an OpenJPA unit (preferring an unnamed OpenJPA unit to |
| * a named one). |
| * |
| * @author Abe White |
| * @nojavadoc |
| */ |
| public class PersistenceProductDerivation |
| extends AbstractProductDerivation |
| implements OpenJPAProductDerivation { |
| |
| public static final String SPEC_JPA = "jpa"; |
| public static final String ALIAS_EJB = "ejb"; |
| public static final String RSRC_GLOBAL = "META-INF/openjpa.xml"; |
| public static final String RSRC_DEFAULT = "META-INF/persistence.xml"; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (PersistenceProductDerivation.class); |
| |
| private static List<URL> defaultPersistenceFiles; |
| |
| public void putBrokerFactoryAliases(Map m) { |
| } |
| |
| public int getType() { |
| return TYPE_SPEC; |
| } |
| |
| @Override |
| public void validate() |
| throws Exception { |
| // make sure JPA is available |
| AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction( |
| javax.persistence.EntityManagerFactory.class)); |
| } |
| |
| @Override |
| public boolean beforeConfigurationLoad(Configuration c) { |
| if (!(c instanceof OpenJPAConfigurationImpl)) |
| return false; |
| |
| OpenJPAConfigurationImpl conf = (OpenJPAConfigurationImpl) c; |
| conf.metaFactoryPlugin.setAlias(ALIAS_EJB, |
| PersistenceMetaDataFactory.class.getName()); |
| conf.metaFactoryPlugin.setAlias(SPEC_JPA, |
| PersistenceMetaDataFactory.class.getName()); |
| |
| conf.addValue(new EntityManagerFactoryValue()); |
| return true; |
| } |
| |
| @Override |
| public boolean afterSpecificationSet(Configuration c) { |
| if (!(c instanceof OpenJPAConfigurationImpl) |
| || !SPEC_JPA.equals(((OpenJPAConfiguration) c).getSpecification())) |
| return false; |
| |
| OpenJPAConfigurationImpl conf = (OpenJPAConfigurationImpl) c; |
| conf.metaFactoryPlugin.setDefault(SPEC_JPA); |
| conf.metaFactoryPlugin.setString(SPEC_JPA); |
| conf.lockManagerPlugin.setDefault("version"); |
| conf.lockManagerPlugin.setString("version"); |
| conf.nontransactionalWrite.setDefault("true"); |
| conf.nontransactionalWrite.set(true); |
| return true; |
| } |
| |
| /** |
| * Load configuration from the given persistence unit with the specified |
| * user properties. |
| */ |
| public ConfigurationProvider load(PersistenceUnitInfo pinfo, Map m) |
| throws IOException { |
| if (pinfo == null) |
| return null; |
| if (!isOpenJPAPersistenceProvider(pinfo, null)) { |
| warnUnknownProvider(pinfo); |
| return null; |
| } |
| |
| ConfigurationProviderImpl cp = new ConfigurationProviderImpl(); |
| cp.addProperties(PersistenceUnitInfoImpl.toOpenJPAProperties(pinfo)); |
| cp.addProperties(m); |
| if (pinfo instanceof PersistenceUnitInfoImpl) { |
| PersistenceUnitInfoImpl impl = (PersistenceUnitInfoImpl) pinfo; |
| if (impl.getPersistenceXmlFileUrl() != null) |
| cp.setSource(impl.getPersistenceXmlFileUrl().toString()); |
| } |
| return cp; |
| } |
| |
| /** |
| * Load configuration from the given resource and unit names, which may |
| * be null. |
| */ |
| public ConfigurationProvider load(String rsrc, String name, Map m) |
| throws IOException { |
| boolean explicit = !StringUtils.isEmpty(rsrc); |
| if (!explicit) |
| rsrc = RSRC_DEFAULT; |
| ConfigurationProviderImpl cp = new ConfigurationProviderImpl(); |
| Boolean ret = load(cp, rsrc, name, m, null, explicit); |
| if (ret != null) |
| return (ret.booleanValue()) ? cp : null; |
| if (explicit) |
| return null; |
| |
| // persistence.xml does not exist; just load map |
| PersistenceUnitInfoImpl pinfo = new PersistenceUnitInfoImpl(); |
| pinfo.fromUserProperties(m); |
| if (!isOpenJPAPersistenceProvider(pinfo, null)) { |
| warnUnknownProvider(pinfo); |
| return null; |
| } |
| cp.addProperties(pinfo.toOpenJPAProperties()); |
| return cp; |
| } |
| |
| @Override |
| public ConfigurationProvider load(String rsrc, String anchor, |
| ClassLoader loader) |
| throws IOException { |
| if (rsrc != null && !rsrc.endsWith(".xml")) |
| return null; |
| ConfigurationProviderImpl cp = new ConfigurationProviderImpl(); |
| if (load(cp, rsrc, anchor, null, loader, true) == Boolean.TRUE) |
| return cp; |
| return null; |
| } |
| |
| @Override |
| public ConfigurationProvider load(File file, String anchor) |
| throws IOException { |
| if (!file.getName().endsWith(".xml")) |
| return null; |
| |
| ConfigurationParser parser = new ConfigurationParser(null); |
| parser.parse(file); |
| return load(findUnit((List<PersistenceUnitInfoImpl>) |
| parser.getResults(), anchor, null), null); |
| } |
| |
| @Override |
| public String getDefaultResourceLocation() { |
| return RSRC_DEFAULT; |
| } |
| |
| @Override |
| public List getAnchorsInFile(File file) throws IOException { |
| ConfigurationParser parser = new ConfigurationParser(null); |
| try { |
| parser.parse(file); |
| return getUnitNames(parser); |
| } catch (IOException e) { |
| // not all configuration files are XML; return null if unparsable |
| return null; |
| } |
| } |
| |
| private List<String> getUnitNames(ConfigurationParser parser) { |
| List<PersistenceUnitInfoImpl> units = parser.getResults(); |
| List<String> names = new ArrayList<String>(); |
| for (PersistenceUnitInfoImpl unit : units) |
| names.add(unit.getPersistenceUnitName()); |
| return names; |
| } |
| |
| @Override |
| public List getAnchorsInResource(String resource) throws Exception { |
| ConfigurationParser parser = new ConfigurationParser(null); |
| try { |
| ClassLoader loader = (ClassLoader) AccessController.doPrivileged( |
| J2DoPrivHelper.getContextClassLoaderAction()); |
| List<URL> urls = getResourceURLs(resource, loader); |
| if (urls != null) { |
| for (URL url : urls) { |
| parser.parse(url); |
| } |
| } |
| return getUnitNames(parser); |
| } catch (IOException e) { |
| // not all configuration files are XML; return null if unparsable |
| return null; |
| } |
| } |
| |
| @Override |
| public ConfigurationProvider loadGlobals(ClassLoader loader) |
| throws IOException { |
| String[] prefixes = ProductDerivations.getConfigurationPrefixes(); |
| String rsrc = null; |
| for (int i = 0; i < prefixes.length && StringUtils.isEmpty(rsrc); i++) |
| rsrc = (String) AccessController.doPrivileged(J2DoPrivHelper |
| .getPropertyAction(prefixes[i] + ".properties")); |
| boolean explicit = !StringUtils.isEmpty(rsrc); |
| String anchor = null; |
| int idx = (!explicit) ? -1 : rsrc.lastIndexOf('#'); |
| if (idx != -1) { |
| // separate name from <resrouce>#<name> string |
| if (idx < rsrc.length() - 1) |
| anchor = rsrc.substring(idx + 1); |
| rsrc = rsrc.substring(0, idx); |
| } |
| if (StringUtils.isEmpty(rsrc)) |
| rsrc = RSRC_GLOBAL; |
| else if (!rsrc.endsWith(".xml")) |
| return null; |
| |
| ConfigurationProviderImpl cp = new ConfigurationProviderImpl(); |
| if (load(cp, rsrc, anchor, null, loader, explicit) == Boolean.TRUE) |
| return cp; |
| return null; |
| } |
| |
| @Override |
| public ConfigurationProvider loadDefaults(ClassLoader loader) |
| throws IOException { |
| ConfigurationProviderImpl cp = new ConfigurationProviderImpl(); |
| if (load(cp, RSRC_DEFAULT, null, null, loader, false) == Boolean.TRUE) |
| return cp; |
| return null; |
| } |
| |
| private static List<URL> getResourceURLs(String rsrc, ClassLoader loader) |
| throws IOException { |
| List<URL> answer = null; |
| if (RSRC_DEFAULT.equals(rsrc) && defaultPersistenceFiles != null) { |
| answer = new ArrayList(); |
| answer.addAll(defaultPersistenceFiles); |
| } |
| Enumeration<URL> urls = null; |
| try { |
| urls = (Enumeration) AccessController.doPrivileged( |
| J2DoPrivHelper.getResourcesAction(loader, rsrc)); |
| if (!urls.hasMoreElements()) { |
| if (!rsrc.startsWith("META-INF")) |
| urls = (Enumeration) AccessController.doPrivileged( |
| J2DoPrivHelper.getResourcesAction( |
| loader, "META-INF/" + rsrc)); |
| } |
| } catch (PrivilegedActionException pae) { |
| throw (IOException) pae.getException(); |
| } |
| |
| if (urls.hasMoreElements()) { |
| if (answer == null) { |
| answer = Collections.list(urls); |
| } else { |
| answer.addAll(Collections.list(urls)); |
| } |
| } |
| |
| return answer; |
| } |
| |
| /** |
| * Looks through the resources at <code>rsrc</code> for a configuration |
| * file that matches <code>name</code> (or an unnamed one if |
| * <code>name</code> is <code>null</code>), and loads the XML in the |
| * resource into a new {@link PersistenceUnitInfo}. Then, applies the |
| * overrides in <code>m</code>. |
| * |
| * @return {@link Boolean#TRUE} if the resource was loaded, null if it |
| * does not exist, or {@link Boolean#FALSE} if it is not for OpenJPA |
| */ |
| private Boolean load(ConfigurationProviderImpl cp, String rsrc, |
| String name, Map m, ClassLoader loader, boolean explicit) |
| throws IOException { |
| if (loader == null) |
| loader = (ClassLoader) AccessController.doPrivileged( |
| J2DoPrivHelper.getContextClassLoaderAction()); |
| |
| List<URL> urls = getResourceURLs(rsrc, loader); |
| if (urls == null || urls.size() == 0) |
| return null; |
| |
| ConfigurationParser parser = new ConfigurationParser(m); |
| PersistenceUnitInfoImpl pinfo = parseResources(parser, urls, name, |
| loader); |
| if (pinfo == null) { |
| if (!explicit) |
| return Boolean.FALSE; |
| throw new MissingResourceException(_loc.get("missing-xml-config", |
| rsrc, String.valueOf(name)).getMessage(), getClass().getName(), |
| rsrc); |
| } else if (!isOpenJPAPersistenceProvider(pinfo, loader)) { |
| if (!explicit) { |
| warnUnknownProvider(pinfo); |
| return Boolean.FALSE; |
| } |
| throw new MissingResourceException(_loc.get("unknown-provider", |
| rsrc, name, pinfo.getPersistenceProviderClassName()). |
| getMessage(), getClass().getName(), rsrc); |
| } |
| cp.addProperties(pinfo.toOpenJPAProperties()); |
| cp.setSource(pinfo.getPersistenceXmlFileUrl().toString()); |
| return Boolean.TRUE; |
| } |
| |
| /** |
| * Parse resources at the given location. Searches for a |
| * PersistenceUnitInfo with the requested name, or an OpenJPA unit if |
| * no name given (preferring an unnamed OpenJPA unit to a named one). |
| */ |
| private PersistenceUnitInfoImpl parseResources(ConfigurationParser parser, |
| List<URL> urls, String name, ClassLoader loader) |
| throws IOException { |
| List<PersistenceUnitInfoImpl> pinfos = |
| new ArrayList<PersistenceUnitInfoImpl>(); |
| for (URL url : urls) { |
| parser.parse(url); |
| pinfos.addAll((List<PersistenceUnitInfoImpl>) parser.getResults()); |
| } |
| return findUnit(pinfos, name, loader); |
| } |
| |
| /** |
| * Find the unit with the given name, or an OpenJPA unit if no name is |
| * given (preferring an unnamed OpenJPA unit to a named one). |
| */ |
| private PersistenceUnitInfoImpl findUnit(List<PersistenceUnitInfoImpl> |
| pinfos, String name, ClassLoader loader) { |
| PersistenceUnitInfoImpl ojpa = null; |
| for (PersistenceUnitInfoImpl pinfo : pinfos) { |
| // found named unit? |
| if (name != null) { |
| if (name.equals(pinfo.getPersistenceUnitName())) |
| return pinfo; |
| continue; |
| } |
| |
| if (isOpenJPAPersistenceProvider(pinfo, loader)) { |
| // if no name given and found unnamed unit, return it. |
| // otherwise record as default unit unless we find a |
| // better match later |
| if (StringUtils.isEmpty(pinfo.getPersistenceUnitName())) |
| return pinfo; |
| if (ojpa == null) |
| ojpa = pinfo; |
| } |
| } |
| return ojpa; |
| } |
| |
| /** |
| * Return whether the given persistence unit uses an OpenJPA provider. |
| */ |
| private static boolean isOpenJPAPersistenceProvider |
| (PersistenceUnitInfo pinfo, ClassLoader loader) { |
| String provider = pinfo.getPersistenceProviderClassName(); |
| if (StringUtils.isEmpty(provider) |
| || PersistenceProviderImpl.class.getName().equals(provider)) |
| return true; |
| |
| if (loader == null) |
| loader = (ClassLoader) AccessController.doPrivileged( |
| J2DoPrivHelper.getContextClassLoaderAction()); |
| try { |
| if (PersistenceProviderImpl.class.isAssignableFrom |
| (Class.forName(provider, false, loader))) |
| return true; |
| } catch (Throwable t) { |
| log(_loc.get("unloadable-provider", provider, t).getMessage()); |
| return false; |
| } |
| return false; |
| } |
| |
| /** |
| * Warn the user that we could only find an unrecognized persistence |
| * provider. |
| */ |
| private static void warnUnknownProvider(PersistenceUnitInfo pinfo) { |
| log(_loc.get("unrecognized-provider", |
| pinfo.getPersistenceProviderClassName()).getMessage()); |
| } |
| |
| /** |
| * Log a message. |
| */ |
| private static void log(String msg) { |
| // at this point logging isn't configured yet |
| System.err.println(msg); |
| } |
| |
| public static void setDefaultPersistenceFiles(List<URL> urls) { |
| defaultPersistenceFiles = urls; |
| } |
| |
| /** |
| * Custom configuration provider. |
| */ |
| public static class ConfigurationProviderImpl |
| extends MapConfigurationProvider { |
| |
| private String _source; |
| |
| public ConfigurationProviderImpl() { |
| } |
| |
| public ConfigurationProviderImpl(Map props) { |
| super(props); |
| } |
| |
| /** |
| * Set the source of information in this provider. |
| */ |
| public void setSource(String source) { |
| _source = source; |
| } |
| |
| @Override |
| public void setInto(Configuration conf) { |
| if (conf instanceof OpenJPAConfiguration) { |
| OpenJPAConfiguration oconf = (OpenJPAConfiguration) conf; |
| oconf.setSpecification(SPEC_JPA); |
| |
| // we merge several persistence.xml elements into the |
| // MetaDataFactory property implicitly. if the user has a |
| // global openjpa.xml with this property set, its value will |
| // get overwritten by our implicit setting. so instead, combine |
| // the global value with our settings |
| String orig = oconf.getMetaDataFactory(); |
| if (!StringUtils.isEmpty(orig)) { |
| String key = ProductDerivations.getConfigurationKey |
| ("MetaDataFactory", getProperties()); |
| Object override = getProperties().get(key); |
| if (override instanceof String) |
| addProperty(key, Configurations.combinePlugins(orig, |
| (String) override)); |
| } |
| } |
| |
| super.setInto(conf, null); |
| Log log = conf.getConfigurationLog(); |
| if (log.isTraceEnabled()) { |
| String src = (_source == null) ? "?" : _source; |
| log.trace(_loc.get("conf-load", src, getProperties())); |
| } |
| } |
| } |
| |
| /** |
| * SAX handler capable of parsing an JPA persistence.xml file. |
| * Package-protected for testing. |
| */ |
| public static class ConfigurationParser |
| extends XMLMetaDataParser { |
| |
| private final Map _map; |
| private PersistenceUnitInfoImpl _info = null; |
| private URL _source = null; |
| |
| public ConfigurationParser(Map map) { |
| _map = map; |
| setCaching(false); |
| setValidating(true); |
| setParseText(true); |
| } |
| |
| @Override |
| public void parse(URL url) |
| throws IOException { |
| _source = url; |
| super.parse(url); |
| } |
| |
| @Override |
| public void parse(File file) |
| throws IOException { |
| try { |
| _source = (URL) AccessController.doPrivileged(J2DoPrivHelper |
| .toURLAction(file)); |
| } catch (PrivilegedActionException pae) { |
| throw (MalformedURLException) pae.getException(); |
| } |
| super.parse(file); |
| } |
| |
| @Override |
| protected Object getSchemaSource() { |
| return getClass().getResourceAsStream("persistence-xsd.rsrc"); |
| } |
| |
| @Override |
| protected void reset() { |
| super.reset(); |
| _info = null; |
| _source = null; |
| } |
| |
| protected boolean startElement(String name, Attributes attrs) |
| throws SAXException { |
| if (currentDepth() == 1) |
| startPersistenceUnit(attrs); |
| else if (currentDepth() == 3 && "property".equals(name)) |
| _info.setProperty(attrs.getValue("name"), |
| attrs.getValue("value")); |
| return true; |
| } |
| |
| protected void endElement(String name) |
| throws SAXException { |
| if (currentDepth() == 1) { |
| _info.fromUserProperties(_map); |
| addResult(_info); |
| } |
| if (currentDepth() != 2) |
| return; |
| |
| switch (name.charAt(0)) { |
| case 'c': // class |
| _info.addManagedClassName(currentText()); |
| case 'e': // exclude-unlisted-classes |
| _info.setExcludeUnlistedClasses("true".equalsIgnoreCase |
| (currentText())); |
| break; |
| case 'j': |
| if ("jta-data-source".equals(name)) |
| _info.setJtaDataSourceName(currentText()); |
| else // jar-file |
| { |
| try { |
| _info.addJarFileName(currentText()); |
| } catch (IllegalArgumentException iae) { |
| throw getException(iae.getMessage()); |
| } |
| } |
| break; |
| case 'm': // mapping-file |
| _info.addMappingFileName(currentText()); |
| break; |
| case 'n': // non-jta-data-source |
| _info.setNonJtaDataSourceName(currentText()); |
| break; |
| case 'p': |
| if ("provider".equals(name)) |
| _info.setPersistenceProviderClassName(currentText()); |
| break; |
| } |
| } |
| |
| /** |
| * Parse persistence-unit element. |
| */ |
| private void startPersistenceUnit(Attributes attrs) |
| throws SAXException { |
| _info = new PersistenceUnitInfoImpl(); |
| _info.setPersistenceUnitName(attrs.getValue("name")); |
| |
| // we only parse this ourselves outside a container, so default |
| // transaction type to local |
| String val = attrs.getValue("transaction-type"); |
| if (val == null) |
| _info.setTransactionType |
| (PersistenceUnitTransactionType.RESOURCE_LOCAL); |
| else |
| _info.setTransactionType(Enum.valueOf |
| (PersistenceUnitTransactionType.class, val)); |
| |
| if (_source != null) |
| _info.setPersistenceXmlFileUrl(_source); |
| } |
| } |
| } |
| |