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.imaging.caching;
35
36 import info.magnolia.cms.beans.runtime.FileProperties;
37 import info.magnolia.cms.core.Content;
38 import info.magnolia.cms.core.HierarchyManager;
39 import info.magnolia.cms.core.NodeData;
40 import info.magnolia.cms.util.ContentUtil;
41 import info.magnolia.cms.util.NodeDataUtil;
42 import info.magnolia.context.MgnlContext;
43 import info.magnolia.imaging.ImageGenerator;
44 import info.magnolia.imaging.ImageStreamer;
45 import info.magnolia.imaging.ImagingException;
46 import info.magnolia.imaging.ParameterProvider;
47
48 import java.io.ByteArrayInputStream;
49 import java.io.ByteArrayOutputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.util.Calendar;
54 import java.util.concurrent.ExecutionException;
55 import java.util.concurrent.TimeUnit;
56 import java.util.concurrent.locks.ReentrantLock;
57
58 import javax.jcr.PropertyType;
59 import javax.jcr.RepositoryException;
60
61 import org.apache.commons.io.IOUtils;
62
63 import com.google.common.cache.CacheBuilder;
64 import com.google.common.cache.CacheLoader;
65 import com.google.common.cache.LoadingCache;
66
67
68
69
70
71
72
73 public class CachingImageStreamer<P> implements ImageStreamer<P> {
74 private static final String GENERATED_IMAGE_PROPERTY = "generated-image";
75
76 private final HierarchyManager hm;
77 private final CachingStrategy<P> cachingStrategy;
78 private final ImageStreamer<P> delegate;
79
80
81
82
83
84
85
86
87
88
89
90
91 private final LoadingCache<ImageGenerationJob<P>, NodeData> currentJobs;
92
93
94
95
96
97
98
99
100
101
102 private static final ReentrantLock lock = new ReentrantLock();
103
104 public CachingImageStreamer(HierarchyManager hm, CachingStrategy<P> cachingStrategy, ImageStreamer<P> delegate) {
105 this.hm = hm;
106 this.cachingStrategy = cachingStrategy;
107 this.delegate = delegate;
108
109 CacheBuilder<Object, Object> cb = CacheBuilder.newBuilder();
110 this.currentJobs = cb
111
112
113 .expireAfterWrite(500, TimeUnit.MILLISECONDS)
114
115
116 .build(new CacheLoader<ImageGenerationJob<P>, NodeData>() {
117
118 @Override
119 public NodeData load(ImageGenerationJob<P> job) throws Exception {
120 try {
121 return generateAndStore(job.getGenerator(), job.getParams());
122 } catch (IOException e) {
123
124 throw new RuntimeException(e);
125 } catch (ImagingException e) {
126
127 throw new RuntimeException(e);
128 }
129 }
130
131 });
132 }
133
134 @Override
135 public void serveImage(ImageGenerator<ParameterProvider<P>> generator, ParameterProvider<P> params, OutputStream out) throws IOException, ImagingException {
136 NodeData imgProp = fetchFromCache(generator, params);
137 if (imgProp == null) {
138
139 try {
140 imgProp = currentJobs.get(new ImageGenerationJob<P>(generator, params));
141 } catch (ExecutionException e) {
142
143 unwrapRuntimeException(e);
144 }
145 }
146 serve(imgProp, out);
147 }
148
149
150
151
152
153 protected NodeData fetchFromCache(ImageGenerator<ParameterProvider<P>> generator, ParameterProvider<P> parameterProvider) {
154 final String cachePath = cachingStrategy.getCachePath(generator, parameterProvider);
155 if (cachePath == null) {
156
157 return null;
158 }
159 try {
160 if (!hm.isExist(cachePath)) {
161 return null;
162 }
163 final Content imageNode = hm.getContent(cachePath);
164 final NodeData nodeData = imageNode.getNodeData(GENERATED_IMAGE_PROPERTY);
165 if (!nodeData.isExist()) {
166 return null;
167 }
168 InputStream in = null;
169 try {
170 in = nodeData.getStream();
171 } catch (Exception e) {
172
173
174 return null;
175 }
176 IOUtils.closeQuietly(in);
177
178 if (cachingStrategy.shouldRegenerate(nodeData, parameterProvider)) {
179 return null;
180 }
181 return nodeData;
182 } catch (RepositoryException e) {
183 throw new RuntimeException(e);
184 }
185 }
186
187
188 protected void serve(NodeData binary, OutputStream out) throws IOException {
189 final InputStream in = binary.getStream();
190 if (in == null) {
191 throw new IllegalStateException("Can't get InputStream from " + binary.getHandle());
192 }
193 IOUtils.copy(in, out);
194 IOUtils.closeQuietly(in);
195 IOUtils.closeQuietly(out);
196 }
197
198 protected NodeData generateAndStore(final ImageGenerator<ParameterProvider<P>> generator, final ParameterProvider<P> parameterProvider) throws IOException, ImagingException {
199
200 final ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
201 delegate.serveImage(generator, parameterProvider, tempOut);
202
203
204 lock.lock();
205 try {
206 return MgnlContext.doInSystemContext(new MgnlContext.Op<NodeData, RepositoryException>() {
207 @Override
208 public NodeData exec() throws RepositoryException {
209 HierarchyManager systemHM = MgnlContext.getHierarchyManager(hm.getName());
210
211 final String cachePath = cachingStrategy.getCachePath(generator, parameterProvider);
212 final Content cacheNode = ContentUtil.createPath(systemHM, cachePath, false);
213 final NodeData imageData = NodeDataUtil.getOrCreate(cacheNode, GENERATED_IMAGE_PROPERTY, PropertyType.BINARY);
214
215
216 final ByteArrayInputStream tempIn = new ByteArrayInputStream(tempOut.toByteArray());
217 imageData.setValue(tempIn);
218
219 imageData.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, "image/" + generator.getOutputFormat(parameterProvider).getFormatName());
220 imageData.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, Calendar.getInstance());
221
222
223
224 cacheNode.getMetaData().setModificationDate();
225
226
227 systemHM.save();
228 return imageData;
229 }
230 });
231 } catch (RepositoryException e) {
232 throw new ImagingException("Can't store rendered image: " + e.getMessage(), e);
233 } finally {
234 lock.unlock();
235 }
236 }
237
238
239
240
241
242
243
244 private void unwrapRuntimeException(Exception e) throws ImagingException, IOException {
245 final Throwable cause = e.getCause();
246 if (cause instanceof ImagingException) {
247 throw (ImagingException) cause;
248 } else if (cause instanceof IOException) {
249 throw (IOException) cause;
250 } else if (cause instanceof RuntimeException) {
251 unwrapRuntimeException((RuntimeException) cause);
252 } else if (cause == null) {
253
254 throw new IllegalStateException("Unexpected and unhandled exception: " + (e.getMessage() != null ? e.getMessage() : ""), e);
255 } else {
256
257 throw new ImagingException(e.getMessage(), cause);
258 }
259 }
260 }