001: /*
002: * Copyright 2001-2007 Hippo (www.hippo.nl)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package nl.hippo.cms.fileimport;
017:
018: import java.io.BufferedInputStream;
019: import java.io.BufferedOutputStream;
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.net.URL;
028: import java.util.ArrayList;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032:
033: import javax.xml.parsers.ParserConfigurationException;
034: import javax.xml.transform.Transformer;
035: import javax.xml.transform.TransformerException;
036: import javax.xml.transform.TransformerFactory;
037: import javax.xml.transform.stream.StreamResult;
038: import javax.xml.transform.stream.StreamSource;
039:
040: import nl.hippo.cms.Constants;
041: import nl.hippo.cocoon.webdav.Property;
042: import nl.hippo.cocoon.webdav.WebDAVHelper;
043: import nl.hippo.uidgenerator.UIDGenerator;
044: import nl.hippo.uidgenerator.UIDGeneratorException;
045:
046: import org.apache.avalon.framework.CascadingRuntimeException;
047: import org.apache.avalon.framework.context.Context;
048: import org.apache.avalon.framework.context.ContextException;
049: import org.apache.avalon.framework.context.Contextualizable;
050: import org.apache.avalon.framework.logger.AbstractLogEnabled;
051: import org.apache.avalon.framework.parameters.ParameterException;
052: import org.apache.avalon.framework.parameters.Parameterizable;
053: import org.apache.avalon.framework.parameters.Parameters;
054: import org.apache.avalon.framework.service.ServiceException;
055: import org.apache.avalon.framework.service.ServiceManager;
056: import org.apache.avalon.framework.service.Serviceable;
057: import org.apache.cocoon.components.ContextHelper;
058: import org.apache.cocoon.environment.Request;
059: import org.apache.commons.httpclient.HttpState;
060: import org.apache.excalibur.source.ModifiableSource;
061: import org.apache.excalibur.source.Source;
062: import org.apache.excalibur.source.SourceResolver;
063: import org.apache.excalibur.source.SourceUtil;
064: import org.jdom.Document;
065: import org.jdom.Element;
066: import org.jdom.JDOMException;
067: import org.jdom.input.SAXBuilder;
068: import org.jdom.output.XMLOutputter;
069: import org.jdom.xpath.XPath;
070: import org.xml.sax.SAXException;
071:
072: public class FileImporterImpl extends AbstractLogEnabled implements
073: FileImporter, Serviceable, Contextualizable, Parameterizable {
074:
075: private static final String BINARIES_PREFIX_PARAMETER_NAME = "binariesPrefix";
076:
077: private static final String DEFAULT_BINARIES_PREFIX = "/binaries";
078:
079: private static final String FILE_IMPORT_SERVICE_URI_PARAMETER_NAME = "fileImportServiceUri";
080:
081: private static final String RESCALE_BITMAPS_PARAMETER_NAME = "rescaleBitmaps";
082:
083: private static final boolean DEFAULT_RESCALE_BITMAPS = false;
084:
085: private static final String CONVERT_WMF_TO_BITMAPS_PARAMETER_NAME = "convertWmfToBitmaps";
086:
087: private static final boolean DEFAULT_CONVERT_WMF_TO_BITMAPS = false;
088:
089: private static final String DPI_FOR_BITMAPS_PARAMETER_NAME = "dpiForBitmaps";
090:
091: private static final int DEFAULT_DPI_FOR_BITMAPS = 96;
092:
093: // Maximum size of to be imported document. If the size of the uploaded file is bigger than
094: // this number (in bytes), importDocument throws an IOException.
095:
096: // Max size of document to import
097: private static final int DEFAULT_SOURCE_MAX_SIZE = 2100000;
098: // Max size of resulting XML
099: private static final int DEFAULT_XML_RESULT_MAX_SIZE = 100000;
100: // Maximum number of TD's in result XML
101: private static final int DEFAULT_MAX_TD_OCCURANCES = 1000;
102:
103: private static final String HTTP_STATE_ATTRIBUTE_NAME = "httpstate";
104:
105: private static final String BINARIES_LOCATION_ATTRIBUTE_NAME = "binariesLocation";
106:
107: private static final String CONVERSION_PIPELINE_PREFIX = "cocoon://explorer/content/as";
108:
109: private ServiceManager m_manager;
110:
111: private String m_binairesPrefix;
112:
113: private String m_fileImportServiceUri;
114:
115: private boolean m_rescaleBitmaps;
116:
117: private boolean m_convertWmfToBitmaps;
118:
119: private int m_dpiForBitmaps;
120:
121: private Context m_context;
122:
123: public void service(ServiceManager manager) throws ServiceException {
124: m_manager = manager;
125: }
126:
127: public void parameterize(Parameters parameters)
128: throws ParameterException {
129: m_binairesPrefix = parameters
130: .getParameter(BINARIES_PREFIX_PARAMETER_NAME,
131: DEFAULT_BINARIES_PREFIX);
132: m_fileImportServiceUri = parameters
133: .getParameter(FILE_IMPORT_SERVICE_URI_PARAMETER_NAME);
134:
135: m_rescaleBitmaps = parameters
136: .getParameterAsBoolean(RESCALE_BITMAPS_PARAMETER_NAME,
137: DEFAULT_RESCALE_BITMAPS);
138: m_convertWmfToBitmaps = parameters.getParameterAsBoolean(
139: CONVERT_WMF_TO_BITMAPS_PARAMETER_NAME,
140: DEFAULT_CONVERT_WMF_TO_BITMAPS);
141: m_dpiForBitmaps = parameters
142: .getParameterAsInteger(DPI_FOR_BITMAPS_PARAMETER_NAME,
143: DEFAULT_DPI_FOR_BITMAPS);
144: }
145:
146: public void contextualize(Context context) throws ContextException {
147: m_context = context;
148: }
149:
150: public FileImporterImpl() {
151: super ();
152: }
153:
154: /**
155: * Import a document into the repository as a specific type.
156: *
157: * @param documentLocation
158: * The location of the document to import.
159: * @param type
160: * The document type to conver the imported document to.
161: * @param repositoryLocation
162: * The location at which to store the imported document.
163: * @param binariesRoot
164: * The root location at which binaries are stored.
165: * @param relativeBinariesLocation
166: * The path of the binaries relative to binariesRoot.
167: * @throws UIDGeneratorException
168: * @throws ServiceException
169: * @throws ParserConfigurationException
170: * @throws SAXException
171: * @throws IOException
172: * @throws TransformerException
173: */
174: public void importDocument(String documentLocation, String type,
175: String repositoryLocation, String binariesRoot,
176: String relativeBinariesLocation)
177: throws UIDGeneratorException, ServiceException,
178: ParserConfigurationException, SAXException, IOException,
179: TransformerException {
180: Request request = ContextHelper.getRequest(m_context);
181:
182: getLogger().info(
183: "Starting Word conversion for: " + repositoryLocation);
184: getLogger().info("size: " + request.getContentLength());
185: getLogger().info("maxsize: " + DEFAULT_SOURCE_MAX_SIZE);
186:
187: if (request.getContentLength() > DEFAULT_SOURCE_MAX_SIZE) {
188: throw new IOException(
189: "explorer.error.actionfailed.filetoobig");
190: }
191:
192: HttpState httpState = (HttpState) request.getSession()
193: .getAttribute(HTTP_STATE_ATTRIBUTE_NAME);
194: String uid = UIDGenerator.generateUID();
195: StringBuffer putUri = new StringBuffer(concatUriFragments(
196: m_fileImportServiceUri, uid));
197: putUri.append("?rescaleBitmaps=");
198: putUri.append(m_rescaleBitmaps);
199: putUri.append("&convertWmfsToBitmaps=");
200: putUri.append(m_convertWmfToBitmaps);
201: putUri.append("&dpiForBitmaps=");
202: putUri.append(m_dpiForBitmaps);
203: WebDAVHelper.put(putUri.toString(), new BufferedInputStream(
204: new FileInputStream(documentLocation)), httpState);
205:
206: SourceResolver sourceResolver = null;
207: Source sourceSource = null;
208: Source destinationSource = null;
209: try {
210: sourceResolver = (SourceResolver) m_manager
211: .lookup(SourceResolver.ROLE);
212: destinationSource = sourceResolver
213: .resolveURI(repositoryLocation);
214: ContextHelper.getRequest(m_context).setAttribute(
215: BINARIES_LOCATION_ATTRIBUTE_NAME,
216: concatUriFragments(m_binairesPrefix,
217: relativeBinariesLocation));
218: sourceSource = sourceResolver
219: .resolveURI(concatUriFragments(
220: CONVERSION_PIPELINE_PREFIX + type, uid));
221:
222: // First, let's check if the document is not too big
223:
224: // Load the source document using a SAX parser into a DOM Document
225: InputStream resultStream = sourceSource.getInputStream();
226: SAXBuilder parser = new SAXBuilder();
227: Document sourceDoc = parser.build(resultStream);
228: resultStream.close();
229:
230: // Check the XML length and the number of <td> elements in the document
231: XMLOutputter resultOutput = new XMLOutputter();
232: XPath selectTDs = XPath.newInstance("//td");
233: List tableNodes = (List) selectTDs.selectNodes(sourceDoc);
234: getLogger().info("Number of TD's: " + tableNodes.size());
235: getLogger().info(
236: "Result XML string length: "
237: + resultOutput.outputString(sourceDoc)
238: .length());
239:
240: if ((resultOutput.outputString(sourceDoc).length() > DEFAULT_XML_RESULT_MAX_SIZE)
241: || (tableNodes.size() > DEFAULT_MAX_TD_OCCURANCES)) {
242: if (destinationSource != null) {
243: sourceResolver.release(destinationSource);
244: }
245: if (sourceSource != null) {
246: sourceResolver.release(sourceSource);
247: }
248: if (sourceResolver != null) {
249: m_manager.release(sourceResolver);
250: }
251:
252: WebDAVHelper.remove(concatUriFragments(
253: m_fileImportServiceUri, uid), httpState);
254:
255: throw new IOException(
256: "explorer.error.actionfailed.filetoobig");
257: }
258:
259: // Size is fine, so let's go ahead and copy it over
260:
261: XMLOutputter outputter = new XMLOutputter();
262:
263: ByteArrayOutputStream out = new ByteArrayOutputStream();
264: // Prepare a new stream of the sourceDoc so it can be used by SourceUtil.copy
265: // [AC] if there's a better way to do this, please share! :-/
266: //outputter.output(sourceDoc, out);
267:
268: if (destinationSource.exists()) {
269: // Copy just the <page> element keeping the <meta>
270: InputStream destinationDoc = destinationSource
271: .getInputStream();
272: Document destDoc = parser.build(destinationDoc);
273: destinationDoc.close();
274:
275: //TODO:make this configurable
276: XPath pagePath = XPath.newInstance("/document/page");
277:
278: Element newPage = (Element) pagePath
279: .selectSingleNode(sourceDoc);
280: Element oldPage = (Element) pagePath
281: .selectSingleNode(destDoc);
282:
283: if (newPage != null && oldPage != null) {
284: newPage.detach();
285: //Element parent = oldPage.getParentElement();
286:
287: Element root = destDoc.detachRootElement();
288:
289: root.removeContent(oldPage);
290:
291: root.addContent(newPage);
292:
293: try {
294: sourceDoc.setRootElement(root);
295: } catch (Exception e) {
296: getLogger().error("Error: " + e);
297: }
298:
299: outputter.output(sourceDoc, out);
300:
301: ModifiableSource dest = (ModifiableSource) destinationSource;
302: SourceUtil.copy(new ByteArrayInputStream(out
303: .toByteArray()), dest.getOutputStream());
304: } else {
305: outputter.output(sourceDoc, out);
306: ModifiableSource dest = (ModifiableSource) destinationSource;
307: SourceUtil.copy(new ByteArrayInputStream(out
308: .toByteArray()), dest.getOutputStream());
309: }
310: } else {
311: outputter.output(sourceDoc, out);
312: ModifiableSource dest = (ModifiableSource) destinationSource;
313: SourceUtil.copy(new ByteArrayInputStream(out
314: .toByteArray()), dest.getOutputStream());
315: }
316:
317: List propertiesToSet = new ArrayList();
318: propertiesToSet.add(new Property("H",
319: Constants.CMS_1_0_NAMESPACE,
320: Constants.TYPE_PROPERTY_NAME, type));
321: WebDAVHelper.proppatch(repositoryLocation, propertiesToSet,
322: null, httpState);
323:
324: copyImages(concatUriFragments(m_fileImportServiceUri, uid),
325: uid, concatUriFragments(binariesRoot,
326: relativeBinariesLocation), sourceResolver);
327: } catch (JDOMException e) {
328: throw new CascadingRuntimeException(e.getMessage(), e);
329: } finally {
330: if (destinationSource != null) {
331: sourceResolver.release(destinationSource);
332: }
333: if (sourceSource != null) {
334: sourceResolver.release(sourceSource);
335: }
336: if (sourceResolver != null) {
337: m_manager.release(sourceResolver);
338: }
339: }
340:
341: WebDAVHelper.remove(concatUriFragments(m_fileImportServiceUri,
342: uid), httpState);
343: }
344:
345: private void copyImages(String importFileUri, String uid,
346: String binariesLocation, SourceResolver sourceResolver)
347: throws ParserConfigurationException, SAXException,
348: IOException, TransformerException {
349: String contentUrl = concatUriFragments(importFileUri,
350: "content.xml");
351: File imageListFile = File.createTempFile("fis", ".txt");
352: try {
353: InputStream imageListXslStream = getClass()
354: .getResourceAsStream("image-list.xsl");
355: try {
356: InputStream contentStream = new URL(contentUrl)
357: .openStream();
358: try {
359: FileOutputStream fileOutput = new FileOutputStream(
360: imageListFile);
361: try {
362: BufferedOutputStream bufferedOutput = new BufferedOutputStream(
363: fileOutput);
364: try {
365: TransformerFactory tf = TransformerFactory
366: .newInstance();
367:
368: Transformer t = tf
369: .newTransformer(new StreamSource(
370: imageListXslStream));
371: t.setParameter("documentUid", uid);
372: t
373: .setParameter(
374: "convertWmfsToBitmaps",
375: String
376: .valueOf(m_convertWmfToBitmaps));
377:
378: t.transform(
379: new StreamSource(contentStream),
380: new StreamResult(bufferedOutput));
381: } finally {
382: bufferedOutput.close();
383: }
384: } finally {
385: fileOutput.close();
386: }
387: } finally {
388: contentStream.close();
389: }
390: } finally {
391: imageListXslStream.close();
392: }
393:
394: ImageListReader ilr = new ImageListReader();
395: Map imageList = ilr.read(imageListFile.getAbsolutePath());
396: for (Iterator imagePathsIterator = imageList.keySet()
397: .iterator(); imagePathsIterator.hasNext();) {
398: String imagePath = (String) imagePathsIterator.next();
399: Image image = (Image) imageList.get(imagePath);
400: String sourceImageUrl = concatUriFragments(
401: importFileUri, imagePath);
402: String destinationImageUrl = concatUriFragments(
403: binariesLocation, image.getTargetName());
404: Source sourceImageSource = sourceResolver
405: .resolveURI(sourceImageUrl);
406: try {
407: Source destinationImageSource = sourceResolver
408: .resolveURI(destinationImageUrl);
409: try {
410: SourceUtil.copy(sourceImageSource,
411: destinationImageSource);
412: } finally {
413: sourceResolver.release(destinationImageSource);
414: }
415: } finally {
416: sourceResolver.release(sourceImageSource);
417: }
418: }
419: } finally {
420: imageListFile.delete();
421: }
422: }
423:
424: private String concatUriFragments(String first, String second) {
425: String result;
426: if (first.endsWith("/") && second.startsWith("/")) {
427: result = first + second.substring(1);
428: } else if (first.endsWith("/") || second.startsWith("/")) {
429: result = first + second;
430: } else {
431: result = first + "/" + second;
432: }
433: return result;
434: }
435:
436: }
|