1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.doc

File XWikiAttachmentContent.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

18
62
22
1
339
175
38
0.61
2.82
22
1.73

Classes

Class Line # Actions
XWikiAttachmentContent 45 62 0% 38 11
0.8921568489.2%
 

Contributing tests

This file is covered by 79 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.doc;
21   
22    import java.io.ByteArrayInputStream;
23    import java.io.File;
24    import java.io.IOException;
25    import java.io.InputStream;
26    import java.io.OutputStream;
27   
28    import org.apache.commons.fileupload.FileItem;
29    import org.apache.commons.fileupload.disk.DiskFileItem;
30    import org.apache.commons.io.IOUtils;
31    import org.apache.commons.io.input.AutoCloseInputStream;
32    import org.apache.commons.io.input.BoundedInputStream;
33    import org.apache.commons.io.output.ProxyOutputStream;
34    import org.xwiki.environment.Environment;
35    import org.xwiki.store.UnexpectedException;
36   
37    import com.xpn.xwiki.web.Utils;
38   
39    /**
40    * The content of an attachment. Objects of this class hold the actual content which will be downloaded when a user
41    * downloads an attachment.
42    *
43    * @version $Id: 290ebb161a596ad12a7ccd80caccaa91eb746cc6 $
44    */
 
45    public class XWikiAttachmentContent implements Cloneable
46    {
47    /** An empty byte array returned for empty attachment contents. */
48    private static final byte[] NULLFILE = new byte[0];
49   
50    /** The XWikiAttachment (attachment metadata) which this attachment content is associated with. */
51    private XWikiAttachment attachment;
52   
53    /** True if the content is out of sync with the content stored in the database and thus needs to be saved. */
54    private boolean isContentDirty;
55   
56    /** Storage which holds the actual content. */
57    private FileItem file;
58   
59    /** The owner document. */
60    private XWikiDocument ownerDocument;
61   
62    /**
63    * Constructor which clones an existing XWikiAttachmentContent. Used by {@link #clone()}.
64    *
65    * @param original the XWikiAttachmentContent to clone.
66    * @since 2.6M1
67    */
 
68  38542 toggle public XWikiAttachmentContent(XWikiAttachmentContent original)
69    {
70  38542 this.file = original.file;
71  38542 this.attachment = original.attachment;
72  38542 this.isContentDirty = original.isContentDirty;
73  38542 this.ownerDocument = original.ownerDocument;
74    }
75   
76    /**
77    * Constructor with associated attachment specified.
78    *
79    * @param attachment the attachment which this is the content for.
80    */
 
81  877 toggle public XWikiAttachmentContent(XWikiAttachment attachment)
82    {
83  877 this.setAttachment(attachment);
84    }
85   
86    /**
87    * The default Constructor. For creating content which will be associated with an attachment later.
88    */
 
89  22 toggle public XWikiAttachmentContent()
90    {
91    }
92   
93    // Used in FilesystemAttachmentContent.clone()
94    // since 5.2M1
 
95  10 toggle protected XWikiAttachmentContent(XWikiAttachment attachment, FileItem f)
96    {
97  10 this.setAttachment(attachment);
98  10 this.file = f;
99    }
100   
101    /**
102    * @return the underlying storage file.
103    * @since 5.2M1
104    */
 
105  4 toggle protected FileItem getFileItem()
106    {
107  4 return this.file;
108    }
109   
110    /**
111    * @return a new FileItem for temporarily storing attachment content.
112    * @since 4.2M3
113    */
 
114  874 toggle private static FileItem getNewFileItem()
115    {
116  874 final Environment env = Utils.getComponent(Environment.class);
117  874 final File dir = new File(env.getTemporaryDirectory(), "attachment-cache");
118  874 try {
119  874 if (!dir.mkdirs() && !dir.exists()) {
120  0 throw new UnexpectedException("Failed to create directory for attachments " + dir);
121    }
122  874 final DiskFileItem dfi = new DiskFileItem(null, null, false, null, 10000, dir);
123    // This causes the temp file to be created.
124  874 dfi.getOutputStream().close();
125    // Make sure this file is marked for deletion on VM exit because DiskFileItem does not.
126  874 dfi.getStoreLocation().deleteOnExit();
127  874 return dfi;
128    } catch (IOException e) {
129  0 throw new UnexpectedException("Failed to create new attachment temporary file.", e);
130    }
131    }
132   
133    /**
134    * This is used so that Hibernate will associate this content with the right attachment (metadata).
135    *
136    * @return the id of the attachment (metadata) which this content is associated with.
137    */
 
138  1616 toggle public long getId()
139    {
140  1616 return this.attachment.getId();
141    }
142   
143    /**
144    * This function does nothing and exists only for Hibernate to be able to load a value which is not used.
145    *
146    * @param id is ignored.
147    */
 
148  697 toggle public void setId(long id)
149    {
150    // Do nothing here.
151    // The id is taken from the attachment which is set in XWikiHibernateAttachmentStore#loadAttachmentContent.
152    }
153   
 
154  38541 toggle @Override
155    public Object clone()
156    {
157  38541 return new XWikiAttachmentContent(this);
158    }
159   
160    /**
161    * @return a byte array containing the binary content of the attachment.
162    * @deprecated use {@link #getContentInputStream()} instead
163    */
 
164  914 toggle @Deprecated
165    public byte[] getContent()
166    {
167  914 if (this.file == null) {
168  44 return NULLFILE;
169    }
170  870 return this.file.get();
171    }
172   
173    /**
174    * Set the content from a byte array.
175    *
176    * @param content a byte array containing the binary data of the attachment
177    * @deprecated use {@link #setContent(java.io.InputStream, int)} instead
178    */
 
179  297 toggle @Deprecated
180    public void setContent(byte[] content)
181    {
182  297 try {
183  297 byte[] internalContent = new byte[0];
184  297 if (content != null) {
185  297 internalContent = content;
186    }
187   
188  297 this.setContent(new ByteArrayInputStream(internalContent));
189    } catch (IOException e) {
190  0 throw new RuntimeException("Failed to copy data to storage.", e);
191    }
192    }
193   
194    /**
195    * @return which attachment (Metadata) this content belongs to.
196    */
 
197  3 toggle public XWikiAttachment getAttachment()
198    {
199  3 return this.attachment;
200    }
201   
202    /**
203    * @param attachment which attachment (metadata) this content is to be associated with.
204    */
 
205  39453 toggle public void setAttachment(XWikiAttachment attachment)
206    {
207  39453 this.attachment = attachment;
208    }
209   
210    /**
211    * Is the content "dirty" meaning out of sync with the database.
212    *
213    * @return true if the content is out of sync with the database and in need of saving.
214    */
 
215  1327 toggle public boolean isContentDirty()
216    {
217  1327 return this.isContentDirty;
218    }
219   
220    /**
221    * Set the content as "dirty" meaning out of sync with the database.
222    *
223    * @param contentDirty if true then the content is regarded as out of sync with the database and in need of saving,
224    * otherwise it's considered saved.
225    */
 
226  1580 toggle public void setContentDirty(boolean contentDirty)
227    {
228  1580 this.isContentDirty = contentDirty;
229  1581 if (contentDirty && this.ownerDocument != null) {
230  4 this.ownerDocument.setMetaDataDirty(contentDirty);
231    }
232    }
233   
234    /**
235    * @return an InputStream to read the binary content of this attachment.
236    * @since 2.3M2
237    */
 
238  678 toggle public InputStream getContentInputStream()
239    {
240  678 if (this.file == null) {
241  0 return new ByteArrayInputStream(NULLFILE);
242    }
243  678 try {
244  678 return new AutoCloseInputStream(this.file.getInputStream());
245    } catch (IOException e) {
246  0 throw new RuntimeException("Failed to get InputStream", e);
247    }
248    }
249   
250    /**
251    * Set the content of the attachment by writing to a provided OutputStream. Content is *not* appended, this method
252    * clears the content and creates new content. If you want to append content, you can call
253    * {@link #getContentInputStream()} and copy the content of that into the provided OutputStream. Before closing this
254    * OutputStream the content will remain the old content prior to the change.
255    *
256    * @return an OutputStream into which the caller can set the content of the attachments.
257    * @since 4.2M3
258    */
 
259  874 toggle public OutputStream getContentOutputStream()
260    {
261  874 final FileItem fi = getNewFileItem();
262  874 final XWikiAttachmentContent xac = this;
263  874 final OutputStream fios;
264  874 try {
265  874 fios = fi.getOutputStream();
266    } catch (IOException e) {
267    // DiskFileItem does not do anything which could cause an exception to be thrown.
268    // so unless it is modified, this should not happen.
269  0 throw new RuntimeException("Exception getting attachment OutputStream.", e);
270    }
271  874 return (new ProxyOutputStream(fios)
272    {
 
273  874 toggle @Override
274    public void close() throws IOException
275    {
276  874 super.close();
277  874 xac.file = fi;
278  873 xac.setContentDirty(true);
279  874 if (xac.attachment != null) {
280  874 xac.attachment.setFilesize(xac.getSize());
281    }
282    }
283    });
284    }
285   
286    /**
287    * Set the content of the attachment from a portion of an InputStream.
288    *
289    * @param is the input stream that will be read
290    * @param len the number of bytes to read from the beginning of the stream
291    * @throws IOException when an error occurs during streaming operation
292    * @since 2.3M2
293    */
 
294  1 toggle public void setContent(InputStream is, int len) throws IOException
295    {
296  1 this.setContent(new BoundedInputStream(is, len));
297    }
298   
299    /**
300    * Set the content of the attachment from an InputStream.
301    *
302    * @param is the input stream that will be read
303    * @throws IOException when an error occurs during streaming operation
304    * @since 2.6M1
305    */
 
306  871 toggle public void setContent(InputStream is) throws IOException
307    {
308  871 OutputStream fios = getContentOutputStream();
309  871 try {
310  871 IOUtils.copy(is, fios);
311    } finally {
312  871 fios.close();
313    }
314    }
315   
316    /**
317    * @return the true size of the content of the attachment.
318    * @since 2.3M2
319    */
 
320  1184 toggle public int getSize()
321    {
322  1183 return (this.file != null) ? (int) this.file.getSize() : 0;
323    }
324   
325    /**
326    * Set the owner document in order to propagate the content dirty flag.
327    *
328    * @param ownerDocument the owner document.
329    */
 
330  116476 toggle public void setOwnerDocument(XWikiDocument ownerDocument)
331    {
332  116478 if (this.ownerDocument != ownerDocument) {
333  116446 this.ownerDocument = ownerDocument;
334  116446 if (this.isContentDirty && ownerDocument != null) {
335  1508 ownerDocument.setMetaDataDirty(true);
336    }
337    }
338    }
339    }