001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.service;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Set;
026
027import javax.xml.XMLConstants;
028import javax.xml.transform.stream.StreamSource;
029import javax.xml.validation.Schema;
030import javax.xml.validation.SchemaFactory;
031import javax.xml.validation.Validator;
032
033import org.apache.oozie.ErrorCode;
034import org.apache.oozie.util.IOUtils;
035import org.apache.oozie.util.schema.ResourceResolver;
036import org.apache.xerces.xni.XMLResourceIdentifier;
037import org.apache.xerces.xni.XNIException;
038import org.apache.xerces.xni.parser.XMLEntityResolver;
039import org.apache.xerces.xni.parser.XMLInputSource;
040import org.xml.sax.SAXException;
041import org.xml.sax.SAXNotRecognizedException;
042import org.xml.sax.SAXNotSupportedException;
043
044
045/**
046 * Service that loads Oozie workflow definition schema and registered extension
047 * schemas.
048 */
049
050public class SchemaService implements Service {
051
052    public static final String CONF_PREFIX = Service.CONF_PREFIX + "SchemaService.";
053
054    public static final String WF_CONF_SCHEMAS = CONF_PREFIX + "wf.schemas";
055
056    public static final String WF_CONF_EXT_SCHEMAS = CONF_PREFIX + "wf.ext.schemas";
057
058    public static final String COORD_CONF_SCHEMAS = CONF_PREFIX + "coord.schemas";
059
060    public static final String COORD_CONF_EXT_SCHEMAS = CONF_PREFIX + "coord.ext.schemas";
061
062    public static final String BUNDLE_CONF_SCHEMAS = CONF_PREFIX + "bundle.schemas";
063
064    public static final String BUNDLE_CONF_EXT_SCHEMAS = CONF_PREFIX + "bundle.ext.schemas";
065
066    public static final String SLA_CONF_SCHEMAS = CONF_PREFIX + "sla.schemas";
067
068    public static final String SLA_CONF_EXT_SCHEMAS = CONF_PREFIX + "sla.ext.schemas";
069
070    @Deprecated
071    public static final String SLA_NAME_SPACE_URI = "uri:oozie:sla:0.1";
072
073    public static final String SLA_NAMESPACE_URI_2 = "uri:oozie:sla:0.2";
074
075    public static final String COORDINATOR_NAMESPACE_URI_1 = "uri:oozie:coordinator:0.1";
076
077    private Schema wfSchema;
078
079    private Schema coordSchema;
080
081    private Schema bundleSchema;
082
083    private Schema slaSchema;
084
085    private SchemaFactory schemaFactory;
086
087    private static NoXMLEntityResolver xmlEntityResolver;
088
089    private Schema loadSchema(String baseSchemas, String extSchema) throws SAXException, IOException {
090        Set<String> schemaNames = new HashSet<String>();
091        String[] schemas = ConfigurationService.getStrings(baseSchemas);
092        if (schemas != null) {
093            for (String schema : schemas) {
094                schema = schema.trim();
095                if (!schema.isEmpty()) {
096                    schemaNames.add(schema);
097                }
098            }
099        }
100        schemas = ConfigurationService.getStrings(extSchema);
101        if (schemas != null) {
102            for (String schema : schemas) {
103                schema = schema.trim();
104                if (!schema.isEmpty()) {
105                    schemaNames.add(schema);
106                }
107            }
108        }
109        List<StreamSource> sources = new ArrayList<StreamSource>();
110        for (String schemaName : schemaNames) {
111            StreamSource s = new StreamSource(IOUtils.getResourceAsStream(schemaName, -1));
112            s.setSystemId(schemaName);
113            sources.add(s);
114        }
115        schemaFactory.setResourceResolver(new ResourceResolver());
116        return schemaFactory.newSchema(sources.toArray(new StreamSource[sources.size()]));
117    }
118
119    /**
120     * Initialize the service.
121     *
122     * @param services services instance.
123     * @throws ServiceException thrown if the service could not be initialized.
124     */
125    @Override
126    public void init(Services services) throws ServiceException {
127        try {
128            schemaFactory = createSchemaFactory();
129            wfSchema = loadSchema(WF_CONF_SCHEMAS, WF_CONF_EXT_SCHEMAS);
130            coordSchema = loadSchema(COORD_CONF_SCHEMAS, COORD_CONF_EXT_SCHEMAS);
131            bundleSchema = loadSchema(BUNDLE_CONF_SCHEMAS, BUNDLE_CONF_EXT_SCHEMAS);
132            slaSchema = loadSchema(SLA_CONF_SCHEMAS, SLA_CONF_EXT_SCHEMAS);
133            xmlEntityResolver = new NoXMLEntityResolver();
134        }
135        catch (SAXException ex) {
136            throw new ServiceException(ErrorCode.E0130, ex.getMessage(), ex);
137        }
138        catch (IOException ex) {
139            throw new ServiceException(ErrorCode.E0131, ex.getMessage(), ex);
140        }
141    }
142
143    /**
144     * Creates schema factory
145     * @return
146     * @throws SAXNotRecognizedException
147     * @throws SAXNotSupportedException
148     */
149    private SchemaFactory createSchemaFactory() throws SAXNotRecognizedException, SAXNotSupportedException {
150        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
151        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
152        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,true);
153        return factory;
154    }
155
156    /**
157     * Return the public interface of the service.
158     *
159     * @return {@link SchemaService}.
160     */
161    @Override
162    public Class<? extends Service> getInterface() {
163        return SchemaService.class;
164    }
165
166    /**
167     * Destroy the service.
168     */
169    @Override
170    public void destroy() {
171        wfSchema = null;
172        bundleSchema = null;
173        slaSchema = null;
174        coordSchema = null;
175    }
176
177    /**
178     * Return the schema for XML validation of application definitions.
179     *
180     * @param schemaName Name of schema definition (i.e.
181     *        WORKFLOW/COORDINATOR/BUNDLE)
182     * @return the schema for XML validation of application definitions.
183     */
184    public Schema getSchema(SchemaName schemaName) {
185        Schema returnSchema = null;
186        if (schemaName == SchemaName.WORKFLOW) {
187            returnSchema = wfSchema;
188        }
189        else if (schemaName == SchemaName.COORDINATOR) {
190            returnSchema = coordSchema;
191        }
192        else if (schemaName == SchemaName.BUNDLE) {
193            returnSchema = bundleSchema;
194        }
195        else if (schemaName == SchemaName.SLA_ORIGINAL) {
196            returnSchema = slaSchema;
197        }
198        else {
199            throw new RuntimeException("No schema found with name " + schemaName);
200        }
201        return returnSchema;
202    }
203
204    public enum SchemaName {
205        WORKFLOW(1), COORDINATOR(2), SLA_ORIGINAL(3), BUNDLE(4);
206        private final int id;
207
208        private SchemaName(int id) {
209            this.id = id;
210        }
211
212        public int getId() {
213            return id;
214        }
215    }
216
217    /**
218     * Returns validator for schema
219     * @param schemaName
220     * @return
221     * @throws SAXException
222     */
223    public Validator getValidator(SchemaName schemaName) throws SAXException {
224        return getValidator(getSchema(schemaName));
225    }
226
227    /**
228     * Returns validator for schema
229     * @param schema
230     * @return
231     * @throws SAXException
232     */
233    public static Validator getValidator(Schema schema) throws SAXException {
234        Validator validator = schema.newValidator();
235        validator.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
236        validator.setFeature("http://xml.org/sax/features/external-general-entities", false);
237        validator.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
238        validator.setProperty("http://apache.org/xml/properties/internal/entity-resolver", xmlEntityResolver);
239        return validator;
240    }
241
242    private static class NoXMLEntityResolver implements XMLEntityResolver {
243        @Override
244        public XMLInputSource resolveEntity(XMLResourceIdentifier xmlResourceIdentifier) throws XNIException, IOException {
245            throw new IOException("DOCTYPE is disallowed when the feature http://apache.org/xml/features/disallow-doctype-decl "
246                    + "set to true.");
247        }
248    }
249}