001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.xsl.transform;
042:
043: import java.io.File;
044: import java.io.IOException;
045: import java.io.OutputStream;
046: import java.util.*;
047: import java.net.URL;
048: import java.net.MalformedURLException;
049: import java.net.UnknownHostException;
050: import java.awt.Dialog;
051: import java.awt.event.ActionListener;
052: import java.awt.event.ActionEvent;
053: import java.awt.event.WindowAdapter;
054: import java.awt.event.WindowEvent;
055: import java.beans.PropertyVetoException;
056:
057: import org.openide.cookies.SaveCookie;
058:
059: import org.xml.sax.*;
060: import javax.xml.parsers.*;
061: import javax.xml.transform.*;
062: import javax.xml.transform.sax.*;
063: import javax.xml.transform.stream.*;
064:
065: import org.openide.*;
066: import org.openide.awt.HtmlBrowser;
067: import org.openide.filesystems.*;
068: import org.openide.nodes.Node;
069: import org.openide.loaders.DataObject;
070: import org.openide.util.HelpCtx;
071: import org.openide.util.RequestProcessor;
072:
073: import org.netbeans.api.xml.cookies.*;
074:
075: import org.netbeans.modules.xml.actions.InputOutputReporter;
076: import org.netbeans.modules.xml.lib.FileUtilities;
077: import org.netbeans.modules.xml.lib.GuiUtil;
078: import org.netbeans.modules.xsl.ui.TransformPanel;
079: import org.netbeans.modules.xsl.settings.TransformHistory;
080: import org.netbeans.modules.xsl.actions.TransformAction;
081: import org.netbeans.modules.xsl.utils.TransformUtil;
082: import org.openide.loaders.DataObjectNotFoundException;
083: import org.openide.util.NbBundle;
084:
085: /**
086: * Handle workflow of transformation action, gather UI info and
087: * launch the processor.
088: * <p>
089: * This class has very/needlessly complicated workflow.
090: *
091: * @author Libor Kramolis
092: * @version 0.1
093: */
094: public class TransformPerformer {
095: /** Represent transformation output window. */
096: private InputOutputReporter cookieObserver = null;
097: private Node[] nodes;
098:
099: // instance freshness state
100: private volatile boolean stalled = false;
101: private volatile boolean active = true;
102:
103: public TransformPerformer(Node[] nodes) {
104: this .nodes = nodes;
105: }
106:
107: /** If the Data Object is modified, then is saved.
108: * Fix for issue #61608
109: */
110: private void saveBeforeTransformation(DataObject dObject) {
111: if (dObject.isModified()) {
112: SaveCookie save;
113: save = (SaveCookie) dObject.getCookie(SaveCookie.class);
114: if (save != null) {
115: try {
116: save.save();
117: } catch (IOException ex) {
118: ErrorManager.getDefault().notify(
119: ErrorManager.INFORMATIONAL, ex);
120: }
121: }
122: }
123: }
124:
125: /**
126: * Entry point called from transform action.
127: * There is a fresh instance per call.
128: */
129: public void perform() {
130:
131: if (stalled)
132: throw new IllegalStateException();
133:
134: try {
135: if (nodes.length == 2) {
136:
137: // automatically detect if one of selected nodes is transformation
138: // in such case suppose that user want to it to transform second file
139:
140: DataObject do1 = (DataObject) nodes[0]
141: .getCookie(DataObject.class);
142: boolean xslt1 = TransformUtil.isXSLTransformation(do1);
143: DataObject do2 = (DataObject) nodes[1]
144: .getCookie(DataObject.class);
145: boolean xslt2 = TransformUtil.isXSLTransformation(do2);
146:
147: // fix for issue #61608
148: saveBeforeTransformation(do1);
149: saveBeforeTransformation(do2);
150:
151: //if ( Util.THIS.isLoggable() ) /* then */ {
152: // Util.THIS.debug("TransformAction.performAction:");
153: // Util.THIS.debug(" do1 [" + xslt1 + "] = " + do1);
154: // Util.THIS.debug(" do2 [" + xslt2 + "] = " + do2);
155: //}
156:
157: if (xslt1 != xslt2) {
158: TransformableCookie transformable;
159: DataObject xmlDO;
160: DataObject xslDO;
161: if (xslt1) {
162: transformable = (TransformableCookie) nodes[1]
163: .getCookie(TransformableCookie.class);
164: xmlDO = do2;
165: xslDO = do1;
166: } else {
167: transformable = (TransformableCookie) nodes[0]
168: .getCookie(TransformableCookie.class);
169: xmlDO = do1;
170: xslDO = do2;
171: }
172: DoublePerformer performer = new DoublePerformer(
173: transformable, xmlDO, xslDO);
174: performer.perform();
175: } else {
176: TransformableCookie transformable1 = (TransformableCookie) nodes[0]
177: .getCookie(TransformableCookie.class);
178: SinglePerformer performer = new SinglePerformer(
179: transformable1, do1, xslt1);
180: performer.setLastInBatch(false);
181: performer.perform();
182:
183: TransformableCookie transformable2 = (TransformableCookie) nodes[1]
184: .getCookie(TransformableCookie.class);
185: performer = new SinglePerformer(transformable2,
186: do2, xslt2);
187: performer.perform();
188: }
189: } else { // nodes.length != 2
190: for (int i = 0; i < nodes.length; i++) {
191: DataObject dataObject = (DataObject) nodes[i]
192: .getCookie(DataObject.class);
193: // fix for issue #61608
194: saveBeforeTransformation(dataObject);
195: TransformableCookie transformable = null;
196: boolean xslt = TransformUtil
197: .isXSLTransformation(dataObject);
198: if (xslt == false) {
199: transformable = (TransformableCookie) nodes[i]
200: .getCookie(TransformableCookie.class);
201: }
202: SinglePerformer performer = new SinglePerformer(
203: transformable, dataObject, xslt);
204: performer.setLastInBatch(i == (nodes.length - 1));
205: performer.perform();
206: }
207: }
208: } finally {
209: stalled = true;
210: }
211: }
212:
213: /**
214: * Is still running
215: */
216: public boolean isActive() {
217: return active;
218: }
219:
220: /**
221: * Always return an instance. Shareable by all children nested performers.
222: */
223: private InputOutputReporter getCookieObserver() {
224: if (cookieObserver == null) {
225: String label = NbBundle.getMessage(
226: TransformPerformer.class,
227: "PROP_transformation_io_name");
228: cookieObserver = new InputOutputReporter(label);
229: }
230: return cookieObserver;
231: }
232:
233: //
234: // class AbstractPerformer
235: //
236:
237: private abstract class AbstractPerformer extends WindowAdapter
238: implements ActionListener {
239: // if called on TransformableCookie node
240: private TransformableCookie transformableCookie;
241: // input XML source DataObject
242: protected DataObject xmlDO;
243: // <?xml-stylesheet
244: protected Source xmlStylesheetSource;
245: // input XSLT script DataObject
246: protected DataObject xslDO;
247: // used to resolve relative path
248: protected FileObject baseFO;
249: // URL of base FileObject
250: protected URL baseURL;
251: // XML Source
252: private Source xmlSource;
253: // XSLT Source
254: private Source xslSource;
255: // Result FileObject
256: private FileObject resultFO;
257:
258: private TransformPanel transformPanel;
259: private DialogDescriptor dialogDescriptor;
260: private Dialog dialog;
261:
262: private TransformPanel.Data data;
263: private boolean last = true;
264:
265: // was window closed by
266: private boolean workaround31850 = true;
267:
268: public AbstractPerformer(TransformableCookie transformable) {
269: this .transformableCookie = transformable;
270: }
271:
272: /**
273: * It shows a dialog and let user selct his options. Then it performs them.
274: */
275: public final void perform() {
276: try {
277: init(); // throws IOException
278: showDialog(); // throws IOException
279: } catch (IOException exc) {
280: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(exc);
281:
282: NotifyDescriptor nd = new NotifyDescriptor.Message(exc
283: .getLocalizedMessage(),
284: NotifyDescriptor.WARNING_MESSAGE);
285: DialogDisplayer.getDefault().notify(nd);
286:
287: if (isLastInBatch()) {
288: active = false;
289: }
290: }
291: }
292:
293: protected abstract void init() throws IOException;
294:
295: protected abstract void storeData();
296:
297: private void showDialog() throws IOException {
298: String xmlStylesheetName = null;
299: if (xmlStylesheetSource != null) {
300: xmlStylesheetName = xmlStylesheetSource.getSystemId();
301: }
302: transformPanel = new TransformPanel(xmlDO,
303: xmlStylesheetName, xslDO);
304:
305: dialogDescriptor = new DialogDescriptor(transformPanel,
306: NbBundle.getMessage(TransformPerformer.class,
307: "NAME_transform_panel_title"), true,
308: DialogDescriptor.OK_CANCEL_OPTION,
309: DialogDescriptor.OK_OPTION,
310: DialogDescriptor.BOTTOM_ALIGN, new HelpCtx(
311: TransformAction.class), null);
312: dialogDescriptor
313: .setClosingOptions(new Object[] { DialogDescriptor.CANCEL_OPTION });
314: dialogDescriptor.setButtonListener(this );
315:
316: dialog = DialogDisplayer.getDefault().createDialog(
317: dialogDescriptor);
318: dialog.addWindowListener(this ); // #31850 workaround
319: dialog.show();
320: }
321:
322: protected void prepareData() throws IOException,
323: FileStateInvalidException, MalformedURLException,
324: ParserConfigurationException, SAXException {
325: data = transformPanel.getData();
326:
327: // if ( Util.THIS.isLoggable() ) /* then */ {
328: // Util.THIS.debug("TransformPerformer...performTransformation");
329: // Util.THIS.debug(" transformable = " + transformableCookie);
330: // Util.THIS.debug(" baseFileObject = " + baseFO);
331: // Util.THIS.debug(" data = " + data);
332: // }
333:
334: try {
335: xmlSource = TransformUtil.createSource(baseURL, data
336: .getInput()); // throws IOException, MalformedURLException, FileStateInvalidException, ParserConfigurationException, SAXException
337: } catch (IOException ex) {
338: ErrorManager.getDefault().annotate(
339: ex,
340: NbBundle.getMessage(TransformPerformer.class,
341: "MSG_sourceError"));
342: throw ex;
343: }
344: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(" xmlSource = " + xmlSource.getSystemId());
345:
346: if (data.getXSL() != null) {
347: try {
348: xslSource = TransformUtil.createSource(baseURL,
349: data.getXSL()); // throws IOException, MalformedURLException, FileStateInvalidException, ParserConfigurationException, SAXException
350: } catch (IOException ex) {
351: ErrorManager.getDefault().annotate(
352: ex,
353: NbBundle.getMessage(
354: TransformPerformer.class,
355: "MSG_transError"));
356: throw ex;
357: }
358: } else {
359: xslSource = xmlStylesheetSource;
360: }
361:
362: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(" xslSource = " + xslSource.getSystemId());
363:
364: if (data.getOutput() != null) { // not Preview
365: String fileName = data.getOutput().toString().replace(
366: '\\', '/');
367: try {
368: resultFO = FileUtilities.createFileObject(baseFO
369: .getParent(), fileName, data
370: .isOverwriteOutput()); // throws IOException
371: } catch (IOException ex) {
372: ErrorManager.getDefault().annotate(
373: ex,
374: NbBundle.getMessage(
375: TransformPerformer.class,
376: "MSG_resultError"));
377: throw ex;
378: }
379:
380: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(" resultFO = " + resultFO);
381: }
382: }
383:
384: protected void updateHistory(DataObject dataObject, boolean xslt) {
385: FileObject fileObject = dataObject.getPrimaryFile();
386: TransformHistory history = (TransformHistory) fileObject
387: .getAttribute(TransformHistory.TRANSFORM_HISTORY_ATTRIBUTE);
388: if (history == null) {
389: history = new TransformHistory();
390: }
391: String outputStr = null;
392: if (data.getOutput() != null) {
393: outputStr = data.getOutput().toString();
394: }
395: if (xslt) {
396: history.addXML(data.getInput(), outputStr);
397: } else {
398: history.addXSL(data.getXSL(), outputStr);
399: }
400: history.setOverwriteOutput(data.isOverwriteOutput());
401: history.setProcessOutput(data.getProcessOutput());
402:
403: try {
404: fileObject.setAttribute(
405: TransformHistory.TRANSFORM_HISTORY_ATTRIBUTE,
406: history);
407: } catch (IOException exc) {
408: // ... will not be persistent!
409: ErrorManager.getDefault().notify(
410: ErrorManager.INFORMATIONAL, exc);
411: }
412: }
413:
414: /**
415: * Inicializes a servet and then provokes it by opening browser poiting to it.
416: * External XSLT processor is called from the servlet.
417: */
418: private void previewOutput() throws MalformedURLException,
419: UnknownHostException {
420: TransformServlet.prepare(transformableCookie, xmlSource,
421: xslSource);
422: showURL(TransformServlet.getServletURL());
423: }
424:
425: /**
426: * External XSLT processor is called from this method.
427: */
428: private void fileOutput() throws IOException,
429: FileStateInvalidException, TransformerException {
430: OutputStream outputStream = null;
431: FileLock fileLock = null;
432:
433: try {
434: fileLock = resultFO.lock();
435: outputStream = resultFO.getOutputStream(fileLock);
436:
437: Result outputResult = new StreamResult(outputStream); // throws IOException, FileStateInvalidException
438:
439: // if ( Util.THIS.isLoggable() ) /* then */ {
440: // Util.THIS.debug(" resultFO = " + resultFO);
441: // Util.THIS.debug(" outputResult = " + outputResult);
442: // }
443: String xmlName = data.getInput();
444: String xslName = data.getXSL();
445: TransformPerformer.this .getCookieObserver().message(
446: NbBundle.getMessage(TransformPerformer.class,
447: "MSG_transformation_1", xmlName,
448: xslName));
449: TransformUtil.transform(xmlSource, transformableCookie,
450: xslSource, outputResult,
451: TransformPerformer.this .getCookieObserver()); // throws TransformerException
452:
453: // revalidate DataObject associated with possibly partially written file #28079
454: try {
455: DataObject dataObject = DataObject.find(resultFO);
456: dataObject.setValid(false);
457: } catch (DataObjectNotFoundException dnf) {
458: throw new IllegalStateException();
459: } catch (PropertyVetoException pve) {
460: ErrorManager.getDefault().log(
461: ErrorManager.INFORMATIONAL,
462: "Cannot invalidate " + resultFO);
463: }
464: } catch (FileAlreadyLockedException exc) {
465: throw (FileAlreadyLockedException) ErrorManager
466: .getDefault()
467: .annotate(
468: exc,
469: NbBundle
470: .getMessage(
471: TransformPerformer.class,
472: "ERR_FileAlreadyLockedException_output"));
473: } finally {
474: if (fileLock != null) {
475: fileLock.releaseLock();
476: }
477: if (outputStream != null) {
478: outputStream.close();
479: }
480: }
481: // vlv # 103384
482: if (data.getProcessOutput() == TransformHistory.APPLY_DEFAULT_ACTION) {
483: GuiUtil.performDefaultAction(resultFO);
484: } else if (data.getProcessOutput() == TransformHistory.OPEN_IN_BROWSER) {
485: showURL(resultFO.getURL());
486: }
487: }
488:
489: private void showURL(URL url) {
490: HtmlBrowser.URLDisplayer.getDefault().showURL(url);
491: GuiUtil.setStatusText(NbBundle.getMessage(
492: TransformPerformer.class, "MSG_opening_browser"));
493: }
494:
495: //
496: // from ActionListener
497: //
498:
499: public final void actionPerformed(ActionEvent e) {
500: // if ( Util.THIS.isLoggable() ) /* then */ {
501: // Util.THIS.debug("[TransformPerformer::AbstractPerformer] actionPerformed: " + e);
502: // Util.THIS.debug(" ActionEvent.getSource(): " + e.getSource());
503: // }
504:
505: workaround31850 = false;
506: if (DialogDescriptor.OK_OPTION.equals(e.getSource())) {
507: try {
508: prepareData(); // throws IOException(, FileStateInvalidException, MalformedURLException), ParserConfigurationException, SAXException
509:
510: if ((data.getOutput() != null)
511: && (resultFO == null)) {
512: return;
513: }
514:
515: dialog.dispose();
516: storeData();
517: async();
518:
519: } catch (Exception exc) { // IOException, ParserConfigurationException, SAXException
520: // during prepareData(), previewOutput() and fileOutput()
521: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(exc);
522:
523: // NotifyDescriptor nd = new NotifyDescriptor.Message (exc.getLocalizedMessage(), NotifyDescriptor.WARNING_MESSAGE);
524: // TopManager.getDefault().notify (nd);
525:
526: ErrorManager.getDefault().notify(
527: ErrorManager.WARNING, exc);
528: if (isLastInBatch()) {
529: active = false;
530: }
531: }
532: } else {
533: active = false;
534: }
535: }
536:
537: // WindowAdapter #31850 workaround
538: public void windowClosed(WindowEvent e) {
539: super .windowClosed(e);
540: if (workaround31850) {
541: active = false;
542: }
543: }
544:
545: /**
546: * Perform the transformatin itself asynchronously ... (#29614)
547: */
548: private void async() {
549: RequestProcessor rp = RequestProcessor.getDefault();
550: rp.post(new Runnable() {
551: public void run() {
552: try {
553: if (data.getOutput() == null) { // Preview
554: previewOutput(); // throws IOException (MalformedURLException, UnknownHostException)
555: } else {
556: fileOutput(); // throws IOException(, FileStateInvalidException), TransformerException
557: }
558: } catch (TransformerException exc) { // during fileOutput();
559: // ignore it -> it should be displayed by CookieObserver!
560: } catch (Exception exc) { // IOException, ParserConfigurationException, SAXException
561: // during prepareData(), previewOutput() and fileOutput()
562: //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug(exc);
563:
564: // NotifyDescriptor nd = new NotifyDescriptor.Message (exc.getLocalizedMessage(), NotifyDescriptor.WARNING_MESSAGE);
565: // TopManager.getDefault().notify (nd);
566:
567: ErrorManager.getDefault().notify(
568: ErrorManager.WARNING, exc);
569: } finally {
570: if (isLastInBatch()) {
571: InputOutputReporter cookieObserver = getCookieObserver();
572: if (cookieObserver != null) {
573: cookieObserver
574: .message(NbBundle
575: .getMessage(
576: TransformPerformer.class,
577: "MSG_transformation_2"));
578: cookieObserver.moveToFront(true);
579: }
580: active = false;
581: }
582: }
583: }
584: });
585: }
586:
587: /**
588: * If possible it finds "file:" URL if <code>fileObject</code> is on LocalFileSystem.
589: * @return URL of <code>fileObject</code>.
590: */
591: protected URL preferFileURL(FileObject fileObject)
592: throws MalformedURLException, FileStateInvalidException {
593: URL fileURL = null;
594: File file = FileUtil.toFile(fileObject);
595:
596: if (file != null) {
597: fileURL = file.toURL();
598: } else {
599: fileURL = fileObject.getURL();
600: }
601: return fileURL;
602: }
603:
604: public final void setLastInBatch(boolean last) {
605: this .last = last;
606: }
607:
608: /**
609: * Return if caller uses more perfomers and this one is the last one.
610: */
611: public final boolean isLastInBatch() {
612: return last;
613: }
614: } // class AbstractPerformer
615:
616: //
617: // class SinglePerformer
618: //
619:
620: private class SinglePerformer extends AbstractPerformer {
621: private DataObject dataObject;
622: private boolean xslt;
623:
624: public SinglePerformer(TransformableCookie transformable,
625: DataObject dataObject, boolean xslt) {
626: super (transformable);
627:
628: this .dataObject = dataObject;
629: this .xslt = xslt;
630: }
631:
632: /**
633: * @throws FileStateInvalidException from baseFO.getURL();
634: */
635: protected void init() throws IOException {
636: baseFO = dataObject.getPrimaryFile();
637: baseURL = preferFileURL(baseFO);
638:
639: if (xslt) {
640: xmlDO = null;
641: xmlStylesheetSource = null;
642: xslDO = dataObject;
643: } else {
644: xmlDO = dataObject;
645: xmlStylesheetSource = TransformUtil
646: .getAssociatedStylesheet(baseURL);
647: xslDO = null;
648: }
649: }
650:
651: protected void storeData() {
652: updateHistory(dataObject, xslt);
653: }
654:
655: } // class SinglePerformer
656:
657: //
658: // class DoublePerformer
659: //
660:
661: private class DoublePerformer extends AbstractPerformer {
662:
663: public DoublePerformer(TransformableCookie transformable,
664: DataObject xmlDO, DataObject xslDO) {
665: super (transformable);
666:
667: this .xmlDO = xmlDO;
668: this .xslDO = xslDO;
669: }
670:
671: /**
672: * @throws FileStateInvalidException from baseFO.getURL();
673: */
674: protected void init() throws IOException {
675: baseFO = xmlDO.getPrimaryFile();
676: baseURL = preferFileURL(baseFO);
677: }
678:
679: protected void storeData() {
680: updateHistory(xmlDO, false);
681: updateHistory(xslDO, true);
682: }
683:
684: } // class DoublePerformer
685:
686: }
|