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    }