View Javadoc
1   package org.argeo.jcr.fs;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.nio.ByteBuffer;
7   import java.nio.channels.Channels;
8   import java.nio.channels.FileChannel;
9   import java.nio.channels.ReadableByteChannel;
10  import java.nio.channels.SeekableByteChannel;
11  import java.nio.file.Files;
12  import java.nio.file.Path;
13  import java.nio.file.StandardOpenOption;
14  
15  import javax.jcr.Binary;
16  import javax.jcr.Node;
17  import javax.jcr.Property;
18  import javax.jcr.RepositoryException;
19  import javax.jcr.Session;
20  import javax.jcr.nodetype.NodeType;
21  
22  import org.argeo.jcr.JcrUtils;
23  
24  /** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */
25  public class BinaryChannel implements SeekableByteChannel {
26  	private final Node file;
27  	private Binary binary;
28  	private boolean open = true;
29  
30  	private long position = 0;
31  
32  	private FileChannel fc = null;
33  
34  	public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
35  		this.file = file;
36  		Session session = file.getSession();
37  		synchronized (session) {
38  			if (file.isNodeType(NodeType.NT_FILE)) {
39  				if (file.hasNode(Node.JCR_CONTENT)) {
40  					Node data = file.getNode(Property.JCR_CONTENT);
41  					this.binary = data.getProperty(Property.JCR_DATA).getBinary();
42  				} else {
43  					Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
44  					data.addMixin(NodeType.MIX_LAST_MODIFIED);
45  					try (InputStream in = new ByteArrayInputStream(new byte[0])) {
46  						this.binary = data.getSession().getValueFactory().createBinary(in);
47  					}
48  					data.setProperty(Property.JCR_DATA, this.binary);
49  
50  					// MIME type
51  					String mime = Files.probeContentType(path);
52  					// String mime = fileTypeMap.getContentType(file.getName());
53  					data.setProperty(Property.JCR_MIMETYPE, mime);
54  
55  					session.refresh(true);
56  					session.save();
57  					session.notifyAll();
58  				}
59  			} else {
60  				throw new IllegalArgumentException(
61  						"Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
62  			}
63  		}
64  	}
65  
66  	@Override
67  	public synchronized boolean isOpen() {
68  		return open;
69  	}
70  
71  	@Override
72  	public synchronized void close() throws IOException {
73  		if (isModified()) {
74  			Binary newBinary = null;
75  			try {
76  				Session session = file.getSession();
77  				synchronized (session) {
78  					fc.position(0);
79  					InputStream in = Channels.newInputStream(fc);
80  					newBinary = session.getValueFactory().createBinary(in);
81  					file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
82  					session.refresh(true);
83  					session.save();
84  					open = false;
85  					session.notifyAll();
86  				}
87  			} catch (RepositoryException e) {
88  				throw new IOException("Cannot close " + file, e);
89  			} finally {
90  				JcrUtils.closeQuietly(newBinary);
91  				// IOUtils.closeQuietly(fc);
92  				if (fc != null) {
93  					fc.close();
94  				}
95  			}
96  		} else {
97  			clearReadState();
98  			open = false;
99  		}
100 	}
101 
102 	@Override
103 	public int read(ByteBuffer dst) throws IOException {
104 		if (isModified()) {
105 			return fc.read(dst);
106 		} else {
107 
108 			try {
109 				int read;
110 				byte[] arr = dst.array();
111 				read = binary.read(arr, position);
112 
113 				if (read != -1)
114 					position = position + read;
115 				return read;
116 			} catch (RepositoryException e) {
117 				throw new IOException("Cannot read into buffer", e);
118 			}
119 		}
120 	}
121 
122 	@Override
123 	public int write(ByteBuffer src) throws IOException {
124 		int written = getFileChannel().write(src);
125 		return written;
126 	}
127 
128 	@Override
129 	public long position() throws IOException {
130 		if (isModified())
131 			return getFileChannel().position();
132 		else
133 			return position;
134 	}
135 
136 	@Override
137 	public SeekableByteChannel position(long newPosition) throws IOException {
138 		if (isModified()) {
139 			getFileChannel().position(position);
140 		} else {
141 			this.position = newPosition;
142 		}
143 		return this;
144 	}
145 
146 	@Override
147 	public long size() throws IOException {
148 		if (isModified()) {
149 			return getFileChannel().size();
150 		} else {
151 			try {
152 				return binary.getSize();
153 			} catch (RepositoryException e) {
154 				throw new IOException("Cannot get size", e);
155 			}
156 		}
157 	}
158 
159 	@Override
160 	public SeekableByteChannel truncate(long size) throws IOException {
161 		getFileChannel().truncate(size);
162 		return this;
163 	}
164 
165 	private FileChannel getFileChannel() throws IOException {
166 		try {
167 			if (fc == null) {
168 				Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
169 				fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
170 						StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
171 				ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
172 				fc.transferFrom(readChannel, 0, binary.getSize());
173 				clearReadState();
174 			}
175 			return fc;
176 		} catch (RepositoryException e) {
177 			throw new IOException("Cannot get temp file channel", e);
178 		}
179 	}
180 
181 	private boolean isModified() {
182 		return fc != null;
183 	}
184 
185 	private void clearReadState() {
186 		position = -1;
187 		JcrUtils.closeQuietly(binary);
188 		binary = null;
189 	}
190 }