Index: src/test/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerTest.java =================================================================== --- src/test/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerTest.java (revision 23606) +++ src/test/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerTest.java (working copy) @@ -113,6 +113,7 @@ assertEquals("Directory/File.txt", newAttachment.getFilename()); assertEquals(zipFileContent.length(), newAttachment.getFilesize()); + assertEquals(zipFileContent.length(), newAttachment.getContentSize(context)); assertEquals(zipFileContent, new String(newAttachment.getContent(context))); } @@ -220,7 +221,11 @@ mockAttachment.stubs().method("getDoc").will(returnValue(document)); mockAttachment.stubs().method("getAuthor").will(returnValue("Vincent")); mockAttachment.stubs().method("getDate").will(returnValue(new Date())); - mockAttachment.stubs().method("getContent").will(returnValue(content)); + mockAttachment.stubs().method("getFilesize").will(returnValue((content == null) ? 0 : content.length)); + mockAttachment.stubs().method("getContentSize").will(returnValue((content == null) ? 0 : content.length)); + mockAttachment.stubs().method("getContent").will(returnValue((content == null) ? new byte[0] : content)); + mockAttachment.stubs().method("getContentInputStream").will( + returnValue(new ByteArrayInputStream((content == null) ? new byte[0] : content))); return (XWikiAttachment) mockAttachment.proxy(); } Index: src/test/java/com/xpn/xwiki/doc/XWikiDocumentArchiveTest.java =================================================================== --- src/test/java/com/xpn/xwiki/doc/XWikiDocumentArchiveTest.java (revision 23606) +++ src/test/java/com/xpn/xwiki/doc/XWikiDocumentArchiveTest.java (working copy) @@ -21,8 +21,11 @@ import java.util.Date; +import org.jmock.Mock; import org.suigeneris.jrcs.rcs.Version; +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiConfig; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.test.AbstractBridgedXWikiComponentTestCase; @@ -35,10 +38,19 @@ public class XWikiDocumentArchiveTest extends AbstractBridgedXWikiComponentTestCase { private XWikiContext context; + + private Mock mockXWiki; + protected void setUp() throws Exception { super.setUp(); + + this.mockXWiki = mock(XWiki.class); + this.mockXWiki.stubs().method("getEncoding").will(returnValue("iso-8859-1")); + this.mockXWiki.stubs().method("getConfig").will(returnValue(new XWikiConfig())); + this.context = new XWikiContext(); + this.context.setWiki((XWiki) this.mockXWiki.proxy()); } /** Index: src/main/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerPlugin.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerPlugin.java (revision 23606) +++ src/main/java/com/xpn/xwiki/plugin/zipexplorer/ZipExplorerPlugin.java (working copy) @@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; @@ -127,13 +128,11 @@ newAttachment.setDate(attachment.getDate()); try { - byte[] stream = attachment.getContent(context); - ByteArrayInputStream bais = new ByteArrayInputStream(stream); - if (!isZipFile(bais)) { + if (!isZipFile(attachment.getContentInputStream(context))) { return attachment; } - ZipInputStream zis = new ZipInputStream(bais); + ZipInputStream zis = new ZipInputStream(attachment.getContentInputStream(context)); ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { @@ -142,13 +141,16 @@ if (entryName.equals(filename)) { newAttachment.setFilename(entryName); - // Note: We're copying the content of the file in the ZIP in memory. This is - // potentially going to cause an error if the file's size is greater than the - // maximum size of a byte[] array in Java or if there's not enough memomry. - byte[] data = IOUtils.toByteArray(zis); + if (entry.getSize() == -1) { + // Note: We're copying the content of the file in the ZIP in memory. This is + // potentially going to cause an error if the file's size is greater than the + // maximum size of a byte[] array in Java or if there's not enough memomry. + byte[] data = IOUtils.toByteArray(zis); - newAttachment.setFilesize(data.length); - newAttachment.setContent(data); + newAttachment.setContent(data); + } else { + newAttachment.setContent(zis, (int) entry.getSize()); + } break; } } @@ -290,7 +292,7 @@ * @param filecontent the content of the file * @return true if the file is in zip format (.zip, .jar etc) or false otherwise */ - protected boolean isZipFile(ByteArrayInputStream filecontent) + protected boolean isZipFile(InputStream filecontent) { int standardZipHeader = 0x504b0304; try { @@ -302,8 +304,12 @@ } catch (IOException e) { // The file doesn't have 4 bytes, so it isn't a zip file } finally { - // Reset the input stream to the beginning. This is needed for further reading the archive. - filecontent.reset(); + // Reset the input stream to the beginning. This may be needed for further reading the archive. + try { + filecontent.reset(); + } catch (IOException e) { + e.printStackTrace(); + } } return false; } Index: src/main/java/com/xpn/xwiki/plugin/fileupload/FileUploadPlugin.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/fileupload/FileUploadPlugin.java (revision 23606) +++ src/main/java/com/xpn/xwiki/plugin/fileupload/FileUploadPlugin.java (working copy) @@ -280,15 +280,16 @@ */ public byte[] getFileItemData(String formfieldName, XWikiContext context) throws XWikiException { - FileItem fileitem = getFile(formfieldName, context); + int size = getFileItemSize(formfieldName, context); - if (fileitem == null) { + if (size == 0) { return null; } - byte[] data = new byte[(int) fileitem.getSize()]; + byte[] data = new byte[size]; + try { - InputStream fileis = fileitem.getInputStream(); + InputStream fileis = getFileItemInputStream(formfieldName, context); if (fileis != null) { fileis.read(data); fileis.close(); @@ -306,6 +307,44 @@ } /** + * Allows to retrieve the contents of an uploaded file as a stream. {@link #loadFileList(XWikiContext)} needs to be + * called beforehand. + * + * @param formfieldName The name of the form field. + * @param context Context of the request. + * @return a InputStream on the file content + * @throws IOException if I/O problem occurs + */ + public InputStream getFileItemInputStream(String formfieldName, XWikiContext context) throws IOException + { + FileItem fileitem = getFile(formfieldName, context); + + if (fileitem == null) { + return null; + } + + return fileitem.getInputStream(); + } + + /** + * Retrieve the size of a file content in byte. {@link #loadFileList(XWikiContext)} needs to be called beforehand. + * + * @param formfieldName The name of the form field. + * @param context Context of the request. + * @return the size of the file in byte + */ + public int getFileItemSize(String formfieldName, XWikiContext context) + { + FileItem fileitem = getFile(formfieldName, context); + + if (fileitem == null) { + return 0; + } + + return ((int) fileitem.getSize()); + } + + /** * Allows to retrieve the contents of an uploaded file as a string. {@link #loadFileList(XWikiContext)} needs to be * called beforehand. * Index: src/main/java/com/xpn/xwiki/plugin/packaging/PackageAPI.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/packaging/PackageAPI.java (revision 23606) +++ src/main/java/com/xpn/xwiki/plugin/packaging/PackageAPI.java (working copy) @@ -22,6 +22,7 @@ package com.xpn.xwiki.plugin.packaging; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -199,6 +200,11 @@ return this.plugin.Import(file, getXWikiContext()); } + public String Import(InputStream file) throws IOException, XWikiException + { + return this.plugin.Import(file, getXWikiContext()); + } + public int testInstall() { return this.plugin.testInstall(false, getXWikiContext()); @@ -215,7 +221,7 @@ this.export(); } - public String toXml() + public String toXml() throws XWikiException { return this.plugin.toXml(getXWikiContext()); } Index: src/main/java/com/xpn/xwiki/plugin/packaging/Package.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/packaging/Package.java (revision 23606) +++ src/main/java/com/xpn/xwiki/plugin/packaging/Package.java (working copy) @@ -28,7 +28,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; @@ -44,7 +43,6 @@ import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; import org.xwiki.query.QueryException; import com.xpn.xwiki.XWiki; @@ -53,6 +51,8 @@ import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.classes.BaseClass; +import com.xpn.xwiki.util.UnclosableInputStream; +import com.xpn.xwiki.util.XMLWriter; public class Package { @@ -362,8 +362,12 @@ public String Import(byte file[], XWikiContext context) throws IOException, XWikiException { - ByteArrayInputStream bais = new ByteArrayInputStream(file); - ZipInputStream zis = new ZipInputStream(bais); + return Import(new ByteArrayInputStream(file), context); + } + + public String Import(InputStream file, XWikiContext context) throws IOException, XWikiException + { + ZipInputStream zis = new ZipInputStream(file); ZipEntry entry; Document description = null; @@ -372,8 +376,8 @@ if (description == null) { throw new PackageException(XWikiException.ERROR_XWIKI_UNKNOWN, "Could not find the package definition"); } - bais = new ByteArrayInputStream(file); - zis = new ZipInputStream(bais); + file.reset(); + zis = new ZipInputStream(file); while ((entry = zis.getNextEntry()) != null) { // Don't read the package.xml file (since we've already read it above), // directories or any file in the META-INF directory (we use that directory @@ -384,7 +388,7 @@ } else { XWikiDocument doc = null; try { - doc = readFromXML(readByteArrayFromInputStream(zis, entry.getSize())); + doc = readFromXML(new UnclosableInputStream(zis)); } catch (Throwable ex) { LOG.warn("Failed to parse document [" + entry.getName() + "] from XML during import, thus it will not be installed. " + "The error was: " @@ -705,18 +709,6 @@ } } - private ByteArrayInputStream readByteArrayFromInputStream(ZipInputStream zin, long size) throws IOException - { - ByteArrayOutputStream baos = new ByteArrayOutputStream((size > 0) ? (int) size : 4096); - byte[] data = new byte[4096]; - int cnt; - while ((cnt = zin.read(data, 0, 4096)) != -1) { - baos.write(data, 0, cnt); - } - - return new ByteArrayInputStream(baos.toByteArray()); - } - /** * Create a {@link XWikiDocument} from xml stream. * @@ -764,71 +756,74 @@ return null; } - public String toXml(XWikiContext context) + public String toXml(XWikiContext context) throws XWikiException { - OutputFormat outputFormat = new OutputFormat("", true); - outputFormat.setEncoding(context.getWiki().getEncoding()); - StringWriter out = new StringWriter(); - XMLWriter writer = new XMLWriter(out, outputFormat); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - writer.write(toXmlDocument()); - - return out.toString(); + toXML(baos, context); + return baos.toString(context.getWiki().getEncoding()); } catch (IOException e) { e.printStackTrace(); - return ""; } } - private Document toXmlDocument() + public void toXML(XMLWriter wr) throws IOException { - Document doc = new DOMDocument(); Element docel = new DOMElement("package"); - doc.setRootElement(docel); + wr.writeOpen(docel); + Element elInfos = new DOMElement("infos"); - docel.add(elInfos); + wr.write(elInfos); Element el = new DOMElement("name"); el.addText(this.name); - elInfos.add(el); + wr.write(el); el = new DOMElement("description"); el.addText(this.description); - elInfos.add(el); + wr.write(el); el = new DOMElement("licence"); el.addText(this.licence); - elInfos.add(el); + wr.write(el); el = new DOMElement("author"); el.addText(this.authorName); - elInfos.add(el); + wr.write(el); el = new DOMElement("version"); el.addText(this.version); - elInfos.add(el); + wr.write(el); el = new DOMElement("backupPack"); el.addText(new Boolean(this.backupPack).toString()); - elInfos.add(el); + wr.write(el); el = new DOMElement("preserveVersion"); el.addText(new Boolean(this.preserveVersion).toString()); - elInfos.add(el); + wr.write(el); Element elfiles = new DOMElement("files"); - docel.add(elfiles); + wr.writeOpen(elfiles); for (DocumentInfo docInfo : this.files) { Element elfile = new DOMElement("file"); elfile.addAttribute("defaultAction", String.valueOf(docInfo.getAction())); elfile.addAttribute("language", String.valueOf(docInfo.getLanguage())); elfile.addText(docInfo.getFullName()); - elfiles.add(elfile); + wr.write(elfile); } + } - return doc; + public void toXML(OutputStream out, XWikiContext context) throws XWikiException, IOException + { + XMLWriter wr = new XMLWriter(out, new OutputFormat("", true, context.getWiki().getEncoding())); + + Document doc = new DOMDocument(); + wr.writeDocumentStart(doc); + toXML(wr); + wr.writeDocumentEnd(doc); } private void addInfosToZip(ZipOutputStream zos, XWikiContext context) @@ -837,7 +832,7 @@ String zipname = DefaultPackageFileName; ZipEntry zipentry = new ZipEntry(zipname); zos.putNextEntry(zipentry); - zos.write(toXml(context).getBytes(context.getWiki().getEncoding())); + toXML(zos, context); zos.closeEntry(); } catch (Exception e) { e.printStackTrace(); @@ -879,29 +874,10 @@ } public void addToZip(XWikiDocument doc, ZipOutputStream zos, boolean withVersions, XWikiContext context) - throws IOException + throws XWikiException, IOException { - try { - String zipname = getPathFromDocument(doc, context); - ZipEntry zipentry = new ZipEntry(zipname); - zos.putNextEntry(zipentry); - String docXml = doc.toXML(true, false, true, withVersions, context); - - // Ensure that a non-admin user do not get to see user's passwords. Note that this - // is for backward compatibility for passwords that are stored in clear. As of - // XWiki 1.0 RC2 passwords are now hashed and thus it's no longer important that users - // get to see them. - if (!context.getWiki().getRightService().hasAdminRights(context)) { - docXml = - context.getUtil().substitute("s/.*?<\\/password>/********<\\/password>/goi", - docXml); - } - - zos.write(docXml.getBytes(context.getWiki().getEncoding())); - zos.closeEntry(); - } catch (Exception e) { - e.printStackTrace(); - } + String zipname = getPathFromDocument(doc, context); + doc.addToZip(zos, zipname, withVersions, context); } public void addToDir(XWikiDocument doc, File dir, boolean withVersions, XWikiContext context) throws XWikiException @@ -919,14 +895,8 @@ } String filename = getFileNameFromDocument(doc, context); File file = new File(spacedir, filename); - String xml = doc.toXML(true, false, true, withVersions, context); - if (!context.getWiki().getRightService().hasAdminRights(context)) { - xml = - context.getUtil().substitute("s/.*?<\\/password>/********<\\/password>/goi", - xml); - } FileOutputStream fos = new FileOutputStream(file); - fos.write(xml.getBytes(context.getWiki().getEncoding())); + doc.toXML(fos, true, false, true, withVersions, context); fos.flush(); fos.close(); } catch (ExcludeDocumentException e) { @@ -946,7 +916,7 @@ String filename = DefaultPackageFileName; File file = new File(dir, filename); FileOutputStream fos = new FileOutputStream(file); - fos.write(toXml(context).getBytes(context.getWiki().getEncoding())); + toXML(fos, context); fos.flush(); fos.close(); } catch (Exception e) { @@ -983,11 +953,6 @@ return domdoc; } - protected void readDependencies() - { - - } - public void addAllWikiDocuments(XWikiContext context) throws XWikiException { XWiki wiki = context.getWiki(); Index: src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java =================================================================== --- src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java (revision 23606) +++ src/main/java/com/xpn/xwiki/doc/XWikiAttachmentContent.java (working copy) @@ -21,6 +21,13 @@ package com.xpn.xwiki.doc; +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.hibernate.Hibernate; + public class XWikiAttachmentContent implements Cloneable { private XWikiAttachment attachment; @@ -71,6 +78,7 @@ return attachmentcontent; } + @Deprecated public byte[] getContent() { if (this.content == null) { @@ -80,6 +88,7 @@ } } + @Deprecated public void setContent(byte[] content) { if (content == null) { @@ -112,4 +121,37 @@ { this.isContentDirty = contentDirty; } + + public InputStream getContentInputStream() + { + return new ByteArrayInputStream(getContent()); + } + + public void setContent(InputStream is, int len) throws IOException + { + setContent(readData(is, len)); + } + + public int getSize() + { + return content.length; + } + + private byte[] readData(InputStream is, int len) throws IOException + { + if (is == null) + return null; + + int off = 0; + byte[] buf = new byte[len]; + while (len > 0) { + int n = is.read(buf, off, len); + if (n == -1) { + throw new EOFException(); + } + off += n; + len -= n; + } + return buf; + } } Index: src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java =================================================================== --- src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java (revision 23606) +++ src/main/java/com/xpn/xwiki/doc/XWikiAttachment.java (working copy) @@ -21,9 +21,10 @@ package com.xpn.xwiki.doc; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.StringReader; -import java.io.StringWriter; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -39,12 +40,13 @@ import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; import org.suigeneris.jrcs.rcs.Archive; import org.suigeneris.jrcs.rcs.Version; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.util.DOMXMLWriter; +import com.xpn.xwiki.util.XMLWriter; public class XWikiAttachment implements Cloneable { @@ -156,6 +158,15 @@ this.filesize = filesize; } + public int getContentSize(XWikiContext context) throws XWikiException + { + if (this.attachment_content == null) { + this.doc.loadAttachmentContent(this, context); + } + + return this.attachment_content.getSize(); + } + public String getFilename() { return this.filename; @@ -286,21 +297,14 @@ public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) throws XWikiException { - // implement - Element ele = toXML(bWithAttachmentContent, bWithVersions, context); - - Document doc = new DOMDocument(); - doc.setRootElement(ele); - OutputFormat outputFormat = new OutputFormat("", true); - if ((context == null) || (context.getWiki() == null)) - outputFormat.setEncoding("UTF-8"); - else - outputFormat.setEncoding(context.getWiki().getEncoding()); - StringWriter out = new StringWriter(); - XMLWriter writer = new XMLWriter(out, outputFormat); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - writer.write(doc); - return out.toString(); + XMLWriter wr = new XMLWriter(baos, new OutputFormat("", true, context.getWiki().getEncoding())); + Document doc = new DOMDocument(); + wr.writeDocumentStart(doc); + toXML(wr, bWithAttachmentContent, bWithVersions, context); + wr.writeDocumentEnd(doc); + return baos.toString(context.getWiki().getEncoding()); } catch (IOException e) { e.printStackTrace(); return ""; @@ -312,34 +316,44 @@ return toXML(false, false, context); } - public Element toXML(boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) - throws XWikiException + /** + * @param wr + * @param bWithAttachmentContent + * @param bWithVersions + * @param context + * @throws IOException + * @throws XWikiException + */ + public void toXML(XMLWriter wr, boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) + throws IOException, XWikiException { Element docel = new DOMElement("attachment"); + wr.writeOpen(docel); + Element el = new DOMElement("filename"); el.addText(getFilename()); - docel.add(el); + wr.write(el); el = new DOMElement("filesize"); el.addText("" + getFilesize()); - docel.add(el); + wr.write(el); el = new DOMElement("author"); el.addText(getAuthor()); - docel.add(el); + wr.write(el); long d = getDate().getTime(); el = new DOMElement("date"); el.addText("" + d); - docel.add(el); + wr.write(el); el = new DOMElement("version"); el.addText(getVersion()); - docel.add(el); + wr.write(el); el = new DOMElement("comment"); el.addText(getComment()); - docel.add(el); + wr.write(el); if (bWithAttachmentContent) { el = new DOMElement("content"); @@ -347,13 +361,11 @@ loadContent(context); XWikiAttachmentContent acontent = getAttachment_content(); if (acontent != null) { - byte[] bcontent = getAttachment_content().getContent(); - String content = new String(Base64.encodeBase64(bcontent)); - el.addText(content); + wr.writeBase64(el, getAttachment_content().getContentInputStream()); } else { el.addText(""); + wr.write(el); } - docel.add(el); } if (bWithVersions) { @@ -363,16 +375,29 @@ el = new DOMElement("versions"); try { el.addText(new String(aarchive.getArchive())); + wr.write(el); } catch (XWikiException e) { - return null; } - docel.add(el); } } - return docel; + wr.writeClose(docel); } + public Element toXML(boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) + throws XWikiException + { + Document doc = new DOMDocument(); + DOMXMLWriter wr = new DOMXMLWriter(doc, new OutputFormat("", true, context.getWiki().getEncoding())); + + try { + toXML(wr, bWithAttachmentContent, bWithVersions, context); + } catch (IOException e) { + throw new RuntimeException(e); + } + return doc.getRootElement(); + } + public void fromXML(String data) throws XWikiException { SAXReader reader = new SAXReader(); @@ -433,6 +458,7 @@ this.attachment_archive = attachment_archive; } + @Deprecated public byte[] getContent(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { @@ -442,6 +468,15 @@ return this.attachment_content.getContent(); } + public InputStream getContentInputStream(XWikiContext context) throws XWikiException + { + if (this.attachment_content == null) { + this.doc.loadAttachmentContent(this, context); + } + + return this.attachment_content.getContentInputStream(); + } + public Archive getArchive() { if (this.attachment_archive == null) { @@ -500,6 +535,7 @@ return list; } + @Deprecated public void setContent(byte[] data) { if (this.attachment_content == null) { @@ -510,6 +546,16 @@ this.attachment_content.setContent(data); } + public void setContent(InputStream is, int length) throws IOException + { + if (this.attachment_content == null) { + this.attachment_content = new XWikiAttachmentContent(); + this.attachment_content.setAttachment(this); + } + + this.attachment_content.setContent(is, length); + } + public void loadContent(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { @@ -578,4 +624,5 @@ return loadArchive(context).getRevision(this, rev, context); } + } Index: src/main/java/com/xpn/xwiki/doc/XWikiDocument.java =================================================================== --- src/main/java/com/xpn/xwiki/doc/XWikiDocument.java (revision 23606) +++ src/main/java/com/xpn/xwiki/doc/XWikiDocument.java (working copy) @@ -22,8 +22,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.StringReader; -import java.io.StringWriter; import java.lang.ref.SoftReference; import java.lang.reflect.Method; import java.net.URL; @@ -65,7 +65,6 @@ import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; import org.suigeneris.jrcs.diff.Diff; import org.suigeneris.jrcs.diff.DifferentiationFailedException; import org.suigeneris.jrcs.diff.Revision; @@ -124,7 +123,9 @@ import com.xpn.xwiki.store.XWikiAttachmentStoreInterface; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.store.XWikiVersioningStoreInterface; +import com.xpn.xwiki.util.DOMXMLWriter; import com.xpn.xwiki.util.Util; +import com.xpn.xwiki.util.XMLWriter; import com.xpn.xwiki.validation.XWikiValidationInterface; import com.xpn.xwiki.validation.XWikiValidationStatus; import com.xpn.xwiki.web.EditForm; @@ -2425,19 +2426,15 @@ public String toXML(Document doc, XWikiContext context) { - OutputFormat outputFormat = new OutputFormat("", true); - if ((context == null) || (context.getWiki() == null)) { - outputFormat.setEncoding("UTF-8"); - } else { - outputFormat.setEncoding(context.getWiki().getEncoding()); - } - StringWriter out = new StringWriter(); - XMLWriter writer = new XMLWriter(out, outputFormat); + String encoding = context.getWiki().getEncoding(); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); try { - writer.write(doc); - return out.toString(); + XMLWriter wr = new XMLWriter(os, new OutputFormat("", true, encoding)); + wr.write(doc); + return os.toString(encoding); } catch (IOException e) { - e.printStackTrace(); + LOG.error("Exception while doc.toXML", e); return ""; } } @@ -2445,14 +2442,12 @@ public String getXMLContent(XWikiContext context) throws XWikiException { XWikiDocument tdoc = getTranslatedDocument(context); - Document doc = tdoc.toXMLDocument(true, true, false, false, context); - return toXML(doc, context); + return tdoc.toXML(true, true, false, false, context); } public String toXML(XWikiContext context) throws XWikiException { - Document doc = toXMLDocument(context); - return toXML(doc, context); + return toXML(true, false, false, false, context); } public String toFullXML(XWikiContext context) throws XWikiException @@ -2460,140 +2455,130 @@ return toXML(true, false, true, true, context); } - public void addToZip(ZipOutputStream zos, boolean withVersions, XWikiContext context) throws IOException + public void addToZip(ZipOutputStream zos, String zipname, boolean withVersions, XWikiContext context) + throws XWikiException, IOException { - try { - String zipname = getSpace() + "/" + getName(); - String language = getLanguage(); - if ((language != null) && (!language.equals(""))) { - zipname += "." + language; - } - ZipEntry zipentry = new ZipEntry(zipname); - zos.putNextEntry(zipentry); - zos.write(toXML(true, false, true, withVersions, context).getBytes()); - zos.closeEntry(); - } catch (Exception e) { - e.printStackTrace(); - } + ZipEntry zipentry = new ZipEntry(zipname); + zos.putNextEntry(zipentry); + toXML(zos, true, false, true, withVersions, context); + zos.closeEntry(); } - public void addToZip(ZipOutputStream zos, XWikiContext context) throws IOException + public void addToZip(ZipOutputStream zos, boolean withVersions, XWikiContext context) throws XWikiException, + IOException { - addToZip(zos, true, context); + String zipname = getSpace() + "/" + getName(); + String language = getLanguage(); + if ((language != null) && (!language.equals(""))) { + zipname += "." + language; + } + addToZip(zos, zipname, withVersions, context); } - public String toXML(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, - boolean bWithVersions, XWikiContext context) throws XWikiException + public void addToZip(ZipOutputStream zos, XWikiContext context) throws XWikiException, IOException { - Document doc = toXMLDocument(bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); - return toXML(doc, context); + addToZip(zos, true, context); } - public Document toXMLDocument(XWikiContext context) throws XWikiException + public void toXML(XMLWriter wr, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, + boolean bWithVersions, XWikiContext context) throws XWikiException, IOException { - return toXMLDocument(true, false, false, false, context); - } - - public Document toXMLDocument(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, - boolean bWithVersions, XWikiContext context) throws XWikiException - { - Document doc = new DOMDocument(); Element docel = new DOMElement("xwikidoc"); - doc.setRootElement(docel); + wr.writeOpen(docel); Element el = new DOMElement("web"); el.addText(getSpace()); - docel.add(el); + wr.write(el); el = new DOMElement("name"); el.addText(getName()); - docel.add(el); + wr.write(el); el = new DOMElement("language"); el.addText(getLanguage()); - docel.add(el); + wr.write(el); el = new DOMElement("defaultLanguage"); el.addText(getDefaultLanguage()); - docel.add(el); + wr.write(el); el = new DOMElement("translation"); el.addText("" + getTranslation()); - docel.add(el); + wr.write(el); el = new DOMElement("parent"); el.addText(getParent()); - docel.add(el); + wr.write(el); el = new DOMElement("creator"); el.addText(getCreator()); - docel.add(el); + wr.write(el); el = new DOMElement("author"); el.addText(getAuthor()); - docel.add(el); + wr.write(el); el = new DOMElement("customClass"); el.addText(getCustomClass()); - docel.add(el); + wr.write(el); el = new DOMElement("contentAuthor"); el.addText(getContentAuthor()); - docel.add(el); + wr.write(el); long d = getCreationDate().getTime(); el = new DOMElement("creationDate"); el.addText("" + d); - docel.add(el); + wr.write(el); d = getDate().getTime(); el = new DOMElement("date"); el.addText("" + d); - docel.add(el); + wr.write(el); d = getContentUpdateDate().getTime(); el = new DOMElement("contentUpdateDate"); el.addText("" + d); - docel.add(el); + wr.write(el); el = new DOMElement("version"); el.addText(getVersion()); - docel.add(el); + wr.write(el); el = new DOMElement("title"); el.addText(getTitle()); - docel.add(el); + wr.write(el); el = new DOMElement("template"); el.addText(getTemplate()); - docel.add(el); + wr.write(el); el = new DOMElement("defaultTemplate"); el.addText(getDefaultTemplate()); - docel.add(el); + wr.write(el); el = new DOMElement("validationScript"); el.addText(getValidationScript()); - docel.add(el); + wr.write(el); el = new DOMElement("comment"); el.addText(getComment()); - docel.add(el); + wr.write(el); el = new DOMElement("minorEdit"); el.addText(String.valueOf(isMinorEdit())); - docel.add(el); + wr.write(el); el = new DOMElement("syntaxId"); el.addText(getSyntaxId()); - docel.add(el); + wr.write(el); el = new DOMElement("hidden"); el.addText(String.valueOf(isHidden())); - docel.add(el); + wr.write(el); for (XWikiAttachment attach : getAttachmentList()) { - docel.add(attach.toXML(bWithAttachmentContent, bWithVersions, context)); + attach.toXML(wr, bWithAttachmentContent, bWithVersions, context); } if (bWithObjects) { @@ -2601,7 +2586,7 @@ BaseClass bclass = getxWikiClass(); if (bclass.getFieldList().size() > 0) { // If the class has fields, add class definition and field information to XML - docel.add(bclass.toXML(null)); + wr.write(bclass.toXML(null)); } // Add Objects (THEIR ORDER IS MOLDED IN STONE!) @@ -2614,7 +2599,7 @@ } else { objclass = obj.getxWikiClass(context); } - docel.add(obj.toXML(objclass)); + wr.write(obj.toXML(objclass)); } } } @@ -2628,7 +2613,7 @@ // String newcontent = encodedXMLStringAsUTF8(getContent()); String newcontent = this.content; el.addText(newcontent); - docel.add(el); + wr.write(el); if (bWithRendering) { el = new DOMElement("renderedcontent"); @@ -2637,22 +2622,63 @@ } catch (XWikiException e) { el.addText("Exception with rendering content: " + e.getFullMessage()); } - docel.add(el); + wr.write(el); } if (bWithVersions) { el = new DOMElement("versions"); try { el.addText(getDocumentArchive(context).getArchive(context)); - docel.add(el); + wr.write(el); } catch (XWikiException e) { LOG.error("Document [" + this.getFullName() + "] has malformed history"); } } + } - return doc; + public void toXML(OutputStream out, boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, + boolean bWithVersions, XWikiContext context) throws XWikiException, IOException + { + XMLWriter wr = new XMLWriter(out, new OutputFormat("", true, context.getWiki().getEncoding())); + + Document doc = new DOMDocument(); + wr.writeDocumentStart(doc); + toXML(wr, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); + wr.writeDocumentEnd(doc); } + public String toXML(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, + boolean bWithVersions, XWikiContext context) throws XWikiException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + toXML(baos, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); + return baos.toString(context.getWiki().getEncoding()); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + public Document toXMLDocument(XWikiContext context) throws XWikiException + { + return toXMLDocument(true, false, false, false, context); + } + + public Document toXMLDocument(boolean bWithObjects, boolean bWithRendering, boolean bWithAttachmentContent, + boolean bWithVersions, XWikiContext context) throws XWikiException + { + Document doc = new DOMDocument(); + DOMXMLWriter wr = new DOMXMLWriter(doc, new OutputFormat("", true, context.getWiki().getEncoding())); + + try { + toXML(wr, bWithObjects, bWithRendering, bWithAttachmentContent, bWithVersions, context); + return doc; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + protected String encodedXMLStringAsUTF8(String xmlString) { if (xmlString == null) { Index: src/main/java/com/xpn/xwiki/web/DownloadAction.java =================================================================== --- src/main/java/com/xpn/xwiki/web/DownloadAction.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/DownloadAction.java (working copy) @@ -28,6 +28,8 @@ import java.io.IOException; +import org.apache.commons.io.IOUtils; + public class DownloadAction extends XWikiAction { /** @@ -94,24 +96,20 @@ response.setDateHeader("Last-Modified", attachment.getDate().getTime()); // Sending the content of the attachment - byte[] data; try { - data = attachment.getContent(context); + response.setContentLength(attachment.getContentSize(context)); + IOUtils.copy(attachment.getContentInputStream(context), response.getOutputStream()); } catch (XWikiException e) { Object[] args = {filename}; throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND, "Attachment content {0} not found", null, args); - } - - response.setContentLength(data.length); - try { - response.getOutputStream().write(data); } catch (IOException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, "Exception while sending response", e); } + return null; } } Index: src/main/java/com/xpn/xwiki/web/ImportAction.java =================================================================== --- src/main/java/com/xpn/xwiki/web/ImportAction.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/ImportAction.java (working copy) @@ -1,15 +1,15 @@ package com.xpn.xwiki.web; +import java.util.List; + import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.doc.XWikiAttachment; -import com.xpn.xwiki.plugin.packaging.PackageAPI; +import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.plugin.packaging.DocumentInfo; import com.xpn.xwiki.plugin.packaging.DocumentInfoAPI; +import com.xpn.xwiki.plugin.packaging.PackageAPI; -import java.util.List; - public class ImportAction extends XWikiAction { @@ -39,21 +39,20 @@ if ("getPackageInfos".equals(action)) { response.setContentType("text/xml"); XWikiAttachment packFile = doc.getAttachment(name); - importer.Import(packFile.getContent(context)); + importer.Import(packFile.getContentInputStream(context)); String xml = importer.toXml(); response.setContentLength(xml.getBytes().length); response.getWriter().write(xml); return null; } else if ("import".equals(action)) { XWikiAttachment packFile = doc.getAttachment(name); - importer.Import(packFile.getContent(context)); + importer.Import(packFile.getContentInputStream(context)); String all = request.get("all"); if (!"1".equals(all)) { if (pages != null) { - List filelist = importer.getFiles(); + List filelist = importer.getFiles(); for (int i = 0; i < filelist.size(); i++) { - DocumentInfoAPI dia = (DocumentInfoAPI) filelist.get(i); - dia.setAction(DocumentInfo.ACTION_SKIP); + filelist.get(i).setAction(DocumentInfo.ACTION_SKIP); } for (int i = 0; i < pages.length; i++) { Index: src/main/java/com/xpn/xwiki/web/UploadAction.java =================================================================== --- src/main/java/com/xpn/xwiki/web/UploadAction.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/UploadAction.java (working copy) @@ -21,6 +21,7 @@ package com.xpn.xwiki.web; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -150,8 +151,6 @@ XWikiResponse response = context.getResponse(); String username = context.getUser(); - byte[] data = fileupload.getFileItemData(fieldName, context); - // Read XWikiAttachment XWikiAttachment attachment = doc.getAttachment(filename); @@ -159,7 +158,21 @@ attachment = new XWikiAttachment(); doc.getAttachmentList().add(attachment); } - attachment.setContent(data); + + int fisize = fileupload.getFileItemSize(fieldName, context); + + try { + if (fisize > 0) { + InputStream fiis = fileupload.getFileItemInputStream(fieldName, context); + attachment.setContent(fiis, fisize); + } else { + attachment.setContent(new byte[0]); + } + } catch (IOException e) { + throw new XWikiException(XWikiException.MODULE_XWIKI_APP, + XWikiException.ERROR_XWIKI_APP_UPLOAD_FILE_EXCEPTION, "Exception while reading uploaded parsed file", e); + } + attachment.setFilename(filename); attachment.setAuthor(username); Index: src/main/java/com/xpn/xwiki/web/DownloadRevAction.java =================================================================== --- src/main/java/com/xpn/xwiki/web/DownloadRevAction.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/DownloadRevAction.java (working copy) @@ -9,6 +9,8 @@ import java.io.IOException; +import org.apache.commons.io.IOUtils; + public class DownloadRevAction extends XWikiAction { public String render(XWikiContext context) throws XWikiException { XWikiRequest request = context.getRequest(); @@ -69,10 +71,9 @@ response.setDateHeader("Last-Modified", attachment.getDate().getTime()); // Sending the content of the attachment - byte[] data = attachment.getContent(context); - response.setContentLength(data.length); try { - response.getOutputStream().write(data); + response.setContentLength(attachment.getContentSize(context)); + IOUtils.copy(attachment.getContentInputStream(context), response.getOutputStream()); } catch (IOException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_APP, XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION, Index: src/main/java/com/xpn/xwiki/web/ExportURLFactory.java =================================================================== --- src/main/java/com/xpn/xwiki/web/ExportURLFactory.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/ExportURLFactory.java (working copy) @@ -11,6 +11,8 @@ import java.util.Iterator; import java.util.Set; +import org.apache.commons.io.IOUtils; + import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; @@ -226,9 +228,8 @@ context.getWiki().getDocument( db + XWikiDocument.DB_SPACE_SEP + space + XWikiDocument.SPACE_NAME_SEP + name, context); XWikiAttachment attachment = doc.getAttachment(filename); - byte[] data = attachment.getContent(context); FileOutputStream fos = new FileOutputStream(file); - fos.write(data); + IOUtils.copy(attachment.getContentInputStream(context), fos); fos.close(); } Index: src/main/java/com/xpn/xwiki/web/SkinAction.java =================================================================== --- src/main/java/com/xpn/xwiki/web/SkinAction.java (revision 23606) +++ src/main/java/com/xpn/xwiki/web/SkinAction.java (working copy) @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.Date; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -321,15 +322,18 @@ if (attachment != null) { XWiki xwiki = context.getWiki(); XWikiResponse response = context.getResponse(); - byte[] data = attachment.getContent(context); String mimetype = xwiki.getEngineContext().getMimeType(filename.toLowerCase()); if (isCssMimeType(mimetype) || isJavascriptMimeType(mimetype)) { + byte[] data = attachment.getContent(context); // Always force UTF-8, as this is the assumed encoding for text files. data = context.getWiki().parseContent(new String(data, ENCODING), context).getBytes(ENCODING); response.setCharacterEncoding(ENCODING); + setupHeaders(response, mimetype, attachment.getDate(), data.length); + response.getOutputStream().write(data); + } else { + setupHeaders(response, mimetype, attachment.getDate(), attachment.getContentSize(context)); + IOUtils.copy(attachment.getContentInputStream(context), response.getOutputStream()); } - setupHeaders(response, mimetype, attachment.getDate(), data.length); - response.getOutputStream().write(data); return true; } else { LOG.debug("Attachment not found"); Index: src/main/java/com/xpn/xwiki/store/jcr/XWikiJcrAttachmentStore.java =================================================================== --- src/main/java/com/xpn/xwiki/store/jcr/XWikiJcrAttachmentStore.java (revision 23606) +++ src/main/java/com/xpn/xwiki/store/jcr/XWikiJcrAttachmentStore.java (working copy) @@ -43,7 +43,7 @@ Node dirattNode = JcrUtil.getOrCreateSubNode(docNode, "attach", ntXWikiAttachments); Node attachNode = JcrUtil.getOrCreateSubNode(dirattNode, attachment.getFilename(), ntXWikiAttachment); Node attachContentNode = JcrUtil.getOrCreateSubNode(attachNode, "content", ntXWikiAttachmentContent); - attachContentNode.setProperty("jcr:data", new ByteArrayInputStream(content.getContent())); + attachContentNode.setProperty("jcr:data", content.getContentInputStream()); attachContentNode.setProperty("attach", attachNode); Node attachArchiveNode = JcrUtil.getOrCreateSubNode(attachNode, "archive", ntXWikiAttachmentArchive); attachArchiveNode.setProperty("jcr:data", new ByteArrayInputStream(archive.getArchive())); Index: src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java =================================================================== --- src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java (revision 23606) +++ src/main/java/com/xpn/xwiki/pdf/impl/PdfURLFactory.java (working copy) @@ -30,6 +30,8 @@ import java.io.FileOutputStream; import java.net.URL; +import org.apache.commons.io.IOUtils; + public class PdfURLFactory extends XWikiServletURLFactory { public PdfURLFactory() @@ -46,10 +48,8 @@ XWikiDocument doc = null; doc = context.getWiki().getDocument(web + "." + name, context); XWikiAttachment attachment = doc.getAttachment(filename); - byte[] data = new byte[0]; - data = attachment.getContent(context); FileOutputStream fos = new FileOutputStream(file); - fos.write(data); + IOUtils.copy(attachment.getContentInputStream(context), fos); fos.close(); } return file.toURL(); @@ -69,10 +69,8 @@ XWikiDocument doc = null; doc = context.getWiki().getDocument(web + "." + name, context); XWikiAttachment attachment = doc.getAttachment(filename).getAttachmentRevision(revision, context); - byte[] data = new byte[0]; - data = attachment.getContent(context); FileOutputStream fos = new FileOutputStream(file); - fos.write(data); + IOUtils.copy(attachment.getContentInputStream(context), fos); fos.close(); } return file.toURL(); Index: src/main/java/com/xpn/xwiki/util/UnclosableInputStream.java =================================================================== --- src/main/java/com/xpn/xwiki/util/UnclosableInputStream.java (revision 0) +++ src/main/java/com/xpn/xwiki/util/UnclosableInputStream.java (revision 0) @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009 - SOFTEC sa - All rights reserved + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package com.xpn.xwiki.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Utility filter stream that prevent closing of the embedded input stream + * + * @author Denis Gervalle + * @version $Id: $ + */ +public class UnclosableInputStream extends FilterInputStream +{ + + /** + * @param in the stream to embed and avoid closing on + */ + public UnclosableInputStream(InputStream in) + { + super(in); + } + + /** + * Do not close anything + * + * @see java.io.FilterInputStream#close() + */ + @Override + public void close() throws IOException + { + // Dot close embedded stream + } +} Index: src/main/java/com/xpn/xwiki/util/UnclosableOutputStream.java =================================================================== --- src/main/java/com/xpn/xwiki/util/UnclosableOutputStream.java (revision 0) +++ src/main/java/com/xpn/xwiki/util/UnclosableOutputStream.java (revision 0) @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009 - SOFTEC sa - All rights reserved + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package com.xpn.xwiki.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Utility filter stream that prevent closing of the embedded output stream + * + * @author Denis Gervalle + * @version $Id: $ + */ +public class UnclosableOutputStream extends FilterOutputStream +{ + + /** + * @param out the stream to embed and avoid closing on + */ + public UnclosableOutputStream(OutputStream out) + { + super(out); + } + + /** + * Do not close anything + * + * @see java.io.FilterOutputStream#close() + */ + @Override + public void close() throws IOException + { + // Dot close embedded stream + } +} Index: src/main/java/com/xpn/xwiki/util/DOMXMLWriter.java =================================================================== --- src/main/java/com/xpn/xwiki/util/DOMXMLWriter.java (revision 0) +++ src/main/java/com/xpn/xwiki/util/DOMXMLWriter.java (revision 0) @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2009 - SOFTEC sa - All rights reserved + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package com.xpn.xwiki.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.util.EmptyStackException; + +import org.apache.commons.codec.binary.Base64OutputStream; +import org.apache.commons.io.IOUtils; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.dom.DOMDocument; +import org.dom4j.io.OutputFormat; + +/** + * This a minimal implementation to transform an XMLWriter into a {@link DOMDocument} builder. + *

+ * This implementation allow the use of a same function accepting an XMLWriter, to either produce output into an + * {@link OutputStream} or to create a {@link DOMDocument}. Here a sample of the way to do so: + *

+ *
+ *     public void toXML(XMLWriter wr) throws IOException
+ *     {
+ *          Element docel = new DOMElement("html");
+ *          wr.writeOpen(docel);
+ *          Element hbel = new DOMElement("head");
+ *          wr.writeOpen(hbel);
+ *          Element el = new DOMElement("title");
+ *          el.addText("My Title");
+ *          wr.write(el);
+ *          wr.writeClose(hbel);
+ *          hbel = new DOMElement("body");
+ *          wr.writeOpen(hbel);
+ *          el = new DOMElement("p");
+ *          el.addText("My Body");
+ *          wr.write(el);
+ *     }
+ * 
+ *     public void toXML(OutputStream out) throws IOException
+ *     {
+ *          XMLWriter wr = new XMLWriter(out, new OutputFormat("  ", true, "UTF-8"));
+ * 
+ *          Document doc = new DOMDocument();
+ *          wr.writeDocumentStart(doc);
+ *          toXML(wr);
+ *          wr.writeDocumentEnd(doc);
+ *     }
+ * 
+ *     public Document toXMLDocument()
+ *     {
+ *          Document doc = new DOMDocument();
+ *          DOMXMLWriter wr = new DOMXMLWriter(doc, new OutputFormat("  ", true, "UTF-8"));
+ * 
+ *          try {
+ *              toXML(wr);
+ *              return doc;
+ *          } catch (IOException e) {
+ *              throw new RuntimeException(e);
+ *          }
+ *     }
+ * 
+ *

+ * WARNING - This implementation in INCOMPLETE and a minimal support. It should be improve as needed over time + *

+ * + * @author Denis Gervalle + * @version $Id: $ + */ +public class DOMXMLWriter extends XMLWriter +{ + /** + * The {@link Document} currently built by this writer + */ + private Document doc; + + /** + * The {@link OutputFormat} providing the encoding requested + */ + private OutputFormat format; + + /** + * Create a new {@link DOMXMLWriter} that will build into the provided document using the default + * encoding. + * + * @param doc {@link Document} that will be build by this writer + */ + public DOMXMLWriter(Document doc) + { + this.format = DEFAULT_FORMAT; + this.doc = doc; + } + + /** + * Create a new {@link DOMXMLWriter} that will build into the provided document using the encoding + * provided in the {@link OutputFormat}. + * + * @param doc {@link Document} that will be build by this writer + * @param format {@link OutputFormat} used to retrieve the proper encoding + */ + public DOMXMLWriter(Document doc, OutputFormat format) + { + this.format = format; + this.doc = doc; + } + + /** + * Add the element into the {@link Document} as a children of the element at the top of the stack of + * opened elements, putting the whole stream content as text in the content of the {@link Element}. The + * stream is converted to a String encoded in the current encoding. + * + * @see com.xpn.xwiki.util.XMLWriter#write(org.dom4j.Element, java.io.InputStream) + */ + @Override + public void write(Element element, InputStream is) throws IOException + { + element.addText(IOUtils.toString(is, format.getEncoding())); + write(element); + } + + /** + * Add the element into the {@link Document} as a children of the element at the top of the stack of of + * the {@link Element}. + * + * @see com.xpn.xwiki.util.XMLWriter#write(org.dom4j.Element, java.io.Reader) + */ + @Override + public void write(Element element, Reader rd) throws IOException + { + element.addText(IOUtils.toString(rd)); + write(element); + } + + /** + * Add the element into the {@link Document} as a children of the element at the top of the stack of + * opened elements, putting the whole stream content as Base64 text in the content of the + * {@link Element}. + * + * @see com.xpn.xwiki.util.XMLWriter#writeBase64(org.dom4j.Element, java.io.InputStream) + */ + @Override + public void writeBase64(Element element, InputStream is) throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Base64OutputStream out = new Base64OutputStream(baos, true, 0, null); + IOUtils.copy(is, out); + out.close(); + element.addText(baos.toString(format.getEncoding())); + write(element); + } + + /** + * Cleanup the stack of opened elements. + * + * @see com.xpn.xwiki.util.XMLWriter#writeDocumentEnd(org.dom4j.Document) + */ + @Override + public void writeDocumentEnd(Document doc) throws IOException + { + if (!parent.isEmpty()) { + writeClose(parent.firstElement()); + } + } + + /** + * Does nothing, avoid default action + * + * @see com.xpn.xwiki.util.XMLWriter#writeDocumentStart(org.dom4j.Document) + */ + @Override + public void writeDocumentStart(Document doc) throws IOException + { + } + + /** + * Add the element into the {@link Document} as a children of the element at the top of the stack of + * opened elements. + * + * @see org.dom4j.io.XMLWriter#write(org.dom4j.Element) + */ + @Override + public void write(Element element) throws IOException + { + if (parent.isEmpty()) { + doc.setRootElement(element); + } else { + parent.add(element); + } + } + + /** + * Cleanup the stack of opened elements up to the given element. + * + * @see org.dom4j.io.XMLWriter#writeClose(org.dom4j.Element) + */ + @Override + public void writeClose(Element element) throws IOException + { + try { + while (parent.peek() != element) { + parent.pop(); + } + parent.pop(); + } catch (EmptyStackException e) { + throw new IOException("FATAL: Closing a element that have never been opened", e); + } + } + + /** + * Add the element into the {@link Document} as a children of the element at the top of the stack of + * opened elements. Add this element at the top of the stack of opened elements. + * + * @see org.dom4j.io.XMLWriter#writeOpen(org.dom4j.Element) + */ + @Override + public void writeOpen(Element element) throws IOException + { + if (parent.isEmpty()) { + doc.setRootElement(element); + } else { + parent.peek().add(element); + } + + parent.push(element); + } +} Index: src/main/java/com/xpn/xwiki/util/XMLWriter.java =================================================================== --- src/main/java/com/xpn/xwiki/util/XMLWriter.java (revision 0) +++ src/main/java/com/xpn/xwiki/util/XMLWriter.java (revision 0) @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2009 - SOFTEC sa - All rights reserved + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package com.xpn.xwiki.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.Stack; + +import org.apache.commons.codec.binary.Base64OutputStream; +import org.apache.commons.io.IOUtils; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; + +/** + * Extension to {@link org.dom4j.io.XMLWriter} to allow filling some element content with an input stream, + * minimizing the memory footprint of the operation. + *

+ * This extension is not intended to be used to format a DOM4J tree to a stream, but to immediately write out the tags + * produced without building the document tree in memory. It is not compatible with the SAX part of the original + * {@link org.dom4j.io.XMLWriter} which is itself incompatible with its own DOM part. + *

+ *

+ * An improvement to the writeOpen/writeClose functions ensure better handling of independent opening and closing of + * tags by maintaining a state stack of opened tags. New writeDocumentStart/End function also ensure proper starting and + * ending of the document it self. + *

+ * + * @author Denis Gervalle + * @version $Id: $ + */ +public class XMLWriter extends org.dom4j.io.XMLWriter +{ + /** + * {@link Stack} of currently opened {@link Element}, the first + * {@link Element} is the document root element, and the top of the stack is the last opened + * {@link Element}. + */ + protected Stack parent = new Stack(); + + /** + * Current {@link OutputStream} of this writer + */ + private OutputStream out; + + /** + * Default constructor used by {@link DOMXMLWriter}. + * + * @see DOMXMLWriter + */ + protected XMLWriter() + { + } + + /** + * Create a new XMLWriter writing to a provided OutputStream in a given format. Note that other constructor of the + * original DOM4J XMLWriter are unsupported since a OutputStream is the only way we can support the extensions + * provided here. + *

+ * Note that the writer is buffered and only a call to flush() or writeDocuemntEnd() will ensure the output has been + * fully written to the {@link OutputStream}. + *

+ * + * @param out an {@link OutputStream} where to output the XML produced. + * @param format an {@link OutputFormat} defining the encoding that should be used and esthetics of the + * produced XML. + * @throws UnsupportedEncodingException the requested encoding is unsupported. + */ + public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException + { + super(out, format); + this.out = out; + } + + /** + * Write the {@link Document} declaration, and its {@link DocumentType} if available to + * the output stream. + * + * @param doc {@link Document} to be started, may specify a {@link DocumentType}. + * @throws IOException a problem occurs during writing + */ + public void writeDocumentStart(Document doc) throws IOException + { + writeDeclaration(); + + if (doc.getDocType() != null) { + indent(); + writeDocType(doc.getDocType()); + } + } + + /** + * Close all remaining opened {@link Element} including the root element to terminate the current + * document. Also flush the writer to ensure the whole document has been written to the + * {@link OutputStream}. + * + * @param doc {@link Document} to be end, actually unused. + * @throws IOException a problem occurs during writing. + */ + public void writeDocumentEnd(Document doc) throws IOException + { + if (!parent.isEmpty()) { + writeClose(parent.firstElement()); + } + writePrintln(); + flush(); + } + + /** + * Writes the {@link Element}, including its {@link + * Attribute}s, using the {@link Reader} + * for its content. + *

+ * Note that proper decoding/encoding will occurs during this operation, converting the encoding of the Reader into + * the encoding of the Writer. + *

+ * + * @param element {@link Element} to output. + * @param rd {@link Reader} that will be fully read and transfered into the element content. + * @throws IOException a problem occurs during reading or writing. + */ + public void write(Element element, Reader rd) throws IOException + { + writeOpen(element); + IOUtils.copy(rd, writer); + writeClose(element); + } + + /** + * Writes the {@link Element}, including its {@link + * Attribute}s, using the + * {@link InputStream} for its content. + *

+ * Note that no decoding/encoding of the InputStream will be ensured during this operation. The byte content is + * transfered untouched. + *

+ * + * @param element {@link Element} to output. + * @param is {@link InputStream} that will be fully read and transfered into the element content. + * @throws IOException a problem occurs during reading or writing. + */ + public void write(Element element, InputStream is) throws IOException + { + writeOpen(element); + flush(); + IOUtils.copy(is, out); + writeClose(element); + } + + /** + * Writes the {@link Element}, including its {@link + * Attribute}s, using the + * {@link InputStream} encoded in Base64 for its content. + * + * @param element {@link Element} to output. + * @param is {@link InputStream} that will be fully read and encoded in Base64 into the element + * content. + * @throws IOException a problem occurs during reading or writing. + */ + public void writeBase64(Element element, InputStream is) throws IOException + { + writeOpen(element); + flush(); + Base64OutputStream base64 = new Base64OutputStream(new UnclosableOutputStream(out), true, 0, null); + IOUtils.copy(is, base64); + base64.close(); + writeClose(element); + } + + /** + * Writes the opening tag of an {@link Element}, including its {@link Attribute}s but without its content. + *

+ * Compared to the DOM4J implementation, this function keeps track of opened elements. + *

+ * + * @param elemen {@link Element} to output. + * @throws IOException a problem occurs during writing. + * @see org.dom4j.io.XMLWriter#writeOpen(org.dom4j.Element) + */ + @Override + public void writeOpen(Element element) throws IOException + { + super.writeOpen(element); + parent.push(element); + } + + /** + * Writes the closing tag of an {@link Element}. + *

+ * Compared to the DOM4J implementation, this function ensure closing of all opened element including the one that + * is requested to be closed. + *

+ * + * @param elemen {@link Element} to output. + * @throws IOException a problem occurs during writing. + * @see org.dom4j.io.XMLWriter#writeClose(org.dom4j.Element) + */ + @Override + public void writeClose(Element element) throws IOException + { + while (parent.peek() != element) { + super.writeClose(parent.pop()); + } + super.writeClose(parent.pop()); + } +} Index: pom.xml =================================================================== --- pom.xml (revision 23606) +++ pom.xml (working copy) @@ -267,6 +267,7 @@ commons-codec commons-codec + 1.4 commons-beanutils