blob: 1386bbe873f2d04f0d7e344c35f2250fdff568ad [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package com.sun.star.wizards.web;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.wizards.common.ConfigSet;
import com.sun.star.wizards.common.FileAccess;
import com.sun.star.wizards.common.PropertyNames;
import com.sun.star.wizards.common.UCB;
import com.sun.star.wizards.ui.event.Task;
import com.sun.star.wizards.web.data.CGContent;
import com.sun.star.wizards.web.data.CGDocument;
import com.sun.star.wizards.web.data.CGExporter;
import com.sun.star.wizards.web.data.CGLayout;
import com.sun.star.wizards.web.data.CGPublish;
import com.sun.star.wizards.web.data.CGSettings;
import com.sun.star.wizards.web.export.Exporter;
/**
* @author rpiterman
* This class is used to process a CGSession object
* and generate a site. </br>
* it does the following: <br/>
* 1. create a temporary directory.<br/>
* 2. export documents to the temporary directory.<br/>
* 3. generate the TOC page, includes copying images from the
* web wizard work directory and other layout files.<br/>
* 4. publish, or copy, from the temporary directory to
* different destinations.<br/>
* 5. delete the temporary directory.<br/>
* <br/>
* to follow up the status/errors it uses a TaskListener object,
* and an ErrorHandler. <br/>
* in practice, the TaskListener is the status dialog,
* and the Errorhandler does the interaction with the user,
* if something goes wrong.<br/>
* Note that this class takes it in count that
* the given session object is prepared for it -
* all preparations are done in WWD_Events.finishWizard methods.
* <br/>
* <br/>
*
* note on error handling: <br/>
* on "catch" clauses I tries to decide whether the
* exception is fatal or not. For fatal exception an error message
* is displayed (or rather: the errorHandler is being called...)
* and a false is returned.
* In less-fatal errors, the errorHandler "should decide" which means,
* the user is given the option to "OK" or to "Cancel" and depending
* on that interaction I cary on.
*/
public class Process implements WebWizardConst, ProcessErrors
{
private static final int TASKS_PER_DOC = 5;
private static final int TASKS_PER_XSL = 2;
private static final int TASKS_PER_PUBLISH = 2;
private static final int TASKS_IN_PREPARE = 1;
private static final int TASKS_IN_EXPORT = 2;
private static final int TASKS_IN_GENERATE = 2;
private static final int TASKS_IN_PUBLISH = 2;
private static final int TASKS_IN_FINISHUP = 1;
private CGSettings settings;
private XMultiServiceFactory xmsf;
private ErrorHandler errorHandler;
private String tempDir;
private FileAccess fileAccess;
private UCB ucb;
public Task myTask;
/**
* This is a cache for exporters, so I do not need to
* instanciate the same exporter more than once.
*/
private Map exporters = new Hashtable(3);
private boolean result;
public Process(
CGSettings settings,
XMultiServiceFactory xmsf,
ErrorHandler er)
throws Exception
{
this.xmsf = xmsf;
this.settings = settings;
fileAccess = new FileAccess(xmsf);
errorHandler = er;
ucb = new UCB(xmsf);
int taskSteps = getTaskSteps();
myTask = new Task(TASK, TASK_PREPARE, taskSteps);
}
/**
* @return to how many destinations should the
* generated site be published.
*/
private int countPublish()
{
int count = 0;
ConfigSet publishers = settings.cp_DefaultSession.cp_Publishing;
for (int i = 0; i < publishers.getSize(); i++)
{
if (((CGPublish) publishers.getElementAt(i)).cp_Publish)
{
count++;
}
}
return count;
}
/**
* @return the number of task steps that this
* session should have
*/
private int getTaskSteps()
{
int docs = settings.cp_DefaultSession.cp_Content.cp_Documents.getSize();
int xsl = 0;
try
{
xsl = settings.cp_DefaultSession.getLayout().getTemplates(xmsf).size();
}
catch (Exception ex)
{
}
int publish = countPublish();
return
TASKS_IN_PREPARE +
TASKS_IN_EXPORT + docs * TASKS_PER_DOC +
TASKS_IN_GENERATE + xsl * TASKS_PER_XSL +
TASKS_IN_PUBLISH + publish * TASKS_PER_PUBLISH +
TASKS_IN_FINISHUP;
}
/**
* does the job
*/
public void runProcess()
{
myTask.start();
try
{
try
{
/*
* I use here '&&' so if one of the
* methods returns false, the next
* will not be called.
*/
result = createTempDir(myTask) && export(myTask) && generate(tempDir, myTask) && publish(tempDir, myTask);
}
finally
{
//cleanup must be called.
result = result & cleanup(myTask);
}
}
catch (Exception ex)
{
result = false;
}
if (!result)
{
myTask.fail(); //this is a bug protection.
}
while (myTask.getStatus() < myTask.getMax())
{
myTask.advance(true);
}
}
/**
* creates a temporary directory.
* @param task
* @return true should continue
*/
private boolean createTempDir(Task task)
{
tempDir = fileAccess.createNewDir(getSOTempDir(xmsf), "wwiztemp");
if (tempDir == null)
{
error(null, null, ERROR_MKDIR, ErrorHandler.ERROR_PROCESS_FATAL);
return false;
}
else
{
task.advance(true);
return true;
}
}
/**
* @param xmsf
* @return the staroffice /openoffice temporary directory
*/
static String getSOTempDir(XMultiServiceFactory xmsf)
{
try
{
return FileAccess.getOfficePath(xmsf, "Temp", PropertyNames.EMPTY_STRING, PropertyNames.EMPTY_STRING);
}
catch (Exception e)
{
}
return null;
}
// CLEANUP
/**
* delete the temporary directory
* @return true should continue
*/
private boolean cleanup(Task task)
{
task.setSubtaskName(TASK_FINISH);
boolean b = fileAccess.delete(tempDir);
if (!b)
{
error(null, null, ERROR_CLEANUP, ErrorHandler.ERROR_WARNING);
}
task.advance(b);
return b;
}
// /**
// * deletes the given directory
// * @param dir the directory to delete
// * @return true if should continue
// */
// private boolean cleanup(String dir) {
//
// boolean success = true;
//
// if (dir != null && fileAccess.exists(dir,false)) {
//
// String[] files = fileAccess.listFiles(dir,true);
//
// for (int i = 0; i < files.length; i++) {
// if (fileAccess.isDirectory(files[i]))
// success = success && cleanup(files[i]);
// else
// success = success && fileAccess.delete(files[i]);
//
// }
// }
// return success && fileAccess.delete(dir);
// }
/**
* This method is used to copy style files to a target
* Directory: css and background.
* Note that this method is static since it is
* also used when displaying a "preview"
*/
public static void copyMedia(UCB copy, CGSettings settings, String targetDir, Task task) throws Exception
{
//1. .css
String sourceDir = FileAccess.connectURLs(settings.workPath, "styles");
String filename = settings.cp_DefaultSession.getStyle().cp_CssHref;
copy.copy(sourceDir, filename, targetDir, "style.css");
task.advance(true);
//2. background image
String background = settings.cp_DefaultSession.cp_Design.cp_BackgroundImage;
if (background != null && !background.equals(PropertyNames.EMPTY_STRING))
{
sourceDir = FileAccess.getParentDir(background);
filename = background.substring(sourceDir.length());
copy.copy(sourceDir, filename, targetDir + "/images", "background.gif");
}
task.advance(true);
}
/**
* Copy "static" files (which are always the same,
* thus not user-input-dependant) to a target directory.
* Note that this method is static since it is
* also used when displaying a "preview"
* @param copy
* @param settings
* @param targetDir
* @throws Exception
*/
public static void copyStaticImages(UCB copy, CGSettings settings, String targetDir)
throws Exception
{
copy.copy(FileAccess.connectURLs(settings.workPath, "images"), targetDir + "/images");
}
/**
* publish the given directory.
* @param dir the source directory to publish from
* @param task task tracking.
* @return true if should continue
*/
private boolean publish(String dir, Task task)
{
task.setSubtaskName(TASK_PUBLISH_PREPARE);
ConfigSet set = settings.cp_DefaultSession.cp_Publishing;
try
{
copyMedia(ucb, settings, dir, task);
copyStaticImages(ucb, settings, dir);
task.advance(true);
}
catch (Exception ex)
{
//error in copying media
error(ex, PropertyNames.EMPTY_STRING, ERROR_PUBLISH_MEDIA, ErrorHandler.ERROR_PROCESS_FATAL);
return false;
}
boolean result = true;
for (int i = 0; i < set.getSize(); i++)
{
CGPublish p = (CGPublish) set.getElementAt(i);
if (p.cp_Publish)
{
String key = (String) set.getKey(p);
task.setSubtaskName(key);
if (key.equals(ZIP_PUBLISHER))
{
fileAccess.delete(p.cp_URL);
}
if (!publish(dir, p, ucb, task))
{
return false;
}
}
}
return result;
}
/**
* publish the given directory to the
* given target CGPublish.
* @param dir the dir to copy from
* @param publish the object that specifies the target
* @param copy ucb encapsulation
* @param task task tracking
* @return true if should continue
*/
private boolean publish(String dir, CGPublish publish, UCB copy, Task task)
{
try
{
//copy.deleteDirContent(publish.url);
task.advance(true);
copy.copy(dir, publish.url);
task.advance(true);
return true;
}
catch (Exception e)
{
task.advance(false);
return error(e, publish, ERROR_PUBLISH, ErrorHandler.ERROR_NORMAL_IGNORE);
}
}
//GENERATING METHODS
/**
* Generates the TOC pages for the current session.
* @param targetDir generating to this directory.
*/
public boolean generate(String targetDir, Task task)
{
boolean result = false;
task.setSubtaskName(TASK_GENERATE_PREPARE);
CGLayout layout = settings.cp_DefaultSession.getLayout();
try
{
/*
* here I create the DOM of the TOC to pass to the XSL
*/
Document doc = (Document) settings.cp_DefaultSession.createDOM();
generate(xmsf, layout, doc, fileAccess, targetDir, task);
}
catch (Exception ex)
{
error(ex, PropertyNames.EMPTY_STRING, ERROR_GENERATE_XSLT, ErrorHandler.ERROR_PROCESS_FATAL);
return false;
}
/* copy files which are not xsl from layout directory to
* website root.
*/
try
{
task.setSubtaskName(TASK_GENERATE_COPY);
copyLayoutFiles(ucb, fileAccess, settings, layout, targetDir);
task.advance(true);
result = true;
}
catch (Exception ex)
{
task.advance(false);
return error(ex, null, ERROR_GENERATE_COPY, ErrorHandler.ERROR_NORMAL_ABORT);
}
return result;
}
/**
* copies layout files which are not .xsl files
* to the target directory.
* @param ucb UCB encapsulatzion object
* @param fileAccess filaAccess encapsulation object
* @param settings web wizard settings
* @param layout the layout object
* @param targetDir the target directory to copy to
* @throws Exception
*/
public static void copyLayoutFiles(UCB ucb, FileAccess fileAccess, CGSettings settings, CGLayout layout, String targetDir)
throws Exception
{
String filesPath = fileAccess.getURL(
FileAccess.connectURLs(settings.workPath, "layouts/"), layout.cp_FSName);
ucb.copy(filesPath, targetDir, new ExtensionVerifier("xsl"));
}
/**
* generates the TOC page for the given layout.
* This method might generate more than one file, depending
* on how many .xsl files are in the
* directory specifies by the given layout object.
* @param xmsf
* @param layout specifies the layout to use.
* @param doc the DOM representation of the web wizard session
* @param fileAccess encapsulation of FileAccess
* @param targetPath target directory
* @param task
* @throws Exception
*/
public static void generate(
XMultiServiceFactory xmsf,
CGLayout layout,
Document doc,
FileAccess fileAccess,
String targetPath,
Task task)
throws Exception
{
/*
* a map that contains xsl templates. the keys are the xsl file names.
*/
Map templates = layout.getTemplates(xmsf);
task.advance(true, TASK_GENERATE_XSL);
/*
* each template generates a page.
*/
for (Iterator i = templates.keySet().iterator(); i.hasNext();)
{
String key = PropertyNames.EMPTY_STRING;
key = (String) i.next();
Transformer transformer = ((Templates) templates.get(key)).newTransformer();
doc.normalize();
task.advance(true);
/*
* The target file name is like the xsl template filename
* without the .xsl extension.
*/
String fn = fileAccess.getPath(targetPath, key.substring(0, key.length() - 4));
File f = new File(fn);
FileOutputStream oStream = new FileOutputStream(f);
// Due to a problem occuring when using Xalan-Java 2.6.0 and
// Java 1.5.0, wrap f in a FileOutputStream here (otherwise, the
// StreamResult's getSystemId would return a "file:/..." URL while
// the Xalan code expects a "file:///..." URL):
transformer.transform(
new DOMSource(doc), new StreamResult(oStream));
oStream.close();
task.advance(true);
}
}
/**
* I broke the export method to two methods
* in a time where a tree with more than one contents was planned.
* I left it that way, because it may be used in the future.
* @param task
* @return
*/
private boolean export(Task task)
{
return export(settings.cp_DefaultSession.cp_Content, tempDir, task);
}
/**
* This method could actually, with light modification, use recursion.
* In the present situation, where we only use a "flat" list of
* documents, instead of the original plan to use a tree,
* the recursion is not implemented.
* @param content the content ( directory-like, contains documents)
* @param dir (target directory for exporting this content.
* @param task
* @return true if should continue
*/
private boolean export(CGContent content, String dir, Task task)
{
int toPerform = 1;
String contentDir = dir;
try
{
task.setSubtaskName(TASK_EXPORT_PREPARE);
/* 1. create a content directory.
* each content (at the moment there is only one :-( )
* is created in its own directory.
* faileure here is fatal.
*/
contentDir = fileAccess.createNewDir(dir, content.cp_Name);
if (contentDir == null || contentDir.equals(PropertyNames.EMPTY_STRING))
{
throw new IOException("Directory " + dir + " could not be created.");
}
content.dirName = FileAccess.getFilename(contentDir);
task.advance(true, TASK_EXPORT_DOCUMENTS);
toPerform--;
/*2. export all documents and sub contents.
* (at the moment, only documents, no subcontents)
*/
Object item = null;
for (int i = 0; i < content.cp_Documents.getSize(); i++)
{
try
{
item = content.cp_Documents.getElementAt(i);
/*
* In present this is always the case.
* may be in the future, when
* a tree is used, it will be abit different.
*/
if (item instanceof CGDocument)
{
if (!export((CGDocument) item, contentDir, task))
{
return false;
}
}
else /*
* we never get here since we
* did not implement sub-contents.
*/ if (!export((CGContent) item, contentDir, task))
{
return false;
}
}
catch (SecurityException sx)
{
// nonfatal
if (!error(sx, item, ERROR_EXPORT_SECURITY, ErrorHandler.ERROR_NORMAL_IGNORE))
{
return false;
}
result = false;
}
}
}
catch (IOException iox)
{
//nonfatal
return error(iox, content, ERROR_EXPORT_IO, ErrorHandler.ERROR_NORMAL_IGNORE);
}
catch (SecurityException se)
{
//nonfatal
return error(se, content, ERROR_EXPORT_SECURITY, ErrorHandler.ERROR_NORMAL_IGNORE);
}
failTask(task, toPerform);
return true;
}
/**
* exports a single document
* @param doc the document to export
* @param dir the target directory
* @param task task tracking
* @return true if should continue
*/
private boolean export(CGDocument doc, String dir, Task task)
{
//first I check if the document was already validated...
if (!doc.valid)
{
try
{
doc.validate(xmsf, null);
}
catch (Exception ex)
{
//fatal
error(ex, doc, ERROR_DOC_VALIDATE, ErrorHandler.ERROR_PROCESS_FATAL);
return false;
}
//get the exporter specified for this document
}
CGExporter exporter = (CGExporter) settings.cp_Exporters.getElement(doc.cp_Exporter);
try
{
/*
* here I calculate the destination filename.
* I take the original filename (docFilename), substract the extension, (docExt) -> (fn)
* and find an available filename which starts with
* this filename, but with the new extension. (destExt)
*/
String docFilename = FileAccess.getFilename(doc.cp_URL);
String docExt = FileAccess.getExtension(docFilename);
String fn = doc.localFilename.substring(0, doc.localFilename.length() - docExt.length() - 1); //filename without extension
/*
* the copyExporter does not change
* the extension of the target...
*/
String destExt = (exporter.cp_Extension.equals(PropertyNames.EMPTY_STRING)
? FileAccess.getExtension(docFilename)
: exporter.cp_Extension);
/* if this filter needs to export to its own directory...
* this is the case in, for example, impress html export
*/
if (exporter.cp_OwnDirectory)
{ //+++
dir = fileAccess.createNewDir(dir, fn);
doc.dirName = FileAccess.getFilename(dir);
}
/*
* if two files with the same name
* need to be exported ? So here
* i get a new filename, so I do not
* overwrite files...
*/
String file = fileAccess.getNewFile(dir, fn, destExt);
/* set filename with extension.
* this will be used by the exporter,
* and to generate the TOC.
*/
doc.urlFilename = FileAccess.getFilename(file);
task.advance(true);
try
{
//export
getExporter(exporter).export(doc, file, xmsf, task);
task.advance(true);
}
/*
* getExporter(..) throws
* IllegalAccessException, InstantiationException, ClassNotFoundException
* export() throws Exception
*/
catch (Exception ex)
{
//nonfatal
if (!error(ex, doc, ERROR_EXPORT, ErrorHandler.ERROR_NORMAL_IGNORE))
{
return false;
}
}
}
catch (Exception ex)
{
//nonfatal
if (!error(ex, doc, ERROR_EXPORT_MKDIR, ErrorHandler.ERROR_NORMAL_ABORT))
{
return false;
}
}
return true;
}
/**
* submit an error.
* @param ex the exception
* @param arg1 error argument
* @param arg2 error argument 2
* @param errType error type
* @return the interaction result
*/
private boolean error(Exception ex, Object arg1, int arg2, int errType)
{
result = false;
return errorHandler.error(ex, arg1, arg2, errType);
}
/**
* advances the given task in the given count of steps,
* marked as failed.
* @param task the task to advance
* @param count the number of steps to advance
*/
private void failTask(Task task, int count)
{
while (count-- > 0)
{
task.advance(false);
}
}
/**
* creates an instance of the exporter class
* as specified by the
* exporter object.
* @param export specifies the exporter to be created
* @return the Exporter instance
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private Exporter createExporter(CGExporter export)
throws ClassNotFoundException,
IllegalAccessException,
InstantiationException
{
Exporter e = (Exporter) Class.forName(export.cp_ExporterClass).newInstance();
e.init(export);
return e;
}
/**
* searches the an exporter for the given CGExporter object
* in the cache.
* If its not there, creates it, stores it in the cache and
* returns it.
* @param export specifies the needed exporter.
* @return an Exporter instance
* @throws ClassNotFoundException thrown when using Class.forName(string)
* @throws IllegalAccessException thrown when using Class.forName(string)
* @throws InstantiationException thrown when using Class.forName(string)
*/
private Exporter getExporter(CGExporter export)
throws ClassNotFoundException,
IllegalAccessException,
InstantiationException
{
Exporter exp = (Exporter) exporters.get(export);
if (exp == null)
{
exp = createExporter(export);
exporters.put(export, exp);
}
return exp;
}
/**
* @return tru if everything went smooth, false
* if error(s) accured.
*/
public boolean getResult()
{
return (myTask.getFailed() == 0) && result;
}
}