001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.sequencer.xml; 017 018import java.io.IOException; 019import java.io.InputStream; 020import javax.jcr.Binary; 021import javax.jcr.NamespaceRegistry; 022import javax.jcr.Node; 023import javax.jcr.Property; 024import javax.jcr.RepositoryException; 025import org.modeshape.common.util.CheckArg; 026import org.modeshape.jcr.api.nodetype.NodeTypeManager; 027import org.modeshape.jcr.api.sequencer.Sequencer; 028import org.xml.sax.InputSource; 029import org.xml.sax.SAXException; 030import org.xml.sax.XMLReader; 031import org.xml.sax.helpers.XMLReaderFactory; 032 033/** 034 * A sequencer for XML files, which maintains DTD, entity, comments, and other content. Note that by default the sequencer uses 035 * the {@link XmlSequencer.AttributeScoping#USE_DEFAULT_NAMESPACE default namespace} for unqualified attribute rather than 036 * {@link XmlSequencer.AttributeScoping#INHERIT_ELEMENT_NAMESPACE inheriting the namespace from the element}. (See also 037 * {@link InheritingXmlSequencer}. 038 */ 039public class XmlSequencer extends Sequencer { 040 041 public static final class MimeTypeConstants { 042 public static final String WSDL = "application/wsdl+xml"; 043 public static final String APPLICATION_XML = "application/xml"; 044 public static final String TEXT_XML = "text/xml"; 045 public static final String HTML_XML = "application/xhtml+xml"; 046 public static final String XOP_XML = "application/xop+xml"; 047 public static final String XSLT = "application/xslt+xml"; 048 public static final String XSFP = "application/xsfp+xml"; 049 public static final String MXML = "application/xv+xml"; 050 } 051 052 /** 053 * The choices for how attributes that have no namespace prefix should be assigned a namespace. 054 * 055 * @author Randall Hauch 056 */ 057 public enum AttributeScoping { 058 /** 059 * The attribute's namespace is the default namespace 060 */ 061 USE_DEFAULT_NAMESPACE, 062 /** 063 * The attribute's namespace is the same namespace as the containing element 064 */ 065 INHERIT_ELEMENT_NAMESPACE 066 } 067 068 static final String DECL_HANDLER_FEATURE = "http://xml.org/sax/properties/declaration-handler"; 069 static final String ENTITY_RESOLVER_2_FEATURE = "http://xml.org/sax/features/use-entity-resolver2"; 070 static final String LEXICAL_HANDLER_FEATURE = "http://xml.org/sax/properties/lexical-handler"; 071 static final String RESOLVE_DTD_URIS_FEATURE = "http://xml.org/sax/features/resolve-dtd-uris"; 072 static final String LOAD_EXTERNAL_DTDS_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; 073 074 private AttributeScoping scoping = AttributeScoping.USE_DEFAULT_NAMESPACE; 075 076 /** 077 * @param scoping Sets scoping to the specified value. 078 */ 079 protected void setAttributeScoping( AttributeScoping scoping ) { 080 this.scoping = scoping; 081 } 082 083 @Override 084 public void initialize( NamespaceRegistry registry, 085 NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException { 086 super.registerNodeTypes("xml.cnd", nodeTypeManager, true); 087 registerDefaultMimeTypes(MimeTypeConstants.APPLICATION_XML, 088 MimeTypeConstants.TEXT_XML, 089 MimeTypeConstants.HTML_XML, 090 MimeTypeConstants.XOP_XML, 091 MimeTypeConstants.XSLT, 092 MimeTypeConstants.XSFP, 093 MimeTypeConstants.MXML); 094 } 095 096 @Override 097 public boolean execute( Property inputProperty, 098 Node outputNode, 099 Context context ) throws Exception { 100 Binary binaryValue = inputProperty.getBinary(); 101 CheckArg.isNotNull(binaryValue, "binary"); 102 103 if (!outputNode.isNew()) { 104 outputNode = outputNode.addNode(XmlLexicon.DOCUMENT); 105 } 106 107 XmlSequencerHandler sequencingHandler = new XmlSequencerHandler(outputNode, scoping); 108 // Create the reader ... 109 XMLReader reader = XMLReaderFactory.createXMLReader(); 110 reader.setContentHandler(sequencingHandler); 111 reader.setErrorHandler(sequencingHandler); 112 // Ensure handler acting as entity resolver 2 113 reader.setProperty(DECL_HANDLER_FEATURE, sequencingHandler); 114 // Ensure handler acting as lexical handler 115 reader.setProperty(LEXICAL_HANDLER_FEATURE, sequencingHandler); 116 // Ensure handler acting as entity resolver 2 117 setFeature(reader, ENTITY_RESOLVER_2_FEATURE, true); 118 // Prevent loading of external DTDs 119 setFeature(reader, LOAD_EXTERNAL_DTDS_FEATURE, false); 120 // Prevent the resolving of DTD entities into fully-qualified URIS 121 setFeature(reader, RESOLVE_DTD_URIS_FEATURE, false); 122 // Parse XML document 123 try (InputStream stream = binaryValue.getStream()) { 124 reader.parse(new InputSource(stream)); 125 } 126 return true; 127 } 128 129 /** 130 * Sets the reader's named feature to the supplied value, only if the feature is not already set to that value. This method 131 * does nothing if the feature is not known to the reader. 132 * 133 * @param reader the reader; may not be null 134 * @param featureName the name of the feature; may not be null 135 * @param value the value for the feature 136 */ 137 void setFeature( XMLReader reader, 138 String featureName, 139 boolean value ) { 140 try { 141 if (reader.getFeature(featureName) != value) { 142 reader.setFeature(featureName, value); 143 } 144 } catch (SAXException e) { 145 getLogger().warn(e, "Cannot set feature " + featureName); 146 } 147 } 148 149}