001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999-2004 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: ArchiveConfigMBean.java 9118 2006-07-05 13:23:44Z sauthieg $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas.resource;
025:
026: import java.io.BufferedReader;
027: import java.io.File;
028: import java.io.FileNotFoundException;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.InputStreamReader;
033: import java.io.OutputStream;
034: import java.util.Enumeration;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.jar.JarEntry;
038: import java.util.jar.JarFile;
039: import java.util.jar.JarOutputStream;
040: import java.util.zip.ZipEntry;
041:
042: import javax.management.MBeanException;
043: import javax.xml.XMLConstants;
044: import javax.xml.parsers.DocumentBuilder;
045: import javax.xml.parsers.DocumentBuilderFactory;
046: import javax.xml.parsers.ParserConfigurationException;
047: import javax.xml.transform.dom.DOMSource;
048: import javax.xml.validation.Schema;
049: import javax.xml.validation.SchemaFactory;
050: import javax.xml.validation.Validator;
051:
052: import org.apache.commons.modeler.BaseModelMBean;
053: import org.objectweb.jonas.common.Log;
054: import org.objectweb.jonas_lib.deployment.validation.JLSResourceResolver;
055: import org.objectweb.jonas_lib.xml.XMLSerializer;
056: import org.objectweb.util.monolog.api.BasicLevel;
057: import org.objectweb.util.monolog.api.Logger;
058: import org.w3c.dom.Document;
059: import org.xml.sax.EntityResolver;
060: import org.xml.sax.SAXException;
061:
062: /**
063: * A generic archive configuration MBean class which provides
064: * the ability to extract an XML configuration file from an archive
065: * (jar/war/ear/rar) as either a Document or as a String of XML. As
066: * well as storing an updated configuration file.
067: *
068: * @author Patrick Smith
069: * @author Greg Lapouchnian
070: */
071: public class ArchiveConfigMBean extends BaseModelMBean {
072:
073: /**
074: * Default constructor, constructs a BaseModelMBean.
075: * @throws MBeanException from the super class.
076: */
077: public ArchiveConfigMBean() throws MBeanException {
078: super ();
079: }
080:
081: /**
082: * Takes an <code>org.w3c.dom.Document</code> object
083: * and returns a <code>String</code> representation of the
084: * XML Documnt.
085: * @param doc the Document representation of the XML.
086: * @return a String representation of the XML.
087: */
088: private static String serializeDocument(Document doc) {
089: XMLSerializer ser = new XMLSerializer(doc);
090: ser.setIndent(4);
091: ser.setLineWidth(0);
092:
093: OutputStream out = new java.io.ByteArrayOutputStream();
094: try {
095: ser.serialize(out);
096: } catch (IOException e) {
097: e.printStackTrace();
098: }
099: return out.toString();
100: }
101:
102: public void createArchiveWithXmlFile(String archiveName,
103: String xmlFilePath, String doc) throws IOException {
104: JarEntry newEntry;
105: File tempJarFile;
106: JarOutputStream tempJar;
107:
108: try {
109: newEntry = new JarEntry(xmlFilePath);
110: tempJarFile = new File(archiveName);
111:
112: if (tempJarFile.exists()) {
113: throw new Exception("File already exists.");
114: }
115:
116: tempJar = new JarOutputStream(new FileOutputStream(
117: tempJarFile));
118: } catch (Exception e) {
119: throw new IOException("Unable to update archive: "
120: + archiveName + "\n" + e.getMessage());
121: }
122:
123: try {
124:
125: byte[] bytes = doc.getBytes();
126: tempJar.putNextEntry(newEntry);
127: tempJar.write(bytes, 0, bytes.length);
128:
129: } catch (Exception e) {
130: throw new IOException("Unable to update archive: "
131: + archiveName + "\n" + e.getMessage());
132: } finally {
133: tempJar.close();
134: }
135: }
136:
137: public void addXML(String archiveName, String xmlFilePath,
138: String docString) throws IOException {
139: File jarFile;
140: JarFile rarFile;
141: JarEntry newEntry;
142: File tempJarFile;
143: JarOutputStream tempJar;
144: boolean updated = false;
145:
146: try {
147: jarFile = new File(archiveName);
148: rarFile = new JarFile(jarFile);
149: newEntry = new JarEntry(xmlFilePath);
150: tempJarFile = new File(archiveName + ".tmp");
151:
152: tempJar = new JarOutputStream(new FileOutputStream(
153: tempJarFile));
154: } catch (Exception e) {
155: throw new IOException("Unable to update archive: "
156: + archiveName + "\n" + e.getMessage());
157: }
158:
159: try {
160:
161: // Allocate a buffer for reading entry data.
162: byte[] buffer = new byte[1024];
163: int bytesRead;
164:
165: byte[] bytes = docString.getBytes();
166: tempJar.putNextEntry(newEntry);
167: tempJar.write(bytes, 0, bytes.length);
168:
169: for (Enumeration entries = rarFile.entries(); entries
170: .hasMoreElements();) {
171: // Get the next entry.
172: JarEntry entry = (JarEntry) entries.nextElement();
173:
174: // If the entry has not been added already, add it.
175: if (!entry.getName().equals(xmlFilePath)) {
176: // Get an input stream for the entry.
177: InputStream entryStream = rarFile
178: .getInputStream(entry);
179:
180: // Read the entry and write it to the temp jar.
181: tempJar.putNextEntry(entry);
182:
183: while ((bytesRead = entryStream.read(buffer)) != -1) {
184: tempJar.write(buffer, 0, bytesRead);
185: }
186: }
187: }
188: updated = true;
189: } catch (Exception e) {
190: throw new IOException("Unable to update archive: "
191: + archiveName + "\n" + e.getMessage());
192: } finally {
193: rarFile.close();
194: tempJar.close();
195:
196: if (updated) {
197: jarFile.delete();
198: tempJarFile.renameTo(jarFile);
199: }
200: }
201:
202: }
203:
204: /**
205: * Saves the given <code>org.w3c.dom.Document</code> back into an archive
206: * of the name given by archiveName.
207: *
208: * The Java Jar/Archive classes apparently do not provide support for
209: * simply updating a file in place in an archive, so a temporary archive
210: * must be created and have the contents of the existing archive copied over
211: * (with the exception of the updated Document file) then remove the existing
212: * archive and rename the temporary one.
213: *
214: * @param archiveName The name (and path) of the archive to updated.
215: * @param xmlFilePath The path within the archive to the XML file being updated.
216: * @param doc The Document representation of the XML file being updated.
217: * @throws IOException If the updating fails.
218: */
219: public void saveXML(String archiveName, String xmlFilePath,
220: Document doc) throws IOException {
221: File jarFile;
222: JarFile rarFile;
223: JarEntry newEntry;
224: File tempJarFile;
225: JarOutputStream tempJar;
226: boolean updated = false;
227:
228: try {
229: jarFile = new File(archiveName);
230: rarFile = new JarFile(jarFile);
231: newEntry = new JarEntry(xmlFilePath);
232: tempJarFile = new File(archiveName + ".tmp");
233:
234: tempJar = new JarOutputStream(new FileOutputStream(
235: tempJarFile));
236: } catch (Exception e) {
237: throw new IOException("Unable to update archive: "
238: + archiveName + "\n" + e.getMessage());
239: }
240:
241: try {
242:
243: // Allocate a buffer for reading entry data.
244: byte[] buffer = new byte[1024];
245: int bytesRead;
246:
247: String docString = serializeDocument(doc);
248: byte[] bytes = docString.getBytes();
249: tempJar.putNextEntry(newEntry);
250: tempJar.write(bytes, 0, bytes.length);
251:
252: for (Enumeration entries = rarFile.entries(); entries
253: .hasMoreElements();) {
254: // Get the next entry.
255: JarEntry entry = (JarEntry) entries.nextElement();
256:
257: // If the entry has not been added already, add it.
258: if (!entry.getName().equals(xmlFilePath)) {
259: // Get an input stream for the entry.
260: InputStream entryStream = rarFile
261: .getInputStream(entry);
262:
263: // Read the entry and write it to the temp jar.
264: tempJar.putNextEntry(entry);
265:
266: while ((bytesRead = entryStream.read(buffer)) != -1) {
267: tempJar.write(buffer, 0, bytesRead);
268: }
269: }
270: }
271: updated = true;
272: } catch (Exception e) {
273: throw new IOException("Unable to update archive: "
274: + archiveName + "\n" + e.getMessage());
275: } finally {
276: rarFile.close();
277: tempJar.close();
278:
279: if (updated) {
280: jarFile.delete();
281: tempJarFile.renameTo(jarFile);
282: }
283: }
284:
285: }
286:
287: /**
288: * Verifies that the Document is correct with respect to the schemas
289: * associated with the XML. If no exception is thrown then the XML is
290: * either valid or no schemas were available for this XML. In which case
291: * the Document can be considered verified.
292: * @param doc The Document to validate.
293: * @throws SAXException If the document fails validation.
294: * @throws IOException If there is an error opening a schema.
295: */
296: public void verifyDocument(Document doc) throws SAXException,
297: IOException {
298: SchemaFactory factory = SchemaFactory
299: .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
300:
301: // use a custom ResourceResolver in order to load Schemas and DTDs
302: // locally when validating
303: factory.setResourceResolver(new JLSResourceResolver(
304: getEntityResolver()));
305:
306: String schemaPath = getSchema(doc);
307:
308: if (schemaPath != null) {
309: java.net.URL url = new java.net.URL(schemaPath);
310:
311: Schema schema = factory.newSchema(url);
312: // Create a Validator object, which can be used to validate
313: // an instance document.
314: Validator validator = schema.newValidator();
315: // Validate the DOM tree.
316: validator.validate(new DOMSource(doc));
317: } else {
318: // No schemas were provided for verification.
319: // The generic "ArchiveConfigMBean" will not validate, this is
320: // left for MBean extension classes to do.
321: // This is not an abstract method/class because we may want
322: // to use this as a generic, non-validating editor.
323: }
324: }
325:
326: /**
327: * Return an EntityResolver for this MBean.
328: * @return an EntityResolver for this MBean.
329: */
330: protected EntityResolver getEntityResolver() {
331: // For the generic MBean (for now) return null
332: // because we don't know which schemas/dtds to add to the resolver.
333: return null;
334: }
335:
336: /**
337: * Extracts a Document representation of the XML file provided by
338: * <code>xmlFilePath</code> within the archive <code>archiveName</code>.
339: * @param archiveName The archive from which to extract the XML.
340: * @param xmlFilePath The path within the archive to the XML file.
341: * @return A document representation of the XML file.
342: * @throws Exception if the document cannot be extracted.
343: */
344: public Document extractDocument(String archiveName,
345: String xmlFilePath) throws Exception {
346: Document result = null;
347: // Input Stream
348: InputStream raInputStream = null;
349: // ZipEntry
350: ZipEntry raZipEntry = null;
351:
352: try {
353: JarFile rarFile = new JarFile(archiveName);
354: //Check the ra entry
355: raZipEntry = rarFile.getEntry(xmlFilePath);
356: if (raZipEntry != null) {
357: //Get the stream
358: raInputStream = rarFile.getInputStream(raZipEntry);
359: result = newDocument(raInputStream, xmlFilePath);
360: } else {
361: throw new FileNotFoundException("Could not find "
362: + xmlFilePath + " in " + archiveName);
363: }
364: } catch (Exception e) {
365: Logger logger = Log.getLogger(Log.JONAS_ADMIN_PREFIX);
366: logger.log(BasicLevel.WARN, "Could not load XML file '"
367: + xmlFilePath + "' from '" + archiveName + "': "
368: + e.getMessage());
369: throw e;
370: }
371:
372: return result;
373: }
374:
375: /**
376: * Extracts a String representation of the XML file provided by
377: * <code>xmlFilePath</code> within the archive <code>archiveName</code>.
378: * @param archiveName The archive from which to extract the XML.
379: * @param xmlFilePath The path within the archive to the XML file.
380: * @return A String representation of the XML file.
381: * @throws Exception if the XML cannot be extracted.
382: */
383: public String extractXML(String archiveName, String xmlFilePath)
384: throws Exception {
385:
386: String result = null;
387: InputStream inputStream = null;
388: ZipEntry zipEntry = null;
389:
390: try {
391: JarFile rarFile = new JarFile(archiveName);
392:
393: zipEntry = rarFile.getEntry(xmlFilePath);
394: if (zipEntry != null) {
395: //Get the stream
396: inputStream = rarFile.getInputStream(zipEntry);
397:
398: // read from the stream and create the string representing the xml
399: InputStreamReader reader = new InputStreamReader(
400: inputStream);
401: BufferedReader br = new BufferedReader(reader);
402: StringBuffer sb = new StringBuffer();
403: String line = null;
404:
405: while ((line = br.readLine()) != null) {
406: sb.append(line + "\n");
407: }
408: result = sb.toString();
409:
410: } else {
411: throw new FileNotFoundException("Could not find "
412: + xmlFilePath + " in " + archiveName);
413: }
414: } catch (Exception e) {
415: Logger logger = Log.getLogger(Log.JONAS_ADMIN_PREFIX);
416: logger.log(BasicLevel.WARN, "Could not load XML file '"
417: + xmlFilePath + "' from '" + archiveName + "': "
418: + e.getMessage());
419: throw e;
420: }
421:
422: return result;
423: }
424:
425: /**
426: * Returns a new Document representation of the XML within the
427: * InputStream with the name <code>name</code>.
428: * @param is The InputStream containing the XMl.
429: * @param name The name of the XML file.
430: * @return A new Document representation of the XML.
431: * @throws ParserConfigurationException if the parser configuration is incorrect.
432: * @throws SAXException if the parsing fails.
433: * @throws IOException if the files cannot be opened.
434: */
435: private Document newDocument(InputStream is, String name)
436: throws ParserConfigurationException, SAXException,
437: IOException {
438:
439: DocumentBuilderFactory factory = DocumentBuilderFactory
440: .newInstance();
441: factory.setNamespaceAware(true);
442: DocumentBuilder builder = factory.newDocumentBuilder();
443:
444: // get the entity resolved if one was provided by the subclass
445: if (getEntityResolver() != null) {
446: builder.setEntityResolver(getEntityResolver());
447: }
448: Document doc = builder.parse(is);
449:
450: // close InputStream when finished parsing
451: is.close();
452:
453: return doc;
454: }
455:
456: /**
457: * Returns the path for the schema based on the xsi:schemalocation property
458: * @param doc The Document to get schemas for.
459: * @return The schema for the specified document.
460: */
461: private String getSchema(Document doc) {
462: String schemaPath = null;
463: if (doc.getDocumentElement().getAttribute("xsi:schemaLocation") != null) {
464: String[] parts = doc.getDocumentElement().getAttribute(
465: "xsi:schemaLocation").trim().split(" ");
466: String schemaURL = parts[parts.length - 1];
467:
468: // get the name of the schema file referenced
469: String schemaName = schemaURL.substring(schemaURL
470: .lastIndexOf('/') + 1);
471:
472: if (getSchemaList() != null) {
473: // find the schema that ends in the same file name as the one
474: // specified in the document
475: Iterator i = getSchemaList().iterator();
476: while (i.hasNext()) {
477: String schema = (String) i.next();
478: String file = schema.substring(schema
479: .lastIndexOf('/') + 1);
480:
481: // we've found the right schema, we will return its URL
482: if (file.equals(schemaName)) {
483: schemaPath = schema;
484: }
485: }
486: }
487: }
488: return schemaPath;
489: }
490:
491: /**
492: * Get a list of possible schema locations to validate against.
493: * @return a list of schemas that are available for this type of deployment
494: * descriptor.
495: */
496: protected List getSchemaList() {
497: return null;
498: }
499: }
|