package org.bodington.server.resources;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipOutputStream;

import org.apache.log4j.Logger;
import org.bodington.assessment.McqPaperSession;
import org.bodington.assessment.QuestionnaireSession;
import org.bodington.database.PrimaryKey;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSession;
import org.bodington.server.BuildingSessionManagerImpl;
import org.bodington.server.events.ExportEvent;
import org.bodington.server.realm.Permission;
import org.bodington.servlet.Request;
import org.bodington.servlet.facilities.DocumentFacility;
import org.bodington.util.BodingtonURL;

public class ContentZipper {

	private Request request;
	private ZipOutputStream zipOutputStream;
	protected List errors;
	protected List ignoreStrings;
	protected List fileList;
	private List includeResourceTypes;

	private boolean recursive; // export content in child resources
	private boolean childrenOnly; // export content in children only (for recursive QTI initiated in rooms)
	private boolean includeDescriptions;
	private boolean includeIntroductions;
	private boolean includeQuickLinks;
	private boolean includeInternalQuickLinks;
	private boolean collateDocumentSections;
	private boolean includeContentListing;
	private boolean combineImageBlock;
	private boolean includeAssessmentQTI;
	

	protected static final String HTML_FILE_SUFFIX=".html";
	protected static final String INTRODUCTION_FILE_SUFFIX="-intro.html";
	protected static final String DESCRIPTION_FILE_SUFFIX="-descrip.html";
	protected static final String QUICKLINK_FILE_SUFFIX = ".url";
	
	// MCQ and Questionnaire QTI export used to save a snapshot of QTI XML as an Uploaded File,
	// they are most likely out of synch with current state, and are now exported via virtual URL instead.
	// Skip files with either this filename or {resourcename}.xml:
	private String excludeQTIfiles = "qtiexp.xml";

	private Logger logger = Logger.getLogger(ContentZipper.class);
	
	/**
	 * Class to record metadata regarding the file/s generated for inclusion in the zip archive.
	 * As more user options are included in the export functionality, so the files and names
	 *  actually included in the zip archive get too complex to track easily.
	 *  Used by subclass which generates IMS manifest file of metadata.
	 * @author sers0022
	 *
	 */
	protected class FileData
    {
		protected PrimaryKey identifier;
		protected PrimaryKey parentID;
		protected PrimaryKey resourceID; // used when object is UploadedFile.
		protected String path; // path to resource/folder/file, includes filename.
		protected String filename;
		protected String fileSuffix; // to differentiate between intro/descrip, used by subclass creating XML manifest.
		protected String title;
		protected boolean isFolder;
        
		/**
         * Constructor for Uploaded Files. 
         * @param id
         * @param path
         * @param filename
         */
        private FileData (UploadedFile ufile, String path)
        {
        	this.identifier = ufile.getUploadedFileId();
        	this.parentID = ufile.getParentUploadedFileId();
        	this.resourceID = ufile.getResourceId();
			this.filename = ufile.getName();
			this.fileSuffix = "";
			this.title = ufile.getName(); // same as filename
			this.isFolder = ufile.isFolder();
        	try {
				this.path = path + ufile.getURLInResource();
			} catch (BuildingServerException e) {}
        }
        
        /**
         * Constructor for files generated from resource content 
         * @param id
         * @param path
         * @param fileName
         */
        private FileData(PrimaryKey id, String path, String fileName, String fileSuffix)
        {
        	this.identifier = id;
//        	this.parentID needs to be null, uses resource id
        	this.resourceID = id;
            // need suffix to differentiate between intro and descrip:
            this.fileSuffix = fileSuffix;
            this.filename = fileName + fileSuffix;
        	this.path = path + fileName + fileSuffix;
            this.title = fileName + fileSuffix;
			this.isFolder = false;
        }

        public String toString()
        {
        	return "identifier: "+identifier
        	+" parentID: " + parentID
        	+" resourceID: " + resourceID
        	+" path: " + path
            +" fileName: " + filename
            +" fileSuffix: " + fileSuffix
            +" title: " + title
        	+" isFolder: " + isFolder;
        } 
    }
	
