View Javadoc
1   package org.argeo.jcr.fs;
2   
3   import java.io.IOException;
4   import java.nio.channels.SeekableByteChannel;
5   import java.nio.file.AccessMode;
6   import java.nio.file.CopyOption;
7   import java.nio.file.DirectoryNotEmptyException;
8   import java.nio.file.DirectoryStream;
9   import java.nio.file.DirectoryStream.Filter;
10  import java.nio.file.FileStore;
11  import java.nio.file.LinkOption;
12  import java.nio.file.NoSuchFileException;
13  import java.nio.file.OpenOption;
14  import java.nio.file.Path;
15  import java.nio.file.attribute.BasicFileAttributes;
16  import java.nio.file.attribute.FileAttribute;
17  import java.nio.file.attribute.FileAttributeView;
18  import java.nio.file.spi.FileSystemProvider;
19  import java.util.Calendar;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Set;
23  
24  import javax.jcr.Node;
25  import javax.jcr.Property;
26  import javax.jcr.PropertyIterator;
27  import javax.jcr.PropertyType;
28  import javax.jcr.Repository;
29  import javax.jcr.RepositoryException;
30  import javax.jcr.Session;
31  import javax.jcr.nodetype.NodeType;
32  import javax.jcr.nodetype.PropertyDefinition;
33  
34  import org.argeo.jcr.JcrUtils;
35  
36  /** Operations on a {@link JcrFileSystem}. */
37  public abstract class JcrFileSystemProvider extends FileSystemProvider {
38  
39  	@Override
40  	public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
41  			throws IOException {
42  		Node node = toNode(path);
43  		try {
44  			if (node == null) {
45  				Node parent = toNode(path.getParent());
46  				if (parent == null)
47  					throw new IOException("No parent directory for " + path);
48  				if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
49  						|| parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
50  					throw new IOException(path + " parent is a file");
51  
52  				String fileName = path.getFileName().toString();
53  				fileName = Text.escapeIllegalJcrChars(fileName);
54  				node = parent.addNode(fileName, NodeType.NT_FILE);
55  				node.addMixin(NodeType.MIX_CREATED);
56  //				node.addMixin(NodeType.MIX_LAST_MODIFIED);
57  			}
58  			if (!node.isNodeType(NodeType.NT_FILE))
59  				throw new UnsupportedOperationException(node + " must be a file");
60  			return new BinaryChannel(node, path);
61  		} catch (RepositoryException e) {
62  			discardChanges(node);
63  			throw new IOException("Cannot read file", e);
64  		}
65  	}
66  
67  	@Override
68  	public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
69  		try {
70  			Node base = toNode(dir);
71  			if (base == null)
72  				throw new IOException(dir + " is not a JCR node");
73  			JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
74  			return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
75  		} catch (RepositoryException e) {
76  			throw new IOException("Cannot list directory", e);
77  		}
78  	}
79  
80  	@Override
81  	public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
82  		Node node = toNode(dir);
83  		try {
84  			if (node == null) {
85  				Node parent = toNode(dir.getParent());
86  				if (parent == null)
87  					throw new IOException("Parent of " + dir + " does not exist");
88  				Session session = parent.getSession();
89  				synchronized (session) {
90  					if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
91  							|| parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
92  						throw new IOException(dir + " parent is a file");
93  					String fileName = dir.getFileName().toString();
94  					fileName = Text.escapeIllegalJcrChars(fileName);
95  					node = parent.addNode(fileName, NodeType.NT_FOLDER);
96  					node.addMixin(NodeType.MIX_CREATED);
97  					node.addMixin(NodeType.MIX_LAST_MODIFIED);
98  					save(session);
99  				}
100 			} else {
101 				// if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
102 				// throw new FileExistsException(dir + " exists and is not a directory");
103 			}
104 		} catch (RepositoryException e) {
105 			discardChanges(node);
106 			throw new IOException("Cannot create directory " + dir, e);
107 		}
108 	}
109 
110 	@Override
111 	public void delete(Path path) throws IOException {
112 		Node node = toNode(path);
113 		try {
114 			if (node == null)
115 				throw new NoSuchFileException(path + " does not exist");
116 			Session session = node.getSession();
117 			synchronized (session) {
118 				session.refresh(false);
119 				if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
120 					node.remove();
121 				else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
122 					if (node.hasNodes())// TODO check only files
123 						throw new DirectoryNotEmptyException(path.toString());
124 					node.remove();
125 				}
126 				save(session);
127 			}
128 		} catch (RepositoryException e) {
129 			discardChanges(node);
130 			throw new IOException("Cannot delete " + path, e);
131 		}
132 
133 	}
134 
135 	@Override
136 	public void copy(Path source, Path target, CopyOption... options) throws IOException {
137 		Node sourceNode = toNode(source);
138 		Node targetNode = toNode(target);
139 		try {
140 			Session targetSession = targetNode.getSession();
141 			synchronized (targetSession) {
142 				JcrUtils.copy(sourceNode, targetNode);
143 				save(targetSession);
144 			}
145 		} catch (RepositoryException e) {
146 			discardChanges(sourceNode);
147 			discardChanges(targetNode);
148 			throw new IOException("Cannot copy from " + source + " to " + target, e);
149 		}
150 	}
151 
152 	@Override
153 	public void move(Path source, Path target, CopyOption... options) throws IOException {
154 		Node sourceNode = toNode(source);
155 		try {
156 			Session session = sourceNode.getSession();
157 			synchronized (session) {
158 				session.move(sourceNode.getPath(), target.toString());
159 				save(session);
160 			}
161 		} catch (RepositoryException e) {
162 			discardChanges(sourceNode);
163 			throw new IOException("Cannot move from " + source + " to " + target, e);
164 		}
165 	}
166 
167 	@Override
168 	public boolean isSameFile(Path path, Path path2) throws IOException {
169 		if (path.getFileSystem() != path2.getFileSystem())
170 			return false;
171 		boolean equ = path.equals(path2);
172 		if (equ)
173 			return true;
174 		else {
175 			try {
176 				Node node = toNode(path);
177 				Node node2 = toNode(path2);
178 				return node.isSame(node2);
179 			} catch (RepositoryException e) {
180 				throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
181 			}
182 		}
183 
184 	}
185 
186 	@Override
187 	public boolean isHidden(Path path) throws IOException {
188 		return path.getFileName().toString().charAt(0) == '.';
189 	}
190 
191 	@Override
192 	public FileStore getFileStore(Path path) throws IOException {
193 		JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
194 		return fileSystem.getFileStore(path.toString());
195 	}
196 
197 	@Override
198 	public void checkAccess(Path path, AccessMode... modes) throws IOException {
199 		Node node = toNode(path);
200 		if (node == null)
201 			throw new NoSuchFileException(path + " does not exist");
202 		// TODO check access via JCR api
203 	}
204 
205 	@Override
206 	public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
207 		throw new UnsupportedOperationException();
208 	}
209 
210 	@SuppressWarnings("unchecked")
211 	@Override
212 	public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
213 			throws IOException {
214 		// TODO check if assignable
215 		Node node = toNode(path);
216 		if (node == null) {
217 			throw new IOException("JCR node not found for " + path);
218 		}
219 		return (A) new JcrBasicfileAttributes(node);
220 	}
221 
222 	@Override
223 	public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
224 		try {
225 			Node node = toNode(path);
226 			String pattern = attributes.replace(',', '|');
227 			Map<String, Object> res = new HashMap<String, Object>();
228 			PropertyIterator it = node.getProperties(pattern);
229 			props: while (it.hasNext()) {
230 				Property prop = it.nextProperty();
231 				PropertyDefinition pd = prop.getDefinition();
232 				if (pd.isMultiple())
233 					continue props;
234 				int requiredType = pd.getRequiredType();
235 				switch (requiredType) {
236 				case PropertyType.LONG:
237 					res.put(prop.getName(), prop.getLong());
238 					break;
239 				case PropertyType.DOUBLE:
240 					res.put(prop.getName(), prop.getDouble());
241 					break;
242 				case PropertyType.BOOLEAN:
243 					res.put(prop.getName(), prop.getBoolean());
244 					break;
245 				case PropertyType.DATE:
246 					res.put(prop.getName(), prop.getDate());
247 					break;
248 				case PropertyType.BINARY:
249 					byte[] arr = JcrUtils.getBinaryAsBytes(prop);
250 					res.put(prop.getName(), arr);
251 					break;
252 				default:
253 					res.put(prop.getName(), prop.getString());
254 				}
255 			}
256 			return res;
257 		} catch (RepositoryException e) {
258 			throw new IOException("Cannot read attributes of " + path, e);
259 		}
260 	}
261 
262 	@Override
263 	public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
264 		Node node = toNode(path);
265 		try {
266 			Session session = node.getSession();
267 			synchronized (session) {
268 				if (value instanceof byte[]) {
269 					JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
270 				} else if (value instanceof Calendar) {
271 					node.setProperty(attribute, (Calendar) value);
272 				} else {
273 					node.setProperty(attribute, value.toString());
274 				}
275 				save(session);
276 			}
277 		} catch (RepositoryException e) {
278 			discardChanges(node);
279 			throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
280 		}
281 	}
282 
283 	protected Node toNode(Path path) {
284 		try {
285 			return ((JcrPath) path).getNode();
286 		} catch (RepositoryException e) {
287 			throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
288 		}
289 	}
290 
291 	/** Discard changes in the underlying session */
292 	protected void discardChanges(Node node) {
293 		if (node == null)
294 			return;
295 		try {
296 			// discard changes
297 			node.getSession().refresh(false);
298 		} catch (RepositoryException e) {
299 			e.printStackTrace();
300 			// TODO log out session?
301 			// TODO use Commons logging?
302 		}
303 	}
304 
305 	/** Make sure save is robust. */
306 	protected void save(Session session) throws RepositoryException {
307 		session.refresh(true);
308 		session.save();
309 		session.notifyAll();
310 	}
311 
312 	/**
313 	 * To be overriden in order to support the ~ path, with an implementation
314 	 * specific concept of user home.
315 	 * 
316 	 * @return null by default
317 	 */
318 	public Node getUserHome(Repository session) {
319 		return null;
320 	}
321 
322 }