001: /*
002: * Copyright 2005 Ralf Joachim
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 org.exolab.castor.mapping;
017:
018: import java.io.IOException;
019: import java.net.MalformedURLException;
020: import java.net.URL;
021: import java.util.ArrayList;
022: import java.util.Collections;
023: import java.util.HashSet;
024: import java.util.List;
025: import java.util.Set;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.castor.mapping.MappingSource;
030: import org.castor.util.Messages;
031: import org.exolab.castor.mapping.xml.MappingRoot;
032: import org.exolab.castor.net.util.URIUtils;
033: import org.exolab.castor.util.DTDResolver;
034: import org.xml.sax.EntityResolver;
035: import org.xml.sax.InputSource;
036: import org.xml.sax.SAXException;
037:
038: /**
039: * Utility class for loading mapping files and providing them to the
040: * XML marshaller, JDO engine etc. The mapping file can be loaded from
041: * a URL, input stream or SAX <tt>InputSource</tt>.
042: * <p>
043: * Multiple mapping files can be loaded with the same <tt>Mapping</tt>
044: * object. When loading master mapping files that include other mapping
045: * files it might be convenient to use {@link #setBaseURL} or {@link
046: * #setEntityResolver}.
047: * <p>
048: * If the desired class loader is different than the one used by Castor
049: * (e.g. if Castor is installed as a Java extension), the <tt>Mapping</tt>
050: * object can be constructed with the proper class loader.
051: * <p>
052: * The following example loads two mapping files:
053: * <pre>
054: * Mapping mapping;
055: *
056: * mapping = new Mapping( getClass().getClassLoader() );
057: * mapping.loadMapping( "mapping.xml" );
058: * mapping.loadMapping( url );
059: * </pre>
060: *
061: * @author <a href="mailto:arkin AT intalio DOT com">Assaf Arkin</a>
062: * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
063: * @version $Revision: 6907 $ $Date: 2006-04-25 16:09:10 -0600 (Tue, 25 Apr 2006) $
064: */
065: public final class Mapping {
066: //--------------------------------------------------------------------------
067:
068: /** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta Commons
069: * Logging </a> instance used for all logging. */
070: private static final Log LOG = LogFactory.getLog(Mapping.class);
071:
072: private static final String DEFAULT_SOURCE_TYPE = "CastorXmlMapping";
073:
074: /** List of mapping sources to resolve. */
075: private final List _mappings = new ArrayList();
076:
077: /** Set of processed systemID's. */
078: private final Set _processed = new HashSet();
079:
080: /** The loaded mapping. */
081: private final MappingRoot _root = new MappingRoot();
082:
083: /** The class loader to use. */
084: private final ClassLoader _classLoader;
085:
086: /** The entity resolver to use. May be null. */
087: private DTDResolver _resolver = new DTDResolver();
088:
089: //--------------------------------------------------------------------------
090:
091: /**
092: * Constructs a new mapping.
093: *
094: * @param loader The class loader to use, null for the default
095: */
096: public Mapping(final ClassLoader loader) {
097: if (loader == null) {
098: _classLoader = getClass().getClassLoader();
099: } else {
100: _classLoader = loader;
101: }
102: }
103:
104: /**
105: * Constructs a new mapping.
106: */
107: public Mapping() {
108: this (null);
109: }
110:
111: //--------------------------------------------------------------------------
112:
113: /**
114: * Get list of mapping sources to resolve.
115: *
116: * @return List of mapping sources to resolve.
117: * @throws MappingException If no mapping source has been loaded previously.
118: */
119: public List getMappingSources() throws MappingException {
120: if (_mappings.size() == 0) {
121: throw new MappingException("Must call loadMapping first");
122: }
123: return Collections.unmodifiableList(_mappings);
124: }
125:
126: /**
127: * Marks the given mapping as having been processed.
128: *
129: * @param id systemID or stream to identify the mapping to mark.
130: */
131: public void markAsProcessed(final Object id) {
132: _processed.add(id);
133: }
134:
135: /**
136: * Returns true if the given systemID or stream has been marked as processed.
137: *
138: * @param id systemID or stream to check for being marked as processed.
139: * @return true if the given systemID or stream has been marked as processed.
140: */
141: public boolean processed(final Object id) {
142: return _processed.contains(id);
143: }
144:
145: /**
146: * Get the loaded mapping.
147: *
148: * @return The loaded mapping.
149: */
150: public MappingRoot getRoot() {
151: return _root;
152: }
153:
154: //--------------------------------------------------------------------------
155:
156: /**
157: * Returns the class loader used by this mapping object. The returned
158: * class loaded may be the one passed in the constructor, the one used
159: * to load Castor, or in some 1.1 JVMs null.
160: *
161: * @return The class loader used by this mapping object (may be null)
162: */
163: public ClassLoader getClassLoader() {
164: return _classLoader;
165: }
166:
167: /**
168: * Sets the entity resolver. The entity resolver can be used to
169: * resolve external entities and cached documents that are used
170: * from within mapping files.
171: *
172: * @param resolver The entity resolver to use
173: */
174: public void setEntityResolver(final EntityResolver resolver) {
175: _resolver = new DTDResolver(resolver);
176: }
177:
178: /**
179: * Sets the base URL for the mapping and related files. If the base
180: * URL is known, files can be included using relative names. Any URL
181: * can be passed, if the URL can serve as a base URL it will be used.
182: * If url is an absolute path, it is converted to a file URL.
183: *
184: * @param url The base URL
185: */
186: public void setBaseURL(final String url) {
187: String location = url;
188: //-- remove filename if necessary:
189: if (location != null) {
190: int idx = location.lastIndexOf('/');
191: if (idx < 0)
192: idx = location.lastIndexOf('\\');
193: if (idx >= 0) {
194: int extIdx = location.indexOf('.', idx);
195: if (extIdx > 0) {
196: location = location.substring(0, idx);
197: }
198: }
199: }
200:
201: try {
202: _resolver.setBaseURL(new URL(location));
203: } catch (MalformedURLException except) {
204: // try to parse the url as an absolute path
205: try {
206: LOG.info(Messages.format("mapping.wrongURL", location));
207: _resolver.setBaseURL(new URL("file", null, location));
208: } catch (MalformedURLException except2) {
209: }
210: }
211: }
212:
213: //--------------------------------------------------------------------------
214:
215: /**
216: * Loads the mapping from the specified URL with type defaults to
217: * 'CastorXmlMapping'. If an entity resolver was specified, will use
218: * the entity resolver to resolve the URL. This method is also used
219: * to load mappings referenced from another mapping or configuration
220: * file.
221: *
222: * @param url The URL of the mapping file.
223: * @throws IOException An error occured when reading the mapping file.
224: * @throws MappingException The mapping file is invalid.
225: */
226: public void loadMapping(final String url) throws IOException,
227: MappingException {
228: loadMapping(url, DEFAULT_SOURCE_TYPE);
229: }
230:
231: /**
232: * Loads the mapping from the specified URL. If an entity resolver
233: * was specified, will use the entity resolver to resolve the URL.
234: * This method is also used to load mappings referenced from another
235: * mapping or configuration file.
236: *
237: * @param url The URL of the mapping file.
238: * @param type The source type.
239: * @throws IOException An error occured when reading the mapping file.
240: * @throws MappingException The mapping file is invalid.
241: */
242: public void loadMapping(final String url, final String type)
243: throws IOException, MappingException {
244: String location = url;
245: if (_resolver.getBaseURL() == null) {
246: setBaseURL(location);
247: location = URIUtils.getRelativeURI(location);
248: }
249: try {
250: InputSource source = _resolver
251: .resolveEntity(null, location);
252: if (source == null) {
253: source = new InputSource(location);
254: }
255: if (source.getSystemId() == null) {
256: source.setSystemId(location);
257: }
258: LOG.info(Messages.format("mapping.loadingFrom", location));
259: loadMapping(source, type);
260: } catch (SAXException ex) {
261: throw new MappingException(ex);
262: }
263: }
264:
265: /**
266: * Loads the mapping from the specified URL with type defaults to
267: * 'CastorXmlMapping'.
268: *
269: * @param url The URL of the mapping file.
270: * @throws IOException An error occured when reading the mapping file.
271: * @throws MappingException The mapping file is invalid.
272: */
273: public void loadMapping(final URL url) throws IOException,
274: MappingException {
275: loadMapping(url, DEFAULT_SOURCE_TYPE);
276: }
277:
278: /**
279: * Loads the mapping from the specified URL.
280: *
281: * @param url The URL of the mapping file.
282: * @param type The source type.
283: * @throws IOException An error occured when reading the mapping file.
284: * @throws MappingException The mapping file is invalid.
285: */
286: public void loadMapping(final URL url, final String type)
287: throws IOException, MappingException {
288: try {
289: if (_resolver.getBaseURL() == null) {
290: _resolver.setBaseURL(url);
291: }
292: InputSource source = _resolver.resolveEntity(null, url
293: .toExternalForm());
294: if (source == null) {
295: source = new InputSource(url.toExternalForm());
296: source.setByteStream(url.openStream());
297: } else
298: source.setSystemId(url.toExternalForm());
299: LOG.info(Messages.format("mapping.loadingFrom", url
300: .toExternalForm()));
301: loadMapping(source, type);
302: } catch (SAXException ex) {
303: throw new MappingException(ex);
304: }
305: }
306:
307: /**
308: * Loads the mapping from the specified input source with type defaults to
309: * 'CastorXmlMapping'.
310: *
311: * @param source The input source.
312: */
313: public void loadMapping(final InputSource source) {
314: loadMapping(source, DEFAULT_SOURCE_TYPE);
315: }
316:
317: /**
318: * Loads the mapping from the specified input source.
319: *
320: * @param source The input source.
321: * @param type The source type.
322: */
323: public void loadMapping(final InputSource source, final String type) {
324: _mappings.add(new MappingSource(source, type, _resolver));
325: }
326:
327: //--------------------------------------------------------------------------
328: }
|