1 package org.modeshape.connector.filesystem;
2
3 import java.io.BufferedInputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collection;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import org.modeshape.common.i18n.I18n;
19 import org.modeshape.common.util.FileUtil;
20 import org.modeshape.common.util.IoUtil;
21 import org.modeshape.graph.ExecutionContext;
22 import org.modeshape.graph.JcrLexicon;
23 import org.modeshape.graph.JcrNtLexicon;
24 import org.modeshape.graph.Location;
25 import org.modeshape.graph.ModeShapeLexicon;
26 import org.modeshape.graph.connector.RepositorySourceException;
27 import org.modeshape.graph.connector.base.PathNode;
28 import org.modeshape.graph.connector.base.PathWorkspace;
29 import org.modeshape.graph.mimetype.MimeTypeDetector;
30 import org.modeshape.graph.property.Binary;
31 import org.modeshape.graph.property.BinaryFactory;
32 import org.modeshape.graph.property.DateTimeFactory;
33 import org.modeshape.graph.property.Name;
34 import org.modeshape.graph.property.NameFactory;
35 import org.modeshape.graph.property.NamespaceRegistry;
36 import org.modeshape.graph.property.Path;
37 import org.modeshape.graph.property.PathFactory;
38 import org.modeshape.graph.property.PathNotFoundException;
39 import org.modeshape.graph.property.Property;
40 import org.modeshape.graph.property.PropertyFactory;
41 import org.modeshape.graph.property.Path.Segment;
42 import org.modeshape.graph.request.Request;
43
44
45
46
47 class FileSystemWorkspace extends PathWorkspace<PathNode> {
48 private static final String DEFAULT_MIME_TYPE = "application/octet";
49 private static final Set<Name> VALID_PRIMARY_TYPES = new HashSet<Name>(Arrays.asList(new Name[] {JcrNtLexicon.FOLDER,
50 JcrNtLexicon.FILE, JcrNtLexicon.RESOURCE, ModeShapeLexicon.RESOURCE}));
51
52 private final FileSystemSource source;
53 private final FileSystemRepository repository;
54 private final ExecutionContext context;
55 private final File workspaceRoot;
56
57 public FileSystemWorkspace( String name,
58 FileSystemWorkspace originalToClone,
59 File workspaceRoot ) {
60 super(name, originalToClone.getRootNodeUuid());
61
62 this.source = originalToClone.source;
63 this.context = originalToClone.context;
64 this.workspaceRoot = workspaceRoot;
65 this.repository = originalToClone.repository;
66
67 cloneWorkspace(originalToClone);
68 }
69
70 public FileSystemWorkspace( FileSystemRepository repository,
71 String name ) {
72 super(name, repository.getRootNodeUuid());
73 this.workspaceRoot = repository.getWorkspaceDirectory(name);
74 this.repository = repository;
75 this.context = repository.getContext();
76 this.source = repository.source;
77 }
78
79 private void cloneWorkspace( FileSystemWorkspace original ) {
80 File originalRoot = repository.getWorkspaceDirectory(original.getName());
81 File newRoot = repository.getWorkspaceDirectory(this.getName());
82
83 try {
84 FileUtil.copy(originalRoot, newRoot, source.filenameFilter());
85 } catch (IOException ioe) {
86 throw new IllegalStateException(ioe);
87 }
88 }
89
90 @Override
91 public PathNode moveNode( PathNode node,
92 PathNode newNode ) {
93 PathFactory pathFactory = context.getValueFactories().getPathFactory();
94 Path newPath = pathFactory.create(newNode.getParent(), newNode.getName());
95
96 File originalFile = fileFor(pathFactory.create(node.getParent(), node.getName()));
97 File newFile = fileFor(newPath, false);
98
99 if (newFile.exists()) {
100 newFile.delete();
101 }
102
103 originalFile.renameTo(newFile);
104
105 return getNode(newPath);
106 }
107
108 @Override
109 public PathNode putNode( PathNode node ) {
110 NameFactory nameFactory = context.getValueFactories().getNameFactory();
111 PathFactory pathFactory = context.getValueFactories().getPathFactory();
112 NamespaceRegistry registry = context.getNamespaceRegistry();
113 CustomPropertiesFactory customPropertiesFactory = source.customPropertiesFactory();
114
115 Map<Name, Property> properties = node.getProperties();
116
117 if (node.getParent() == null) {
118
119 Path rootPath = pathFactory.createRootPath();
120 Location rootLocation = Location.create(rootPath, repository.getRootNodeUuid());
121 customPropertiesFactory.recordDirectoryProperties(context,
122 source.getName(),
123 rootLocation,
124 workspaceRoot,
125 node.getProperties());
126 return getNode(rootPath);
127 }
128
129
130
131
132 Path parentPath = node.getParent();
133 boolean isRoot = parentPath == null;
134 File parentFile = fileFor(parentPath);
135
136 Path newPath = isRoot ? pathFactory.createRootPath() : pathFactory.create(parentPath, node.getName());
137 Name name = node.getName().getName();
138 String newName = name.getString(registry);
139 File newFile = new File(parentFile, newName);
140
141
142
143
144 Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE);
145
146
147 Name primaryType = primaryTypeProp == null ? JcrNtLexicon.FOLDER : nameFactory.create(primaryTypeProp.getFirstValue());
148
149 if (JcrNtLexicon.FILE.equals(primaryType)) {
150
151
152 if (!parentFile.canWrite()) {
153 I18n msg = FileSystemI18n.parentIsReadOnly;
154 throw new RepositorySourceException(source.getName(), msg.text(parentPath, this.getName(), source.getName()));
155 }
156
157 try {
158 ensureValidPathLength(newFile);
159
160
161 if (!newFile.exists() && !newFile.createNewFile()) {
162 I18n msg = FileSystemI18n.fileAlreadyExists;
163 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
164 }
165 } catch (IOException ioe) {
166 I18n msg = FileSystemI18n.couldNotCreateFile;
167 throw new RepositorySourceException(source.getName(), msg.text(parentPath,
168 getName(),
169 source.getName(),
170 ioe.getMessage()), ioe);
171 }
172
173 customPropertiesFactory.recordFileProperties(context, source.getName(), Location.create(newPath), newFile, properties);
174 } else if (JcrNtLexicon.RESOURCE.equals(primaryType) || ModeShapeLexicon.RESOURCE.equals(primaryType)) {
175 assert parentFile != null;
176
177 if (!JcrLexicon.CONTENT.equals(name)) {
178 I18n msg = FileSystemI18n.invalidNameForResource;
179 String nodeName = name.getString();
180 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName(), nodeName));
181 }
182
183 if (!parentFile.isFile()) {
184 I18n msg = FileSystemI18n.invalidPathForResource;
185 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
186 }
187
188 if (!parentFile.canWrite()) {
189 I18n msg = FileSystemI18n.parentIsReadOnly;
190 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
191 }
192
193
194 FileOutputStream fos = null;
195 try {
196 File temp = File.createTempFile("dna", null);
197 fos = new FileOutputStream(temp);
198
199 Property dataProp = properties.get(JcrLexicon.DATA);
200 if (dataProp == null) {
201 I18n msg = FileSystemI18n.missingRequiredProperty;
202 String dataPropName = JcrLexicon.DATA.getString();
203 throw new RepositorySourceException(source.getName(), msg.text(parentPath,
204 getName(),
205 source.getName(),
206 dataPropName));
207 }
208
209 BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
210 Binary binary = binaryFactory.create(properties.get(JcrLexicon.DATA).getFirstValue());
211
212 IoUtil.write(binary.getStream(), fos);
213
214 if (!FileUtil.delete(parentFile)) {
215 I18n msg = FileSystemI18n.deleteFailed;
216 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
217 }
218
219 if (!temp.renameTo(parentFile)) {
220 I18n msg = FileSystemI18n.couldNotUpdateData;
221 throw new RepositorySourceException(source.getName(), msg.text(parentPath, getName(), source.getName()));
222 }
223 } catch (IOException ioe) {
224 I18n msg = FileSystemI18n.couldNotWriteData;
225 throw new RepositorySourceException(source.getName(), msg.text(parentPath,
226 getName(),
227 source.getName(),
228 ioe.getMessage()), ioe);
229
230 } finally {
231 try {
232 if (fos != null) fos.close();
233 } catch (Exception ex) {
234 }
235 }
236 customPropertiesFactory.recordResourceProperties(context,
237 source.getName(),
238 Location.create(parentPath),
239 newFile,
240 properties);
241
242 } else if (JcrNtLexicon.FOLDER.equals(primaryType) || primaryType == null) {
243 ensureValidPathLength(newFile);
244
245 if (!newFile.exists() && !newFile.mkdir()) {
246 I18n msg = FileSystemI18n.couldNotCreateFile;
247 throw new RepositorySourceException(source.getName(),
248 msg.text(parentPath,
249 getName(),
250 source.getName(),
251 primaryType == null ? "null" : primaryType.getString(registry)));
252 }
253 customPropertiesFactory.recordDirectoryProperties(context,
254 source.getName(),
255 Location.create(newPath),
256 newFile,
257 properties);
258
259 } else {
260
261 I18n msg = FileSystemI18n.unsupportedPrimaryType;
262 throw new RepositorySourceException(source.getName(), msg.text(primaryType.getString(registry),
263 parentPath,
264 getName(),
265 source.getName()));
266 }
267
268 node = getNode(newPath);
269
270 return node;
271 }
272
273 @Override
274 public PathNode removeNode( Path nodePath ) {
275 File nodeFile;
276
277 if (!nodePath.isRoot() && JcrLexicon.CONTENT.equals(nodePath.getLastSegment().getName())) {
278 nodeFile = fileFor(nodePath.getParent());
279 if (!nodeFile.exists()) return null;
280
281 FileOutputStream fos = null;
282 try {
283 fos = new FileOutputStream(nodeFile);
284 IoUtil.write("", fos);
285 } catch (IOException ioe) {
286 throw new RepositorySourceException(source.getName(), FileSystemI18n.deleteFailed.text(nodePath,
287 getName(),
288 source.getName()));
289 } finally {
290 if (fos != null) try {
291 fos.close();
292 } catch (IOException ioe) {
293 }
294 }
295 } else {
296 nodeFile = fileFor(nodePath);
297 if (!nodeFile.exists()) return null;
298
299 FileUtil.delete(nodeFile);
300 }
301
302 return null;
303 }
304
305 @Override
306 public PathNode getRootNode() {
307 return getNode(context.getValueFactories().getPathFactory().createRootPath());
308 }
309
310 @Override
311 public PathNode getNode( Path path ) {
312 Map<Name, Property> properties = new HashMap<Name, Property>();
313
314 PropertyFactory factory = context.getPropertyFactory();
315 PathFactory pathFactory = context.getValueFactories().getPathFactory();
316 DateTimeFactory dateFactory = context.getValueFactories().getDateFactory();
317 MimeTypeDetector mimeTypeDetector = context.getMimeTypeDetector();
318 CustomPropertiesFactory customPropertiesFactory = source.customPropertiesFactory();
319 NamespaceRegistry registry = context.getNamespaceRegistry();
320 Location location = Location.create(path);
321
322 if (!path.isRoot() && JcrLexicon.CONTENT.equals(path.getLastSegment().getName())) {
323 File file = fileFor(path.getParent());
324 if (file == null) return null;
325
326 String mimeType = null;
327 InputStream contents = null;
328 try {
329 contents = new BufferedInputStream(new FileInputStream(file));
330 mimeType = mimeTypeDetector.mimeTypeOf(file.getName(), contents);
331 if (mimeType == null) mimeType = DEFAULT_MIME_TYPE;
332 properties.put(JcrLexicon.MIMETYPE, factory.create(JcrLexicon.MIMETYPE, mimeType));
333 } catch (IOException e) {
334 I18n msg = FileSystemI18n.couldNotReadData;
335 throw new RepositorySourceException(source.getName(), msg.text(source.getName(),
336 getName(),
337 path.getString(registry)));
338 } finally {
339 if (contents != null) {
340 try {
341 contents.close();
342 } catch (IOException e) {
343 }
344 }
345 }
346
347
348 Collection<Property> customProps = customPropertiesFactory.getResourceProperties(context, location, file, mimeType);
349 for (Property customProp : customProps) {
350 properties.put(customProp.getName(), customProp);
351 }
352
353
354
355
356 properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.RESOURCE));
357 properties.put(JcrLexicon.LAST_MODIFIED, factory.create(JcrLexicon.LAST_MODIFIED,
358 dateFactory.create(file.lastModified())));
359
360
361
362
363 BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
364 properties.put(JcrLexicon.DATA, factory.create(JcrLexicon.DATA, binaryFactory.create(file)));
365
366 return new PathNode(null, path.getParent(), path.getLastSegment(), properties, Collections.<Segment>emptyList());
367 }
368
369 File file = fileFor(path);
370 if (file == null) return null;
371
372 if (file.isDirectory()) {
373 String[] childNames = file.list(source.filenameFilter());
374 Arrays.sort(childNames);
375
376 List<Segment> childSegments = new ArrayList<Segment>(childNames.length);
377 for (String childName : childNames) {
378 childSegments.add(pathFactory.createSegment(childName));
379 }
380
381 Collection<Property> customProps = customPropertiesFactory.getDirectoryProperties(context, location, file);
382 for (Property customProp : customProps) {
383 properties.put(customProp.getName(), customProp);
384 }
385
386 if (path.isRoot()) {
387 properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.ROOT));
388
389 return new PathNode(source.getRootNodeUuidObject(), path.getParent(), path.getLastSegment(), properties,
390 childSegments);
391
392 }
393 properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER));
394
395 return new PathNode(null, path.getParent(), path.getLastSegment(), properties, childSegments);
396
397 }
398
399 Collection<Property> customProps = customPropertiesFactory.getFileProperties(context, location, file);
400 for (Property customProp : customProps) {
401 properties.put(customProp.getName(), customProp);
402 }
403 properties.put(JcrLexicon.PRIMARY_TYPE, factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE));
404 properties.put(JcrLexicon.CREATED, factory.create(JcrLexicon.CREATED, dateFactory.create(file.lastModified())));
405
406
407 return new PathNode(null, path.getParent(), path.getLastSegment(), properties,
408 Collections.singletonList(pathFactory.createSegment(JcrLexicon.CONTENT)));
409 }
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429 protected File fileFor( Path path ) {
430 return fileFor(path, true);
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453 protected File fileFor( Path path,
454 boolean existingFilesOnly ) {
455 if (path == null || path.isRoot()) {
456 return workspaceRoot;
457 }
458
459 if (path.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
460
461 path = path.getParent();
462 }
463 File file = workspaceRoot;
464 for (Path.Segment segment : path) {
465 String localName = segment.getName().getLocalName();
466
467 if (segment.getIndex() > 1) {
468 I18n msg = FileSystemI18n.sameNameSiblingsAreNotAllowed;
469 throw new RepositorySourceException(source.getName(), msg.text(source.getName()));
470 }
471
472 String defaultNamespaceUri = context.getNamespaceRegistry().getDefaultNamespaceUri();
473 if (!segment.getName().getNamespaceUri().equals(defaultNamespaceUri)) {
474 I18n msg = FileSystemI18n.onlyTheDefaultNamespaceIsAllowed;
475 throw new RepositorySourceException(source.getName(), msg.text(source.getName()));
476 }
477
478
479 file = new File(file, localName);
480
481 if (existingFilesOnly && (!file.canRead() || !file.exists())) {
482 return null;
483 }
484 }
485 assert file != null;
486 return file;
487 }
488
489 protected void validate( PathNode node ) {
490
491 if (node.getParent() == null) return;
492
493 NameFactory nameFactory = context.getValueFactories().getNameFactory();
494 Map<Name, Property> properties = node.getProperties();
495 Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE);
496 Name primaryType = primaryTypeProp == null ? JcrNtLexicon.FOLDER : nameFactory.create(primaryTypeProp.getFirstValue());
497
498 if (!VALID_PRIMARY_TYPES.contains(primaryType)) {
499
500 I18n msg = FileSystemI18n.unsupportedPrimaryType;
501 NamespaceRegistry registry = context.getNamespaceRegistry();
502 Path parentPath = node.getParent();
503 throw new RepositorySourceException(source.getName(), msg.text(primaryType.getString(registry),
504 parentPath,
505 getName(),
506 source.getName()));
507
508 }
509
510 Path nodePath = context.getValueFactories().getPathFactory().create(node.getParent(), node.getName());
511 ensureValidPathLength(fileFor(nodePath, false));
512 }
513
514 protected void ensureValidPathLength( File file ) {
515 ensureValidPathLength(file, 0);
516 }
517
518
519
520
521
522
523
524
525
526
527
528
529 protected void ensureValidPathLength( File root,
530 int delta ) {
531 try {
532 int len = root.getCanonicalPath().length();
533 if (len > source.getMaxPathLength() - delta) {
534 String msg = FileSystemI18n.maxPathLengthExceeded.text(source.getMaxPathLength(),
535 source.getName(),
536 root.getCanonicalPath(),
537 delta);
538 throw new RepositorySourceException(source.getName(), msg);
539 }
540
541 if (root.isDirectory()) {
542 for (File child : root.listFiles(source.filenameFilter())) {
543 ensureValidPathLength(child, delta);
544 }
545
546 }
547 } catch (IOException ioe) {
548 throw new RepositorySourceException(source.getName(), FileSystemI18n.getCanonicalPathFailed.text(), ioe);
549 }
550 }
551
552 }