1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.doc

File XWikiAttachmentContent.java

 

Coverage histogram

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

Code metrics

20
63
23
1
351
180
40
0.63
2.74
23
1.74

Classes

Class Line # Actions
XWikiAttachmentContent 45 63 0% 40 16
0.849056684.9%
 

Contributing tests

This file is covered by 80 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: dc5662374cc6bc7f3e60c85d3c34247552832fc7 $
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    // TODO: use TemporaryDeferredFileRepository instead (see DeletedAttachment)
58    private FileItem file;
59   
60    /** The owner document. */
61    private XWikiDocument ownerDocument;
62   
63    /**
64    * Constructor which clones an existing XWikiAttachmentContent. Used by {@link #clone()}.
65    *
66    * @param original the XWikiAttachmentContent to clone.
67    * @since 2.6M1
68    */
 
69  109272 toggle public XWikiAttachmentContent(XWikiAttachmentContent original)
70    {
71  109274 this.file = original.file;
72  109273 this.attachment = original.attachment;
73  109271 this.isContentDirty = original.isContentDirty;
74  109270 this.ownerDocument = original.ownerDocument;
75    }
76   
77    /**
78    * Constructor with associated attachment specified.
79    *
80    * @param attachment the attachment which this is the content for.
81    */
 
82  1533 toggle public XWikiAttachmentContent(XWikiAttachment attachment)
83    {
84  1533 this.setAttachment(attachment);
85    }
86   
87    /**
88    * The default Constructor. For creating content which will be associated with an attachment later.
89    */
 
90  22 toggle public XWikiAttachmentContent()
91    {
92    }
93   
94    // Used in FilesystemAttachmentContent.clone()
95    // since 5.2M1
 
96  1285 toggle protected XWikiAttachmentContent(XWikiAttachment attachment, FileItem f)
97    {
98  1286 this.setAttachment(attachment);
99  1286 this.file = f;
100    }
101   
102    /**
103    * @return the underlying storage file.
104    * @since 5.2M1
105    */
 
106  111661 toggle protected FileItem getFileItem()
107    {
108  111670 return this.file;
109    }
110   
111    /**
112    * @return a new FileItem for temporarily storing attachment content.
113    * @since 4.2M3
114    */
 
115  1496 toggle private static FileItem getNewFileItem()
116    {
117  1496 final Environment env = Utils.getComponent(Environment.class);
118  1496 final File dir = new File(env.getTemporaryDirectory(), "attachment-cache");
119  1496 try {
120  1496 if (!dir.mkdirs() && !dir.exists()) {
121  0 throw new UnexpectedException("Failed to create directory for attachments " + dir);
122    }
123  1496 final DiskFileItem dfi = new DiskFileItem(null, null, false, null, 10000, dir);
124    // This causes the temp file to be created.
125  1496 dfi.getOutputStream().close();
126  1496 return dfi;
127    } catch (IOException e) {
128  0 throw new UnexpectedException("Failed to create new attachment temporary file.", e);
129    }
130    }
131   
132    /**
133    * This is used so that Hibernate will associate this content with the right attachment (metadata).
134    *
135    * @return the id of the attachment (metadata) which this content is associated with.
136    */
 
137  451 toggle public long getId()
138    {
139  451 return this.attachment.getId();
140    }
141   
142    /**
143    * This function does nothing and exists only for Hibernate to be able to load a value which is not used.
144    *
145    * @param id is ignored.
146    */
 
147  323 toggle public void setId(long id)
148    {
149    // Do nothing here.
150    // The id is taken from the attachment which is set in XWikiHibernateAttachmentStore#loadAttachmentContent.
151    }
152   
 
153  2758 toggle @Override
154    public Object clone()
155    {
156  2758 return new XWikiAttachmentContent(this);
157    }
158   
159    /**
160    * @return a byte array containing the binary content of the attachment.
161    * @deprecated use {@link #getContentInputStream()} instead
162    */
 
163  20 toggle @Deprecated
164    public byte[] getContent()
165    {
166  20 if (this.file == null) {
167  18 return NULLFILE;
168    }
169  2 return this.file.get();
170    }
171   
172    /**
173    * Set the content from a byte array.
174    *
175    * @param content a byte array containing the binary data of the attachment
176    * @deprecated use {@link #setContent(java.io.InputStream, int)} instead
177    */
 
178  338 toggle @Deprecated
179    public void setContent(byte[] content)
180    {
181  338 try {
182  338 byte[] internalContent = new byte[0];
183  338 if (content != null) {
184  338 internalContent = content;
185    }
186   
187  338 setContent(new ByteArrayInputStream(internalContent));
188    } catch (IOException e) {
189  0 throw new RuntimeException("Failed to copy data to storage.", e);
190    }
191    }
192   
193    /**
194    * @return which attachment (Metadata) this content belongs to.
195    */
 
196  0 toggle public XWikiAttachment getAttachment()
197    {
198  0 return this.attachment;
199    }
200   
201    /**
202    * @param attachment which attachment (metadata) this content is to be associated with.
203    */
 
