KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > content > AbstractContentWriter


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.content;
18
19 import java.io.BufferedOutputStream JavaDoc;
20 import java.io.ByteArrayInputStream JavaDoc;
21 import java.io.File JavaDoc;
22 import java.io.FileInputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.io.OutputStream JavaDoc;
26 import java.nio.channels.Channels JavaDoc;
27 import java.nio.channels.FileChannel JavaDoc;
28 import java.nio.channels.ReadableByteChannel JavaDoc;
29 import java.nio.channels.WritableByteChannel JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.List JavaDoc;
32
33 import org.alfresco.error.AlfrescoRuntimeException;
34 import org.alfresco.repo.content.filestore.FileContentWriter;
35 import org.alfresco.service.cmr.repository.ContentAccessor;
36 import org.alfresco.service.cmr.repository.ContentIOException;
37 import org.alfresco.service.cmr.repository.ContentReader;
38 import org.alfresco.service.cmr.repository.ContentStreamListener;
39 import org.alfresco.service.cmr.repository.ContentWriter;
40 import org.alfresco.util.TempFileProvider;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.springframework.aop.framework.ProxyFactory;
44 import org.springframework.util.FileCopyUtils;
45
46 /**
47  * Implements all the convenience methods of the interface. The only methods
48  * that need to be implemented, i.e. provide low-level content access are:
49  * <ul>
50  * <li>{@link #getDirectWritableChannel()} to write content to the repository</li>
51  * </ul>
52  *
53  * @author Derek Hulley
54  */

