001: /*
002: * Copyright 2006-2007 The Scriptella Project Team.
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: */
016: package scriptella.configuration;
017:
018: import org.w3c.dom.Document;
019: import org.xml.sax.EntityResolver;
020: import org.xml.sax.ErrorHandler;
021: import org.xml.sax.InputSource;
022: import org.xml.sax.SAXParseException;
023: import scriptella.expression.PropertiesSubstitutor;
024: import scriptella.spi.ParametersCallback;
025: import scriptella.spi.support.HierarchicalParametersCallback;
026: import scriptella.spi.support.MapParametersCallback;
027: import scriptella.spi.support.NullParametersCallback;
028:
029: import javax.xml.parsers.DocumentBuilder;
030: import javax.xml.parsers.DocumentBuilderFactory;
031: import java.io.IOException;
032: import java.net.URL;
033: import java.util.Map;
034: import java.util.logging.Logger;
035:
036: /**
037: * Factory class for ETL {@link ConfigurationEl configuration} files.
038: *
039: * @author Fyodor Kupolov
040: * @version 1.0
041: */
042: public class ConfigurationFactory {
043: private static final Logger LOG = Logger
044: .getLogger(ConfigurationFactory.class.getName());
045: private static final DocumentBuilderFactory DBF = DocumentBuilderFactory
046: .newInstance();
047: private static final String DTD_NAME = "etl.dtd";
048: private URL resourceURL;
049: private ParametersCallback externalParameters;
050:
051: static {
052: setValidating(true);
053: }
054:
055: /**
056: * Sets validation option.
057: *
058: * @param validating true if XML file validation should be performed.
059: */
060: public static void setValidating(boolean validating) {
061: DBF.setValidating(validating);
062: }
063:
064: public ConfigurationFactory() {
065: }
066:
067: public URL getResourceURL() {
068: return resourceURL;
069: }
070:
071: public void setResourceURL(final URL resourceURL) {
072: this .resourceURL = resourceURL;
073: }
074:
075: /**
076: * Sets additional properties.
077: * <p>External properties takes precedence over properties specified
078: * in ETL <properties> element.
079: * <p>Intended for integration with other systems like ant.
080: *
081: * @param externalProperties external properties. Nulls allowed.
082: */
083: public void setExternalParameters(
084: final Map<String, ?> externalProperties) {
085: setExternalParameters(externalProperties == null ? null
086: : new MapParametersCallback(externalProperties));
087: }
088:
089: /**
090: * Sets additional parameters.
091: * <p>These parameters take precedence over properties specified in the <properties> section of an ETL file.
092: * <p>Intended for integration with other systems like ant.
093: *
094: * @param externalParameters external parameters.
095: */
096: public void setExternalParameters(
097: final ParametersCallback externalParameters) {
098: this .externalParameters = externalParameters;
099: }
100:
101: /**
102: * Parses XML file and creates a configuration based on a specified parameters.
103: *
104: * @return configuration element.
105: */
106: public ConfigurationEl createConfiguration() {
107: if (resourceURL == null) {
108: throw new ConfigurationException(
109: "Configuration URL is required");
110: }
111: try {
112: DocumentBuilder db = DBF.newDocumentBuilder();
113: db.setEntityResolver(ETL_ENTITY_RESOLVER);
114: db.setErrorHandler(ETL_ERROR_HANDLER);
115:
116: final InputSource inputSource = new InputSource(resourceURL
117: .toString());
118: final Document document = db.parse(inputSource);
119: HierarchicalParametersCallback params = new HierarchicalParametersCallback(
120: externalParameters == null ? NullParametersCallback.INSTANCE
121: : externalParameters, null);
122: PropertiesSubstitutor ps = new PropertiesSubstitutor(params);
123:
124: return new ConfigurationEl(new XmlElement(document
125: .getDocumentElement(), resourceURL, ps), params);
126: } catch (IOException e) {
127: throw new ConfigurationException(
128: "Unable to load document: " + e, e);
129: } catch (Exception e) {
130: throw new ConfigurationException(
131: "Unable to parse document: " + e, e);
132: }
133: }
134:
135: //XML-related stuff - resolver+error handler
136: private static final EntityResolver ETL_ENTITY_RESOLVER = new EntityResolver() {
137: public InputSource resolveEntity(final String publicId,
138: final String systemId) {
139: if (systemId != null && systemId.trim().endsWith(DTD_NAME)) {
140: return new InputSource(ConfigurationFactory.class
141: .getResourceAsStream("/scriptella/dtd/"
142: + DTD_NAME));
143: }
144: return null;
145: }
146: };
147: private static final ErrorHandler ETL_ERROR_HANDLER = new ErrorHandler() {
148: public void warning(final SAXParseException exception) {
149: LOG.warning(messageFor(exception));
150: }
151:
152: public void error(final SAXParseException exception) {
153: LOG.warning(messageFor(exception));
154: }
155:
156: private String messageFor(final SAXParseException exception) {
157: StringBuilder sb = new StringBuilder(32);
158: sb.append("XML configuration warning in ");
159:
160: final String sid = exception.getSystemId();
161:
162: if (sid != null) {
163: sb.append(sid);
164: } else {
165: sb.append("the document");
166: }
167:
168: sb.append('(');
169: sb.append(exception.getLineNumber());
170: sb.append(':');
171: sb.append(exception.getColumnNumber());
172: sb.append("): ");
173: sb.append(exception.getMessage());
174:
175: return sb.toString();
176: }
177:
178: public void fatalError(final SAXParseException exception) {
179: LOG.severe(messageFor(exception));
180: }
181: };
182:
183: }
|