	/**
	 * Set export options from the request params.
	 */
	public void setRequestOptions()
	{
		this.recursive = parseParam("recursive", recursive);
		this.childrenOnly = parseParam("childrenOnly", childrenOnly);
		this.includeDescriptions = parseParam("includeDescriptions", includeDescriptions);
		this.includeIntroductions = parseParam("includeIntroductions", includeIntroductions);
		this.includeQuickLinks = parseParam("includeQuickLinks", includeQuickLinks);
		this.includeInternalQuickLinks = parseParam("includeInternalQuickLinks", includeInternalQuickLinks);
		this.collateDocumentSections = parseParam("collateDocumentSections", collateDocumentSections);
		this.includeContentListing = parseParam("includeContentListing", includeContentListing);
		this.combineImageBlock = parseParam("combineImageBlock", combineImageBlock);
		this.includeAssessmentQTI = parseParam("includeAssessmentQTI", includeAssessmentQTI);
		
		String[] resourceTypes = request.getParameterValues("includeResourceTypes");
		if (null == resourceTypes)
			includeResourceTypes = new Vector();
		else
			includeResourceTypes = Arrays.asList(resourceTypes);
	}
	
	/**
	 * Get boolean from request param.
	 * @param param_name
	 * @param defaultValue
	 * @return boolean
	 */
	private boolean parseParam(String param_name, boolean defaultValue)
	{
		String value = request.getParameter(param_name);
		if ( value != null)
			return Boolean.parseBoolean(value);

		return defaultValue;
	}
	
	protected void init(Request request, OutputStream outputStream ) {
		
		setRequest(request);
		setRequestOptions();
		setZipOutputStream(new ZipOutputStream( outputStream ));
		errors = new ArrayList();
		fileList = new Vector();
		ignoreStrings = Arrays.asList(new String[] {"", "<!-- -->", "<!>", "<p>&nbsp;</p>", "<!-- No Introduction -->", "<!-- No Description -->"});
	}
	
	public void packageResource( Request request, OutputStream outputStream ) throws BuildingServerException {

		try {
			init( request, outputStream );
			Resource resource = request.getResource();

			if ( resource.checkPermission( Permission.MANAGE ) )
			{
				processResource(resource, "");
				if ( !errors.isEmpty() )
					writeErrorFileToZip(formatErrorMessages());
				finishProcessing();
			}
			else
			{
				String message = "You need Manage access to use the Export tool in this location";
				throw new BuildingServerException(message);
			}

		} catch (IOException e) {
			throw new BuildingServerException(e.getMessage());
		}
	}

	/**
	 * Adds content to archive for a given resource.
	 * Is called recursively as it walks down the tree.<br/>
	 * @param resource
	 * @param path
	 * @param containingElement
	 * @throws IOException
	 */
	public void processResource( Resource resource, String path ) throws IOException
	{
		if (isIncludedResourceType(resource, path))	
		{
			if ( !resource.checkPermission( Permission.MANAGE ) )
			{
				errors.add("Resource not added, you need Manage access for " + path);
				return;
			}
		
			// handle special cases (non-generic behaviour) here:
			if (resource instanceof QuickLink)
				processQuickLink((QuickLink)resource, path);
			else if (resource instanceof TextBlockResource)
				processTextBlock((TextBlockResource)resource, path);
			else if (resource instanceof ImageBlockResource)
				processImageBlock((ImageBlockResource)resource, path);
			else if (resource instanceof EasyEditResource)
				processEasyWriter((EasyEditResource)resource, path);
			else if (resource.getResourceType() == Resource.RESOURCE_DOCUMENT)
				processStructuredDocument(resource, path);
			else if (resource.getResourceType() == Resource.RESOURCE_MCQ ||
					resource.getResourceType() == Resource.RESOURCE_QUESTIONNAIRE)
				processAssessmentResource(resource, path);
			else
				processGenericResource(resource, path);

			ExportEvent event = new ExportEvent(ExportEvent.EVENT_ADD_RESOURCE_TO_ZIP, resource);
			event.save();

		}
		
		if ( recursive ) {
			processChildren(resource, path);
		}
	}

	/**
	 * Check whether to include the resource supplied.
	 * Included are:
	 * <ul>
	 *  <li>resource where export was initiated, unless 'childrenOnly' is set. :-)</li>
	 *  <li>resource is in the list of types to include.</li>
	 * </ul>
	 * 
	 * @param resource
	 * @return
	 */
	 
