KickJava   Java API By Example, From Geeks To Geeks.

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


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.BufferedInputStream JavaDoc;
20 import java.io.ByteArrayOutputStream JavaDoc;
21 import java.io.File JavaDoc;
22 import java.io.FileOutputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.io.InputStreamReader JavaDoc;
26 import java.io.OutputStream JavaDoc;
27 import java.io.Reader JavaDoc;
28 import java.nio.channels.Channels JavaDoc;
29 import java.nio.channels.FileChannel JavaDoc;
30 import java.nio.channels.ReadableByteChannel JavaDoc;
31 import java.util.ArrayList JavaDoc;
32 import java.util.List JavaDoc;
33
34 import org.alfresco.error.AlfrescoRuntimeException;
35 import org.alfresco.repo.content.filestore.FileContentWriter;
36 import org.alfresco.service.cmr.repository.ContentAccessor;
37 import org.alfresco.service.cmr.repository.ContentIOException;
38 import org.alfresco.service.cmr.repository.ContentReader;
39 import org.alfresco.service.cmr.repository.ContentStreamListener;
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 #getDirectReadableChannel()} to read content from the repository</li>
51  * </ul>
52  *
53  * @author Derek Hulley
54  */

55 public abstract class AbstractContentReader extends AbstractContentAccessor implements ContentReader
56 {
57     private static final Log logger = LogFactory.getLog(AbstractContentReader.class);
58     
59     private List JavaDoc<ContentStreamListener> listeners;
60     private ReadableByteChannel JavaDoc channel;
61     
62     /**
63      * @param contentUrl the content URL - this should be relative to the root of the store
64      * and not absolute: to enable moving of the stores
65      */

66     protected AbstractContentReader(String JavaDoc contentUrl)
67     {
68         super(contentUrl);
69         
70         listeners = new ArrayList JavaDoc<ContentStreamListener>(2);
71     }
72
73     /**
74      * Adds the listener after checking that the output stream isn't already in
75      * use.
76      */

77     public synchronized void addListener(ContentStreamListener listener)
78     {
79         if (channel != null)
80         {
81             throw new RuntimeException JavaDoc("Channel is already in use");
82         }
83         listeners.add(listener);
84     }
85     
86     /**
87      * A factory method for subclasses to implement that will ensure the proper
88      * implementation of the {@link ContentReader#getReader()} method.
89      * <p>
90      * Only the instance need be constructed. The required mimetype, encoding, etc
91      * will be copied across by this class.
92      *
93      * @return Returns a reader onto the location referenced by this instance.
94      * The instance must <b>always</b> be a new instance.
95      * @throws ContentIOException
96      */

97     protected abstract ContentReader createReader() throws ContentIOException;
98     
99     /**
100      * Performs checks and copies required reader attributes
101      */

102     public final ContentReader getReader() throws ContentIOException
103     {
104         ContentReader reader = createReader();
105         if (reader == null)
106         {
107             throw new AlfrescoRuntimeException("ContentReader failed to create new reader: \n" +
108                     " reader: " + this);
109         }
110         else if (reader.getContentUrl() == null || !reader.getContentUrl().equals(getContentUrl()))
111         {
112             throw new AlfrescoRuntimeException("ContentReader has different URL: \n" +
113                     " reader: " + this + "\n" +
114                     " new reader: " + reader);
115         }
116         // copy across common attributes
117
reader.setMimetype(this.getMimetype());
118         reader.setEncoding(this.getEncoding());
119         // done
120
if (logger.isDebugEnabled())
121         {
122             logger.debug("Reader spawned new reader: \n" +
123                     " reader: " + this + "\n" +
124                     " new reader: " + reader);
125         }
126         return reader;
127     }
128
129     /**
130      * An automatically created listener sets the flag
131      */

132     public synchronized final boolean isClosed()
133     {
134         if (channel != null)
135         {
136             return !channel.isOpen();
137         }
138         else
139         {
140             return false;
141         }
142     }
143
144     /** helper implementation for base class */
145     protected boolean isChannelOpen()
146     {
147         if (channel != null)
148         {
149             return channel.isOpen();
150         }
151         else
152         {
153             return false;
154         }
155     }
156
157     /**
158      * Provides low-level access to read content from the repository.
159      * <p>
160      * This is the only of the content <i>reading</i> methods that needs to be implemented
161      * by derived classes. All other content access methods make use of this in their
162      * underlying implementations.
163      *
164      * @return Returns a channel from which content can be read
165      * @throws ContentIOException if the channel could not be opened or the underlying content
166      * has disappeared
167      */

168     protected abstract ReadableByteChannel JavaDoc getDirectReadableChannel() throws ContentIOException;
169
170     /**
171      * Create a channel that performs callbacks to the given listeners.
172      *
173      * @param directChannel the result of {@link #getDirectReadableChannel()}
174      * @param listeners the listeners to call
175      * @return Returns a channel
176      * @throws ContentIOException
177      */

178     private ReadableByteChannel JavaDoc getCallbackReadableChannel(
179             ReadableByteChannel JavaDoc directChannel,
180             List JavaDoc<ContentStreamListener> listeners)
181             throws ContentIOException
182     {
183         ReadableByteChannel JavaDoc callbackChannel = null;
184         if (directChannel instanceof FileChannel JavaDoc)
185         {
186             callbackChannel = getCallbackFileChannel((FileChannel JavaDoc) directChannel, listeners);
187         }
188         else
189         {
190             // introduce an advistor to handle the callbacks to the listeners
191
ChannelCloseCallbackAdvise advise = new ChannelCloseCallbackAdvise(listeners);
192             ProxyFactory proxyFactory = new ProxyFactory(directChannel);
193             proxyFactory.addAdvice(advise);
194             callbackChannel = (ReadableByteChannel JavaDoc) proxyFactory.getProxy();
195         }
196         // done
197
if (logger.isDebugEnabled())
198         {
199             logger.debug("Created callback byte channel: \n" +
200                     " original: " + directChannel + "\n" +
201                     " new: " + callbackChannel);
202         }
203         return callbackChannel;
204     }
205
206     /**
207      * @see #getDirectReadableChannel()
208      * @see #getCallbackReadableChannel(ReadableByteChannel, List)
209      */

210     public synchronized final ReadableByteChannel JavaDoc getReadableChannel() throws ContentIOException
211     {
212         // this is a use-once object
213
if (channel != null)
214         {
215             throw new RuntimeException JavaDoc("A channel has already been opened");
216         }
217         ReadableByteChannel JavaDoc directChannel = getDirectReadableChannel();
218         channel = getCallbackReadableChannel(directChannel, listeners);
219
220         // notify that the channel was opened
221
super.channelOpened();
222         // done
223
if (logger.isDebugEnabled())
224         {
225             logger.debug("Opened channel onto content: " + this);
226         }
227         return channel;
228     }
229     
230     
231     /**
232      * @inheritDoc
233      */

234     public FileChannel JavaDoc getFileChannel() throws ContentIOException
235     {
236         /*
237          * Where the underlying support is not present for this method, a temporary
238          * file will be used as a substitute. When the write is complete, the
239          * results are copied directly to the underlying channel.
240          */

241         
242         // get the underlying implementation's best readable channel
243
channel = getReadableChannel();
244         // now use this channel if it can provide the random access, otherwise spoof it
245
FileChannel JavaDoc clientFileChannel = null;
246         if (channel instanceof FileChannel JavaDoc)
247         {
248             // all the support is provided by the underlying implementation
249
clientFileChannel = (FileChannel JavaDoc) channel;
250             // debug
251
if (logger.isDebugEnabled())
252             {
253                 logger.debug("Content reader provided direct support for FileChannel: \n" +
254                         " reader: " + this);
255             }
256         }
257         else
258         {
259             // No random access support is provided by the implementation.
260
// Spoof it by providing a 2-stage read from a temp file
261
File JavaDoc tempFile = TempFileProvider.createTempFile("random_read_spoof_", ".bin");
262             FileContentWriter spoofWriter = new FileContentWriter(tempFile);
263             // pull the content in from the underlying channel
264
FileChannel JavaDoc spoofWriterChannel = spoofWriter.getFileChannel(false);
265             try
266             {
267                 long spoofFileSize = this.getSize();
268                 spoofWriterChannel.transferFrom(channel, 0, spoofFileSize);
269             }
270             catch (IOException JavaDoc e)
271             {
272                 throw new ContentIOException("Failed to copy from permanent channel to spoofed temporary channel: \n" +
273                         " reader: " + this + "\n" +
274                         " temp: " + spoofWriter,
275                         e);
276             }
277             finally
278             {
279                 try { spoofWriterChannel.close(); } catch (IOException JavaDoc e) {}
280             }
281             // get a reader onto the spoofed content
282
final ContentReader spoofReader = spoofWriter.getReader();
283             // Attach a listener
284
// - ensure that the close call gets propogated to the underlying channel
285
ContentStreamListener spoofListener = new ContentStreamListener()
286                     {
287                         public void contentStreamClosed() throws ContentIOException
288                         {
289                             try
290                             {
291                                 channel.close();
292                             }
293                             catch (IOException JavaDoc e)
294                             {
295                                 throw new ContentIOException("Failed to close underlying channel", e);
296                             }
297                         }
298                     };
299             spoofReader.addListener(spoofListener);
300             // we now have the spoofed up channel that the client can work with
301
clientFileChannel = spoofReader.getFileChannel();
302             // debug
303
if (logger.isDebugEnabled())
304             {
305                 logger.debug("Content writer provided indirect support for FileChannel: \n" +
306                         " writer: " + this + "\n" +
307                         " temp writer: " + spoofWriter);
308             }
309         }
310         // the file is now available for random access
311
return clientFileChannel;
312     }
313
314     /**
315      * @see Channels#newInputStream(java.nio.channels.ReadableByteChannel)
316      */

317     public InputStream JavaDoc getContentInputStream() throws ContentIOException
318     {
319         try
320         {
321             ReadableByteChannel JavaDoc channel = getReadableChannel();
322             InputStream JavaDoc is = new BufferedInputStream JavaDoc(Channels.newInputStream(channel));
323             // done
324
return is;
325         }
326         catch (Throwable JavaDoc e)
327         {
328             throw new ContentIOException("Failed to open stream onto channel: \n" +
329                     " accessor: " + this,
330                     e);
331         }
332     }
333
334     /**
335      * Copies the {@link #getContentInputStream() input stream} to the given
336      * <code>OutputStream</code>
337      */

338     public final void getContent(OutputStream JavaDoc os) throws ContentIOException
339     {
340         try
341         {
342             InputStream JavaDoc is = getContentInputStream();
343             FileCopyUtils.copy(is, os); // both streams are closed
344
// done
345
}
346         catch (IOException JavaDoc e)
347         {
348             throw new ContentIOException("Failed to copy content to output stream: \n" +
349                     " accessor: " + this,
350                     e);
351         }
352     }
353
354     public final void getContent(File JavaDoc file) throws ContentIOException
355     {
356         try
357         {
358             InputStream JavaDoc is = getContentInputStream();
359             FileOutputStream JavaDoc os = new FileOutputStream JavaDoc(file);
360             FileCopyUtils.copy(is, os); // both streams are closed
361
// done
362
}
363         catch (IOException JavaDoc e)
364         {
365             throw new ContentIOException("Failed to copy content to file: \n" +
366                     " accessor: " + this + "\n" +
367                     " file: " + file,
368                     e);
369         }
370     }
371     
372     public final String JavaDoc getContentString(int length) throws ContentIOException
373     {
374         if (length < 0 || length > Integer.MAX_VALUE)
375         {
376             throw new IllegalArgumentException JavaDoc("Character count must be positive and within range");
377         }
378         Reader JavaDoc reader = null;
379         try
380         {
381             // just create buffer of the required size
382
char[] buffer = new char[length];
383             
384             String JavaDoc encoding = getEncoding();
385             // create a reader from the input stream
386
if (encoding == null)
387             {
388                 reader = new InputStreamReader JavaDoc(getContentInputStream());
389             }
390             else
391             {
392                 reader = new InputStreamReader JavaDoc(getContentInputStream(), encoding);
393             }
394             // read it all, if possible
395
int count = reader.read(buffer, 0, length);
396             // there may have been fewer characters - create a new string
397
String JavaDoc result = new String JavaDoc(buffer, 0, count);
398             // done
399
return result;
400         }
401         catch (IOException JavaDoc e)
402         {
403             throw new ContentIOException("Failed to copy content to string: \n" +
404                     " accessor: " + this + "\n" +
405                     " length: " + length,
406                     e);
407         }
408         finally
409         {
410             if (reader != null)
411             {
412                 try { reader.close(); } catch (Throwable JavaDoc e) { logger.error(e); }
413             }
414         }
415     }
416
417     /**
418      * Makes use of the encoding, if available, to convert bytes to a string.
419      * <p>
420      * All the content is streamed into memory. So, like the interface said,
421      * be careful with this method.
422      *
423      * @see ContentAccessor#getEncoding()
424      */

425     public final String JavaDoc getContentString() throws ContentIOException
426     {
427         try
428         {
429             // read from the stream into a byte[]
430
InputStream JavaDoc is = getContentInputStream();
431             ByteArrayOutputStream JavaDoc os = new ByteArrayOutputStream JavaDoc();
432             FileCopyUtils.copy(is, os); // both streams are closed
433
byte[] bytes = os.toByteArray();
434             // get the encoding for the string
435
String JavaDoc encoding = getEncoding();
436             // create the string from the byte[] using encoding if necessary
437
String JavaDoc content = (encoding == null) ? new String JavaDoc(bytes) : new String JavaDoc(bytes, encoding);
438             // done
439
return content;
440         }
441         catch (IOException JavaDoc e)
442         {
443             throw new ContentIOException("Failed to copy content to string: \n" +
444                     " accessor: " + this,
445                     e);
446         }
447     }
448 }
449
Popular Tags