/*
* Copyright 2010 Impetus Infotech.
*
* 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.
*/
package com.impetus.kundera.ejb;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
/**
* Class that loads persistence.xml files
*
* @author animesh.kumar
*
*/
public final class PersistenceXmlLoader {
/** The log. */
private static Log log = LogFactory.getLog( PersistenceXmlLoader.class );
/**
* Instantiates a new persistence xml loader.
*/
private PersistenceXmlLoader() {
}
/**
* Gets the document.
*
* @param configURL
* the config url
* @return the document
* @throws Exception
* the exception
*/
private static Document getDocument (URL configURL) throws Exception {
InputStream is = null;
if (configURL != null) {
URLConnection conn = configURL.openConnection();
conn.setUseCaches(false); // avoid JAR locking on Windows and Tomcat
is = conn.getInputStream();
}
if (is == null) {
throw new IOException("Failed to obtain InputStream from url: "
+ configURL);
}
DocumentBuilderFactory docBuilderFactory = null;
docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setValidating( true );
docBuilderFactory.setNamespaceAware( true );
try {
// otherwise Xerces fails in validation
docBuilderFactory.setAttribute("http://apache.org/xml/features/validation/schema", true);
} catch (IllegalArgumentException e) {
docBuilderFactory.setValidating(false);
docBuilderFactory.setNamespaceAware(false);
}
InputSource source = new InputSource(is);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
// docBuilder.setEntityResolver( resolver );
List errors = new ArrayList();
docBuilder.setErrorHandler( new ErrorLogger( "XML InputStream", errors) );
Document doc = docBuilder.parse(source);
if (errors.size() != 0) {
throw new PersistenceException("invalid persistence.xml", (Throwable) errors.get(0));
}
is.close(); //Close input Stream
return doc;
}
/**
* Find persistence units.
*
* @param url
* the url
* @return the list
* @throws Exception
* the exception
*/
public static List<PersistenceMetadata> findPersistenceUnits (URL url) throws Exception {
return findPersistenceUnits (url, PersistenceUnitTransactionType.JTA);
}
/**
* Find persistence units.
*
* @param url
* the url
* @param defaultTransactionType
* the default transaction type
* @return the list
* @throws Exception
* the exception
*/
public static List<PersistenceMetadata> findPersistenceUnits (URL url,
PersistenceUnitTransactionType defaultTransactionType) throws Exception {
Document doc = getDocument ( url );
Element top = doc.getDocumentElement();
NodeList children = top.getChildNodes();
ArrayList<PersistenceMetadata> units = new ArrayList<PersistenceMetadata>();
for ( int i = 0; i < children.getLength() ; i++ ) {
if ( children.item( i ).getNodeType() == Node.ELEMENT_NODE ) {
Element element = (Element) children.item( i );
String tag = element.getTagName();
// look for "persistence-unit" element
if ( tag.equals( "persistence-unit" ) ) {
PersistenceMetadata metadata = parsePersistenceUnit( element );
units.add( metadata );
}
}
}
return units;
}
/**
* Parses the persistence unit.
*
* @param top
* the top
* @return the persistence metadata
* @throws Exception
* the exception
*/
private static PersistenceMetadata parsePersistenceUnit(Element top)
throws Exception {
PersistenceMetadata metadata = new PersistenceMetadata();
String puName = top.getAttribute( "name" );
if ( !isEmpty(puName) ) {
log.trace( "Persistent Unit name from persistence.xml: " + puName );
metadata.setName( puName );
}
NodeList children = top.getChildNodes();
for ( int i = 0; i < children.getLength() ; i++ ) {
if ( children.item( i ).getNodeType() == Node.ELEMENT_NODE ) {
Element element = (Element) children.item( i );
String tag = element.getTagName();
if ( tag.equals( "provider" ) ) {
metadata.setProvider( getElementContent( element ) );
}
else if ( tag.equals( "properties" ) ) {
NodeList props = element.getChildNodes();
for ( int j = 0; j < props.getLength() ; j++ ) {
if ( props.item( j ).getNodeType() == Node.ELEMENT_NODE ) {
Element propElement = (Element) props.item( j );
// if element is not "property" then skip
if ( !"property".equals( propElement.getTagName() ) ) {
continue;
}
String propName = propElement.getAttribute( "name" ).trim();
String propValue = propElement.getAttribute( "value" ).trim();
if ( isEmpty(propValue) ) {
propValue = getElementContent( propElement, "" );
}
metadata.getProps().put( propName, propValue );
}
}
}
// Kundera doesn't support "class", "jar-file" and "excluded-unlisted-classes" for now.. but will someday.
// let's parse it for now.
else if ( tag.equals( "class" ) ) {
metadata.getClasses().add( getElementContent( element ) );
}
else if ( tag.equals( "jar-file" ) ) {
metadata.getJarFiles().add( getElementContent( element ) );
}
else if ( tag.equals( "exclude-unlisted-classes" ) ) {
metadata.setExcludeUnlistedClasses( true );
}
}
}
PersistenceUnitTransactionType transactionType = getTransactionType( top.getAttribute( "transaction-type" ) );
if (transactionType != null) {
metadata.setTransactionType( transactionType );
}
return metadata;
}
/**
* Gets the transaction type.
*
* @param elementContent
* the element content
* @return the transaction type
*/
public static PersistenceUnitTransactionType getTransactionType(String elementContent) {
if ( elementContent == null || elementContent.isEmpty() ) {
return null;
}
else if ( elementContent.equalsIgnoreCase( "JTA" ) ) {
return PersistenceUnitTransactionType.JTA;
}
else if ( elementContent.equalsIgnoreCase( "RESOURCE_LOCAL" ) ) {
return PersistenceUnitTransactionType.RESOURCE_LOCAL;
}
else {
throw new PersistenceException( "Unknown TransactionType: " + elementContent );
}
}
/**
* The Class ErrorLogger.
*/
public static class ErrorLogger implements ErrorHandler {
/** The file. */
private String file;
/** The errors. */
private List errors;
/**
* Instantiates a new error logger.
*
* @param file
* the file
* @param errors
* the errors
*/
ErrorLogger(String file, List errors) {
this.file = file;
this.errors = errors;
}
/* @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) */
public void error(SAXParseException error) {
log.error( "Error parsing XML: " + file + '(' + error.getLineNumber() + ") " + error.getMessage() );
errors.add( error );
}
/* @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) */
public void fatalError(SAXParseException error) {
log.error( "Error parsing XML: " + file + '(' + error.getLineNumber() + ") " + error.getMessage() );
errors.add( error );
}
/* @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) */
public void warning(SAXParseException warn) {
log.warn( "Warning parsing XML: " + file + '(' + warn.getLineNumber() + ") " + warn.getMessage() );
}
}
/**
* Checks if is empty.
*
* @param str
* the str
* @return true, if is empty
*/
private static boolean isEmpty (String str) {
return null == str || str.isEmpty();
}
/**
* Gets the element content.
*
* @param element
* the element
* @return the element content
* @throws Exception
* the exception
*/
public static String getElementContent(final Element element)
throws Exception {
return getElementContent(element, null);
}
/**
* Get the content of the given element.
*
* @param element
* The element to get the content for.
* @param defaultStr
* The default to return when there is no content.
* @return The content of the element or the default.
* @throws Exception
* the exception
*/
private static String getElementContent(Element element, String defaultStr)
throws Exception {
if ( element == null ) {
return defaultStr;
}
NodeList children = element.getChildNodes();
StringBuilder result = new StringBuilder("");
for ( int i = 0; i < children.getLength() ; i++ ) {
if ( children.item( i ).getNodeType() == Node.TEXT_NODE ||
children.item( i ).getNodeType() == Node.CDATA_SECTION_NODE ) {
result.append( children.item( i ).getNodeValue() );
}
}
return result.toString().trim();
}
}