Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.doc; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.dom.DOMDocument; import org.dom4j.dom.DOMElement; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.internal.xml.DOMXMLWriter; import com.xpn.xwiki.internal.xml.XMLWriter; public class XWikiAttachment implements Cloneable { private static final Logger LOGGER = LoggerFactory.getLogger(XWikiAttachment.class); private XWikiDocument doc; private int filesize; private String filename; private String author; private Version version; private String comment; private Date date; private XWikiAttachmentContent attachment_content; private XWikiAttachmentArchive attachment_archive; private boolean isMetaDataDirty = false; public XWikiAttachment(XWikiDocument doc, String filename) { this(); setDoc(doc); setFilename(filename); } public XWikiAttachment() { this.filesize = 0; this.filename = ""; this.author = ""; this.comment = ""; this.date = new Date(); } public long getId() { if (this.doc == null) { return this.filename.hashCode(); } else { return (this.doc.getFullName() + "/" + this.filename).hashCode(); } } public void setDocId(long id) { } public long getDocId() { return this.doc.getId(); } public void setId(long id) { } /** * {@inheritDoc} * * @see java.lang.Object#clone() */ @Override public Object clone() { XWikiAttachment attachment = null; try { attachment = getClass().newInstance(); } catch (Exception e) { // This should not happen LOGGER.error("exception while attach.clone", e); } attachment.setAuthor(getAuthor()); attachment.setComment(getComment()); attachment.setDate(getDate()); attachment.setDoc(getDoc()); attachment.setFilename(getFilename()); attachment.setFilesize(getFilesize()); attachment.setRCSVersion(getRCSVersion()); if (getAttachment_content() != null) { attachment.setAttachment_content((XWikiAttachmentContent) getAttachment_content().clone()); attachment.getAttachment_content().setAttachment(attachment); } if (getAttachment_archive() != null) { attachment.setAttachment_archive((XWikiAttachmentArchive) getAttachment_archive().clone()); attachment.getAttachment_archive().setAttachment(attachment); } return attachment; } /** * @return the cached filesize in byte of the attachment, stored as metadata */ public int getFilesize() { return this.filesize; } /** * Set cached filesize of the attachment that will be stored as metadata * * @param filesize in byte */ public void setFilesize(int filesize) { if (filesize != this.filesize) { setMetaDataDirty(true); } this.filesize = filesize; } /** * @param context current XWikiContext * @return the real filesize in byte of the attachment. We cannot trust the metadata that may be * publicly changed. * @throws XWikiException * @since 2.3M2 */ 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; } public void setFilename(String filename) { filename = filename.replaceAll("\\+", " "); if (!filename.equals(this.filename)) { setMetaDataDirty(true); this.filename = filename; } } public String getAuthor() { return this.author; } public void setAuthor(String author) { if (!author.equals(this.author)) { setMetaDataDirty(true); } this.author = author; } public String getVersion() { return getRCSVersion().toString(); } public void setVersion(String version) { this.version = new Version(version); } public String getNextVersion() { if (this.version == null) { return "1.1"; } else { return ((Version) this.version.clone()).next().toString(); } } public Version getRCSVersion() { if (this.version == null) { return new Version("1.1"); } return (Version) this.version.clone(); } public void setRCSVersion(Version version) { this.version = version; } public String getComment() { return this.comment != null ? this.comment : ""; } public void setComment(String comment) { if (!getComment().equals(comment)) { setMetaDataDirty(true); } this.comment = comment; } public XWikiDocument getDoc() { return this.doc; } public void setDoc(XWikiDocument doc) { this.doc = doc; } public Date getDate() { return this.date; } public void setDate(Date date) { // Make sure we drop milliseconds for consistency with the database if (date != null) { date.setTime((date.getTime() / 1000) * 1000); } this.date = date; } public boolean isContentDirty() { if (this.attachment_content == null) { return false; } else { return this.attachment_content.isContentDirty(); } } public void incrementVersion() { if (this.version == null) { this.version = new Version("1.1"); } else { this.version = this.version.next(); } } public boolean isMetaDataDirty() { return this.isMetaDataDirty; } public void setMetaDataDirty(boolean metaDataDirty) { this.isMetaDataDirty = metaDataDirty; } /** * Retrieve an attachment as an XML string. You should prefer * {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, com.xpn.xwiki.XWikiContext) * to avoid memory loads when appropriate. * * @param bWithAttachmentContent if true, binary content of the attachment is included (base64 encoded) * @param bWithVersions if true, all archived versions are also included * @param context current XWikiContext * @return a string containing an XML representation of the attachment * @throws XWikiException when an error occurs during wiki operations */ public String toStringXML(boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) throws XWikiException { // This is very bad. baos holds the entire attachment on the heap, then it makes a copy when toByteArray // is called, then String forces us to make a copy when we construct a new String. // Unfortunately this can't be fixed because jrcs demands the content as a String. ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { 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); byte[] array = baos.toByteArray(); baos = null; return new String(array, context.getWiki().getEncoding()); } catch (IOException e) { e.printStackTrace(); return ""; } } /** * Retrieve XML representation of attachment's metadata into an {@link Element}. * * @return a {@link Element} containing an XML representation of the attachment without content * @throws XWikiException when an error occurs during wiki operations */ public Element toXML(XWikiContext context) throws XWikiException { return toXML(false, false, context); } /** * Write an XML representation of the attachment into an {@link com.xpn.xwiki.internal.xml.XMLWriter} * * @param wr the XMLWriter to write to * @param bWithAttachmentContent if true, binary content of the attachment is included (base64 encoded) * @param bWithVersions if true, all archive version is also included * @param context current XWikiContext * @throws IOException when an error occurs during streaming operation * @throws XWikiException when an error occurs during xwiki operation * @since 2.3M2 */ public void toXML(XMLWriter wr, boolean bWithAttachmentContent, boolean bWithVersions, XWikiContext context) throws IOException, XWikiException { // IMPORTANT: we don't use SAX apis here because the specified XMLWriter could be a DOMXMLWriter for retro // compatibility reasons Element docel = new DOMElement("attachment"); wr.writeOpen(docel); Element el = new DOMElement("filename"); el.addText(getFilename()); wr.write(el); el = new DOMElement("filesize"); el.addText("" + getFilesize()); wr.write(el); el = new DOMElement("author"); el.addText(getAuthor()); wr.write(el); long d = getDate().getTime(); el = new DOMElement("date"); el.addText("" + d); wr.write(el); el = new DOMElement("version"); el.addText(getVersion()); wr.write(el); el = new DOMElement("comment"); el.addText(getComment()); wr.write(el); if (bWithAttachmentContent) { el = new DOMElement("content"); // We need to make sure content is loaded loadContent(context); XWikiAttachmentContent acontent = getAttachment_content(); if (acontent != null) { wr.writeBase64(el, getAttachment_content().getContentInputStream()); } else { el.addText(""); wr.write(el); } } if (bWithVersions) { // We need to make sure content is loaded XWikiAttachmentArchive aarchive = loadArchive(context); if (aarchive != null) { el = new DOMElement("versions"); try { el.addText(new String(aarchive.getArchive())); wr.write(el); } catch (XWikiException e) { } } } wr.writeClose(docel); } /** * Retrieve XML representation of attachment's metadata into an {@link Element}. You should prefer * {@link #toXML(com.xpn.xwiki.internal.xml.XMLWriter, boolean, boolean, com.xpn.xwiki.XWikiContext) * to avoid memory loads when appropriate. * * @param bWithAttachmentContent if true, binary content of the attachment is included (base64 encoded) * @param bWithVersions if true, all archived versions are also included * @param context current XWikiContext * @return an {@link Element} containing an XML representation of the attachment * @throws XWikiException when an error occurs during wiki operations * @since 2.3M2 */ 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(); Document domdoc = null; try { StringReader in = new StringReader(data); domdoc = reader.read(in); } catch (DocumentException e) { throw new XWikiException(XWikiException.MODULE_XWIKI_DOC, XWikiException.ERROR_DOC_XML_PARSING, "Error parsing xml", e, null); } Element docel = domdoc.getRootElement(); fromXML(docel); } public void fromXML(Element docel) throws XWikiException { setFilename(docel.element("filename").getText()); setFilesize(Integer.parseInt(docel.element("filesize").getText())); setAuthor(docel.element("author").getText()); setVersion(docel.element("version").getText()); setComment(docel.element("comment").getText()); String sdate = docel.element("date").getText(); Date date = new Date(Long.parseLong(sdate)); setDate(date); Element contentel = docel.element("content"); if (contentel != null) { String base64content = contentel.getText(); byte[] content = Base64.decodeBase64(base64content.getBytes()); setContent(content); } Element archiveel = docel.element("versions"); if (archiveel != null) { String archive = archiveel.getText(); setArchive(archive); } } public XWikiAttachmentContent getAttachment_content() { return this.attachment_content; } public void setAttachment_content(XWikiAttachmentContent attachment_content) { this.attachment_content = attachment_content; } public XWikiAttachmentArchive getAttachment_archive() { return this.attachment_archive; } public void setAttachment_archive(XWikiAttachmentArchive attachment_archive) { this.attachment_archive = attachment_archive; } /** * Retrive the content of this attachment as a byte array. * * @param context current XWikiContext * @return a byte array containing the binary data content of the attachment * @throws XWikiException when an error occurs during wiki operation * @deprecated use {@link #getContentInputStream(XWikiContext)} instead */ @Deprecated public byte[] getContent(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { this.doc.loadAttachmentContent(this, context); } return this.attachment_content.getContent(); } /** * Retrive the content of this attachment as an input stream. * * @param context current XWikiContext * @return an InputStream to consume for receiving the content of this attachment * @throws XWikiException when an error occurs during wiki operation * @since 2.3M2 */ public InputStream getContentInputStream(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { this.doc.loadAttachmentContent(this, context); } return this.attachment_content.getContentInputStream(); } /** * @deprecated since 2.6M1 please do not use this, it is bound to a jrcs based implementation. */ @Deprecated public Archive getArchive() { if (this.attachment_archive == null) { return null; } else { return this.attachment_archive.getRCSArchive(); } } /** * @deprecated since 2.6M1 please do not use this, it is bound to a jrcs based implementation. */ @Deprecated public void setArchive(Archive archive) { if (this.attachment_archive == null) { this.attachment_archive = new XWikiAttachmentArchive(); this.attachment_archive.setAttachment(this); } this.attachment_archive.setRCSArchive(archive); } public void setArchive(String data) throws XWikiException { if (this.attachment_archive == null) { this.attachment_archive = new XWikiAttachmentArchive(); this.attachment_archive.setAttachment(this); } this.attachment_archive.setArchive(data.getBytes()); } public synchronized Version[] getVersions() { try { return getAttachment_archive().getVersions(); } catch (Exception ex) { LOGGER.warn(String.format("Cannot retrieve versions of attachment [%s@%s]: %s", getFilename(), getDoc().getFullName(), ex.getMessage())); return new Version[] { new Version(this.getVersion()) }; } } // We assume versions go from 1.1 to the current one // This allows not to read the full archive file public synchronized List<Version> getVersionList() throws XWikiException { List<Version> list = new ArrayList<Version>(); Version v = new Version("1.1"); while (true) { list.add(v); if (v.toString().equals(this.version.toString())) { break; } v.next(); } return list; } /** * Set the content of an attachment from a byte array. * * @param data a byte array with the binary content of the attachment * @deprecated use {@link #setContent(java.io.InputStream, int)} instead */ @Deprecated public void setContent(byte[] data) { if (this.attachment_content == null) { this.attachment_content = new XWikiAttachmentContent(); this.attachment_content.setAttachment(this); } this.attachment_content.setContent(data); } /** * Set the content of an attachment from an InputStream. * * @param is the input stream that will be read * @param length the length in byte to read * @throws IOException when an error occurs during streaming operation * @since 2.3M2 */ 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); } /** * Set the content of the attachment from an InputStream. * * @param is the input stream that will be read * @throws IOException when an error occurs during streaming operation * @since 2.6M1 */ public void setContent(InputStream is) throws IOException { if (this.attachment_content == null) { this.attachment_content = new XWikiAttachmentContent(this); } this.attachment_content.setContent(is); } public void loadContent(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { try { context.getWiki().getAttachmentStore().loadAttachmentContent(this, context, true); } catch (Exception ex) { LOGGER.warn(String.format("Failed to load content for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", getFilename(), (this.doc != null) ? this.doc.getFullName() : "<unknown>", ex.getMessage())); } } } public XWikiAttachmentArchive loadArchive(XWikiContext context) throws XWikiException { if (this.attachment_archive == null) { try { this.attachment_archive = context.getWiki().getAttachmentVersioningStore().loadArchive(this, context, true); } catch (Exception ex) { LOGGER.warn(String.format("Failed to load archive for attachment [%s@%s]. " + "This attachment is broken, please consider re-uploading it. " + "Internal error: %s", getFilename(), (this.doc != null) ? this.doc.getFullName() : "<unknown>", ex.getMessage())); } } return this.attachment_archive; } public void updateContentArchive(XWikiContext context) throws XWikiException { if (this.attachment_content == null) { return; } // XWikiAttachmentArchive no longer uses the byte array passed as it's first parameter making it redundant. loadArchive(context).updateArchive(null, context); } public String getMimeType(XWikiContext context) { // Choose the right content type String mimetype = context.getEngineContext().getMimeType(getFilename().toLowerCase()); if (mimetype != null) { return mimetype; } else { return "application/octet-stream"; } } public boolean isImage(XWikiContext context) { String contenttype = getMimeType(context); if (contenttype.startsWith("image/")) { return true; } else { return false; } } public XWikiAttachment getAttachmentRevision(String rev, XWikiContext context) throws XWikiException { if (StringUtils.equals(rev, this.getVersion())) { return this; } return loadArchive(context).getRevision(this, rev, context); } }