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.module.resources;
35
36 import info.magnolia.cms.core.AggregationState;
37 import info.magnolia.link.LinkUtil;
38 import info.magnolia.resourceloader.Resource;
39 import info.magnolia.resourceloader.ResourceOrigin;
40 import info.magnolia.templating.functions.TemplatingFunctions;
41
42 import java.util.TimeZone;
43 import java.util.regex.Matcher;
44 import java.util.regex.Pattern;
45
46 import javax.inject.Inject;
47 import javax.inject.Provider;
48 import javax.inject.Singleton;
49
50 import org.apache.commons.lang3.StringUtils;
51 import org.apache.commons.lang3.time.FastDateFormat;
52
53
54
55
56
57 @Singleton
58 public class ResourceLinker {
59 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ResourceLinker.class);
60
61
62 private static final Pattern CACHE_PATTERN = Pattern.compile("[~.]\\d{4}(-\\d{2}){5}-\\d{3}[~.]cache");
63
64 private static final FastDateFormat TIME_STAMP_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd-HH-mm-ss-SSS", TimeZone.getTimeZone("GMT"));
65
66 private final ResourceOrigin origin;
67 private final Provider<ResourcesModule> configuration;
68 private final TemplatingFunctions templatingFunctions;
69 private final Provider<AggregationState> aggregationStateProvider;
70
71 @Inject
72 public ResourceLinker(ResourceOrigin origin, Provider<ResourcesModule> configuration, Provider<AggregationState> aggregationStateProvider, TemplatingFunctions templatingFunctions) {
73 this.origin = origin;
74 this.configuration = configuration;
75 this.aggregationStateProvider = aggregationStateProvider;
76 this.templatingFunctions = templatingFunctions;
77 }
78
79 public String getServletMapping() {
80 return cleanDownloadPath() + "/*";
81 }
82
83
84
85
86 protected String cleanDownloadPath() {
87 final String downloadPath = configuration.get().getDownloadPath();
88 return downloadPath.replaceFirst("^/*(.*?)/*$", "/$1");
89 }
90
91 public String linkTo(String path, boolean addFingerPrint) {
92 if (LinkUtil.EXTERNAL_LINK_PATTERN.matcher(path).matches()) {
93
94 log.debug("Ignoring request to transform a link to {}, it's already an external link.", path);
95 return path;
96 }
97
98
99
100 final String linkPrefix = templatingFunctions.linkPrefix(aggregationStateProvider.get().getMainContentNode());
101 final StringBuilder sb = new StringBuilder();
102 sb.append(linkPrefix);
103
104 final String servletMappingPrefix = cleanDownloadPath();
105
106
107 final String resourcePath;
108 if (path.startsWith(servletMappingPrefix)) {
109 resourcePath = path.substring(servletMappingPrefix.length());
110 } else {
111 resourcePath = path;
112 }
113
114 final boolean isKnownResource = origin.hasPath(resourcePath);
115 if (isKnownResource) {
116 sb.append(servletMappingPrefix);
117 if (addFingerPrint) {
118 final Resource resource = origin.getByPath(resourcePath);
119 final String fingerPrint = fingerPrintFor(resource);
120 final String ext = StringUtils.substringAfterLast(resourcePath, ".");
121 final String withoutExt = StringUtils.substringBeforeLast(resourcePath, ".");
122 sb.append(withoutExt).append("~").append(fingerPrint).append("~cache.").append(ext);
123 } else {
124 sb.append(resourcePath);
125 }
126 } else {
127
128 sb.append(path);
129 if (addFingerPrint) {
130 log.warn("Could not generate fingerprint for unknown resource {}", path);
131 }
132 }
133
134 return sb.toString();
135 }
136
137
138
139
140
141
142
143 public Resource getResource(String pathInfo) {
144 final String path = stripFarFutureCachingTimestamp(pathInfo);
145 if (!origin.hasPath(path)) {
146 return null;
147 }
148 return origin.getByPath(path);
149 }
150
151
152
153
154
155
156 protected String stripFarFutureCachingTimestamp(String resourcePath) {
157 final Matcher matcher = CACHE_PATTERN.matcher(resourcePath);
158 return matcher.replaceFirst("");
159 }
160
161 protected String fingerPrintFor(info.magnolia.resourceloader.Resource resource) {
162 final long lastMod = resource.getLastModified();
163 return TIME_STAMP_FORMAT.format(lastMod);
164 }
165
166 }