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.xerces.xni.XMLResourceIdentifier;
036import org.apache.xerces.xni.XNIException;
037import org.apache.xerces.xni.parser.XMLEntityResolver;
038import org.apache.xerces.xni.parser.XMLInputSource;
039import org.xml.sax.SAXException;
040import org.xml.sax.SAXNotRecognizedException;
041import org.xml.sax.SAXNotSupportedException;
042
043/**
044 * Service that loads Oozie workflow definition schema and registered extension
045 * schemas.
046 */
047public class SchemaService implements Service {
048
049    public static final String CONF_PREFIX = Service.CONF_PREFIX + "SchemaService.";
050
051    public static final String WF_CONF_SCHEMAS = CONF_PREFIX + "wf.schemas";
052
053    public static final String WF_CONF_EXT_SCHEMAS = CONF_PREFIX + "wf.ext.schemas";
054
055    public static final String COORD_CONF_SCHEMAS = CONF_PREFIX + "coord.schemas";
056
057    public static final String COORD_CONF_EXT_SCHEMAS = CONF_PREFIX + "coord.ext.schemas";
058
059    public static final String BUNDLE_CONF_SCHEMAS = CONF_PREFIX + "bundle.schemas";
060
061    public static final String BUNDLE_CONF_EXT_SCHEMAS = CONF_PREFIX + "bundle.ext.schemas";
062
063    public static final String SLA_CONF_SCHEMAS = CONF_PREFIX + "sla.schemas";
064
065    public static final String SLA_CONF_EXT_SCHEMAS = CONF_PREFIX + "sla.ext.schemas";
066
067    @Deprecated
068    public static final String SLA_NAME_SPACE_URI = "uri:oozie:sla:0.1";
069
070    public static final String SLA_NAMESPACE_URI_2 = "uri:oozie:sla:0.2";
071
072    public static final String COORDINATOR_NAMESPACE_URI_1 = "uri:oozie:coordinator:0.1";
073
074    private Schema wfSchema;
075
076    private Schema coordSchema;
077
078    private Schema bundleSchema;
079
080    private Schema slaSchema;
081
082    private SchemaFactory schemaFactory;
083
084    private static NoXMLEntityResolver xmlEntityResolver;
085
086    private Schema loadSchema(String baseSchemas, String extSchema) throws SAXException, IOException {
087        Set<String> schemaNames = new HashSet<String>();
088        String[] schemas = ConfigurationService.getStrings(baseSchemas);
089        if (schemas != null) {
090            for (String schema : schemas) {
091                schema = schema.trim();
092                if (!schema.isEmpty()) {
093                    schemaNames.add(schema);
094                }
095            }
096        }
097        schemas = ConfigurationService.getStrings(extSchema);
098        if (schemas != null) {
099            for (String schema : schemas) {
100                schema = schema.trim();
101                if (!schema.isEmpty()) {
102                    schemaNames.add(schema);
103                }
104            }
105        }
106        List<StreamSource> sources = new ArrayList<StreamSource>();
107        for (String schemaName : schemaNames) {
108            sources.add(new StreamSource(IOUtils.getResourceAsStream(schemaName, -1)));
109        }
110        return schemaFactory.newSchema(sources.toArray(new StreamSource[sources.size()]));
111    }
112
113    /**
114     * Initialize the service.
115     *
116     * @param services services instance.
117     * @throws ServiceException thrown if the service could not be initialized.
118     */
119    @Override
120    public void init(Services services) throws ServiceException {
121        try {
122            schemaFactory = createSchemaFactory();
123            wfSchema = loadSchema(WF_CONF_SCHEMAS, WF_CONF_EXT_SCHEMAS);
124            coordSchema = loadSchema(COORD_CONF_SCHEMAS, COORD_CONF_EXT_SCHEMAS);
125            bundleSchema = loadSchema(BUNDLE_CONF_SCHEMAS, BUNDLE_CONF_EXT_SCHEMAS);
126            slaSchema = loadSchema(SLA_CONF_SCHEMAS, SLA_CONF_EXT_SCHEMAS);
127            xmlEntityResolver = new NoXMLEntityResolver();
128        }
129        catch (SAXException ex) {
130            throw new ServiceException(ErrorCode.E0130, ex.getMessage(), ex);
131        }
132        catch (IOException ex) {
133            throw new ServiceException(ErrorCode.E0131, ex.getMessage(), ex);
134        }
135    }
136
137    /**
138     * Creates schema factory
139     * @return
140     * @throws SAXNotRecognizedException
141     * @throws SAXNotSupportedException
142     */
143    private SchemaFactory createSchemaFactory() throws SAXNotRecognizedException, SAXNotSupportedException {
144        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
145        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
146        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,true);
147        return factory;
148    }
149
150    /**
151     * Return the public interface of the service.
152     *
153     * @return {@link SchemaService}.
154     */
155    @Override
156    public Class<? extends Service> getInterface() {
157        return SchemaService.class;
158    }
159
160    /**
161     * Destroy the service.
162     */
163    @Override
164    public void destroy() {
165        wfSchema = null;
166        bundleSchema = null;
167        slaSchema = null;
168        coordSchema = null;
169    }
170
171    /**
172     * Return the schema for XML validation of application definitions.
173     *
174     * @param schemaName: Name of schema definition (i.e.
175     *        WORKFLOW/COORDINATOR/BUNDLE)
176     * @return the schema for XML validation of application definitions.
177     */
178    public Schema getSchema(SchemaName schemaName) {
179        Schema returnSchema = null;
180        if (schemaName == SchemaName.WORKFLOW) {
181            returnSchema = wfSchema;
182        }
183        else if (schemaName == SchemaName.COORDINATOR) {
184            returnSchema = coordSchema;
185        }
186        else if (schemaName == SchemaName.BUNDLE) {
187            returnSchema = bundleSchema;
188        }
189        else if (schemaName == SchemaName.SLA_ORIGINAL) {
190            returnSchema = slaSchema;
191        }
192        else {
193            throw new RuntimeException("No schema found with name " + schemaName);
194        }
195        return returnSchema;
196    }
197
198    public enum SchemaName {
199        WORKFLOW(1), COORDINATOR(2), SLA_ORIGINAL(3), BUNDLE(4);
200        private final int id;
201
202        private SchemaName(int id) {
203            this.id = id;
204        }
205
206        public int getId() {
207            return id;
208        }
209    }
210
211    /**
212     * Returns validator for schema
213     * @param schemaName
214     * @return
215     * @throws SAXException
216     */
217    public Validator getValidator(SchemaName schemaName) throws SAXException {
218        return getValidator(getSchema(schemaName));
219    }
220
221    /**
222     * Returns validator for schema
223     * @param schema
224     * @return
225     * @throws SAXException
226     */
227    public static Validator getValidator(Schema schema) throws SAXException {
228        Validator validator = schema.newValidator();
229        validator.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
230        validator.setFeature("http://xml.org/sax/features/external-general-entities", false);
231        validator.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
232        validator.setProperty("http://apache.org/xml/properties/internal/entity-resolver", xmlEntityResolver);
233        return validator;
234    }
235
236    private static class NoXMLEntityResolver implements XMLEntityResolver {
237        @Override
238        public XMLInputSource resolveEntity(XMLResourceIdentifier xmlResourceIdentifier) throws XNIException, IOException {
239            throw new IOException("DOCTYPE is disallowed when the feature http://apache.org/xml/features/disallow-doctype-decl "
240                    + "set to true.");
241        }
242    }
243}