001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.processor.validation;
018
019import java.io.ByteArrayInputStream;
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.URL;
024
025import javax.xml.XMLConstants;
026import javax.xml.transform.Source;
027import javax.xml.transform.stream.StreamSource;
028import javax.xml.validation.Schema;
029import javax.xml.validation.SchemaFactory;
030
031import org.w3c.dom.ls.LSResourceResolver;
032
033import org.xml.sax.SAXException;
034
035import org.apache.camel.CamelContext;
036import org.apache.camel.converter.IOConverter;
037import org.apache.camel.util.IOHelper;
038import org.apache.camel.util.ObjectHelper;
039import org.apache.camel.util.ResourceHelper;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 * Reads the schema used in the processor {@link ValidatingProcessor}.
045 * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}.
046 */
047public class SchemaReader {
048    
049    /** Key of the global option to switch either off or on  the access to external DTDs in the XML Validator for StreamSources. 
050     * Only effective, if not a custom schema factory is used.*/
051    public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD";
052    
053    private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class);
054    
055    private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
056    // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema
057    private volatile Schema schema;
058    private Source schemaSource;
059    // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema
060    private volatile SchemaFactory schemaFactory;
061    private URL schemaUrl;
062    private File schemaFile;
063    private byte[] schemaAsByteArray;
064    private final String schemaResourceUri;
065    private LSResourceResolver resourceResolver;
066    
067    private final CamelContext camelContext;
068    
069    
070    public SchemaReader() {
071        this.camelContext = null;
072        this.schemaResourceUri = null;
073    }
074    
075    /** Specify a camel context and a schema resource URI in order to read the schema via the class resolver specified in the Camel context. */
076    public SchemaReader(CamelContext camelContext, String schemaResourceUri) {
077        ObjectHelper.notNull(camelContext, "camelContext");
078        ObjectHelper.notNull(schemaResourceUri, "schemaResourceUri");
079        this.camelContext = camelContext;
080        this.schemaResourceUri = schemaResourceUri;
081    }
082
083    public void loadSchema() throws Exception {
084        // force loading of schema
085        schema = createSchema();
086    }
087
088    // Properties
089    // -----------------------------------------------------------------------
090
091    public Schema getSchema() throws IOException, SAXException {
092        if (schema == null) {
093            synchronized (this) {
094                if (schema == null) {
095                    schema = createSchema();
096                }
097            }
098        }
099        return schema;
100    }
101
102    public void setSchema(Schema schema) {
103        this.schema = schema;
104    }
105
106    public String getSchemaLanguage() {
107        return schemaLanguage;
108    }
109
110    public void setSchemaLanguage(String schemaLanguage) {
111        this.schemaLanguage = schemaLanguage;
112    }
113
114    public Source getSchemaSource() throws IOException {
115        if (schemaSource == null) {
116            schemaSource = createSchemaSource();
117        }
118        return schemaSource;
119    }
120
121    public void setSchemaSource(Source schemaSource) {
122        this.schemaSource = schemaSource;
123    }
124
125    public URL getSchemaUrl() {
126        return schemaUrl;
127    }
128
129    public void setSchemaUrl(URL schemaUrl) {
130        this.schemaUrl = schemaUrl;
131    }
132
133    public File getSchemaFile() {
134        return schemaFile;
135    }
136
137    public void setSchemaFile(File schemaFile) {
138        this.schemaFile = schemaFile;
139    }
140
141    public byte[] getSchemaAsByteArray() {
142        return schemaAsByteArray;
143    }
144
145    public void setSchemaAsByteArray(byte[] schemaAsByteArray) {
146        this.schemaAsByteArray = schemaAsByteArray;
147    }
148
149    public SchemaFactory getSchemaFactory() {
150        if (schemaFactory == null) {
151            synchronized (this) {
152                if (schemaFactory == null) {
153                    schemaFactory = createSchemaFactory();
154                }
155            }
156        }
157        return schemaFactory;
158    }
159
160    public void setSchemaFactory(SchemaFactory schemaFactory) {
161        this.schemaFactory = schemaFactory;
162    }
163
164    public LSResourceResolver getResourceResolver() {
165        return resourceResolver;
166    }
167
168    public void setResourceResolver(LSResourceResolver resourceResolver) {
169        this.resourceResolver = resourceResolver;
170    }
171
172    protected SchemaFactory createSchemaFactory() {
173        SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage);
174        if (getResourceResolver() != null) {
175            factory.setResourceResolver(getResourceResolver());
176        }  
177        if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) {
178            try {
179                LOG.debug("Configuring SchemaFactory to not allow access to external DTD/Schema");
180                factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
181            } catch (SAXException e) {
182                LOG.warn(e.getMessage(), e);
183            } 
184        }
185        return factory;
186    }
187
188    protected Source createSchemaSource() throws IOException {
189        throw new IllegalArgumentException("You must specify either a schema, schemaFile, schemaSource, schemaUrl, or schemaUri property");
190    }
191
192    protected Schema createSchema() throws SAXException, IOException {
193        SchemaFactory factory = getSchemaFactory();
194
195        URL url = getSchemaUrl();
196        if (url != null) {
197            synchronized (this) {
198                return factory.newSchema(url);
199            }
200        }
201
202        File file = getSchemaFile();
203        if (file != null) {
204            synchronized (this) {
205                return factory.newSchema(file);
206            }
207        }
208
209        byte[] bytes = getSchemaAsByteArray();
210        if (bytes != null) {
211            synchronized (this) {
212                return factory.newSchema(new StreamSource(new ByteArrayInputStream(schemaAsByteArray)));
213            }
214        }
215        
216        if (schemaResourceUri != null) {
217            synchronized (this) {
218                bytes = readSchemaResource();
219                return factory.newSchema(new StreamSource(new ByteArrayInputStream(bytes)));
220            }          
221        }
222        
223        Source source = getSchemaSource();
224        synchronized (this) {
225            return factory.newSchema(source);
226        }
227
228    }
229    
230    protected byte[] readSchemaResource() throws IOException {
231        LOG.debug("reading schema resource: {}", schemaResourceUri);
232        InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, schemaResourceUri);
233        byte[] bytes = null;
234        try {
235            bytes = IOConverter.toBytes(is);
236        } finally {
237            // and make sure to close the input stream after the schema has been
238            // loaded
239            IOHelper.close(is);
240        }
241        return bytes;
242    }
243
244}