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