View Javadoc
1   /*
2    * Copyright 2005 Ralf Joachim
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package org.exolab.castor.mapping;
15  
16  import java.io.IOException;
17  import java.net.MalformedURLException;
18  import java.net.URL;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.castor.core.util.Messages;
28  import org.castor.mapping.MappingSource;
29  import org.exolab.castor.mapping.xml.MappingRoot;
30  import org.exolab.castor.net.util.URIUtils;
31  import org.exolab.castor.util.DTDResolver;
32  import org.xml.sax.EntityResolver;
33  import org.xml.sax.InputSource;
34  import org.xml.sax.SAXException;
35  
36  /**
37   * Utility class for loading mapping files and providing them to the XML marshaller, JDO engine etc.
38   * The mapping file can be loaded from a URL, input stream or SAX <tt>InputSource</tt>.
39   * <p>
40   * Multiple mapping files can be loaded with the same <tt>Mapping</tt> object. When loading master
41   * mapping files that include other mapping files it might be convenient to use {@link #setBaseURL}
42   * or {@link #setEntityResolver}.
43   * <p>
44   * If the desired class loader is different than the one used by Castor (e.g. if Castor is installed
45   * as a Java extension), the <tt>Mapping</tt> object can be constructed with the proper class
46   * loader.
47   * <p>
48   * The following example loads two mapping files:
49   * 
50   * <pre>
51   * Mapping mapping;
52   *
53   * mapping = new Mapping(getClass().getClassLoader());
54   * mapping.loadMapping("mapping.xml");
55   * mapping.loadMapping(url);
56   * </pre>
57   *
58   * @author <a href="mailto:arkin AT intalio DOT com">Assaf Arkin</a>
59   * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a>
60   * @version $Revision$ $Date: 2006-04-25 16:09:10 -0600 (Tue, 25 Apr 2006) $
61   */
62  public final class Mapping {
63    // --------------------------------------------------------------------------
64  
65    /**
66     * The <a href="http://jakarta.apache.org/commons/logging/">Jakarta Commons Logging </a> instance
67     * used for all logging.
68     */
69    private static final Log LOG = LogFactory.getLog(Mapping.class);
70  
71    private static final String DEFAULT_SOURCE_TYPE = "CastorXmlMapping";
72  
73    /** List of mapping sources to resolve. */
74    private final List<MappingSource> _mappings = new ArrayList<>();
75  
76    /** Set of processed systemID's. */
77    private final Set<Object> _processed = new HashSet<>();
78  
79    /** The loaded mapping. */
80    private final MappingRoot _root = new MappingRoot();
81  
82    /** The class loader to use. */
83    private final ClassLoader _classLoader;
84  
85    /** The entity resolver to use. May be null. */
86    private DTDResolver _resolver = new DTDResolver();
87  
88    // --------------------------------------------------------------------------
89  
90    /**
91     * Constructs a new mapping.
92     *
93     * @param loader The class loader to use, null for the default
94     */
95    public Mapping(final ClassLoader loader) {
96      if (loader == null) {
97        _classLoader = getClass().getClassLoader();
98      } else {
99        _classLoader = loader;
100     }
101   }
102 
103   /**
104    * Constructs a new mapping.
105    */
106   public Mapping() {
107     this(null);
108   }
109 
110   // --------------------------------------------------------------------------
111 
112   /**
113    * Get list of mapping sources to resolve.
114    * 
115    * @return List of mapping sources to resolve.
116    * @throws MappingException If no mapping source has been loaded previously.
117    */
118   public List<MappingSource> getMappingSources() throws MappingException {
119     return Collections.unmodifiableList(_mappings);
120   }
121 
122   /**
123    * Marks the given mapping as having been processed.
124    * 
125    * @param id systemID or stream to identify the mapping to mark.
126    */
127   public void markAsProcessed(final Object id) {
128     _processed.add(id);
129   }
130 
131   /**
132    * Returns true if the given systemID or stream has been marked as processed.
133    * 
134    * @param id systemID or stream to check for being marked as processed.
135    * @return true if the given systemID or stream has been marked as processed.
136    */
137   public boolean processed(final Object id) {
138     return _processed.contains(id);
139   }
140 
141   /**
142    * Get the loaded mapping.
143    * 
144    * @return The loaded mapping.
145    */
146   public MappingRoot getRoot() {
147     return _root;
148   }
149 
150   // --------------------------------------------------------------------------
151 
152   /**
153    * Returns the class loader used by this mapping object. The returned class loaded may be the one
154    * passed in the constructor, the one used to load Castor, or in some 1.1 JVMs null.
155    *
156    * @return The class loader used by this mapping object (may be null)
157    */
158   public ClassLoader getClassLoader() {
159     return _classLoader;
160   }
161 
162   /**
163    * Sets the entity resolver. The entity resolver can be used to resolve external entities and
164    * cached documents that are used from within mapping files.
165    *
166    * @param resolver The entity resolver to use
167    */
168   public void setEntityResolver(final EntityResolver resolver) {
169     _resolver = new DTDResolver(resolver);
170   }
171 
172   /**
173    * Sets the base URL for the mapping and related files. If the base URL is known, files can be
174    * included using relative names. Any URL can be passed, if the URL can serve as a base URL it
175    * will be used. If url is an absolute path, it is converted to a file URL.
176    *
177    * @param url The base URL
178    */
179   public void setBaseURL(final String url) {
180     String location = url;
181     // -- remove filename if necessary:
182     if (location != null) {
183       int idx = location.lastIndexOf('/');
184       if (idx < 0)
185         idx = location.lastIndexOf('\\');
186       if (idx >= 0) {
187         int extIdx = location.indexOf('.', idx);
188         if (extIdx > 0) {
189           location = location.substring(0, idx);
190         }
191       }
192     }
193 
194     try {
195       _resolver.setBaseURL(new URL(location));
196     } catch (MalformedURLException except) {
197       // try to parse the url as an absolute path
198       try {
199         LOG.info(Messages.format("mapping.wrongURL", location));
200         _resolver.setBaseURL(new URL("file", null, location));
201       } catch (MalformedURLException except2) {
202       }
203     }
204   }
205 
206   // --------------------------------------------------------------------------
207 
208   /**
209    * Loads the mapping from the specified URL with type defaults to 'CastorXmlMapping'. If an entity
210    * resolver was specified, will use the entity resolver to resolve the URL. This method is also
211    * used to load mappings referenced from another mapping or configuration file.
212    *
213    * @param url The URL of the mapping file.
214    * @throws IOException An error occured when reading the mapping file.
215    * @throws MappingException The mapping file is invalid.
216    */
217   public void loadMapping(final String url) throws IOException, MappingException {
218     loadMapping(url, DEFAULT_SOURCE_TYPE);
219   }
220 
221   /**
222    * Loads the mapping from the specified URL. If an entity resolver was specified, will use the
223    * entity resolver to resolve the URL. This method is also used to load mappings referenced from
224    * another mapping or configuration file.
225    *
226    * @param url The URL of the mapping file.
227    * @param type The source type.
228    * @throws IOException An error occured when reading the mapping file.
229    * @throws MappingException The mapping file is invalid.
230    */
231   public void loadMapping(final String url, final String type)
232       throws IOException, MappingException {
233     String location = url;
234     if (_resolver.getBaseURL() == null) {
235       setBaseURL(location);
236       location = URIUtils.getRelativeURI(location);
237     }
238     try {
239       InputSource source = _resolver.resolveEntity(null, location);
240       if (source == null) {
241         source = new InputSource(location);
242       }
243       if (source.getSystemId() == null) {
244         source.setSystemId(location);
245       }
246       LOG.info(Messages.format("mapping.loadingFrom", location));
247       loadMapping(source, type);
248     } catch (SAXException ex) {
249       throw new MappingException(ex);
250     }
251   }
252 
253   /**
254    * Loads the mapping from the specified URL with type defaults to 'CastorXmlMapping'.
255    *
256    * @param url The URL of the mapping file.
257    * @throws IOException An error occured when reading the mapping file.
258    * @throws MappingException The mapping file is invalid.
259    */
260   public void loadMapping(final URL url) throws IOException, MappingException {
261     loadMapping(url, DEFAULT_SOURCE_TYPE);
262   }
263 
264   /**
265    * Loads the mapping from the specified URL.
266    *
267    * @param url The URL of the mapping file.
268    * @param type The source type.
269    * @throws IOException An error occured when reading the mapping file.
270    * @throws MappingException The mapping file is invalid.
271    */
272   public void loadMapping(final URL url, final String type) throws IOException, MappingException {
273     try {
274       if (_resolver.getBaseURL() == null) {
275         _resolver.setBaseURL(url);
276       }
277       InputSource source = _resolver.resolveEntity(null, url.toExternalForm());
278       if (source == null) {
279         source = new InputSource(url.toExternalForm());
280         source.setByteStream(url.openStream());
281       } else
282         source.setSystemId(url.toExternalForm());
283       LOG.info(Messages.format("mapping.loadingFrom", url.toExternalForm()));
284       loadMapping(source, type);
285     } catch (SAXException ex) {
286       throw new MappingException(ex);
287     }
288   }
289 
290   /**
291    * Loads the mapping from the specified input source with type defaults to 'CastorXmlMapping'.
292    *
293    * @param source The input source.
294    */
295   public void loadMapping(final InputSource source) {
296     loadMapping(source, DEFAULT_SOURCE_TYPE);
297   }
298 
299   /**
300    * Loads the mapping from the specified input source.
301    *
302    * @param source The input source.
303    * @param type The source type.
304    */
305   public void loadMapping(final InputSource source, final String type) {
306     _mappings.add(new MappingSource(source, type, _resolver));
307   }
308 
309   // --------------------------------------------------------------------------
310 }
311 
312