/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * Licensed 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.object.jpa.parsing; import static com.orientechnologies.orient.object.jpa.parsing.PersistenceXml.ATTR_SCHEMA_VERSION; import static com.orientechnologies.orient.object.jpa.parsing.PersistenceXml.TAG_PERSISTENCE; import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collection; import java.util.EnumSet; import javax.persistence.PersistenceException; import javax.persistence.spi.PersistenceUnitInfo; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Simple handler for persistence.xml files. */ public final class PersistenceXmlUtil { /** * URI for the JPA persistence namespace */ public static final String PERSISTENCE_NS_URI = "http://java.sun.com/xml/ns/persistence"; private final static SchemaFactory schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI); private final static SAXParserFactory parserFactory = SAXParserFactory.newInstance(); static { parserFactory.setNamespaceAware(true); } /** The persistence xml root */ public static final String PERSISTENCE_XML_ROOT = "META-INF/"; public static final String PERSISTENCE_XML_BASE_NAME = "persistence.xml"; /** The persistence XSD location */ public static final String PERSISTENCE_XSD_DIR = PERSISTENCE_XML_ROOT + "persistence/"; /** The persistence XML location */ public static final String PERSISTENCE_XML = PERSISTENCE_XML_ROOT + PERSISTENCE_XML_BASE_NAME; private PersistenceXmlUtil() { } /** * Parse the persistence.xml files referenced by the URLs in the collection * * @param persistenceXml * @return A collection of parsed persistence units, or null if nothing has been found */ public static PersistenceUnitInfo findPersistenceUnit(String unitName, Collection<? extends PersistenceUnitInfo> units) { if (units == null || unitName == null) { return null; } for (PersistenceUnitInfo unit : units) { if (unitName.equals(unit.getPersistenceUnitName())) { return unit; } } return null; } /** * Parse the persistence.xml files referenced by the URLs in the collection * * @param persistenceXml * @return A collection of parsed persistence units. * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ public static Collection<? extends PersistenceUnitInfo> parse(URL persistenceXml) { InputStream is = null; try { // Buffer the InputStream so we can mark it, though we'll be in // trouble if we have to read more than 8192 characters before finding // the schema! is = new BufferedInputStream(persistenceXml.openStream()); JPAVersion jpaVersion = getSchemaVersion(is); Schema schema = getSchema(jpaVersion); if (schema == null) { throw new PersistenceException("Schema is unknown"); } // Get back to the beginning of the stream is = new BufferedInputStream(persistenceXml.openStream()); parserFactory.setNamespaceAware(true); int endIndex = persistenceXml.getPath().length() - PERSISTENCE_XML_BASE_NAME.length(); URL persistenceXmlRoot = new URL("file://" + persistenceXml.getFile().substring(0, endIndex)); return getPersistenceUnits(is, persistenceXmlRoot, jpaVersion); } catch (Exception e) { throw new PersistenceException("Something goes wrong while parsing persistence.xml", e); } finally { if (is != null) try { is.close(); } catch (IOException e) { // No logging necessary, just consume } } } public static Schema getSchema(JPAVersion version) throws SAXException { String schemaPath = PERSISTENCE_XSD_DIR + version.getFilename(); InputStream inputStream = PersistenceXmlUtil.class.getClassLoader().getResourceAsStream(schemaPath); return schemaFactory.newSchema(new StreamSource(inputStream)); } public static JPAVersion getSchemaVersion(InputStream is) throws ParserConfigurationException, SAXException, IOException { SchemaLocatingHandler schemaHandler = parse(is, new SchemaLocatingHandler()); return JPAVersion.parse(schemaHandler.getVersion()); } public static Collection<? extends PersistenceUnitInfo> getPersistenceUnits(InputStream is, URL xmlRoot, JPAVersion version) throws ParserConfigurationException, SAXException, IOException { JPAHandler handler = new JPAHandler(xmlRoot, version); return parse(is, handler).getPersistenceUnits(); } /** * @param is * - xml file to be validated * @param handler * @return handler for chained calls * @throws ParserConfigurationException * @throws SAXException * @throws IOException */ protected static <T extends DefaultHandler> T parse(InputStream is, T handler) throws ParserConfigurationException, SAXException, IOException { try { SAXParser parser = parserFactory.newSAXParser(); parser.parse(is, handler); } catch (StopSAXParser e) { // This is not really an exception, but a way to work out which // version of the persistence schema to use in validation } return handler; } /** * @param uri * @param element * @param attributes * @return XML Schema Version or null * @throws SAXException */ public static String parseSchemaVersion(String uri, PersistenceXml element, Attributes attributes) throws SAXException { if (PERSISTENCE_NS_URI.equals(uri) && TAG_PERSISTENCE == element) { return attributes.getValue(ATTR_SCHEMA_VERSION.toString()); } return null; } } /** * This parser provides a quick mechanism for determining the JPA schema level, and throws an StopSAXParser with the Schema to * validate with */ class SchemaLocatingHandler extends DefaultHandler { /** The value of the version attribute in the xml */ private String schemaVersion = ""; /** * Create a new SchemaLocatingHandler */ public SchemaLocatingHandler() { } /** * Fist tag of 'persistence.xml' (<persistence>) have to have 'version' attribute * * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, * org.xml.sax.Attributes) */ @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { PersistenceXml element = PersistenceXml.parse((localName == null || localName.isEmpty()) ? name : localName); schemaVersion = PersistenceXmlUtil.parseSchemaVersion(uri, element, attributes); // found, stop parsing if (schemaVersion != null) { throw new StopSAXParser(); } // This should never occurs, however check if contain known tag other than TAG_PERSISTENCE if (TAG_PERSISTENCE != element && EnumSet.allOf(PersistenceXml.class).contains(element)) { throw new PersistenceException("Cannot find schema version attribute in <persistence> tag"); } } /** * @return The version of the JPA schema used */ public String getVersion() { return schemaVersion; } } /** * A convenience mechanism for finding the schema to validate with */ class StopSAXParser extends SAXException { /** This class is serializable */ private static final long serialVersionUID = 6173561761817524327L; }