com.sun.enterprise.deployment.node.SaxParserHandler Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2016-2017] [Payara Foundation and/or its affiliates]
package com.sun.enterprise.deployment.node;
import com.sun.enterprise.deployment.EnvironmentProperty;
import com.sun.enterprise.deployment.util.DOLUtils;
import com.sun.enterprise.deployment.xml.DTDRegistry;
import com.sun.enterprise.deployment.xml.TagNames;
import com.sun.enterprise.util.LocalStringManagerImpl;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PerLookup;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.NamespaceSupport;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import org.glassfish.config.support.TranslatedConfigView;
/**
* This class implements all the callbacks for the SAX Parser in JAXP 1.1
*
* @author Jerome Dochez
* @version
*/
@Service
@PerLookup
public class SaxParserHandler extends DefaultHandler {
public static final String JAXP_SCHEMA_LANGUAGE =
"http://java.sun.com/xml/jaxp/properties/schemaLanguage";
public static final String JAXP_SCHEMA_SOURCE =
"http://java.sun.com/xml/jaxp/properties/schemaSource";
public static final String W3C_XML_SCHEMA =
"http://www.w3.org/2001/XMLSchema";
private static final String TRUE_STR = "true";
private static final String FALSE_STR = "false";
private static final class MappingStuff {
public final ConcurrentMap mBundleRegistrationStatus = new ConcurrentHashMap();
public final ConcurrentMap mMapping = new ConcurrentHashMap();
private final ConcurrentMap mRootNodesMutable;
public final Map mRootNodes;
private final CopyOnWriteArraySet mElementsAllowingEmptyValuesMutable;
public final Collection mElementsAllowingEmptyValues;
private final CopyOnWriteArraySet mElementsPreservingWhiteSpaceMutable;
public final Collection mElementsPreservingWhiteSpace;
private final Map> mVersionUpgradeClasses;
private final Map> mVersionUpgrades;
MappingStuff() {
mRootNodesMutable = new ConcurrentHashMap();
mRootNodes = Collections.unmodifiableMap( mRootNodesMutable );
mElementsAllowingEmptyValuesMutable = new CopyOnWriteArraySet();
mElementsAllowingEmptyValues = Collections.unmodifiableSet(mElementsAllowingEmptyValuesMutable);
mElementsPreservingWhiteSpaceMutable = new CopyOnWriteArraySet();
mElementsPreservingWhiteSpace = Collections.unmodifiableSet(mElementsPreservingWhiteSpaceMutable);
mVersionUpgradeClasses = new ConcurrentHashMap>();
mVersionUpgrades = new ConcurrentHashMap>();
}
}
private static final MappingStuff _mappingStuff = new MappingStuff();
private final List nodes = new ArrayList();
public XMLNode topNode = null;
protected String publicID=null;
private StringBuffer elementData=null;
private Map prefixMapping=null;
private boolean stopOnXMLErrors = false;
private boolean pushedNamespaceContext=false;
private NamespaceSupport namespaces = new NamespaceSupport();
private Stack elementStack = new Stack();
private static final LocalStringManagerImpl localStrings=
new LocalStringManagerImpl(SaxParserHandler.class);
protected static Map getMapping() {
return _mappingStuff.mMapping;
}
protected static List getVersionUpgrades(String key) {
List versionUpgradeList = _mappingStuff.mVersionUpgrades.get(key);
if (versionUpgradeList != null)
return versionUpgradeList;
List classList = _mappingStuff.mVersionUpgradeClasses.get(key);
if (classList == null)
return null;
versionUpgradeList = new ArrayList();
for (int n = 0; n < classList.size(); ++n) {
VersionUpgrade versionUpgrade = null;
try {
versionUpgrade = (VersionUpgrade)classList.get(n).newInstance();
} catch (Exception ex) {
}
if (versionUpgrade != null) {
versionUpgradeList.add(versionUpgrade);
}
}
_mappingStuff.mVersionUpgrades.put(key, versionUpgradeList);
return versionUpgradeList;
}
protected static Collection getElementsAllowingEmptyValues() {
return _mappingStuff.mElementsAllowingEmptyValues;
}
protected static Collection getElementsPreservingWhiteSpace() {
return _mappingStuff.mElementsPreservingWhiteSpace;
}
private String rootElement = null;
private List versionUpgradeList = null;
private boolean doDelete = false;
public static void registerBundleNode(BundleNode bn, String bundleTagName) {
/*
* There is exactly one standard node object for each descriptor type.
* The node's registerBundle method itself adds the publicID-to-DTD
* entry to the mapping. This method needs to add the tag-to-node class
* entry to the rootNodes map.
*/
if (_mappingStuff.mBundleRegistrationStatus.containsKey(bundleTagName)) {
return;
}
final HashMap dtdMapping = new HashMap();
final Map> versionUpgrades = new HashMap>();
String rootNodeKey = bn.registerBundle(dtdMapping);
_mappingStuff.mRootNodesMutable.putIfAbsent(rootNodeKey, bn.getClass());
/*
* There can be multiple runtime nodes (for example, sun-xxx and
* glassfish-xxx). So the BundleNode's registerRuntimeBundle
* updates the publicID-to-DTD map and returns a map of tags to
* runtime node classes.
*/
_mappingStuff.mRootNodesMutable.putAll(bn.registerRuntimeBundle(dtdMapping, versionUpgrades));
_mappingStuff.mVersionUpgradeClasses.putAll(versionUpgrades);
// let's remove the URL from the DTD so we use local copies...
for (Map.Entry entry : dtdMapping.entrySet()) {
final String publicID = entry.getKey();
final String dtd = entry.getValue();
String systemIDResolution = resolvePublicID(publicID, dtd);
if (systemIDResolution == null)
_mappingStuff.mMapping.put(publicID, dtd.substring(dtd.lastIndexOf('/') + 1));
else
_mappingStuff.mMapping.put(publicID, systemIDResolution);
}
/*
* This node might know of elements which should permit empty values,
* or elements for which we should preserve white space. Track them.
*/
Collection c = bn.elementsAllowingEmptyValue();
if (c.size() > 0) {
_mappingStuff.mElementsAllowingEmptyValuesMutable.addAll(c);
}
c = bn.elementsPreservingWhiteSpace();
if (c.size() > 0) {
_mappingStuff.mElementsPreservingWhiteSpaceMutable.addAll(c);
}
_mappingStuff.mBundleRegistrationStatus.put(rootNodeKey, Boolean.TRUE);
}
public InputSource resolveEntity(String publicID, String systemID) throws SAXException {
try {
if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Asked to resolve " + publicID + " system id = " + systemID);
}
if (publicID==null) {
// unspecified schema
if (systemID==null || systemID.lastIndexOf('/')==systemID.length()) {
return null;
}
String fileName = null;
String namespaceResolution = resolveSchemaNamespace(systemID);
if (namespaceResolution != null) {
fileName = getSchemaURLFor(namespaceResolution);
} else {
fileName = getSchemaURLFor(systemID.substring(systemID.lastIndexOf('/')+1));
}
// if this is not a request for a schema located in our repository,
// let's hope that the hint provided by schemaLocation is correct
if (fileName==null) {
fileName = systemID;
}
if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Resolved to " + fileName);;
}
return new InputSource(fileName);
}
if ( getMapping().containsKey(publicID)) {
this.publicID = publicID;
return new InputSource(new BufferedInputStream(getDTDUrlFor(getMapping().get(publicID))));
}
} catch(Exception ioe) {
DOLUtils.getDefaultLogger().log(Level.SEVERE, ioe.getMessage(), ioe);
throw new SAXException(ioe);
}
return null;
}
/**
* Sets if the parser should stop parsing and generate an SAXPArseException
* when the xml parsed contains errors in regards to validation
*/
public void setStopOnError(boolean stop) {
stopOnXMLErrors = stop;
}
public void error(SAXParseException spe) throws SAXParseException {
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.invalidDescriptorFailure",
new Object[] {errorReportingString , String.valueOf(spe.getLineNumber()),
String.valueOf(spe.getColumnNumber()), spe.getLocalizedMessage()});
if (stopOnXMLErrors) {
throw spe;
}
}
public void fatalError(SAXParseException spe) throws SAXParseException {
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.invalidDescriptorFailure",
new Object[] {errorReportingString , String.valueOf(spe.getLineNumber()),
String.valueOf(spe.getColumnNumber()), spe.getLocalizedMessage()});
if (stopOnXMLErrors) {
throw spe;
}
}
/**
* @return the input stream for a DTD public ID
*/
protected InputStream getDTDUrlFor(String dtdFileName) {
String dtdLoc = DTDRegistry.DTD_LOCATION.replace('/', File.separatorChar);
File f = new File(dtdLoc +File.separatorChar+ dtdFileName);
try {
return new BufferedInputStream(new FileInputStream(f));
} catch(FileNotFoundException fnfe) {
DOLUtils.getDefaultLogger().fine("Cannot find DTD " + dtdFileName);
return null;
}
}
/**
* @return an URL for the schema location for a schema indentified by the
* passed parameter
* @param schemaSystemID the system id for the schema
*/
public static String getSchemaURLFor(String schemaSystemID) throws IOException {
File f = getSchemaFileFor(schemaSystemID);
if (f!=null) {
return f.toURI().toURL().toString();
} else {
return null;
}
}
/**
* @return a File pointer to the localtion of the schema indentified by the
* passed parameter
* @param schemaSystemID the system id for the schema
*/
public static File getSchemaFileFor(String schemaSystemID) throws IOException {
if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Getting Schema " + schemaSystemID);
}
String schemaLoc = DTDRegistry.SCHEMA_LOCATION.replace('/', File.separatorChar);
File f = new File(schemaLoc +File.separatorChar+ schemaSystemID);
if (!f.exists()) {
DOLUtils.getDefaultLogger().fine("Cannot find schema " + schemaSystemID);
return null;
}
return f;
}
/**
* Determine whether the syatemID starts with a known namespace.
* If so, strip off that namespace and return the rest.
* Otherwise, return null
* @param systemID The systemID to examine
* @return the part if the namespace to find in the file system
* or null if the systemID does not start with a known namespace
*/
public static String resolveSchemaNamespace(String systemID) {
List namespaces = DOLUtils.getProprietarySchemaNamespaces();
for (int n = 0; n < namespaces.size(); ++n) {
String namespace = namespaces.get(n);
if (systemID.startsWith(namespace)) {
return systemID.substring(namespace.length());
}
}
return null;
}
/**
* Determine whether the publicID starts with a known proprietary value.
* If so, strip off that value and return the rest.
* Otherwise, return null
* @param publicID The publicID to examine
* @return the part if the namespace to find in the file system
* or null if the publicID does not start with a known namespace
*/
public static String resolvePublicID(String publicID, String dtd) {
List dtdStarts = DOLUtils.getProprietaryDTDStart();
for (int n = 0; n < dtdStarts.size(); ++n) {
String dtdStart = dtdStarts.get(n);
if (dtd.startsWith(dtdStart)) {
return dtd.substring(dtdStart.length());
}
}
return null;
}
public void notationDecl(java.lang.String name,
java.lang.String publicId,
java.lang.String systemId)
throws SAXException {
if(DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Received notation " + name + " :=: " + publicId + " :=: " + systemId);
}
}
public void startPrefixMapping(String prefix,
String uri)
throws SAXException {
if (prefixMapping==null) {
prefixMapping = new HashMap();
}
// We need one namespace context per element, but any prefix mapping
// callbacks occur *before* startElement is called. So, push a
// context on the first startPrefixMapping callback per element.
if( !pushedNamespaceContext ) {
namespaces.pushContext();
pushedNamespaceContext = true;
}
namespaces.declarePrefix(prefix,uri);
prefixMapping.put(prefix, uri);
}
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if( !pushedNamespaceContext ) {
// We need one namespae context per element, so push a context
// if there weren't any prefix mappings defined.
namespaces.pushContext();
}
// Always reset flag since next callback could be startPrefixMapping
// OR another startElement.
pushedNamespaceContext = false;
doDelete = false;
String lastElement = null;
try {
lastElement = (String)elementStack.pop();
} catch (EmptyStackException ex) {
}
if (lastElement == null) {
rootElement = localName;
versionUpgradeList = getVersionUpgrades(rootElement);
if (versionUpgradeList != null) {
for (int n = 0; n < versionUpgradeList.size(); ++n) {
VersionUpgrade versionUpgrade = versionUpgradeList.get(n);
versionUpgrade.init();
}
}
elementStack.push(localName);
} else {
lastElement += "/" + localName;
elementStack.push(lastElement);
}
if (versionUpgradeList != null) {
for (int n = 0; n < versionUpgradeList.size(); ++n) {
VersionUpgrade versionUpgrade = versionUpgradeList.get(n);
if (VersionUpgrade.UpgradeType.REMOVE_ELEMENT == versionUpgrade.getUpgradeType()) {
Map matchXPath = versionUpgrade.getMatchXPath();
int entriesMatched = 0;
for (Map.Entry entry : matchXPath.entrySet()) {
if (entry.getKey().equals(lastElement)) {
entry.setValue(elementData.toString());
++entriesMatched;
}
}
if (entriesMatched == matchXPath.size()) {
doDelete = true;
break;
}
}
}
}
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINER)) {
DOLUtils.getDefaultLogger().finer("start of element " + uri + " with local name "+ localName + " and " + qName);
}
XMLNode node=null;
elementData=new StringBuffer();
if (nodes.isEmpty()) {
// this must be a root element...
Class rootNodeClass = _mappingStuff.mRootNodes.get(localName);
if (rootNodeClass==null) {
DOLUtils.getDefaultLogger().log(Level.SEVERE, DOLUtils.INVALID_DESC_MAPPING,
new Object[] {localName , " not supported !"});
if (stopOnXMLErrors) {
throw new IllegalArgumentException(localStrings.getLocalString("invalid.root.element", "{0} Element [{1}] is not a valid root element", new Object[]{errorReportingString, localName}));
}
} else {
try {
node = (XMLNode) rootNodeClass.newInstance();
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Instanciating " + node);
}
if (node instanceof RootXMLNode) {
if (publicID!=null) {
((RootXMLNode) node).setDocType(publicID);
}
addPrefixMapping(node);
}
nodes.add(node);
topNode = node;
node.getDescriptor();
} catch(Exception e) {
DOLUtils.getDefaultLogger().log(Level.WARNING, "Error occurred", e);
return;
}
}
} else {
node = nodes.get(nodes.size()-1);
}
if (node!=null) {
XMLElement element = new XMLElement(qName, namespaces);
if (node.handlesElement(element)) {
node.startElement(element, attributes);
} else {
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) { DOLUtils.getDefaultLogger().fine("Asking for new handler for " + element + " to " + node);
}
XMLNode newNode = node.getHandlerFor(element);
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Got " + newNode);
}
nodes.add(newNode);
addPrefixMapping(newNode);
newNode.startElement(element, attributes);
}
}
}
public void endElement(String uri, String localName, String qName) {
String lastElement = null;
try {
lastElement = (String)elementStack.peek();
} catch (EmptyStackException ex) {
}
if(DOLUtils.getDefaultLogger().isLoggable(Level.FINER)) {
DOLUtils.getDefaultLogger().finer("End of element " + uri + " local name "+ localName + " and " + qName + " value " + elementData);
}
if (nodes.size()==0) {
// no more nodes to pop
elementData=null;
return;
}
XMLElement element = new XMLElement(qName, namespaces);
XMLNode topNode = nodes.get(nodes.size()-1);
if (elementData!=null && (elementData.length()!=0 || allowsEmptyValue(element.getQName()))) {
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINER)) {
DOLUtils.getDefaultLogger().finer("For element " + element.getQName() + " And value " + elementData);
}
boolean doReplace = false;
String replacementName = null;
String replacementValue = null;
if (versionUpgradeList != null) {
for (int n = 0; n < versionUpgradeList.size(); ++n) {
VersionUpgrade versionUpgrade = versionUpgradeList.get(n);
if (VersionUpgrade.UpgradeType.REPLACE_ELEMENT == versionUpgrade.getUpgradeType()) {
Map matchXPath = versionUpgrade.getMatchXPath();
int entriesMatched = 0;
for (Map.Entry entry : matchXPath.entrySet()) {
if (entry.getKey().equals(lastElement)) {
entry.setValue(elementData.toString());
++entriesMatched;
}
}
if (entriesMatched == matchXPath.size()) {
if (versionUpgrade.isValid()) {
doReplace = true;
replacementName = versionUpgrade.getReplacementElementName();
replacementValue = versionUpgrade.getReplacementElementValue();
} else {
StringBuffer buf = new StringBuffer();
String errorString = "Invalid upgrade from <";
buf.append(errorString);
for (Map.Entry entry : matchXPath.entrySet()) {
buf.append(entry.getKey() + " " + entry.getValue() + " >");
}
errorString = buf.toString();
DOLUtils.getDefaultLogger().log(Level.SEVERE, errorString);
// Since the elements are not replaced,
// there should be a parsing error
}
break;
}
}
}
}
if (doReplace) {
element = new XMLElement(replacementName, namespaces);
topNode.setElementValue(element, (String)TranslatedConfigView.getTranslatedValue(replacementValue));
} else if (doDelete) {
// don't set a value so that the element is not written out
} else if (getElementsPreservingWhiteSpace().contains(element.getQName())) {
topNode.setElementValue(element, (String)TranslatedConfigView.getTranslatedValue(elementData.toString()));
} else if (element.getQName().equals(
TagNames.ENVIRONMENT_PROPERTY_VALUE)) {
Object envEntryDesc = topNode.getDescriptor();
if (envEntryDesc != null &&
envEntryDesc instanceof EnvironmentProperty) {
EnvironmentProperty envProp =
(EnvironmentProperty)envEntryDesc;
// we need to preserve white space for env-entry-value
// if the env-entry-type is java.lang.String or
// java.lang.Character
if (envProp.getType() != null &&
(envProp.getType().equals("java.lang.String") ||
envProp.getType().equals("java.lang.Character"))) {
topNode.setElementValue(element,
(String)TranslatedConfigView.getTranslatedValue(elementData.toString()));
} else {
topNode.setElementValue(element,
(String)TranslatedConfigView.getTranslatedValue(elementData.toString().trim()));
}
} else {
topNode.setElementValue(element,
(String)TranslatedConfigView.getTranslatedValue(elementData.toString().trim()));
}
} else {
/*
* Allow any case for true/false & convert to lower case
*/
String val = (String)TranslatedConfigView.getTranslatedValue(elementData.toString().trim());
if (TRUE_STR.equalsIgnoreCase(val)) {
topNode.setElementValue(element, val.toLowerCase(Locale.US));
} else if (FALSE_STR.equalsIgnoreCase(val)) {
topNode.setElementValue(element, val.toLowerCase(Locale.US));
} else {
topNode.setElementValue(element, val);
}
}
elementData=null;
}
if (topNode.endElement(element)) {
if (DOLUtils.getDefaultLogger().isLoggable(Level.FINE)) {
DOLUtils.getDefaultLogger().fine("Removing top node " + topNode);
}
nodes.remove(nodes.size()-1);
}
namespaces.popContext();
pushedNamespaceContext=false;
try {
lastElement = (String)elementStack.pop();
} catch (EmptyStackException ex) {
}
if (lastElement != null) {
if (lastElement.lastIndexOf("/") >= 0) {
lastElement = lastElement.substring(0, lastElement.lastIndexOf("/"));
elementStack.push(lastElement);
}
}
}
public void characters(char[] ch, int start, int stop) {
if (elementData!=null) {
elementData = elementData.append(ch,start, stop);
}
}
public XMLNode getTopNode() {
return topNode;
}
public void setTopNode(XMLNode node) {
topNode = node;
nodes.add(node);
}
private void addPrefixMapping(XMLNode node) {
if (prefixMapping != null) {
for (Map.Entry entry : prefixMapping.entrySet()) {
node.addPrefixMapping(entry.getKey(), entry.getValue());
}
prefixMapping = null;
}
}
private String errorReportingString="";
/**
* Sets the error reporting context string
*/
public void setErrorReportingString(String s) {
errorReportingString = s;
}
/**
* Indicates whether the element name is one for which empty values should
* be recorded.
*
* If there were many tags that support empty values, it might make sense to
* have a constant list that contains all those tag names. Then this method
* would search the list for the target elementName. Because this code
* is potentially invoked for many elements that do not support empty values,
* and because the list is very small at the moment, the current
* implementation uses an inelegant but fast equals test.
*
* If the set of tags that should support empty values grows a little,
* extending the expression to
*
* elementName.equals(TAG_1) || elementName.equals(TAG_2) || ...
*
* might make sense. If the set of such tags grows sufficiently large, then
* a list-based approach might make more sense even though it might prove
* to be slower.
* @param elementName the name of the element
* @return boolean indicating whether empty values should be recorded for this element
*/
private boolean allowsEmptyValue(String elementName) {
return getElementsAllowingEmptyValues().contains(elementName);
}
}