001 package org.apache.ojb.broker.metadata;
002
003 /* Copyright 2002-2005 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 import javax.xml.parsers.ParserConfigurationException;
019 import javax.xml.parsers.SAXParser;
020 import javax.xml.parsers.SAXParserFactory;
021 import java.io.File;
022 import java.io.FileInputStream;
023 import java.io.FileOutputStream;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.OutputStream;
027 import java.io.PrintWriter;
028 import java.net.MalformedURLException;
029 import java.net.URL;
030 import java.net.URLConnection;
031 import java.util.Date;
032
033 import org.apache.commons.lang.SerializationUtils;
034 import org.apache.commons.lang.SystemUtils;
035 import org.apache.ojb.broker.util.ClassHelper;
036 import org.apache.ojb.broker.util.configuration.Configurable;
037 import org.apache.ojb.broker.util.configuration.Configuration;
038 import org.apache.ojb.broker.util.configuration.ConfigurationException;
039 import org.apache.ojb.broker.util.configuration.impl.OjbConfigurator;
040 import org.apache.ojb.broker.util.logging.Logger;
041 import org.apache.ojb.broker.util.logging.LoggerFactory;
042 import org.xml.sax.ContentHandler;
043 import org.xml.sax.ErrorHandler;
044 import org.xml.sax.InputSource;
045 import org.xml.sax.SAXException;
046 import org.xml.sax.SAXParseException;
047 import org.xml.sax.XMLReader;
048
049 /**
050 * This class is responsible for reading and writing DescriptorRepository objects
051 * from and to persistent media.
052 * Currently only XML file based persistence is supported.
053 *
054 * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
055 * @author <a href="mailto:armin@codeAuLait.de">Armin Waibel</a>
056 * @version $Id: RepositoryPersistor.java,v 1.1 2007-08-24 22:17:29 ewestfal Exp $
057 */
058 public class RepositoryPersistor implements Configurable
059 {
060 // TODO: Refactoring of the metadata reading/handling?
061
062 private static Logger log = LoggerFactory.getLogger(RepositoryPersistor.class);
063
064 private static final String SER_FILE_SUFFIX = "serialized";
065 private static final String SERIALIZED_REPOSITORY_PATH = "serializedRepositoryPath";
066
067 private boolean useSerializedRepository = false;
068
069 public RepositoryPersistor()
070 {
071 OjbConfigurator.getInstance().configure(this);
072 }
073
074 public void configure(Configuration pConfig) throws ConfigurationException
075 {
076 useSerializedRepository = ((MetadataConfiguration) pConfig).useSerializedRepository();
077 }
078
079 /**
080 * Write the {@link DescriptorRepository} to the given output object.
081 */
082 public void writeToFile(DescriptorRepository repository, ConnectionRepository conRepository, OutputStream out)
083 {
084 RepositoryTags tags = RepositoryTags.getInstance();
085 try
086 {
087 if (log.isDebugEnabled())
088 log.debug("## Write repository file ##" +
089 repository.toXML() +
090 "## End of repository file ##");
091
092
093 String eol = SystemUtils.LINE_SEPARATOR;
094 StringBuffer buf = new StringBuffer();
095 // 1. write XML header
096 buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + eol);
097
098 buf.append("<!DOCTYPE descriptor-repository SYSTEM \"repository.dtd\" >" + eol + eol);
099 // strReturn += "<!DOCTYPE descriptor-repository SYSTEM \"repository.dtd\" [" + eol;
100 // strReturn += "<!ENTITY database-metadata SYSTEM \""+ConnectionRepository.DATABASE_METADATA_FILENAME+"\">" + eol;
101 // strReturn += "<!ENTITY user SYSTEM \"repository_user.xml\">" + eol;
102 // strReturn += "<!ENTITY junit SYSTEM \"repository_junit.xml\">" + eol;
103 // strReturn += "<!ENTITY internal SYSTEM \"repository_internal.xml\"> ]>" + eol + eol;
104
105 buf.append("<!-- OJB RepositoryPersistor generated this file on " + new Date().toString() + " -->" + eol);
106
107 buf.append(tags.getOpeningTagNonClosingById(RepositoryElements.MAPPING_REPOSITORY) + eol);
108 buf.append(" " + tags.getAttribute(RepositoryElements.REPOSITORY_VERSION, DescriptorRepository.getVersion()) + eol);
109 buf.append(" " + tags.getAttribute(RepositoryElements.ISOLATION_LEVEL, repository.getIsolationLevelAsString()) + eol);
110 buf.append(">" + eol);
111
112 if(conRepository != null) buf.append(eol + eol + conRepository.toXML() + eol + eol);
113 if(repository != null) buf.append(repository.toXML());
114
115 buf.append(tags.getClosingTagById(RepositoryElements.MAPPING_REPOSITORY));
116
117 PrintWriter pw = new PrintWriter(out);
118 pw.print(buf.toString());
119 pw.flush();
120 pw.close();
121 }
122 catch (Exception e)
123 {
124 log.error("Could not write to output stream" + out, e);
125 }
126 }
127
128 /**
129 * Read the repository configuration file.
130 * <br>
131 * If configuration property <code>useSerializedRepository</code> is <code>true</code>
132 * all subsequent calls read a serialized version of the repository.
133 * The directory where the serialized repository is stored can be specified
134 * with the <code>serializedRepositoryPath</code> entry in OJB.properties.
135 * Once a serialized repository is found changes to repository.xml will be
136 * ignored. To force consideration of these changes the serialized repository
137 * must be deleted manually.
138 */
139 public DescriptorRepository readDescriptorRepository(String filename)
140 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
141 {
142 DescriptorRepository result;
143 if (useSerializedRepository)
144 // use serialized repository
145 {
146 // build File object pointing to serialized repository location
147 Configuration config = OjbConfigurator.getInstance().getConfigurationFor(null);
148 String pathPrefix = config.getString(SERIALIZED_REPOSITORY_PATH, ".");
149 File serFile = new File(pathPrefix + File.separator + filename + "." + SER_FILE_SUFFIX);
150
151 if (serFile.exists() && serFile.length() > 0)
152 // if file exists load serialized version of repository
153 {
154 try
155 {
156 long duration = System.currentTimeMillis();
157 result = deserialize(serFile);
158 log.info("Read serialized repository in " + (System.currentTimeMillis() - duration) + " ms");
159 }
160 catch (Exception e)
161 {
162 log.error("error in loading serialized repository. Will try to use XML version.", e);
163 result = (DescriptorRepository) buildRepository(filename, DescriptorRepository.class);
164 }
165 }
166 else
167 // if no serialized version exists, read it from xml and write serialized file
168 {
169 long duration = System.currentTimeMillis();
170 result = (DescriptorRepository) buildRepository(filename, DescriptorRepository.class);
171 log.info("Read repository from file took " + (System.currentTimeMillis() - duration) + " ms");
172 serialize(result, serFile);
173 }
174 }
175 // don't use serialized repository
176 else
177 {
178 long duration = System.currentTimeMillis();
179 result = (DescriptorRepository) buildRepository(filename, DescriptorRepository.class);
180 log.info("Read class descriptors took " + (System.currentTimeMillis() - duration) + " ms");
181 }
182 return result;
183 }
184
185 public DescriptorRepository readDescriptorRepository(InputStream inst)
186 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
187 {
188 long duration = System.currentTimeMillis();
189 InputSource inSource = new InputSource(inst);
190 DescriptorRepository result = (DescriptorRepository) readMetadataFromXML(inSource, DescriptorRepository.class);
191 log.info("Read class descriptors took " + (System.currentTimeMillis() - duration) + " ms");
192 return result;
193 }
194
195 /**
196 * Read the repository configuration file and extract connection handling information.
197 */
198 public ConnectionRepository readConnectionRepository(String filename)
199 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
200 {
201 long duration = System.currentTimeMillis();
202 ConnectionRepository result = (ConnectionRepository) buildRepository(filename, ConnectionRepository.class);
203 log.info("Read connection repository took " + (System.currentTimeMillis() - duration) + " ms");
204 return result;
205 }
206
207 /**
208 * Read the repository configuration file and extract connection handling information.
209 */
210 public ConnectionRepository readConnectionRepository(InputStream inst)
211 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
212 {
213 long duration = System.currentTimeMillis();
214 InputSource inSource = new InputSource(inst);
215 ConnectionRepository result = (ConnectionRepository) readMetadataFromXML(inSource, ConnectionRepository.class);
216 log.info("Read connection repository took " + (System.currentTimeMillis() - duration) + " ms");
217 return result;
218 }
219
220 protected DescriptorRepository deserialize(File serFile)
221 {
222 DescriptorRepository result = null;
223 try
224 {
225 FileInputStream fis = new FileInputStream(serFile);
226 // deserialize repository
227 result = (DescriptorRepository) SerializationUtils.deserialize(fis);
228 }
229 catch (Exception e)
230 {
231 log.error("Deserialisation failed, using input path: " + serFile.getAbsolutePath(), e);
232 }
233 return result;
234 }
235
236 protected void serialize(DescriptorRepository repository, File file)
237 {
238 try
239 {
240 FileOutputStream fos = new FileOutputStream(file);
241 // serialize repository
242 SerializationUtils.serialize(repository, fos);
243 }
244 catch (Exception e)
245 {
246 log.error("Serialization failed, using output path: " + file.getAbsolutePath(), e);
247 }
248 }
249
250 /**
251 *
252 * TODO: We should re-design the configuration file reading
253 */
254 private Object buildRepository(String repositoryFileName, Class targetRepository)
255 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
256 {
257 URL url = buildURL(repositoryFileName);
258 /*
259 arminw:
260 strange, when using 'url.openStream()' argument repository
261 could not be parsed
262 ipriha:
263 parser needs a base url to find referenced entities.
264 */
265 // InputSource source = new InputSource(url.openStream());
266
267 String pathName = url.toExternalForm();
268
269 log.info("Building repository from :" + pathName);
270 InputSource source = new InputSource(pathName);
271 URLConnection conn = url.openConnection();
272 conn.setUseCaches(false);
273 conn.connect();
274 InputStream in = conn.getInputStream();
275 source.setByteStream(in);
276 try
277 {
278 return readMetadataFromXML(source, targetRepository);
279 }
280 finally
281 {
282 try
283 {
284 in.close();
285 }
286 catch (IOException x)
287 {
288 log.warn("unable to close repository input stream [" + x.getMessage() + "]", x);
289 }
290 }
291 }
292
293 /**
294 * Read metadata by populating an instance of the target class
295 * using SAXParser.
296 */
297 private Object readMetadataFromXML(InputSource source, Class target)
298 throws MalformedURLException, ParserConfigurationException, SAXException, IOException
299 {
300 // TODO: make this configurable
301 boolean validate = false;
302
303 // get a xml reader instance:
304 SAXParserFactory factory = SAXParserFactory.newInstance();
305 log.info("RepositoryPersistor using SAXParserFactory : " + factory.getClass().getName());
306 if (validate)
307 {
308 factory.setValidating(true);
309 }
310 SAXParser p = factory.newSAXParser();
311 XMLReader reader = p.getXMLReader();
312 if (validate)
313 {
314 reader.setErrorHandler(new OJBErrorHandler());
315 }
316
317 Object result;
318 if (DescriptorRepository.class.equals(target))
319 {
320 // create an empty repository:
321 DescriptorRepository repository = new DescriptorRepository();
322 // create handler for building the repository structure
323 ContentHandler handler = new RepositoryXmlHandler(repository);
324 // tell parser to use our handler:
325 reader.setContentHandler(handler);
326 reader.parse(source);
327 result = repository;
328 }
329 else if (ConnectionRepository.class.equals(target))
330 {
331 // create an empty repository:
332 ConnectionRepository repository = new ConnectionRepository();
333 // create handler for building the repository structure
334 ContentHandler handler = new ConnectionDescriptorXmlHandler(repository);
335 // tell parser to use our handler:
336 reader.setContentHandler(handler);
337 reader.parse(source);
338 //LoggerFactory.getBootLogger().info("loading XML took " + (stop - start) + " msecs");
339 result = repository;
340 }
341 else
342 throw new MetadataException("Could not build a repository instance for '" + target +
343 "', using source " + source);
344 return result;
345 }
346
347 private URL buildURL(String repositoryFileName) throws MalformedURLException
348 {
349 //j2ee compliant lookup of resources
350 URL url = ClassHelper.getResource(repositoryFileName);
351
352 // don't be too strict: if resource is not on the classpath, try ordinary file lookup
353 if (url == null)
354 {
355 try
356 {
357 url = new File(repositoryFileName).toURL();
358 }
359 catch (MalformedURLException ignore)
360 {
361 }
362 }
363
364 if (url != null)
365 {
366 log.info("OJB Descriptor Repository: " + url);
367 }
368 else
369 {
370 throw new MalformedURLException("did not find resource " + repositoryFileName);
371 }
372 return url;
373 }
374
375 // inner class
376 class OJBErrorHandler implements ErrorHandler
377 {
378 public void warning(SAXParseException exception)
379 throws SAXException
380 {
381 logMessage(exception, false);
382 }
383
384 public void error(SAXParseException exception)
385 throws SAXException
386 {
387 logMessage(exception, false);
388 }
389
390 public void fatalError(SAXParseException exception)
391 throws SAXException
392 {
393 logMessage(exception, true);
394 }
395
396 void logMessage(SAXParseException e, boolean isFatal)
397 {
398 String msg = e.getMessage();
399 if (isFatal)
400 {
401 log.error("## " + e.getSystemId() + " - line " + e.getLineNumber() + ": " + msg + " ##");
402 }
403 else
404 {
405 log.warn(e.getSystemId() + " - line " + e.getLineNumber() + ": " + msg);
406 }
407 }
408 }
409 }