View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client.util;
20  
21  import java.io.Closeable;
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.SeekableByteChannel;
25  import java.nio.file.AccessDeniedException;
26  import java.nio.file.Files;
27  import java.nio.file.NoSuchFileException;
28  import java.nio.file.Path;
29  import java.nio.file.StandardOpenOption;
30  import java.util.Iterator;
31  import java.util.NoSuchElementException;
32  
33  import org.eclipse.jetty.client.api.ContentProvider;
34  import org.eclipse.jetty.io.ByteBufferPool;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  
38  /**
39   * <p>A {@link ContentProvider} for files using JDK 7's {@code java.nio.file} APIs.</p>
40   * <p>It is possible to specify, at the constructor, a buffer size used to read
41   * content from the stream, by default 4096 bytes.
42   * If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)},
43   * the buffer will be allocated from that pool, otherwise one buffer will be
44   * allocated and used to read the file.</p>
45   */
46  public class PathContentProvider extends AbstractTypedContentProvider
47  {
48      private static final Logger LOG = Log.getLogger(PathContentProvider.class);
49  
50      private final Path filePath;
51      private final long fileSize;
52      private final int bufferSize;
53      private ByteBufferPool bufferPool;
54  
55      public PathContentProvider(Path filePath) throws IOException
56      {
57          this(filePath, 4096);
58      }
59  
60      public PathContentProvider(Path filePath, int bufferSize) throws IOException
61      {
62          this("application/octet-stream", filePath, bufferSize);
63      }
64  
65      public PathContentProvider(String contentType, Path filePath) throws IOException
66      {
67          this(contentType, filePath, 4096);
68      }
69  
70      public PathContentProvider(String contentType, Path filePath, int bufferSize) throws IOException
71      {
72          super(contentType);
73          if (!Files.isRegularFile(filePath))
74              throw new NoSuchFileException(filePath.toString());
75          if (!Files.isReadable(filePath))
76              throw new AccessDeniedException(filePath.toString());
77          this.filePath = filePath;
78          this.fileSize = Files.size(filePath);
79          this.bufferSize = bufferSize;
80      }
81  
82      @Override
83      public long getLength()
84      {
85          return fileSize;
86      }
87  
88      public ByteBufferPool getByteBufferPool()
89      {
90          return bufferPool;
91      }
92  
93      public void setByteBufferPool(ByteBufferPool byteBufferPool)
94      {
95          this.bufferPool = byteBufferPool;
96      }
97  
98      @Override
99      public Iterator<ByteBuffer> iterator()
100     {
101         return new PathIterator();
102     }
103 
104     private class PathIterator implements Iterator<ByteBuffer>, Closeable
105     {
106         private ByteBuffer buffer;
107         private SeekableByteChannel channel;
108         private long position;
109 
110         @Override
111         public boolean hasNext()
112         {
113             return position < getLength();
114         }
115 
116         @Override
117         public ByteBuffer next()
118         {
119             try
120             {
121                 if (channel == null)
122                 {
123                     buffer = bufferPool == null ?
124                             ByteBuffer.allocateDirect(bufferSize) :
125                             bufferPool.acquire(bufferSize, true);
126                     channel = Files.newByteChannel(filePath, StandardOpenOption.READ);
127                     if (LOG.isDebugEnabled())
128                         LOG.debug("Opened file {}", filePath);
129                 }
130 
131                 buffer.clear();
132                 int read = channel.read(buffer);
133                 if (read < 0)
134                     throw new NoSuchElementException();
135 
136                 if (LOG.isDebugEnabled())
137                     LOG.debug("Read {} bytes from {}", read, filePath);
138 
139                 position += read;
140 
141                 buffer.flip();
142                 return buffer;
143             }
144             catch (NoSuchElementException x)
145             {
146                 close();
147                 throw x;
148             }
149             catch (Throwable x)
150             {
151                 close();
152                 throw (NoSuchElementException)new NoSuchElementException().initCause(x);
153             }
154         }
155 
156         @Override
157         public void close()
158         {
159             try
160             {
161                 if (bufferPool != null && buffer != null)
162                     bufferPool.release(buffer);
163                 if (channel != null)
164                     channel.close();
165             }
166             catch (Throwable x)
167             {
168                 LOG.ignore(x);
169             }
170         }
171     }
172 }