1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.resourceloader.file;
35
36 import info.magnolia.cms.util.ExceptionUtil;
37 import info.magnolia.dirwatch.DirectoryWatcherService;
38 import info.magnolia.init.MagnoliaConfigurationProperties;
39 import info.magnolia.resourceloader.AbstractResourceOrigin;
40 import info.magnolia.resourceloader.ResourceOriginFactory;
41 import info.magnolia.resourceloader.ResourceVisitor;
42
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.nio.charset.Charset;
46 import java.nio.file.DirectoryStream;
47 import java.nio.file.Files;
48 import java.nio.file.LinkOption;
49 import java.nio.file.Path;
50 import java.nio.file.Paths;
51 import java.nio.file.attribute.FileTime;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.List;
55
56 import org.apache.commons.io.FilenameUtils;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import com.google.auto.factory.AutoFactory;
61 import com.google.auto.factory.Provided;
62 import com.google.common.base.Function;
63 import com.google.common.base.Preconditions;
64 import com.google.common.collect.Iterables;
65 import com.google.common.collect.Lists;
66
67
68
69
70 @AutoFactory(implementing = ResourceOriginFactory.class)
71 public class FileSystemResourceOrigin extends AbstractResourceOrigin<FileSystemResource> {
72 private static final Logger log = LoggerFactory.getLogger(FileSystemResourceOrigin.class);
73
74 public static final String RESOURCES_DIR_PROPERTY = "magnolia.resources.dir";
75 private static final LinkOption[] LINK_OPTIONS = new LinkOption[0];
76 private static final List<String> EXCLUDED_DIRECTORIES = Arrays.asList("META-INF", "WEB-INF", "cache", "docroot", "logs", "repositories", "tmp");
77
78 private final Path rootPath;
79 private final DirectoryWatcherService directoryWatcherService;
80
81 private final ExclusionsFilter exclusionsFilter;
82 private final Function<Path, FileSystemResource> transformFunction;
83
84 FileSystemResourceOrigin(@Provided MagnoliaConfigurationProperties mcp, @Provided DirectoryWatcherService directoryWatcherService, String name) {
85 super(name);
86
87 String resourcesDir = mcp.getProperty(RESOURCES_DIR_PROPERTY);
88 if (resourcesDir == null) {
89 log.warn("Could not find magnolia.resources.dir property, please update your magnolia.properties. Falling back to magnolia.home.");
90 resourcesDir = mcp.getProperty("magnolia.home");
91 }
92 this.rootPath = validateRootPath(resourcesDir);
93 this.directoryWatcherService = directoryWatcherService;
94 this.exclusionsFilter = buildExclusionsFilter();
95 this.transformFunction = new Function<Path, FileSystemResource>() {
96 @Override
97 public FileSystemResource apply(Path path) {
98 return newResource(path);
99 }
100 };
101 }
102
103
104
105
106
107
108 protected ExclusionsFilter buildExclusionsFilter() {
109 return new ExclusionsFilter(rootPath, EXCLUDED_DIRECTORIES, Collections.<String>emptyList(), Collections.<String>emptyList());
110 }
111
112 @Override
113 public FileSystemResource getRoot() {
114 return newResource(rootPath);
115 }
116
117 @Override
118 public void watchForChanges(final ResourceVisitor visitor) {
119 log.info("Setting up {} to watch on {}", directoryWatcherService, rootPath);
120 try {
121
122 directoryWatcherService.register(rootPath, exclusionsFilter, new FileWatcherCallback(this, exclusionsFilter, new VisitorFunction(visitor)));
123 } catch (IOException e) {
124 throw new RuntimeException(e);
125 }
126 }
127
128 @Override
129 public FileSystemResource getByPath(final String path) {
130 final Path filePath = getPath(path);
131 if (!existsAndAllowed(filePath)) {
132 throw new ResourceNotFoundException(this, path);
133 }
134 return newResource(filePath);
135 }
136
137 @Override
138 public boolean hasPath(final String path) {
139 final Path filePath = getPath(path);
140 return existsAndAllowed(filePath);
141 }
142
143 @Override
144 protected boolean isFile(FileSystemResource resource) {
145 return Files.isRegularFile(resource.getRealPath());
146 }
147
148 @Override
149 protected boolean isDirectory(FileSystemResource resource) {
150 return Files.isDirectory(resource.getRealPath());
151 }
152
153 @Override
154 protected String getPath(FileSystemResource resource) {
155 return getStringPathForFilePath(resource.getRealPath());
156 }
157
158 private String getStringPathForFilePath(Path resource) {
159 final Path path = rootPath.relativize(resource);
160 return "/" + FilenameUtils.normalize(path.toString(), true);
161 }
162
163 @Override
164 protected String getName(FileSystemResource resource) {
165 return resource.getRealPath().getFileName().toString();
166 }
167
168 @Override
169 protected long getLastModified(FileSystemResource resource) {
170 try {
171 final FileTime lastModifiedTime = Files.getLastModifiedTime(resource.getRealPath(), LINK_OPTIONS);
172 return lastModifiedTime.toMillis();
173 } catch (IOException e) {
174 throw new RuntimeException("Last modified time could not be retrieved for path " + resource + " : " + e, e);
175 }
176 }
177
178 @Override
179 protected List<FileSystemResource> listChildren(FileSystemResource resource) {
180 if (resource.isFile()) {
181 throw new IllegalStateException(resource.getPath() + " is not a directory.");
182 }
183
184 Path parent = resource.getRealPath();
185
186 try (DirectoryStream<Path> ds = Files.newDirectoryStream(parent, exclusionsFilter)) {
187 return Lists.newArrayList(Iterables.transform(ds, transformFunction));
188 } catch (IOException e) {
189 throw new RuntimeException(e);
190 }
191 }
192
193 @Override
194 protected FileSystemResource getParent(FileSystemResource resource) {
195 if (resource == null) {
196 throw new IllegalStateException("The provided path is null.");
197 }
198 if (!resource.getRealPath().startsWith(rootPath)) {
199 throw new IllegalStateException("The provided path '" + resource + "' is not a descendant of this origin's root path.");
200 }
201 if (resource.getRealPath().equals(rootPath)) {
202 return null;
203 }
204 return newResource(resource.getRealPath().getParent());
205 }
206
207 @Override
208 protected InputStream doOpenStream(FileSystemResource resource) throws IOException {
209
210 final Path path = rootPath.resolve(resource.getRealPath());
211 return Files.newInputStream(path);
212 }
213
214 @Override
215 protected Charset getCharsetFor(FileSystemResource resource) {
216 return Charset.forName("UTF-8");
217 }
218
219 protected Path validateRootPath(String path) {
220 Path rootPath = Paths.get(path);
221 if (!Files.exists(rootPath) && Files.exists(rootPath.getParent()) && Files.isDirectory(rootPath.getParent())) {
222 try {
223 Files.createDirectory(rootPath);
224 } catch (IOException e) {
225 throw new IllegalStateException("Tried to create " + rootPath + " and failed: " + ExceptionUtil.exceptionToWords(e), e);
226 }
227 log.info("Created {}", rootPath);
228 }
229
230 if (Files.exists(rootPath) && !Files.isDirectory(rootPath)) {
231 throw new IllegalStateException("Trying to load resources from " + rootPath + ", which is not a directory.");
232 }
233
234 if (!Files.exists(rootPath)) {
235 log.warn("Trying to load resources from {}, but this directory does not exist", rootPath);
236 }
237 return rootPath;
238 }
239
240 protected Path getPath(String path) {
241 return Paths.get(rootPath.toString(), path);
242 }
243
244 protected boolean existsAndAllowed(Path filePath) {
245 return Files.exists(filePath) && exclusionsFilter.apply(filePath);
246 }
247
248
249
250
251
252
253 protected FileSystemResource newResource(Path path) {
254
255 final Path normPath;
256 if (!path.isAbsolute()) {
257 normPath = rootPath.resolve(path).normalize();
258 } else {
259 normPath = path;
260 }
261
262 if (!normPath.startsWith(rootPath)) {
263 throw new IllegalStateException(path + " is not within the bounds of the " + rootPath + " root.");
264 }
265 final boolean isDirectory = Files.isDirectory(normPath);
266 final boolean isFile = Files.isRegularFile(normPath);
267 Preconditions.checkArgument((isDirectory || isFile) && (isDirectory != isFile), "%s is neither or both a directory and/or a file.", normPath);
268 return new FileSystemResource(this, normPath);
269 }
270
271 }