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;
032import org.xml.sax.SAXException;
033import org.apache.camel.CamelContext;
034import org.apache.camel.converter.IOConverter;
035import org.apache.camel.util.IOHelper;
036import org.apache.camel.util.ObjectHelper;
037import org.apache.camel.util.ResourceHelper;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * Reads the schema used in the processor {@link ValidatingProcessor}.
044 * A schema re-reading could be forced using {@link org.apache.camel.component.validator.ValidatorEndpoint#clearCachedSchema()}.
045 */
046public class SchemaReader {
047    
048    /** Key of the global option to switch either off or on  the access to external DTDs in the XML Validator for StreamSources. 
049     * Only effective, if not a custom schema factory is used.*/
050    public static final String ACCESS_EXTERNAL_DTD = "CamelXmlValidatorAccessExternalDTD";
051    
052    private static final Logger LOG = LoggerFactory.getLogger(SchemaReader.class);
053    
054    private String schemaLanguage = XMLConstants.W3C_XML_SCHEMA_NS_URI;
055    // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema
056    private volatile Schema schema;
057    private Source schemaSource;
058    // must be volatile because is accessed from different threads see ValidatorEndpoint.clearCachedSchema
059    private volatile SchemaFactory schemaFactory;
060    private URL schemaUrl;
061    private File schemaFile;
062    private byte[] schemaAsByteArray;
063    private final String schemaResourceUri;
064    private LSResourceResolver resourceResolver;
065    
066    private final CamelContext camelContext;
067    
068    
069    public SchemaReader() {
070        this.camelContext = null;
071        this.schemaResourceUri = null;
072    }
073    
074    /** Specify a camel context and a schema resource URI in order to read the schema via the class resolver specified in the Camel context. */
075    public SchemaReader(CamelContext camelContext, String schemaResourceUri) {
076        ObjectHelper.notNull(camelContext, "camelContext");
077        ObjectHelper.notNull(schemaResourceUri, "schemaResourceUri");
078        this.camelContext = camelContext;
079        this.schemaResourceUri = schemaResourceUri;
080    }
081
082    public void loadSchema() throws Exception {
083        // force loading of schema
084        schema = createSchema();
085    }
086
087    // Properties
088    // -----------------------------------------------------------------------
089
090    public Schema getSchema() throws IOException, SAXException {
091        if (schema == null) {
092            synchronized (this) {
093                if (schema == null) {
094                    schema = createSchema();
095                }
096            }
097        }
098        return schema;
099    }
100
101    public void setSchema(Schema schema) {
102        this.schema = schema;
103    }
104
105    public String getSchemaLanguage() {
106        return schemaLanguage;
107    }
108
109    public void setSchemaLanguage(String schemaLanguage) {
110        this.schemaLanguage = schemaLanguage;
111    }
112
113    public Source getSchemaSource() throws IOException {
114        if (schemaSource == null) {
115            schemaSource = createSchemaSource();
116        }
117        return schemaSource;
118    }
119
120    public void setSchemaSource(Source schemaSource) {
121        this.schemaSource = schemaSource;
122    }
123
124    public URL getSchemaUrl() {
125        return schemaUrl;
126    }
127
128    public void setSchemaUrl(URL schemaUrl) {
129        this.schemaUrl = schemaUrl;
130    }
131
132    public File getSchemaFile() {
133        return schemaFile;
134    }
135
136    public void setSchemaFile(File schemaFile) {
137        this.schemaFile = schemaFile;
138    }
139
140    public byte[] getSchemaAsByteArray() {
141        return schemaAsByteArray;
142    }
143
144    public void setSchemaAsByteArray(byte[] schemaAsByteArray) {
145        this.schemaAsByteArray = schemaAsByteArray;
146    }
147
148    public SchemaFactory getSchemaFactory() {
149        if (schemaFactory == null) {
150            synchronized (this) {
151                if (schemaFactory == null) {
152                    schemaFactory = createSchemaFactory();
153                }
154            }
155        }
156        return schemaFactory;
157    }
158
159    public void setSchemaFactory(SchemaFactory schemaFactory) {
160        this.schemaFactory = schemaFactory;
161    }
162
163    public LSResourceResolver getResourceResolver() {
164        return resourceResolver;
165    }
166
167    public void setResourceResolver(LSResourceResolver resourceResolver) {
168        this.resourceResolver = resourceResolver;
169    }
170
171    protected SchemaFactory createSchemaFactory() {
172        SchemaFactory factory = SchemaFactory.newInstance(schemaLanguage);
173        if (getResourceResolver() != null) {
174            factory.setResourceResolver(getResourceResolver());
175        }  
176        if (camelContext == null || !Boolean.parseBoolean(camelContext.getGlobalOptions().get(ACCESS_EXTERNAL_DTD))) {
177            try {
178                factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
179            } catch (SAXException e) {
180                LOG.warn(e.getMessage(), e);
181            } 
182        }
183        return factory;
184    }
185
186    protected Source createSchemaSource() throws IOException {
187        throw new IllegalArgumentException("You must specify either a schema, schemaFile, schemaSource, schemaUrl, or schemaUri property");
188    }
189
190    protected Schema createSchema() throws SAXException, IOException {
191        SchemaFactory factory = getSchemaFactory();
192
193        URL url = getSchemaUrl();
194        if (url != null) {
195            synchronized (this) {
196                return factory.newSchema(url);
197            }
198        }
199
200        File file = getSchemaFile();
201        if (file != null) {
202            synchronized (this) {
203                return factory.newSchema(file);
204            }
205        }
206
207        byte[] bytes = getSchemaAsByteArray();
208        if (bytes != null) {
209            synchronized (this) {
210                return factory.newSchema(new StreamSource(new ByteArrayInputStream(schemaAsByteArray)));
211            }
212        }
213        
214        if (schemaResourceUri != null) {
215            synchronized (this) {
216                bytes = readSchemaResource();
217                return factory.newSchema(new StreamSource(new ByteArrayInputStream(bytes)));
218            }          
219        }
220        
221        Source source = getSchemaSource();
222        synchronized (this) {
223            return factory.newSchema(source);
224        }
225
226    }
227    
228    protected byte[] readSchemaResource() throws IOException {
229        LOG.debug("reading schema resource: {}", schemaResourceUri);
230        InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, schemaResourceUri);
231        byte[] bytes = null;
232        try {
233            bytes = IOConverter.toBytes(is);
234        } finally {
235            // and make sure to close the input stream after the schema has been
236            // loaded
237            IOHelper.close(is);
238        }
239        return bytes;
240    }
241
242}