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