| /* |
| * 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.axiom.util.stax.dialect; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.jar.Attributes; |
| import java.util.jar.Manifest; |
| |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLOutputFactory; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * Detects StAX dialects and normalizes factories for a given StAX implementation. |
| * <p> |
| * Note that this class internally maintains a cache of detected dialects. The overhead caused by |
| * invocations of methods in this class is thus small. |
| */ |
| public class StAXDialectDetector { |
| private static final Log log = LogFactory.getLog(StAXDialectDetector.class); |
| |
| private static final Attributes.Name IMPLEMENTATION_TITLE = |
| new Attributes.Name("Implementation-Title"); |
| |
| private static final Attributes.Name IMPLEMENTATION_VENDOR = |
| new Attributes.Name("Implementation-Vendor"); |
| |
| private static final Attributes.Name IMPLEMENTATION_VERSION = |
| new Attributes.Name("Implementation-Version"); |
| |
| private static final Attributes.Name BUNDLE_SYMBOLIC_NAME = |
| new Attributes.Name("Bundle-SymbolicName"); |
| |
| private static final Attributes.Name BUNDLE_VENDOR = |
| new Attributes.Name("Bundle-Vendor"); |
| |
| private static final Attributes.Name BUNDLE_VERSION = |
| new Attributes.Name("Bundle-Version"); |
| |
| /** |
| * Map that stores detected dialects by location. The location is the URL corresponding to the |
| * root folder of the classpath entry from which the StAX implementation is loaded. Note that |
| * in the case of a JAR file, this is not the URL pointing to the JAR, but a <tt>jar:</tt> |
| * URL that points to the root folder of the archive. |
| */ |
| private static final Map/*<URL,StAXDialect>*/ dialectByUrl = |
| Collections.synchronizedMap(new HashMap()); |
| |
| private StAXDialectDetector() {} |
| |
| /** |
| * Get the URL corresponding to the root folder of the classpath entry from which a given |
| * resource is loaded. This URL can be used to load other resources from the same classpath |
| * entry (JAR file or directory). |
| * |
| * @return the root URL or <code>null</code> if the resource can't be found or if it is not |
| * possible to determine the root URL |
| */ |
| private static URL getRootUrlForResource(ClassLoader classLoader, String resource) { |
| if (classLoader == null) { |
| // A null class loader means the bootstrap class loader. In this case we use the |
| // system class loader. This is safe since we can assume that the system class |
| // loader uses parent first as delegation policy. |
| classLoader = ClassLoader.getSystemClassLoader(); |
| } |
| URL url = classLoader.getResource(resource); |
| if (url == null) { |
| return null; |
| } |
| String file = url.getFile(); |
| if (file.endsWith(resource)) { |
| try { |
| return new URL(url.getProtocol(), url.getHost(), url.getPort(), |
| file.substring(0, file.length()-resource.length())); |
| } catch (MalformedURLException ex) { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| private static URL getRootUrlForClass(Class cls) { |
| return getRootUrlForResource(cls.getClassLoader(), |
| cls.getName().replace('.', '/') + ".class"); |
| } |
| |
| /** |
| * Detect the dialect of a given {@link XMLInputFactory} and normalize it. |
| * |
| * @param factory the factory to normalize |
| * @return the normalized factory |
| * |
| * @see StAXDialect#normalize(XMLInputFactory) |
| */ |
| public static XMLInputFactory normalize(XMLInputFactory factory) { |
| return getDialect(factory.getClass()).normalize(factory); |
| } |
| |
| /** |
| * Detect the dialect of a given {@link XMLOutputFactory} and normalize it. |
| * |
| * @param factory the factory to normalize |
| * @return the normalized factory |
| * |
| * @see StAXDialect#normalize(XMLOutputFactory) |
| */ |
| public static XMLOutputFactory normalize(XMLOutputFactory factory) { |
| return getDialect(factory.getClass()).normalize(factory); |
| } |
| |
| /** |
| * Detect the dialect of a given StAX implementation. |
| * |
| * @param implementationClass |
| * any class that is part of the StAX implementation; typically this should be a |
| * {@link XMLInputFactory}, {@link XMLOutputFactory}, |
| * {@link javax.xml.stream.XMLStreamReader} or |
| * {@link javax.xml.stream.XMLStreamWriter} implementation |
| * @return the detected dialect |
| */ |
| public static StAXDialect getDialect(Class implementationClass) { |
| URL rootUrl = getRootUrlForClass(implementationClass); |
| if (rootUrl == null) { |
| log.warn("Unable to determine location of StAX implementation containing class " |
| + implementationClass.getName() + "; using default dialect"); |
| return UnknownStAXDialect.INSTANCE; |
| } |
| return getDialect(implementationClass.getClassLoader(), rootUrl); |
| } |
| |
| private static StAXDialect getDialect(ClassLoader classLoader, URL rootUrl) { |
| StAXDialect dialect = (StAXDialect)dialectByUrl.get(rootUrl); |
| if (dialect != null) { |
| return dialect; |
| } else { |
| dialect = detectDialect(classLoader, rootUrl); |
| dialectByUrl.put(rootUrl, dialect); |
| return dialect; |
| } |
| } |
| |
| private static StAXDialect detectDialect(ClassLoader classLoader, URL rootUrl) { |
| StAXDialect dialect = detectDialectFromJarManifest(rootUrl); |
| if (dialect == null) { |
| // Note: We look for well defined classes instead of just checking the package name |
| // of the class passed to getDialect(Class) because in some parsers, the implementations |
| // of the StAX interfaces (factories, readers and writers) are not in the same package. |
| dialect = detectDialectFromClasses(classLoader, rootUrl); |
| } |
| if (dialect == null) { |
| log.warn("Unable to determine dialect of the StAX implementation at " + rootUrl); |
| return UnknownStAXDialect.INSTANCE; |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Detected StAX dialect: " + dialect.getName()); |
| } |
| return dialect; |
| } |
| } |
| |
| private static StAXDialect detectDialectFromJarManifest(URL rootUrl) { |
| Manifest manifest; |
| try { |
| URL metaInfUrl = new URL(rootUrl, "META-INF/MANIFEST.MF"); |
| InputStream is = metaInfUrl.openStream(); |
| try { |
| manifest = new Manifest(is); |
| } finally { |
| is.close(); |
| } |
| } catch (IOException ex) { |
| log.warn("Unable to load manifest for StAX implementation at " + rootUrl); |
| return UnknownStAXDialect.INSTANCE; |
| } |
| Attributes attrs = manifest.getMainAttributes(); |
| String title = attrs.getValue(IMPLEMENTATION_TITLE); |
| String symbolicName = attrs.getValue(BUNDLE_SYMBOLIC_NAME); |
| if (symbolicName != null) { |
| int i = symbolicName.indexOf(';'); |
| if (i != -1) { |
| symbolicName = symbolicName.substring(0, i); |
| } |
| } |
| String vendor = attrs.getValue(IMPLEMENTATION_VENDOR); |
| if (vendor == null) { |
| vendor = attrs.getValue(BUNDLE_VENDOR); |
| } |
| String versionString = attrs.getValue(IMPLEMENTATION_VERSION); |
| if (versionString == null) { |
| versionString = attrs.getValue(BUNDLE_VERSION); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug("StAX implementation at " + rootUrl + " is:\n" + |
| " Title: " + title + "\n" + |
| " Symbolic name: " + symbolicName + "\n" + |
| " Vendor: " + vendor + "\n" + |
| " Version: " + versionString); |
| } |
| |
| // For the moment, the dialect detection is quite simple, but in the future we will probably |
| // have to differentiate by version number |
| if (vendor != null && vendor.toLowerCase().indexOf("woodstox") != -1) { |
| Version version = new Version(versionString); |
| switch (version.getComponent(0)) { |
| case 3: |
| return Woodstox3Dialect.INSTANCE; |
| case 4: |
| return new Woodstox4Dialect(version.getComponent(1) == 0 && version.getComponent(2) < 11 |
| || version.getComponent(1) == 1 && version.getComponent(2) < 3); |
| default: |
| return null; |
| } |
| } else if (title != null && title.indexOf("SJSXP") != -1) { |
| return new SJSXPDialect(false); |
| } else if ("com.bea.core.weblogic.stax".equals(symbolicName)) { |
| // Weblogic's StAX implementation doesn't support CDATA section reporting and there are |
| // a couple of additional test cases (with respect to BEA's reference implementation) |
| // that fail. |
| log.warn("Weblogic's StAX implementation is unsupported and some Axiom features will not work " + |
| "as expected! Please use Woodstox instead."); |
| // This is the best match we can return in this case. |
| return BEADialect.INSTANCE; |
| } else if ("BEA".equals(vendor)) { |
| return BEADialect.INSTANCE; |
| } else if ("com.ibm.ws.prereq.banshee".equals(symbolicName)) { |
| return XLXP2Dialect.INSTANCE; |
| } else { |
| return null; |
| } |
| } |
| |
| private static Class loadClass(ClassLoader classLoader, URL rootUrl, String name) { |
| try { |
| if (classLoader == null) { |
| classLoader = ClassLoader.getSystemClassLoader(); |
| } |
| Class cls = classLoader.loadClass(name); |
| // Cross check if the class was loaded from the same location (JAR) |
| return rootUrl.equals(getRootUrlForClass(cls)) ? cls : null; |
| } catch (ClassNotFoundException ex) { |
| return null; |
| } |
| } |
| |
| private static StAXDialect detectDialectFromClasses(ClassLoader classLoader, URL rootUrl) { |
| Class cls; |
| |
| // Try Sun's implementation found in JREs |
| cls = loadClass(classLoader, rootUrl, "com.sun.xml.internal.stream.XMLOutputFactoryImpl"); |
| if (cls != null) { |
| // Check if the implementation has the bug fixed here: |
| // https://sjsxp.dev.java.net/source/browse/sjsxp/zephyr/src/com/sun/xml/stream/ZephyrWriterFactory.java?rev=1.8&r1=1.4&r2=1.5 |
| boolean isUnsafeStreamResult; |
| try { |
| cls.getDeclaredField("fStreamResult"); |
| isUnsafeStreamResult = true; |
| } catch (NoSuchFieldException ex) { |
| isUnsafeStreamResult = false; |
| } |
| return new SJSXPDialect(isUnsafeStreamResult); |
| } |
| |
| // Try IBM's XL XP-J |
| cls = loadClass(classLoader, rootUrl, "com.ibm.xml.xlxp.api.stax.StAXImplConstants"); |
| if (cls != null) { |
| boolean isSetPrefixBroken; |
| try { |
| cls.getField("IS_SETPREFIX_BEFORE_STARTELEMENT"); |
| isSetPrefixBroken = false; |
| } catch (NoSuchFieldException ex) { |
| isSetPrefixBroken = true; |
| } |
| return new XLXP1Dialect(isSetPrefixBroken); |
| } |
| cls = loadClass(classLoader, rootUrl, "com.ibm.xml.xlxp2.api.stax.StAXImplConstants"); |
| if (cls != null) { |
| return new XLXP2Dialect(); |
| } |
| |
| return null; |
| } |
| } |