204  112111 toggle public void setAttachment(XWikiAttachment attachment)
205    {
206  112110 this.attachment = attachment;
207    }
208   
209    /**
210    * Is the content "dirty" meaning out of sync with the database.
211    *
212    * @return true if the content is out of sync with the database and in need of saving.
213    */
 
214  3401 toggle public boolean isContentDirty()
215    {
216  3401 return this.isContentDirty;
217    }
218   
219    /**
220    * Set the content as "dirty" meaning out of sync with the database.
221    *
222    * @param contentDirty if true then the content is regarded as out of sync with the database and in need of saving,
223    * otherwise it's considered saved.
224    */
 
225  3702 toggle public void setContentDirty(boolean contentDirty)
226    {
227  3702 this.isContentDirty = contentDirty;
228  3702 if (contentDirty && this.ownerDocument != null) {
229  3 this.ownerDocument.setMetaDataDirty(contentDirty);
230    }
231    }
232   
233    /**
234    * @return an InputStream to read the binary content of this attachment.
235    * @since 2.3M2
236    */
 
237  2488 toggle public InputStream getContentInputStream()
238    {
239  2488 if (this.file == null) {
240  0 return new ByteArrayInputStream(NULLFILE);
241    }
242  2488 try {
243  2488 return new AutoCloseInputStream(this.file.getInputStream());
244    } catch (IOException e) {
245  0 throw new RuntimeException("Failed to get InputStream", e);
246    }
247    }
248   
249    /**
250    * Set the content of the attachment by writing to a provided OutputStream. Content is *not* appended, this method
251    * clears the content and creates new content. If you want to append content, you can call
252    * {@link #getContentInputStream()} and copy the content of that into the provided OutputStream. Before closing this
253    * OutputStream the content will remain the old content prior to the change.
254    *
255    * @return an OutputStream into which the caller can set the content of the attachments.
256    * @since 4.2M3
257    */
 
258  1496 toggle public OutputStream getContentOutputStream()
259    {
260  1496 final FileItem fi = getNewFileItem();
261  1496 final XWikiAttachmentContent xac = this;
262  1496 final OutputStream fios;
263  1496 try {
264  1496 fios = fi.getOutputStream();
265    } catch (IOException e) {
266    // DiskFileItem does not do anything which could cause an exception to be thrown.
267    // so unless it is modified, this should not happen.
268  0 throw new RuntimeException("Exception getting attachment OutputStream.", e);
269    }
270  1496 return (new ProxyOutputStream(fios)
271    {
 
272  1496 toggle @Override
273    public void close() throws IOException
274    {
275  1496 super.close();
276  1496 xac.file = fi;
277  1496 xac.setContentDirty(true);
278  1496 if (xac.attachment != null) {
279  1495 xac.attachment.setLongSize(xac.getLongSize());
280    }
281    }
282    });
283    }
284   
285    /**
286    * Set the content of the attachment from a portion of an InputStream.
287    *
288    * @param is the input stream that will be read
289    * @param len the number of bytes to read from the beginning of the stream
290    * @throws IOException when an error occurs during streaming operation
291    * @since 2.3M2
292    */
 
293  1 toggle public void setContent(InputStream is, int len) throws IOException
294    {
295  1 this.setContent(new BoundedInputStream(is, len));
296    }
297   
298    /**
299    * Set the content of the attachment from an InputStream.
300    *
301    * @param is the input stream that will be read
302    * @throws IOException when an error occurs during streaming operation
303    * @since 2.6M1
304    */
 
305  1492 toggle public void setContent(InputStream is) throws IOException
306    {
307  1492 OutputStream fios = getContentOutputStream();
308  1492 try {
309  1492 IOUtils.copy(is, fios);
310    } finally {
311  1492 fios.close();
312    }
313    }
314   
315    /**
316    * @return the true size of the content of the attachment.
317    * @since 2.3M2
318    * @deprecated since 9.0RC1, use {@link #getLongSize()} instead
319    */
 
320  0 toggle @Deprecated
321    public int getSize()
322    {
323  0 long longSize = getLongSize();
324   
325  0 return longSize > (long) Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) longSize;
326    }
327   
328    /**
329    * @return the true size of the content of the attachment. -1 if the size is unknown.
330    * @since 9.0RC1
331    */
 
332  11438 toggle public long getLongSize()
333    {
334  11438 return this.file != null ? this.file.getSize() : -1;
335    }
336   
337    /**
338    * Set the owner document in order to propagate the content dirty flag.
339    *
340    * @param ownerDocument the owner document.
341    */
 
342  219696 toggle public void setOwnerDocument(XWikiDocument ownerDocument)
343    {
344  219700 if (this.ownerDocument != ownerDocument) {
345  218653 this.ownerDocument = ownerDocument;
346  218655 if (this.isContentDirty && ownerDocument != null) {
347  1550 ownerDocument.setMetaDataDirty(true);
348    }
349    }
350    }
351    }