	protected boolean isIncludedResourceType(Resource resource, String path)
	{	
		if (path.equals("") && childrenOnly)
			return false;
		else if (path.equals(""))
			return true;
		else
		{
			String resourceType = String.valueOf(resource.getResourceType());	
			return includeResourceTypes.contains(resourceType);
		}
	}

	/**
	 * Separated processing of children in order to provide override point in subclass.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	protected void processChildren(Resource resource, String path) throws IOException {
		try {
			//recursively get child resources to process:
			Enumeration children = resource.findChildren();
			while (children.hasMoreElements()) {
				Resource child = (Resource)children.nextElement();
				processResource(child, path + child.getName() + "/");
			}
		} catch (BuildingServerException e) {
			String message = "Error in exporting child resource content: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
	}

	/**
	 * Generic behaviour: include uploaded files, and descrip/intro if option set.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	private void processGenericResource(Resource resource, String path) throws IOException
	{
		String name = resource.getName();
		if (includeDescriptions)
			processResourceDescription(resource, path, name, DESCRIPTION_FILE_SUFFIX);
		if (includeIntroductions)
			processResourceIntroduction(resource, path, name, INTRODUCTION_FILE_SUFFIX);

		// uploaded files:
		processUploadedFiles(resource, path);
	}
	
	/**
	 * Include target URL as .url file, uploaded files, descrip & intro.
	 * @param quicklink
	 * @param path
	 * @throws IOException 
	 */
	private void processQuickLink(QuickLink quicklink, String path) throws IOException
	{
		// if flag set to exclude all, return:
		if (!includeQuickLinks)
			return;
		
		// if url hasn't been set: (exclude internal links, where url is null)
		if (quicklink.getURL() == null && !quicklink.isBodURL())
			return;

		// is link is internal and flag to include internal links is not set, return:
		if (quicklink.isBodURL() && !includeInternalQuickLinks)
			return;
		
		// Generate description, intro and any files:
		//TODO: Handle case where link is to uploaded file in QuickLink itself?
		processGenericResource(quicklink, path);
		
		String url;
		if (quicklink.isBodURL())
			url = new BodingtonURL(getRequest()).getResourceUrl(quicklink.getResourceIdLink(), true);
		else
			url = quicklink.getURL();

		String filename = quicklink.getName();
		writeToZip(url, path+filename+QUICKLINK_FILE_SUFFIX);
		fileList.add(new FileData(quicklink.getResourceId(), path, filename, QUICKLINK_FILE_SUFFIX));

	}
	
	/**
	 * EasyWriter content is held in intro, always included regardless of intro flag, uses custom filename.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	private void processEasyWriter(EasyEditResource resource, String path) throws IOException
	{
		String name = resource.getName();
		// do summat with intro and description:
		if (includeDescriptions)
			processResourceDescription(resource, path, name, DESCRIPTION_FILE_SUFFIX);
		// always include intro regardless of flag, use custom filename:
		processResourceIntroduction(resource, path, name, HTML_FILE_SUFFIX);

		// uploaded files:
		processUploadedFiles(resource,path);
		
	}

	/**
	 * Only processes description field, always included regardless of flag, uses custom filename.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	private void processTextBlock(TextBlockResource resource, String path) throws IOException
	{	
		String name = resource.getName();
		// do summat with description only:
		processResourceDescription(resource, path, name, HTML_FILE_SUFFIX);
	}
	

	/**
	 * Combine title (if option set), image and description into single HTML file.
	 * Also adds image properties. If combine option not set, processes generically.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	private void processImageBlock(ImageBlockResource imageblock, String path) throws IOException
	{	
		String name = imageblock.getName();
		
		//TODO writeHTMLFile() uses resource title for title tag regardless of displaytitle setting.
		if (combineImageBlock)
		{
			try {
				StringBuffer buf = new StringBuffer();
				if (imageblock.isDisplayTitle())
					buf.append("<h3>" + imageblock.getTitle() + "</h3>\n");
				
				String url = imageblock.getSourceURL();
				// imageblock uses relative urls from parent, and include resource name:
				url = url.replace(name+"/", "");
				String height = imageblock.getImageHeight();
				String width = imageblock.getImageWidth();
				String alttext = imageblock.getAltTagText();
				String linkHREF = imageblock.getLinkHREF();
				if (null != linkHREF && !linkHREF.equals(""))
					buf.append("<a href=\""+linkHREF+"\">\n");
				buf.append("<img src=\""+url+"\" alt=\""+ alttext+"\"");
				if (null != height && null != width)
					buf.append(" height=\""+height+"\" width=\""+width+"\"");
				buf.append(">\n");
				if (null != linkHREF && !linkHREF.equals(""))
					buf.append("</a>\n");
				
				if (imageblock.isDisplayDescription())
					buf.append(imageblock.getDescription());
				
				createHTMLFile(imageblock, buf.toString(), path, name, HTML_FILE_SUFFIX);
				
				processUploadedFiles(imageblock, path);
				
			} catch (BuildingServerException e) {
				String message = "Error in combining imageblock components into single file: " + e.getMessage();
				errors.add(message);
				logger.info(message, e);
				// processgenericResource()?
			}
		}
		else
			processGenericResource(imageblock, path);

	}
	
	/**
	 * Check option to include sections (uploaded files) in a single file.
	 * If set, collate all document sections into a single HTML file.
	 * Also need to add all non-section related files & folders to archive.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */

