/* * ============================================================================ * GNU Lesser General Public License * ============================================================================ * * Beanlet - JSE Application Container. * Copyright (C) 2006 Leon van Zantvoort * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Leon van Zantvoort * 243 Acalanes Drive #11 * Sunnyvale, CA 94086 * USA * * zantvoort@users.sourceforge.net * http://beanlet.org */ package org.beanlet.persistence.impl; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Logger; import javax.persistence.PersistenceException; import javax.persistence.SharedCacheMode; import javax.persistence.ValidationMode; import javax.persistence.spi.PersistenceUnitTransactionType; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.jargo.ComponentUnit; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * * @author Leon van Zantvoort */ public final class BeanletPersistenceUnitInfoFactory { private static final Logger logger; private static final Method FIND_RESOURCES; private static final XPath xpath; private static final XPathExpression TRANSACTION_TYPE; private static final XPathExpression EXCLUDE_UNLISTED_CLASSES; private static final XPathExpression JAR_FILE_URLS; private static final XPathExpression JTA_DATA_SOURCE; private static final XPathExpression MANAGED_CLASS_NAMES; private static final XPathExpression MAPPING_FILE_NAMES; private static final XPathExpression NON_JTA_DATA_SOURCE; private static final XPathExpression PERSISTENCE_PROVIDER_CLASSNAME; private static final XPathExpression PERSISTENCE_UNIT_NAME; private static final XPathExpression PROPERTIES; private static final XPathExpression VERSION; private static final XPathExpression SHARED_CACHE_MODE; private static final XPathExpression VALIDATION_MODE; private static final Map<ClassLoader, BeanletPersistenceUnitInfoFactory> factories; static { try { logger = Logger.getLogger( BeanletPersistenceUnitInfoFactory.class.getName()); try { FIND_RESOURCES = AccessController.doPrivileged( new PrivilegedExceptionAction<Method>() { public Method run() throws Exception { // PENDING: alternative: subclassing ClassLoader? // PERMISSION: java.lang.RuntimePermission accessDeclaredMembers Method method = ClassLoader.class. getDeclaredMethod("findResources", String.class); // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks method.setAccessible(true); return method; } }); } catch (PrivilegedActionException e) { throw e.getException(); } xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(new SimpleNamespaceContext()); try { TRANSACTION_TYPE = xpath.compile("@transaction-type"); EXCLUDE_UNLISTED_CLASSES = xpath.compile("./:exclude-unlisted-classes/text()"); JAR_FILE_URLS = xpath.compile("./:jar-file/text()"); JTA_DATA_SOURCE = xpath.compile("./:jta-data-source/text()"); MANAGED_CLASS_NAMES = xpath.compile("./:class/text()"); MAPPING_FILE_NAMES = xpath.compile("./:mapping-file/text()"); NON_JTA_DATA_SOURCE = xpath.compile("./:non-jta-data-source/text()"); PERSISTENCE_PROVIDER_CLASSNAME = xpath.compile("./:provider/text()"); PERSISTENCE_UNIT_NAME = xpath.compile("@name"); PROPERTIES = xpath.compile("./:properties/:property"); VERSION = xpath.compile("@version"); SHARED_CACHE_MODE = xpath.compile("./:shared-cache-mode/text()"); VALIDATION_MODE = xpath.compile("./:validation-mode/text()"); } catch (XPathExpressionException e) { throw new AssertionError(e); } factories = new HashMap<ClassLoader, BeanletPersistenceUnitInfoFactory>(); } catch (Exception e) { throw new AssertionError(e); } } public static synchronized BeanletPersistenceUnitInfoFactory getInstance( final ComponentUnit componentUnit) { BeanletPersistenceUnitInfoFactory factory = factories.get( componentUnit.getClassLoader()); if (factory == null) { factory = new BeanletPersistenceUnitInfoFactory(componentUnit); factories.put(componentUnit.getClassLoader(), factory); componentUnit.addDestroyHook(new Runnable() { public void run() { synchronized (BeanletPersistenceUnitInfoFactory.class) { factories.remove(componentUnit.getClassLoader()); } } }); } return factory; } private ComponentUnit componentUnit; private ConcurrentMap<String, BeanletPersistenceUnitInfo> cache; private BeanletPersistenceUnitInfoFactory(ComponentUnit componentUnit) { this.componentUnit = componentUnit; this.cache = new ConcurrentHashMap<String, BeanletPersistenceUnitInfo>(); } public BeanletPersistenceUnitInfo getPersistenceUnitInfo( String persistenceUnitName) throws PersistenceException { BeanletPersistenceUnitInfo unitInfo = cache.get(persistenceUnitName); if (unitInfo == null) { try { @SuppressWarnings("unchecked") Enumeration<URL> urls = (Enumeration<URL>) FIND_RESOURCES. invoke(componentUnit.getClassLoader(), "META-INF/persistence.xml"); DocumentBuilderFactory factory = DocumentBuilderFactory. newInstance(); factory.setAttribute( "http://java.sun.com/xml/jaxp/properties/schemaLanguage", XMLConstants.W3C_XML_SCHEMA_NS_URI); factory.setNamespaceAware(true); factory.setValidating(true); // Validates the document according to the schema definition. DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(new SimpleEntityResolver()); builder.setErrorHandler(new SimpleErrorHandler()); List<Node> nodes = new ArrayList<Node>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); logger.finest("Looking up persistence unit info '" + persistenceUnitName + "' at " + url.toExternalForm() + "."); URLConnection connection = url.openConnection(); connection.setUseCaches(false); InputStream stream = new BufferedInputStream(connection.getInputStream()); try { Document document = builder.parse(stream, url.toExternalForm()); final Node node; if (persistenceUnitName == null || persistenceUnitName.equals("")) { NodeList tmp = (NodeList) xpath.evaluate( "/:persistence/:persistence-unit", document, XPathConstants.NODESET); if (tmp.getLength() == 0) { continue; } else if (tmp.getLength() > 1) { throw new PersistenceException("Multiple persistence units found."); } else { node = tmp.item(0); } } else { node = (Node) xpath.evaluate( "/:persistence/:persistence-unit[@name='" + persistenceUnitName + "']", document, XPathConstants.NODE); } if (node != null) { // Alternative for ComponentUnit.getURL(). // if (connection instanceof JarURLConnection) { // rootUrl = ((JarURLConnection) connection). // getJarFileURL(); // } else { // rootUrl = url.toURI().resolve("..").toURL(); // } logger.finest("Found persistence unit info '" + persistenceUnitName + "' at " + componentUnit.getURL() + "."); nodes.add(node); break; } } finally { stream.close(); } } if (nodes.isEmpty()) { if (persistenceUnitName == null || persistenceUnitName.equals("")) { throw new PersistenceException("No persistence unit found."); } else { throw new PersistenceException("No persistence unit " + "found for '" + persistenceUnitName + "'."); } } else if (nodes.size() > 1) { if (persistenceUnitName == null || persistenceUnitName.equals("")) { throw new PersistenceException("Multiple persistence units found."); } else { throw new PersistenceException("Multiple persistence units " + "found for '" + persistenceUnitName + "'."); } } unitInfo = buildPersistenceUnitInfo(nodes.get(0)); cache.putIfAbsent(persistenceUnitName, unitInfo); } catch (PersistenceException e) { throw e; } catch (InvocationTargetException e) { throw new PersistenceException(e.getTargetException()); } catch (Exception e) { throw new PersistenceException(e); } } unitInfo = cache.get(persistenceUnitName); assert unitInfo != null; return unitInfo; } private BeanletPersistenceUnitInfo buildPersistenceUnitInfo( Node node) throws PersistenceException { final PersistenceUnitTransactionType transactionType; final boolean excludeUnlistedClasses; final ClassLoader classLoader = componentUnit.getClassLoader(); final List<URL> jarFileUrls; final String jtaDataSource; final List<String> managedClassNames; final List<String> mappingFileNames; final ClassLoader newTempClassLoader = new ClassLoader(componentUnit.getClassLoader()){}; final String nonJtaDataSource; final String persistenceProviderClassName; final String persistenceUnitName; final URL persistenceUnitRootUrl = componentUnit.getURL(); final Properties properties; final String persistenceXMLSchemaVersion; final SharedCacheMode sharedCacheMode; final ValidationMode validationMode; try { Node transactionTypeNode = (Node) TRANSACTION_TYPE. evaluate(node, XPathConstants.NODE); if (transactionTypeNode == null) { transactionType = PersistenceUnitTransactionType.JTA; } else { transactionType = PersistenceUnitTransactionType.valueOf( transactionTypeNode.getNodeValue()); } Node excludeUnlistedClassesNode = (Node) EXCLUDE_UNLISTED_CLASSES. evaluate(node, XPathConstants.NODE); if (excludeUnlistedClassesNode == null) { excludeUnlistedClasses = false; } else { excludeUnlistedClasses = Boolean.valueOf( excludeUnlistedClassesNode.getNodeValue()); } List<URL> tmpJarFileUrls = new ArrayList<URL>(); NodeList jarFileUrlsList = (NodeList) JAR_FILE_URLS. evaluate(node, XPathConstants.NODESET); for (int i = 0; i < jarFileUrlsList.getLength(); i++) { Node tmp = jarFileUrlsList.item(i); tmpJarFileUrls.add(new URL(tmp.getNodeValue())); } jarFileUrls = Collections.unmodifiableList(tmpJarFileUrls); String tmpJtaDataSource = (String) JTA_DATA_SOURCE.evaluate(node, XPathConstants.STRING); jtaDataSource = tmpJtaDataSource.equals("") ? null : tmpJtaDataSource; List<String> tmpManagedClassNames = new ArrayList<String>(); NodeList managedClassNamesList = (NodeList) MANAGED_CLASS_NAMES. evaluate(node, XPathConstants.NODESET); for (int i = 0; i < managedClassNamesList.getLength(); i++) { Node tmp = managedClassNamesList.item(i); tmpManagedClassNames.add(tmp.getNodeValue()); } managedClassNames = Collections.unmodifiableList(tmpManagedClassNames); List<String> tmpMappingFileNames = new ArrayList<String>(); NodeList mappingFileNamesList = (NodeList) MAPPING_FILE_NAMES. evaluate(node, XPathConstants.NODESET); for (int i = 0; i < mappingFileNamesList.getLength(); i++) { Node tmp = mappingFileNamesList.item(i); tmpMappingFileNames.add(tmp.getNodeValue()); } mappingFileNames = Collections.unmodifiableList(tmpMappingFileNames); String tmpNonJtaDataSource = (String) NON_JTA_DATA_SOURCE. evaluate(node, XPathConstants.STRING); nonJtaDataSource = tmpNonJtaDataSource.equals("") ? null : tmpNonJtaDataSource; String tmpPersistenceProviderClassName = (String) PERSISTENCE_PROVIDER_CLASSNAME. evaluate(node, XPathConstants.STRING); persistenceProviderClassName = tmpPersistenceProviderClassName.equals("") ? null : tmpPersistenceProviderClassName; persistenceUnitName = (String) PERSISTENCE_UNIT_NAME.evaluate(node, XPathConstants.STRING); properties = new Properties(); NodeList propertiesList = (NodeList) PROPERTIES.evaluate(node, XPathConstants.NODESET); for (int i = 0; i < propertiesList.getLength(); i++) { Node tmp = propertiesList.item(i); String name = (String) xpath.evaluate("@name", tmp, XPathConstants.STRING); String value = (String) xpath.evaluate("@value", tmp, XPathConstants.STRING); properties.setProperty(name, value); } Node versionNode = (Node) VERSION. evaluate(node, XPathConstants.NODE); if (versionNode == null) { persistenceXMLSchemaVersion = "2.0"; } else { persistenceXMLSchemaVersion = versionNode.getNodeValue(); } Node sharedCacheModeNode = (Node) SHARED_CACHE_MODE. evaluate(node, XPathConstants.NODE); if (sharedCacheModeNode == null) { sharedCacheMode = SharedCacheMode.ENABLE_SELECTIVE; } else { sharedCacheMode = SharedCacheMode.valueOf(sharedCacheModeNode.getNodeValue()); } Node validationModeNode = (Node) VALIDATION_MODE. evaluate(node, XPathConstants.NODE); if (validationModeNode == null) { validationMode = ValidationMode.AUTO; } else { validationMode = ValidationMode.valueOf(validationModeNode.getNodeValue()); } } catch (PersistenceException e) { throw e; } catch (Exception e) { throw new PersistenceException(e); } // PENDING: introduce these checks? // if (transactionType == PersistenceUnitTransactionType.JTA) { // if (jtaDataSource == null) { // throw new PersistenceException("JTA persistence unit '" + // persistenceUnitName + "' does not specify JTA data " + // "source."); // } // } else { // if (nonJtaDataSource == null) { // throw new PersistenceException("RESOURCE_LOCAL persistence unit '" + // persistenceUnitName + "' does not specify non JTA data " + // "source."); // } // } return new BeanletPersistenceUnitInfoImpl( transactionType, excludeUnlistedClasses, classLoader, jarFileUrls, jtaDataSource, managedClassNames, mappingFileNames, newTempClassLoader, nonJtaDataSource, persistenceProviderClassName, persistenceUnitName, persistenceUnitRootUrl, properties, persistenceXMLSchemaVersion, sharedCacheMode, validationMode ); } private class SimpleEntityResolver implements EntityResolver { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { try { final InputSource source; URI uri = new URI(systemId); String path = uri.getPath(); if (path != null) { if (path.startsWith("/")) { path = path.substring(1); } final String p = path; InputStream stream = AccessController. doPrivileged(new PrivilegedAction<InputStream>() { public InputStream run() { return componentUnit.getClassLoader(). getResourceAsStream(p); } }); if (stream != null) { source = new InputSource(stream); } else { source = new InputSource(systemId); } } else { source = new InputSource(systemId); } source.setPublicId(publicId); return source; } catch (URISyntaxException e) { throw new SAXException(e); } } } private class SimpleErrorHandler implements ErrorHandler { public void error(SAXParseException e) throws SAXException { throw e; } public void fatalError(SAXParseException e) throws SAXException { throw e; } public void warning(SAXParseException e) throws SAXException { logger.warning(e.getMessage()); } } private static class SimpleNamespaceContext implements NamespaceContext { public String getNamespaceURI(String prefix) { return "http://java.sun.com/xml/ns/persistence"; } public String getPrefix(String namespaceURI) { return XMLConstants.DEFAULT_NS_PREFIX; } public Iterator getPrefixes(String namespaceURI) { return Collections.singleton( XMLConstants.DEFAULT_NS_PREFIX).iterator(); } }; }