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