	private void processStructuredDocument(Resource structdoc, String path) throws IOException
	{
		//exclude hidden section files; provide parameter?
		// allow contents listing in separate HTML file if not collated?
		//TODO: metadata should include section titles?
		
		String name = structdoc.getName();
		
		if (collateDocumentSections)
		{
			// intro and description first, will appear first in metadata
			if (includeDescriptions)
				processResourceDescription(structdoc, path, name, DESCRIPTION_FILE_SUFFIX);
			if (includeIntroductions)
				processResourceIntroduction(structdoc, path, name, INTRODUCTION_FILE_SUFFIX);

			DocumentFacility facility = (DocumentFacility) ResourceUtils.getFacility(structdoc);
			createCollatedSectionsFile(structdoc, facility, path);
			//process files that aren't sections:
			processStructuredDocumentFiles(structdoc, facility, path);
		}
		else
			processGenericResource(structdoc, path);
	}
	
	private void processAssessmentResource(Resource resource, String path) throws IOException
	{
		if (includeAssessmentQTI)
		{
			try {
				BuildingSession session = BuildingSessionManagerImpl.getSession(resource);
				String filename = resource.getName() + ".xml";
				
				startZipEntry(path + filename);
				if (session instanceof McqPaperSession)
					((McqPaperSession)session).exportXML(getZipOutputStream());
				else if (session instanceof QuestionnaireSession)
					((QuestionnaireSession)session).exportXML(getZipOutputStream());
				closeZipEntry();

			} catch (BuildingServerException e) {
				String message = "Error in exporting content from MCQ: " + e.getMessage();
				errors.add(message);
				logger.info(message, e);
			}
		}
		// do uploaded files, other generic stuff:
		processGenericResource(resource, path);
	}

	/**
	 * Collate all document sections into a single HTML file.
	 * @param structdoc
	 * @param path
	 * @throws IOException 
	 */
	
