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