001: package org.tigris.scarab.util.xmlissues;
002:
003: /* ================================================================
004: * Copyright (c) 2000-2002 CollabNet. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are
008: * met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowlegement: "This product includes
019: * software developed by Collab.Net <http://www.Collab.Net/>."
020: * Alternately, this acknowlegement may appear in the software itself, if
021: * and wherever such third-party acknowlegements normally appear.
022: *
023: * 4. The hosted project names must not be used to endorse or promote
024: * products derived from this software without prior written
025: * permission. For written permission, please contact info@collab.net.
026: *
027: * 5. Products derived from this software may not use the "Tigris" or
028: * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
029: * prior written permission of Collab.Net.
030: *
031: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
032: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
033: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
034: * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
035: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
036: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
037: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
038: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
039: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
040: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
041: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042: *
043: * ====================================================================
044: *
045: * This software consists of voluntary contributions made by many
046: * individuals on behalf of Collab.Net.
047: */
048:
049: import java.beans.BeanDescriptor;
050: import java.beans.IntrospectionException;
051: import java.io.BufferedInputStream;
052: import java.io.BufferedReader;
053: import java.io.File;
054: import java.io.FileInputStream;
055: import java.io.FileReader;
056: import java.io.IOException;
057: import java.io.InputStream;
058: import java.io.InputStreamReader;
059: import java.io.Reader;
060: import java.io.StringReader;
061: import java.io.StringWriter;
062: import java.io.Writer;
063: import java.net.MalformedURLException;
064: import java.util.Collection;
065: import java.util.Hashtable;
066: import java.util.Iterator;
067: import java.util.Locale;
068: import java.util.Map;
069:
070: import javax.xml.parsers.ParserConfigurationException;
071: import javax.xml.transform.OutputKeys;
072: import javax.xml.transform.Result;
073: import javax.xml.transform.Source;
074: import javax.xml.transform.Transformer;
075: import javax.xml.transform.TransformerException;
076: import javax.xml.transform.TransformerFactory;
077: import javax.xml.transform.URIResolver;
078: import javax.xml.transform.dom.DOMSource;
079: import javax.xml.transform.stream.StreamResult;
080: import javax.xml.transform.stream.StreamSource;
081:
082: import org.apache.commons.betwixt.XMLIntrospector;
083: import org.apache.commons.betwixt.io.BeanReader;
084: import org.apache.commons.betwixt.io.BeanWriter;
085: import org.apache.commons.betwixt.strategy.HyphenatedNameMapper;
086: import org.apache.commons.betwixt.strategy.NameMapper;
087: import org.apache.commons.digester.Digester;
088: import org.apache.commons.digester.Rule;
089: import org.apache.commons.fileupload.FileItem;
090: import org.apache.commons.logging.Log;
091: import org.apache.commons.logging.LogFactory;
092: import org.apache.fulcrum.localization.Localization;
093: import org.apache.torque.TorqueException;
094: import org.jdom.Document;
095: import org.jdom.Element;
096: import org.jdom.JDOMException;
097: import org.jdom.input.SAXBuilder;
098: import org.jdom.transform.JDOMSource;
099: import org.tigris.scarab.om.Module;
100: import org.tigris.scarab.om.ScarabUser;
101: import org.tigris.scarab.om.ScarabUserManager;
102: import org.tigris.scarab.tools.localization.L10NKeySet;
103: import org.tigris.scarab.util.ScarabConstants;
104: import org.tigris.scarab.util.ScarabException;
105: import org.tigris.scarab.util.TurbineInitialization;
106: import org.tigris.scarab.workflow.WorkflowFactory;
107: import org.xml.sax.Attributes;
108: import org.xml.sax.ErrorHandler;
109: import org.xml.sax.InputSource;
110: import org.xml.sax.SAXException;
111: import org.xml.sax.SAXNotRecognizedException;
112: import org.xml.sax.SAXNotSupportedException;
113: import org.xml.sax.SAXParseException;
114:
115: /**
116: * This is a bean'ish object which allows one to set values for importing
117: * issues, and then run the actual import.
118: *
119: * Amenable to the ant task wrapper or you can pass an explicit file for
120: * explicit import if turbine is already up and running.
121: *
122: * <p>The way the ant task wrapper works is simple: call all the appropriate
123: * set methods to define the properties. Then you will need to call the init()
124: * and execute methods to start running things. Note: If Turbine is already
125: * initialized, there is no need to call the init() method.</p>
126: *
127: * <p>Instances of this class are not thread-safe.</p>
128: *
129: * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
130: * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
131: * @version $Id: ImportIssues.java 10065 2006-04-23 21:02:14Z hair $
132: * @since Scarab beta 14
133: */
134: public class ImportIssues implements ErrorHandler {
135: private static final Log LOG = LogFactory
136: .getLog(ImportIssues.class);
137: private final transient TransformerFactory transformerFactory = TransformerFactory
138: .newInstance();
139: // private final transient DocumentBuilderFactory documentBuilderFactory
140: // = DocumentBuilderFactory.newInstance();
141:
142: private static final String JIRA_XSL = "/org/tigris/scarab/util/xmlissues/xsls/jira.xsl";
143: private static final String BUGZILLA_XSL = "/org/tigris/scarab/util/xmlissues/xsls/bugzilla.xsl";
144:
145: private static final ImportType SCARAB = ImportType
146: .valueOf("scarab");
147: private static final ImportType BUGZILLA = ImportType
148: .valueOf("bugzilla");
149: private static final ImportType JIRA = ImportType.valueOf("jira");
150:
151: /*
152: * The virtual URL to the document type definition (DTD) used with
153: * this version of Scarab. Though this file doesn't actually
154: * exist, it's what can be used as a friendly way to refer to
155: * Scarab's DTD in an XML file's <code>DOCTYPE</code> declaration.
156: */
157: public static final String SYSTEM_DTD_URI = "http://scarab.tigris.org/dtd/scarab-0.21.0.dtd";
158:
159: /**
160: * The absolute URL to the document type definition (DTD) used
161: * with this version of Scarab.
162: */
163: private static final String INTERNAL_DTD_URI = "http://scarab.tigris.org/source/browse/*checkout*/scarab/trunk/www/dtd/scarab-0.21.0.dtd?content-type=text%2Fxml";
164:
165: /**
166: * The resource location of the DTD in the classpath.
167: */
168: private static final String DTD_RESOURCE = "/org/tigris/scarab/scarab.dtd";
169:
170: /**
171: * Name of the properties file.
172: */
173: private String trProps = "/WEB-INF/conf/TurbineResourcesTest.properties";
174:
175: /**
176: * Name of the xmlimport.properties file used for configuration of log4j.
177: */
178: private String configProps = "/WEB-INF/conf/xmlimport.properties";
179:
180: private File configDir = null;
181: private boolean sendEmail = false;
182: private File xmlFile = null;
183:
184: /**
185: * Current file attachment handling code contains a security hole
186: * which can allow a user to see any file on the host that is
187: * readable by Scarab. It is not easy to exploit this hole (you
188: * have to know about file paths on a host you likely don't have
189: * access to), and there are cases where we want to use the
190: * functionality and can be sure the hole is not being exploited.
191: * So adding a flag to disallow file attachments when importing
192: * through the UI.
193: */
194: private boolean allowFileAttachments;
195:
196: /**
197: * Whether we're in validation mode, or some phase after that.
198: */
199: private boolean validationMode;
200:
201: /**
202: * A list of any errors encountered during the import, likely
203: * added during the validation phase.
204: */
205: private ImportErrors importErrors;
206:
207: public ImportIssues() {
208: this (false);
209: }
210:
211: public ImportIssues(final boolean allowFileAttachments) {
212: this .allowFileAttachments = allowFileAttachments;
213: this .importErrors = new ImportErrors();
214: }
215:
216: /**
217: * Instance of scarabissues we ran the actual insert with.
218: *
219: * Make it available post import so importer can get at info about
220: * what has just been imported.
221: */
222: private ScarabIssues si = null;
223:
224: public boolean getSendEmail() {
225: return this .sendEmail;
226: }
227:
228: public void setSendEmail(final boolean state) {
229: this .sendEmail = state;
230: }
231:
232: public File getXmlFile() {
233: return this .xmlFile;
234: }
235:
236: public void setXmlFile(final File xmlFile) {
237: this .xmlFile = xmlFile;
238: }
239:
240: public File getConfigDir() {
241: return this .configDir;
242: }
243:
244: public void setConfigDir(final File configDir) {
245: this .configDir = configDir;
246: }
247:
248: public String getConfigFile() {
249: return this .configProps;
250: }
251:
252: public void setConfigFile(final String configProps) {
253: this .configProps = configProps;
254: }
255:
256: public String getTurbineResources() {
257: return this .trProps;
258: }
259:
260: public void setTurbineResources(final String trProps) {
261: this .trProps = trProps;
262: }
263:
264: public void init() throws ScarabException, MalformedURLException,
265: IOException {
266: TurbineInitialization
267: .setTurbineResources(getTurbineResources());
268: TurbineInitialization.setUp(getConfigDir().getAbsolutePath(),
269: getConfigFile());
270: }
271:
272: /**
273: * Hook method called by <a
274: * href="http://ant.apache.org/">Ant's</a> Task wrapper.
275: */
276: public void execute() throws ScarabException {
277: runImport(getXmlFile());
278: }
279:
280: /**
281: * Run an import.
282: *
283: * Assumes we're up and running inside of turbine.
284: *
285: * @param importFile File to import.
286: *
287: * @return List of errors if any.
288: *
289: * @exception Exception
290: */
291: public Collection runImport(final File importFile)
292: throws ScarabException {
293: return runImport(importFile, (Module) null);
294: }
295:
296: /**
297: * Run an import.
298: *
299: * Assumes we're up and running inside of turbine.
300: *
301: * @param importFile File to import.
302: * @param currentModule If non-null, run check that import is going
303: * against this module.
304: *
305: * @return List of errors if any.
306: *
307: * @exception Exception
308: */
309: public Collection runImport(final File importFile,
310: final Module currentModule) throws ScarabException {
311: return runImport(importFile.getAbsolutePath(), importFile,
312: currentModule, SCARAB);
313: }
314:
315: /**
316: * Run an import.
317: *
318: * Assumes we're up and running inside of turbine. Awkwardly duplicates
319: * {@link #runImport(File) import} but duplication is so we can do the reget of
320: * the input stream; FileInput "manages" backing up the Upload for us on the
321: * second get of the input stream (It creates new ByteArrayInputStream
322: * w/ the src being a byte array of the file its kept in memory or in
323: * temporary storage on disk).
324: *
325: * @param importFile FileItem reference to use importing.
326: *
327: * @return List of errors if any.
328: *
329: * @exception Exception
330: */
331: public Collection runImport(final FileItem importFile)
332: throws ScarabException {
333: return runImport(importFile, (Module) null, SCARAB);
334: }
335:
336: /**
337: * Run an import.
338: *
339: * Assumes we're up and running inside of turbine. Awkwardly duplicates
340: * {@link #runImport(File) import} but duplication is so we can do the reget of
341: * the input stream; FileInput "manages" backing up the Upload for us on the
342: * second get of the input stream (It creates new ByteArrayInputStream
343: * w/ the src being a byte array of the file its kept in memory or in
344: * temporary storage on disk).
345: *
346: * @param importFile FileItem reference to use importing.
347: * @param currentModule If non-null, run check that import is going
348: * against this module.
349: *
350: * @return List of errors if any.
351: *
352: * @exception Exception
353: */
354: public Collection runImport(final FileItem importFile,
355: final Module currentModule, final ImportType type)
356: throws ScarabException {
357: return runImport(importFile.getName(), importFile,
358: currentModule, type);
359: }
360:
361: /**
362: * @param input A <code>File</code> or <code>FileItem</code>.
363: */
364: protected Collection runImport(final String filePath,
365: final Object input, final Module currentModule,
366: final ImportType type) throws ScarabException {
367:
368: final String msg = "Importing issues from XML '" + filePath
369: + '\'';
370: LOG.debug(msg);
371: try {
372: // Disable workflow and set file attachment flag
373: WorkflowFactory.setForceUseDefault(true);
374:
375: final Reader is = getScarabFormattedReader(input, type,
376: currentModule);
377:
378: final BeanReader reader = createScarabIssuesBeanReader();
379: validate(filePath, is, reader, currentModule);
380:
381: if (importErrors == null || importErrors.isEmpty()) {
382: // Reget the input stream.
383: si = insert(filePath, getScarabFormattedReader(input,
384: type, currentModule), reader);
385: }
386: } catch (ParserConfigurationException e) {
387: LOG.error(msg, e);
388: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
389: } catch (TransformerException e) {
390: LOG.error(msg, e);
391: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
392: } catch (IOException e) {
393: LOG.error(msg, e);
394: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
395: } catch (IntrospectionException e) {
396: LOG.error(msg, e);
397: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
398: } catch (SAXException e) {
399: LOG.error(msg, e);
400: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
401: } catch (TorqueException e) {
402: LOG.error(msg, e);
403: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
404: } catch (JDOMException e) {
405: LOG.error(msg, e);
406: throw new ScarabException(L10NKeySet.ExceptionGeneral, e); //EXCEPTION
407: } catch (ScarabException e) {
408: LOG.error(msg, e);
409: throw e; //EXCEPTION
410: } catch (ArrayIndexOutOfBoundsException e) {
411: LOG.error(msg, e);
412: throw e; //EXCEPTION
413: } finally {
414: // Re-enable workflow.
415: WorkflowFactory.setForceUseDefault(false);
416: }
417:
418: return importErrors;
419: }
420:
421: /**
422: * Coerces a new <code>Reader</code> from <code>input</code>.
423: * Necessary because the stream is read twice by
424: * <code>runImport()</code>, so the source of the stream must be
425: * passed into that method.
426: *
427: * @throws IllegalArgumentException If <code>input</code> is
428: * unrecognized.
429: */
430: private Reader readerFor(Object input) throws IOException {
431: if (input instanceof FileItem) {
432: return new InputStreamReader(((FileItem) input)
433: .getInputStream());
434: } else if (input instanceof File) {
435: return new BufferedReader(new FileReader((File) input));
436: } else {
437: throw new IllegalArgumentException(); //EXCEPTION
438: }
439: }
440:
441: /**
442: * Run validation phase. Starts by performing XML-well
443: * formed-ness and DTD validation (if present), then checks the
444: * content.
445: *
446: * @param name Filename to output in log message. May be null.
447: * @param is Input stream to read.
448: * @param reader ScarabIssues bean reader instance.
449: * @param currentModule If not <code>null</code>, check whether
450: * import is going against this module.
451: *
452: * @return Any errors encountered during XML or content
453: * validation.
454: *
455: * @exception Exception
456: */
457: protected void validate(final String name, final Reader is,
458: final BeanReader reader, final Module currentModule)
459: throws ParserConfigurationException, SAXException,
460: IOException {
461: // While parsing the XML, we perform well formed-ness and DTD
462: // validation (if present, see Xerces dynamic feature).
463: setValidationMode(reader, true);
464: ScarabIssues si = null;
465: try {
466: si = (ScarabIssues) reader.parse(is);
467: } catch (SAXParseException e) {
468: // TODO: L10N this error message from Xerces (somehow),
469: // and provide a prefix that describes that a XML parse
470: // error was encountered.
471: String message = new String("XML parse error at line "
472: + e.getLineNumber() + " column "
473: + e.getColumnNumber() + ": " + e.getMessage());
474: LOG.error(message);
475: importErrors.add(message);
476: }
477:
478: // If the XML is okay, validate the actual content.
479: if (si != null) {
480: // ASSUMPTION: Parse errors prevent entry into this block.
481: validateContent(si, currentModule);
482:
483: // Log any errors encountered during import.
484: if (importErrors != null) {
485: final int nbrErrors = importErrors.size();
486: LOG.error("Found " + nbrErrors + " error"
487: + (nbrErrors == 1 ? "" : "s") + " importing '"
488: + name + "':");
489: for (Iterator itr = importErrors.iterator(); itr
490: .hasNext();) {
491: LOG.error(itr.next());
492: }
493: }
494: }
495: }
496:
497: /**
498: * Helper method for validate() which invokes validation routines
499: * supplied by <code>ScarabIssues</code> plus (conditionally)
500: * additional module validation.
501: */
502: private void validateContent(final ScarabIssues si,
503: final Module currentModule) {
504: if (currentModule != null) {
505: // Make sure the XML module corresponds to the current
506: // module. This is later than we'd like to perform this
507: // check, since we've already parsed the XML. On the
508: // upside, si.getModule() should not return null.
509: final XmlModule xmlModule = si.getModule();
510:
511: // HELP: Check domain also?
512:
513: final String xmlModuleName = xmlModule.getName();
514: final String curModuleName = currentModule.getRealName();
515: if (!curModuleName.equals(xmlModuleName)) {
516: Object[] args = { xmlModuleName, curModuleName };
517: String error = Localization.format(
518: ScarabConstants.DEFAULT_BUNDLE_NAME,
519: getLocale(), "XMLAndCurrentModuleMismatch",
520: args);
521: importErrors.add(error);
522: }
523:
524: final String xmlCode = xmlModule.getCode();
525: if (xmlCode == null
526: || !currentModule.getCode().equals(xmlCode)) {
527: final Object[] args = { xmlCode,
528: currentModule.getCode() };
529: final String error = Localization.format(
530: ScarabConstants.DEFAULT_BUNDLE_NAME,
531: getLocale(), "XMLAndCurrentCodeMismatch", args);
532: importErrors.add(error);
533: }
534: }
535:
536: si.doValidateDependencies();
537: si.doValidateUsers();
538: }
539:
540: /**
541: * Do actual issue insert.
542: *
543: * Assumes issues passed have already been validated. If they haven't
544: * been, could damage scarab.
545: *
546: * @param name Name to use in log messages (E.g. filename). May be null.
547: * @param is Input stream of xml to insert.
548: * @param reader ScarabIssues bean reader instance.
549: *
550: * @return The instance of scarabissues we inserted in case you need to
551: * display info about the issues inserted.
552: */
553: protected ScarabIssues insert(final String name, final Reader is,
554: final BeanReader reader)
555: throws ParserConfigurationException, SAXException,
556: IOException, ScarabException {
557: setValidationMode(reader, false);
558: final ScarabIssues si = (ScarabIssues) reader.parse(is);
559: si.doHandleDependencies();
560: LOG.debug("Successfully imported " + name + '!');
561: return si;
562: }
563:
564: /**
565: * Sets the validation mode for both this instance and the
566: * specified <code>Digester</code>.
567: *
568: * @param reader The XML parser to set the validation mode for.
569: * @param state The validation mode.
570: * @see <a href="http://xml.apache.org/xerces-j/faq-general.html#valid">Xerces validation FAQ</a>
571: * @see <a href="http://xml.apache.org/xerces-j/features.html">Xerces SAX2 feature list</a>
572: */
573: private void setValidationMode(Digester reader, boolean state)
574: throws ParserConfigurationException, SAXException {
575: this .validationMode = state;
576:
577: // Setup the XML parser SAX2 features.
578:
579: // Turn on DTD validation (these are functionally equivalent
580: // with Xerces 1.4.4 and likely most other SAX2 impls).
581: reader.setValidating(state);
582: reader.setFeature("http://xml.org/sax/features/validation",
583: state);
584:
585: // Validate the document only if a grammar is specified
586: // (http://xml.org/sax/features/validation must be state).
587: reader.setFeature(
588: "http://apache.org/xml/features/validation/dynamic",
589: state);
590: }
591:
592: /**
593: * Get instance of the ScarabIssues used importing.
594: *
595: * You'd use this method to get at the instance of scarab issues used
596: * importing for case where you want to print out info on the import thats
597: * just happened. Call after a successful import. Calling before will give
598: * undefined results.
599: *
600: * @return Instance of ScarabIssues we ran the import with.
601: */
602: public ScarabIssues getScarabIssuesBeanReader() {
603: return this .si;
604: }
605:
606: /**
607: * A URI Resolver for use with the XSL transforms
608: *
609: * This resolver will map a URI in the XSL transform to an absolute
610: * file path.
611: */
612: class TransformResolver implements URIResolver {
613:
614: File resources_path; // Absolute path to the resources
615:
616: public TransformResolver(String resources_path) {
617: this .resources_path = new File(resources_path);
618: }
619:
620: public Source resolve(String href, String base) {
621:
622: File resource = new File(resources_path, href);
623:
624: return (resource.exists()) ? new StreamSource(resource)
625: : null;
626: }
627: }
628:
629: /**
630: * Return a bean reader for ScarabIssue.
631: *
632: * @return A bean reader.
633: */
634: protected BeanReader createScarabIssuesBeanReader()
635: throws ParserConfigurationException,
636: IntrospectionException, SAXNotRecognizedException,
637: SAXNotSupportedException {
638: BeanReader reader = new BeanReader() {
639: public InputSource resolveEntity(String publicId,
640: String systemId) throws SAXException {
641: InputSource input = null;
642: if (publicId == null && systemId != null) {
643: // Resolve SYSTEM DOCTYPE.
644: if (SYSTEM_DTD_URI.equalsIgnoreCase(systemId)
645: || INTERNAL_DTD_URI
646: .equalsIgnoreCase(systemId)) {
647: // First look for the DTD in the classpath.
648: input = resolveDTDResource();
649:
650: if (input == null) {
651: // Kick resolution back to Digester.
652: input = super .resolveEntity(publicId,
653: systemId);
654: }
655: }
656: }
657: return input;
658: }
659:
660: /**
661: * Looks for the DTD in the classpath as resouce
662: * {@link #DTD_RESOURCE}.
663: *
664: * @return The DTD, or <code>null</code> if not found.
665: */
666: private InputSource resolveDTDResource() {
667: InputStream stream = getClass().getResourceAsStream(
668: DTD_RESOURCE);
669: if (stream != null) {
670: LOG.debug("Located DTD in classpath using "
671: + "resource path '" + DTD_RESOURCE + '\'');
672: return new InputSource(stream);
673: } else {
674: LOG.debug("DTD resource '" + DTD_RESOURCE
675: + "' not " + "found in classpath");
676: return null;
677: }
678: }
679: };
680:
681: // Connecting Digster's logger to ours logs too verbosely.
682: //reader.setLogger(LOG);
683: reader.register(SYSTEM_DTD_URI, INTERNAL_DTD_URI);
684: // Be forgiving about the encodings we accept.
685: reader.setFeature(
686: "http://apache.org/xml/features/allow-java-encodings",
687: true);
688: reader.setXMLIntrospector(createXMLIntrospector());
689: reader.registerBeanClass(ScarabIssues.class);
690: NameMapper nm = reader.getXMLIntrospector().getNameMapper();
691: reader.addRule(nm.mapTypeToElementName(new BeanDescriptor(
692: ScarabIssues.class).getName()),
693: new ScarabIssuesSetupRule());
694: reader.setErrorHandler(this );
695: return reader;
696: }
697:
698: protected XMLIntrospector createXMLIntrospector() {
699: XMLIntrospector introspector = new XMLIntrospector();
700:
701: // set elements for attributes to true
702: introspector.setAttributesForPrimitives(false);
703:
704: // wrap collections in an XML element
705: //introspector.setWrapCollectionsInElement(true);
706:
707: // turn bean elements into lower case
708: introspector.setElementNameMapper(new HyphenatedNameMapper());
709:
710: return introspector;
711: }
712:
713: /**
714: * A rule to perform setup of the a ScarabIssues instance.
715: */
716: class ScarabIssuesSetupRule extends Rule {
717: public void begin(String namespace, String name,
718: Attributes attributes) {
719: ScarabIssues si = (ScarabIssues) getDigester().peek();
720: si.allowFileAttachments(allowFileAttachments);
721: si.inValidationMode(validationMode);
722: si.importErrors = importErrors;
723: }
724: }
725:
726: /**
727: * Method to output the bean object as XML.
728: *
729: * Not used right now.
730: */
731: protected void write(final Object bean, final Writer out)
732: throws IOException, SAXException, IntrospectionException {
733: final BeanWriter writer = new BeanWriter(out);
734: writer.setXMLIntrospector(createXMLIntrospector());
735: writer.enablePrettyPrint();
736: writer.setWriteIDs(false);
737: writer.write(bean);
738: }
739:
740: private Locale getLocale() {
741: return ScarabConstants.DEFAULT_LOCALE;
742: }
743:
744: // ---- org.xml.sax.ErrorHandler implementation ------------------------
745:
746: /** Receive notification of a recoverable error. */
747: public void error(SAXParseException e) throws SAXParseException {
748: LOG.error("Parse Error at line " + e.getLineNumber()
749: + " column " + e.getColumnNumber() + ": "
750: + e.getMessage(), e);
751: throw e; //EXCEPTION
752: }
753:
754: /** Receive notification of a non-recoverable error. */
755: public void fatalError(SAXParseException e)
756: throws SAXParseException {
757: LOG.error("Parse Fatal Error at line " + e.getLineNumber()
758: + " column " + e.getColumnNumber() + ": "
759: + e.getMessage(), e);
760: throw e; //EXCEPTION
761: }
762:
763: /** Receive notification of a warning. */
764: public void warning(SAXParseException e) {
765: // Warnings are non-fatal. At some point we should report
766: // these back to the end user.
767: LOG.debug("Parse Warning at line " + e.getLineNumber()
768: + " column " + e.getColumnNumber() + ": "
769: + e.getMessage());
770: }
771:
772: /** Handles transforming other xml (bugzilla or jira) formats into
773: * the scarab xml format
774: **/
775: private Reader getScarabFormattedReader(final Object input,
776: final ImportType type, final Module currModule)
777: throws IOException, TransformerException, TorqueException,
778: JDOMException {
779: Reader returnValue = null;
780:
781: if (SCARAB == type) {
782: // is in correct format already, just return the input stream
783: returnValue = readerFor(input);
784: } else if (BUGZILLA == type) {
785: // Location of the extensions directory for Bugzilla
786: // Transform configuration, mappings and attachments are here
787: String extensions = System.getProperty("catalina.home")
788: + "/../extensions/bugzilla";
789:
790: // Locate the Bugzilla to Scarab XSL transform
791: final InputStream xsl = getClass().getResourceAsStream(
792: BUGZILLA_XSL);
793:
794: // Transform Bugzilla xml to scarab format
795: // (Trailing '/' to resources is deliberate)
796: final Reader result = transformXML(new StreamSource(
797: readerFor(input)), xsl, currModule, extensions);
798:
799: // insert missing information (module)
800: returnValue = insertModuleNode(result, currModule);
801: } else if (JIRA == type) {
802: // transform xml to scarab format
803: final InputStream xsl = getClass().getResourceAsStream(
804: JIRA_XSL);
805: final Reader result = transformXML(new StreamSource(
806: readerFor(input)), xsl, currModule, null);
807: // insert missing information (module)
808: returnValue = insertModuleNode(result, currModule);
809: }
810: return returnValue;
811: }
812:
813: private Reader transformXML(final Source xmlSource,
814: final InputStream xsl, final Module currModule,
815: final String resources) throws TransformerException {
816: final StringWriter writer = new StringWriter();
817: final StreamResult result = new StreamResult(writer);
818:
819: // The resolver will help find the transform's resources
820: if (resources != null) {
821: transformerFactory.setURIResolver(new TransformResolver(
822: resources));
823: }
824:
825: final Transformer transformer = (xsl != null) ? transformerFactory
826: .newTransformer(new StreamSource(xsl))
827: : transformerFactory.newTransformer();
828:
829: transformer.setOutputProperty(OutputKeys.INDENT, "yes");
830:
831: // Pass this parameter to the transform to locate resources,
832: // particularly attachments. For attachments, the
833: // Scarab instance must be able to see this absolute path
834: transformer.setParameter("resources_path", resources);
835:
836: // Tell the transformer the module_code
837: transformer.setParameter("module_code", currModule.getCode());
838:
839: if (xsl == null) {
840: // plain outputting (used on a DomSource)
841: transformer.setOutputProperty(OutputKeys.METHOD, "xml");
842: transformer.setOutputProperty(
843: OutputKeys.OMIT_XML_DECLARATION, "yes");
844: }
845: transformer.transform(xmlSource, result);
846: System.out.println(writer.toString());
847: return new StringReader(writer.toString());
848: }
849:
850: private Reader insertModuleNode(final Reader result,
851: final Module currModule) throws TorqueException,
852: JDOMException, IOException, TransformerException {
853:
854: final ScarabUser user = ScarabUserManager
855: .getInstance(currModule.getOwnerId());
856:
857: // Core Java: org.w3c.dom version (jdk1.4+ compatible)
858: // final DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
859: // final Document doc = docBuilder.parse( new InputSource(result) );
860: // // insert module
861: // final Element root = doc.getDocumentElement();
862: // final Element moduleNode = doc.createElement("module");
863: // final Element idNode = doc.createElement("id");
864: // final Element parentIdNode = doc.createElement("parent-id");
865: // final Element nameNode = doc.createElement("name");
866: // final Element ownerNode = doc.createElement("owner");
867: // final Element descriptionNode = doc.createElement("description");
868: // final Element urlNode = doc.createElement("url");
869: // final Element domainNode = doc.createElement("domain");
870: // final Element codeNode = doc.createElement("code");
871: //
872: // idNode.appendChild(doc.createTextNode(String.valueOf(currModule.getModuleId())));
873: // parentIdNode.appendChild(doc.createTextNode(String.valueOf(currModule.getParentId())));
874: // nameNode.appendChild(doc.createTextNode(currModule.getName()));
875: // ownerNode.appendChild(doc.createTextNode(user.getUserName()));
876: // descriptionNode.appendChild(doc.createTextNode(currModule.getDescription()));
877: // urlNode.appendChild(doc.createTextNode(currModule.getUrl()));
878: // domainNode.appendChild(doc.createTextNode(currModule.getHttpDomain()));
879: // codeNode.appendChild(doc.createTextNode(currModule.getCode()));
880: //
881: // moduleNode.appendChild(idNode);
882: // moduleNode.appendChild(parentIdNode);
883: // moduleNode.appendChild(nameNode);
884: // moduleNode.appendChild(ownerNode);
885: // moduleNode.appendChild(descriptionNode);
886: // moduleNode.appendChild(urlNode);
887: // moduleNode.appendChild(domainNode);
888: // moduleNode.appendChild(codeNode);
889: //
890: // root.appendChild(moduleNode);
891:
892: // JDom version (jdk1.3 compatible)
893:
894: final SAXBuilder builder = new SAXBuilder();
895: final Document doc = builder.build(result);
896: final Element root = doc.getRootElement();
897:
898: final Element moduleNode = new Element("module");
899: final Element idNode = new Element("id");
900: final Element parentIdNode = new Element("parent-id");
901: final Element nameNode = new Element("name");
902: final Element ownerNode = new Element("owner");
903: final Element descriptionNode = new Element("description");
904: final Element urlNode = new Element("url");
905: final Element domainNode = new Element("domain");
906: final Element codeNode = new Element("code");
907:
908: idNode.setText(String.valueOf(currModule.getModuleId()));
909: parentIdNode.setText(String.valueOf(currModule.getParentId()));
910: nameNode.setText(currModule.getRealName());
911: ownerNode.setText(user.getUserName());
912: descriptionNode.setText(currModule.getDescription());
913: urlNode.setText(currModule.getUrl());
914: domainNode.setText(currModule.getHttpDomain());
915: codeNode.setText(currModule.getCode());
916:
917: moduleNode.addContent(idNode).addContent(parentIdNode)
918: .addContent(nameNode).addContent(ownerNode).addContent(
919: descriptionNode)
920: /*
921: * These are excluded for now, since your database domain may
922: * not correspond to currModule.getHttpDomain().
923: .addContent(urlNode)
924: .addContent(domainNode)
925: */
926: .addContent(codeNode);
927: root.addContent(2, moduleNode);
928:
929: return transformXML(new JDOMSource(doc), null, currModule, null);
930: }
931:
932: public final static class ImportType {
933:
934: private final String type;
935: private static final Map instances = new Hashtable();
936:
937: private ImportType(final String type) {
938: if (type == null) {
939: throw new IllegalArgumentException(
940: "Not allowed null type");
941: }
942: this .type = type;
943: instances.put(type, this );
944: }
945:
946: public static ImportType valueOf(final String type) {
947: ImportType instance = (ImportType) instances.get(type);
948: if (instance == null) {
949: instance = new ImportType(type);
950: }
951: return instance;
952: }
953: }
954: }
|