	protected void createCollatedSectionsFile(Resource structdoc, DocumentFacility facility, String path) throws IOException
	{
		String name = structdoc.getName();
		StringWriter swrite = new StringWriter();
		// writer to write document section content:
		PrintWriter writer = new PrintWriter(swrite);
		// buffer to hold index listing
		StringBuilder buf = new StringBuilder();
		//TODO: find better way with Writers and buffer, etc?
        
		try {
			// output content of sections, and get set of HTML links for contents listing:
			List titleLinks = facility.outputSections(writer, BuildingSessionManagerImpl.getSession(structdoc));
			if (titleLinks.size() == 0)
				return;
			
			if (includeContentListing && titleLinks.size() > 1) {
				buf.append("<h3>Contents</h3>\n");
				buf.append("<ul>\n");
				for (Iterator iterator = titleLinks.iterator(); iterator.hasNext();)
					buf.append("<li>" + iterator.next() + "</li>\n");
				buf.append("</ul>\n");
			}
			buf.append(swrite.toString());
			createHTMLFile(structdoc, buf.toString(), path, name, HTML_FILE_SUFFIX);
			
		} catch (BuildingServerException e) {
			String message = "Error in collating document sections into single file: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
		writer.close();

	}

	/**
	 * Add files that aren't document sections to zip archive.
	 * There may be additional images, etc that are used in document content.
	 * @param resource
	 * @param path
	 * @throws IOException 
	 */
	private void processStructuredDocumentFiles(Resource resource, DocumentFacility facility, String path) throws IOException
	{
		try {
			// Obtain a list of entries in the root directory and its descendants (exclude deleted files/folders):
			BuildingSession session = BuildingSessionManagerImpl.getSession( resource );
			// Files and folders are represented as a flat summaries array:
			UploadedFileSummary[] summaries = session.getUploadedFileSession().getFileAndDescendentSummaries( "/", true );
			// Obtain the location of the filestore for the entries
			File source_filestore = resource.getWebPublishFolder();
			for ( int summaryIndex = 0; summaryIndex < summaries.length; summaryIndex++ )
			{
				UploadedFileSummary summary = summaries[summaryIndex];
				if (summary.getParentUploadedFileId() == null) // root folder
					continue;
				if (null == facility.parseFileName(summary)) // check whether file represents numbered section content
				{
					UploadedFile f = UploadedFile.findUploadedFile( summary.getUploadedFileId() );
					processUploadedFile(f, path, source_filestore);
				}
			}
		} catch (BuildingServerException e) {
			String message = "Error adding additional files in Structured Document: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
	}
	
	/**
	 * Create HTML file from resource description. </br>
	 * @param resource
     * @param path path to add to in zip archive.
     * @param filename
	 * @throws IOException 
	 */
	
	private void processResourceDescription( Resource resource, String path, String filename, String fileSuffix ) throws IOException
	{
		try {
			// if empty description, then return:
			String description = resource.getDescription();
			if ( ignoreStrings.contains(description))
				return;

			createHTMLFile(resource, description, path, filename, fileSuffix);

		} catch (BuildingServerException e) {
			String message = "Error in exporting description as file: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
	}
	
	/**
	 * Create HTML file from resource introduction. </br>
	 * @param resource
     * @param path path to add to in zip archive.
     * @param filename
	 * @throws IOException 
	 */
	
	private void processResourceIntroduction( Resource resource, String path, String filename, String fileSuffix ) throws IOException
	{
		try {
			// if empty intro, then return:
			String intro = resource.getIntroduction();
			if ( ignoreStrings.contains(intro))
				return;

			createHTMLFile(resource, intro, path, filename, fileSuffix);

		} catch (BuildingServerException e) {
			String message = "Error in exporting introduction as file: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
	}

	private void createHTMLFile(Resource resource, String content, String path, String filename, String fileSuffix)
	throws IOException
	{
		try {
			PrintWriter writer = new PrintWriter(getZipOutputStream());
			startZipEntry(path + filename + fileSuffix);
			writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
			writer.println("<html>");
			writer.println("<head>");
			writer.println("<title>" + resource.getTitle() + "</title>");
			writer.println("<meta name=\"bod_path\" value=\"" + resource.getFullName() + "\" >");
			writer.println("<meta name=\"bod_resourceid\" value=\"" + resource.getResourceId() + "\" >");
			writer.println("</head>");
			writer.println("<body>");
			writer.println(content);
			writer.println("</body>");
			writer.println("</html>");
			writer.flush();
//			writer.close();/// can't close, it closes the ZipOutputStream.
			closeZipEntry();
			
		} catch (BuildingServerException e) {
			String message = "Error in generating HTML file: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
		
		fileList.add(new FileData(resource.getResourceId(), path, filename, fileSuffix));
	}

	private void processUploadedFiles( Resource resource, String path ) throws IOException
	{
		try {
			// Obtain a list of entries in the root directory and its descendants (exclude deleted files/folders):
			BuildingSession session = BuildingSessionManagerImpl.getSession( resource );
			// Files and folders are represented as a flat summaries array:
			UploadedFileSummary[] summaries = session.getUploadedFileSession().getFileAndDescendentSummaries( "/", true );
			// Obtain the location of the filestore for the entries
			File source_filestore = resource.getWebPublishFolder();

			for ( int summaryIndex = 0; summaryIndex < summaries.length; summaryIndex++ )
			{
				PrimaryKey fileID = summaries[summaryIndex].getUploadedFileId();

				if ( fileID == null ) continue; //no root folder

				UploadedFile f = UploadedFile.findUploadedFile( fileID );
				PrimaryKey parentID = f.getParentUploadedFileId();

				if ( parentID == null )
					continue; // root folder
				
				// skip files that were generated by QTI export (and most likely out of synch with current state):
				String filename = f.getName();
				if (filename.equals(excludeQTIfiles) || filename.equals(resource.getName() +".xml"))
					continue;
				
				processUploadedFile(f, path, source_filestore);

			}
		} catch (BuildingServerException e) {
			String message = "Error in processing uploaded files: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		}
	}

    private void processUploadedFile( UploadedFile f, String path, File source_filestore ) throws IOException
    {
    	// don't need to do anything with folders, get containing folder name/s from files instead...
    	if (f.isFolder())
    	{
    		fileList.add(new FileData(f, path));
    		return;
    	}
		
		try {
			// add file to zip archive:
			File source = new File(source_filestore, f.getRealNameInResource());
			FileInputStream inputStream = new FileInputStream( source );
			writeToZip(inputStream, path + f.getNameInResource());
			fileList.add(new FileData(f, path));
			
		} catch (BuildingServerException e) {
			String message = "Error in adding file to zip archive: " + e.getMessage();
			errors.add(message);
			logger.info(message, e);
		} catch (FileNotFoundException e) {
			String message = "Error in adding file to zip archive: " + e.getMessage();
			errors.add(message);
			logger.info(message);
		}
    }

    private void startZipEntry( String zipEntryName ) throws IOException
    {
    	//create and add zip entry
		ZipEntry zipEntry = new ZipEntry( zipEntryName );
		getZipOutputStream().putNextEntry( zipEntry );
    }
    
    private void closeZipEntry() throws IOException
    {
    	getZipOutputStream().closeEntry();
    }
    
    private void writeToZip( InputStream inputStream, String zipEntryName ) throws IOException
    {
    	try {
    		//create and add zip entry
    		ZipEntry zipEntry = new ZipEntry( zipEntryName );
    		getZipOutputStream().putNextEntry( zipEntry );
    		//write  bytes from the inputStream
    		byte[] readBuffer = new byte[8192];
    		int bytesIn = 0;
    		while( ( bytesIn = inputStream.read( readBuffer ) ) != -1 ) {
    			getZipOutputStream().write( readBuffer, 0, bytesIn );
    		}
    	}
    	finally {
    		//close the stream and zipEntry
    		inputStream.close();
    		getZipOutputStream().closeEntry();
    	}
    }
	
    private void writeToZip( String text, String zipEntryName ) throws IOException
    {
    	writeToZip(new ByteArrayInputStream(text.getBytes("UTF-8")), zipEntryName);
    }
    
    protected void writeErrorFileToZip( String message )
	{
		try {
			writeToZip(message, "errors.txt");
		} catch (IOException e) {
			logger.info("Failed to write error file to content package.", e);
		}
	}
    
    protected void finishProcessing() throws IOException
    {
    	try {
    		getZipOutputStream().close();
    	} catch (ZipException ze) {
    		// probably because there was no content to zip up...
    		writeErrorFileToZip("Error occurred in creating archive, possibly because no content was found.");
    		getZipOutputStream().close();
    	}		
    }
	
	protected String formatErrorMessages()
	{
		StringWriter swriter = new StringWriter();
		PrintWriter writer = new PrintWriter(swriter);
		for (Iterator iterator = errors.iterator(); iterator.hasNext();) {
			writer.println((String) iterator.next());
		}
		return swriter.toString();
	}
	
	protected ZipOutputStream getZipOutputStream()
	{
		return zipOutputStream;
	}

	protected void setZipOutputStream(ZipOutputStream zipOutputStream)
	{
		this.zipOutputStream = zipOutputStream;
	}

	protected void setRequest(Request request) {
		this.request = request;
	}
	
	protected Request getRequest() {
		return request;
	}
	
}