55 public abstract class AbstractContentWriter extends AbstractContentAccessor implements ContentWriter
56 {
57     private static final Log logger = LogFactory.getLog(AbstractContentWriter.class);
58     
59     private List JavaDoc<ContentStreamListener> listeners;
60     private WritableByteChannel JavaDoc channel;
61     private ContentReader existingContentReader;
62     
63     /**
64      * @param contentUrl the content URL
65      * @param existingContentReader a reader of a previous version of this content
66      */

67     protected AbstractContentWriter(String JavaDoc contentUrl, ContentReader existingContentReader)
68     {
69         super(contentUrl);
70         this.existingContentReader = existingContentReader;
71         
72         listeners = new ArrayList JavaDoc<ContentStreamListener>(2);
73     }
74
75     /**
76      * @return Returns a reader onto the previous version of this content
77      */

78     protected ContentReader getExistingContentReader()
79     {
80         return existingContentReader;
81     }
82
83     /**
84      * Adds the listener after checking that the output stream isn't already in
85      * use.
86      */

87     public synchronized void addListener(ContentStreamListener listener)
88     {
89         if (channel != null)
90         {
91             throw new RuntimeException JavaDoc("Channel is already in use");
92         }
93         listeners.add(listener);
94     }
95
96     /**
97      * A factory method for subclasses to implement that will ensure the proper
98      * implementation of the {@link ContentWriter#getReader()} method.
99      * <p>
100      * Only the instance need be constructed. The required mimetype, encoding, etc
101      * will be copied across by this class.
102      * <p>
103      *
104      * @return Returns a reader onto the location referenced by this instance.
105      * The instance must <b>always</b> be a new instance and never null.
106      * @throws ContentIOException
107      */

108     protected abstract ContentReader createReader() throws ContentIOException;
109     
110     /**
111      * Performs checks and copies required reader attributes
112      */

113     public final ContentReader getReader() throws ContentIOException
114     {
115         if (!isClosed())
116         {
117             return null;
118         }
119         ContentReader reader = createReader();
120         if (reader == null)
121         {
122             throw new AlfrescoRuntimeException("ContentReader failed to create new reader: \n" +
123                     " writer: " + this);
124         }
125         else if (reader.getContentUrl() == null || !reader.getContentUrl().equals(getContentUrl()))
126         {
127             throw new AlfrescoRuntimeException("ContentReader has different URL: \n" +
128                     " writer: " + this + "\n" +
129                     " new reader: " + reader);
130         }
131         // copy across common attributes
132
reader.setMimetype(this.getMimetype());
133         reader.setEncoding(this.getEncoding());
134         // done
135
if (logger.isDebugEnabled())
136         {
137             logger.debug("Writer spawned new reader: \n" +
138                     " writer: " + this + "\n" +
139                     " new reader: " + reader);
140         }
141         return reader;
142     }
143
144     /**
145      * An automatically created listener sets the flag
146      */

147     public synchronized final boolean isClosed()
148     {
149         if (channel != null)
150         {
151             return !channel.isOpen();
152         }
153         else
154         {
155             return false;
156         }
157     }
158
159     /** helper implementation for base class */
160     protected boolean isChannelOpen()
161     {
162         if (channel != null)
163         {
164             return channel.isOpen();
165         }
166         else
167         {
168             return false;
169         }
170     }
171     
172     /**
173      * Provides low-level access to write content to the repository.
174      * <p>
175      * This is the only of the content <i>writing</i> methods that needs to be implemented
176      * by derived classes. All other content access methods make use of this in their
177      * underlying implementations.
178      *
179      * @return Returns a channel with which to write content
180      * @throws ContentIOException if the channel could not be opened
181      */

182     protected abstract WritableByteChannel JavaDoc getDirectWritableChannel() throws ContentIOException;
183     
184     /**
185      * Create a channel that performs callbacks to the given listeners.
186      *
187      * @param directChannel the result of {@link #getDirectWritableChannel()}
188      * @param listeners the listeners to call
189      * @return Returns a channel that executes callbacks
190      * @throws ContentIOException
191      */

192     private WritableByteChannel JavaDoc getCallbackWritableChannel(
193             WritableByteChannel JavaDoc directChannel,
194             List JavaDoc<ContentStreamListener> listeners)
195             throws ContentIOException
196     {
197         WritableByteChannel JavaDoc callbackChannel = null;
198         if (directChannel instanceof FileChannel JavaDoc)
199         {
200             callbackChannel = getCallbackFileChannel((FileChannel JavaDoc) directChannel, listeners);
201         }
202         else
203         {
204             // introduce an advistor to handle the callbacks to the listeners
205
ChannelCloseCallbackAdvise advise = new ChannelCloseCallbackAdvise(listeners);
206             ProxyFactory proxyFactory = new ProxyFactory(directChannel);
207             proxyFactory.addAdvice(advise);
208             callbackChannel = (WritableByteChannel JavaDoc) proxyFactory.getProxy();
209         }
210         // done
211
if (logger.isDebugEnabled())
212         {
213             logger.debug("Created callback byte channel: \n" +
214                     " original: " + directChannel + "\n" +
215                     " new: " + callbackChannel);
216         }
217         return callbackChannel;
218     }
219
220     /**
221      * @see #getDirectWritableChannel()
222      * @see #getCallbackWritableChannel()
223      */

224     public synchronized final WritableByteChannel JavaDoc getWritableChannel() throws ContentIOException
225     {
226         // this is a use-once object
227
if (channel != null)
228         {
229             throw new RuntimeException JavaDoc("A channel has already been opened");
230         }
231         WritableByteChannel JavaDoc directChannel = getDirectWritableChannel();
232         channel = getCallbackWritableChannel(directChannel, listeners);
233
234         // notify that the channel was opened
235
super.channelOpened();
236         // done
237
if (logger.isDebugEnabled())
238         {
239             logger.debug("Opened channel onto content: \n" +
240                     " content: " + this + "\n" +
241                     " channel: " + channel);
242         }
243         return channel;
244     }
245     
246     /**
247      * @inheritDoc
248      */

249     public FileChannel JavaDoc getFileChannel(boolean truncate) throws ContentIOException
250     {
251         /*
252          * By calling this method, clients indicate that they wish to make random
253          * changes to the file. It is possible that the client might only want
254          * to update a tiny proportion of the file (truncate == false) or
255          * start afresh (truncate == true).
256          *
257          * Where the underlying support is not present for this method, a temporary
258          * file will be used as a substitute. When the write is complete, the
259          * results are copied directly to the underlying channel.
260          */

261         
262         // get the underlying implementation's best writable channel
263
channel = getWritableChannel();
264         // now use this channel if it can provide the random access, otherwise spoof it
265
FileChannel JavaDoc clientFileChannel = null;
266         if (channel instanceof FileChannel JavaDoc)
267         {
268             // all the support is provided by the underlying implementation
269
clientFileChannel = (FileChannel JavaDoc) channel;
270             // copy over the existing content, if required
271
if (!truncate && existingContentReader != null)
272             {
273                 ReadableByteChannel JavaDoc existingContentChannel = existingContentReader.getReadableChannel();
274                 long existingContentLength = existingContentReader.getSize();
275                 // copy the existing content
276
try
277                 {
278                     clientFileChannel.transferFrom(existingContentChannel, 0, existingContentLength);
279                     // copy complete
280
if (logger.isDebugEnabled())
281                     {
282                         logger.debug("Copied content for random access: \n" +
283                                 " writer: " + this + "\n" +
284                                 " existing: " + existingContentReader);
285                     }
286                 }
287                 catch (IOException JavaDoc e)
288                 {
289                     throw new ContentIOException("Failed to copy from existing content to enable random access: \n" +
290                             " writer: " + this + "\n" +
291                             " existing: " + existingContentReader,
292                             e);
293                 }
294                 finally
295                 {
296                     try { existingContentChannel.close(); } catch (IOException JavaDoc e) {}
297                 }
298             }
299             // debug
300
if (logger.isDebugEnabled())
301             {
302                 logger.debug("Content writer provided direct support for FileChannel: \n" +
303                         " writer: " + this);
304             }
305         }
306         else
307         {
308             // No random access support is provided by the implementation.
309
// Spoof it by providing a 2-stage write via a temp file
310
File JavaDoc tempFile = TempFileProvider.createTempFile("random_write_spoof_", ".bin");
311             final FileContentWriter spoofWriter = new FileContentWriter(
312                     tempFile, // the file to write to
313
getExistingContentReader()); // this ensures that the existing content is pulled in
314
// Attach a listener
315
// - to ensure that the content gets loaded from the temp file once writing has finished
316
// - to ensure that the close call gets passed on to the underlying channel
317
ContentStreamListener spoofListener = new ContentStreamListener()
318             {
319                 public void contentStreamClosed() throws ContentIOException
320                 {
321                     // the spoofed temp channel has been closed, so get a new reader for it
322
ContentReader spoofReader = spoofWriter.getReader();
323                     FileChannel JavaDoc spoofChannel = spoofReader.getFileChannel();
324                     // upload all the temp content to the real underlying channel
325
try
326                     {
327                         long spoofFileSize = spoofChannel.size();
328                         spoofChannel.transferTo(0, spoofFileSize, channel);
329                     }
330                     catch (IOException JavaDoc e)
331                     {
332                         throw new ContentIOException("Failed to copy from spoofed temporary channel to permanent channel: \n" +
333                                 " writer: " + this + "\n" +
334                                 " temp: " + spoofReader,
335                                 e);
336                     }
337                     finally
338                     {
339                         try { spoofChannel.close(); } catch (Throwable JavaDoc e) {}
340                         try
341                         {
342                             channel.close();
343                         }
344                         catch (IOException JavaDoc e)
345                         {
346                             throw new ContentIOException("Failed to close underlying channel", e);
347                         }
348                     }
349                 }
350             };
351             spoofWriter.addListener(spoofListener);
352             // we now have the spoofed up channel that the client can work with
353
clientFileChannel = spoofWriter.getFileChannel(truncate);
354             // debug
355
if (logger.isDebugEnabled())
356             {
357                 logger.debug("Content writer provided indirect support for FileChannel: \n" +
358                         " writer: " + this + "\n" +
359                         " temp writer: " + spoofWriter);
360             }
361         }
362         // the file is now available for random access
363
return clientFileChannel;
364     }
365
366     /**
367      * @see Channels#newOutputStream(java.nio.channels.WritableByteChannel)
368      */

369     public OutputStream JavaDoc getContentOutputStream() throws ContentIOException
370     {
371         try
372         {
373             WritableByteChannel JavaDoc channel = getWritableChannel();
374             OutputStream JavaDoc is = new BufferedOutputStream JavaDoc(Channels.newOutputStream(channel));
375             // done
376
return is;
377         }
378         catch (Throwable JavaDoc e)
379         {
380             throw new ContentIOException("Failed to open stream onto channel: \n" +
381                     " writer: " + this,
382                     e);
383         }
384     }
385
386     /**
387      * @see ContentReader#getContentInputStream()
388      * @see #putContent(InputStream)
389      */

390     public void putContent(ContentReader reader) throws ContentIOException
391     {
392         try
393         {
394             // get the stream to read from
395
InputStream JavaDoc is = reader.getContentInputStream();
396             // put the content
397
putContent(is);
398         }
399         catch (Throwable JavaDoc e)
400         {
401             throw new ContentIOException("Failed to copy reader content to writer: \n" +
402                     " writer: " + this + "\n" +
403                     " source reader: " + reader,
404                     e);
405         }
406     }
407
408     public final void putContent(InputStream JavaDoc is) throws ContentIOException
409     {
410         try
411         {
412             OutputStream JavaDoc os = getContentOutputStream();
413             FileCopyUtils.copy(is, os); // both streams are closed
414
// done
415
}
416         catch (IOException JavaDoc e)
417         {
418             throw new ContentIOException("Failed to copy content from input stream: \n" +
419                     " writer: " + this,
420                     e);
421         }
422     }
423     
424     public final void putContent(File JavaDoc file) throws ContentIOException
425     {
426         try
427         {
428             OutputStream JavaDoc os = getContentOutputStream();
429             FileInputStream JavaDoc is = new FileInputStream JavaDoc(file);
430             FileCopyUtils.copy(is, os); // both streams are closed
431
// done
432
}
433         catch (IOException JavaDoc e)
434         {
435             throw new ContentIOException("Failed to copy content from file: \n" +
436                     " writer: " + this + "\n" +
437                     " file: " + file,
438                     e);
439         }
440     }
441     
442     /**
443      * Makes use of the encoding, if available, to convert the string to bytes.
444      *
445      * @see ContentAccessor#getEncoding()
446      */

447     public final void putContent(String JavaDoc content) throws ContentIOException
448     {
449         try
450         {
451             // attempt to use the correct encoding
452
String JavaDoc encoding = getEncoding();
453             byte[] bytes = (encoding == null) ? content.getBytes() : content.getBytes(encoding);
454             // get the stream
455
OutputStream JavaDoc os = getContentOutputStream();
456             ByteArrayInputStream JavaDoc is = new ByteArrayInputStream JavaDoc(bytes);
457             FileCopyUtils.copy(is, os); // both streams are closed
458
// done
459
}
460         catch (IOException JavaDoc e)
461         {
462             throw new ContentIOException("Failed to copy content from string: \n" +
463                     " writer: " + this +
464                     " content length: " + content.length(),
465                     e);
466         }
467     }
468 }
469
Popular Tags