1 /**
2 * This file Copyright (c) 2015-2017 Magnolia International
3 * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4 *
5 *
6 * This file is dual-licensed under both the Magnolia
7 * Network Agreement and the GNU General Public License.
8 * You may elect to use one or the other of these licenses.
9 *
10 * This file is distributed in the hope that it will be
11 * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12 * implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14 * Redistribution, except as permitted by whichever of the GPL
15 * or MNA you select, is prohibited.
16 *
17 * 1. For the GPL license (GPL), you can redistribute and/or
18 * modify this file under the terms of the GNU General
19 * Public License, Version 3, as published by the Free Software
20 * Foundation. You should have received a copy of the GNU
21 * General Public License, Version 3 along with this program;
22 * if not, write to the Free Software Foundation, Inc., 51
23 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24 *
25 * 2. For the Magnolia Network Agreement (MNA), this file
26 * and the accompanying materials are made available under the
27 * terms of the MNA which accompanies this distribution, and
28 * is available at http://www.magnolia-cms.com/mna.html
29 *
30 * Any modifications to this file must keep this entire header
31 * intact.
32 *
33 */
34 package info.magnolia.resourceloader.file;
35
36 import static info.magnolia.resourceloader.ResourceOriginChange.Type.*;
37 import static info.magnolia.resourceloader.ResourceOriginChange.resourceChange;
38
39 import info.magnolia.dirwatch.WatcherCallback;
40 import info.magnolia.resourceloader.ResourceOriginChange;
41
42 import java.io.IOException;
43 import java.nio.file.FileVisitResult;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.SimpleFileVisitor;
47 import java.nio.file.attribute.BasicFileAttributes;
48
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import com.google.common.base.Predicate;
53
54 /**
55 * A {@link WatcherCallback Watcher callback implementation} which dispatches the file system modification events to the {@link FileSystemResourceOrigin}.
56 */
57 class FileWatcherCallback implements WatcherCallback {
58
59 private static final Logger log = LoggerFactory.getLogger(FileWatcherCallback.class);
60
61 private final FileSystemResourceOrigin origin;
62 private final Predicate<Path> watchedPathFilter;
63
64 FileWatcherCallback(FileSystemResourceOrigin origin, Predicate<Path> watchedPathFilter) {
65 this.origin = origin;
66 this.watchedPathFilter = watchedPathFilter;
67 }
68
69 /**
70 * Contents of the added folders are recursively handled in the same fashion. This is done due to the fact
71 * that only a single event is generated by the file system when a file/folder is added.
72 *
73 * @see <a href="https://jira.magnolia-cms.com/browse/MAGNOLIA-6278">MAGNOLIA-6278</a>
74 */
75 @Override
76 public void added(Path path) {
77 dispatchResourceChange(path, ADDED, true);
78 }
79
80 /**
81 * Resource modifications are handled non-recursively since:
82 * - the case of a mere file is essentially non-recursive
83 * - most of the folder modification events are generated in addition to content structure modification (file/sub-directory removed/added).
84 * and => do not need additional care (even can be ignored)
85 */
86 @Override
87 public void modified(Path path) {
88 dispatchResourceChange(path, MODIFIED, false);
89 }
90
91 /**
92 * To be consistent with the logic of {@link #added(Path)} - contents of the removed folders have to be handled recursively as well.
93 * The bad thing is that by the time the folder deletion event is processed - its contents become non-resolvable
94 * and => cannot be easily traversed.
95 */
96 @Override
97 public void removed(Path path) {
98 dispatchResourceChange(path, REMOVED, false);
99 }
100
101 /**
102 * Communicate file system resource changes to the {@link FileSystemResource file system resource origin}.
103 *
104 * @param handleDirectoryContents indicates whether directory contents should be communicated (recursively) as well
105 */
106 private void dispatchResourceChange(Path changedResourcePath, ResourceOriginChange.Type type, boolean handleDirectoryContents) {
107 if (!watchedPathFilter.apply(changedResourcePath)) {
108 return;
109 }
110
111 final ResourceOriginChange.Builder resourceChange =
112 resourceChange().
113 ofType(type).
114 at(origin.parseResourcePath(changedResourcePath)).
115 inOrigin(origin);
116
117 origin.dispatchResourceChange(resourceChange.build());
118
119 if (handleDirectoryContents && Files.isDirectory(changedResourcePath)) {
120 try {
121 Files.walkFileTree(changedResourcePath, new SimpleFileVisitor<Path>() {
122 @Override
123 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
124 if (watchedPathFilter.apply(file)) {
125 origin.dispatchResourceChange(resourceChange.at(origin.parseResourcePath(file)).build());
126 }
127 return FileVisitResult.CONTINUE;
128 }
129 });
130 } catch (IOException e) {
131 log.error("Failed to communicate file system resource changes recursively: {}", e.getMessage(), e);
132 }
133 }
134 }
135 }