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 .build(new CacheLoader<ImageGenerationJob<P>, NodeData>() {
116
117 @Override
118 public NodeData load(ImageGenerationJob<P> job) throws Exception {
119 try {
120 return generateAndStore(job.getGenerator(), job.getParams());
121 } catch (IOException e) {
122
123 throw new RuntimeException(e);
124 } catch (ImagingException e) {
125
126 throw new RuntimeException(e);
127 }
128 }
129
130 });
131 }
132
133 @Override
134 public void serveImage(ImageGenerator<ParameterProvider<P>> generator, ParameterProvider<P> params, OutputStream out) throws IOException, ImagingException {
135 NodeData imgProp = fetchFromCache(generator, params);
136 if (imgProp == null) {
137
138 try {
139 imgProp = currentJobs.get(new ImageGenerationJob<P>(generator, params));
140 } catch (ExecutionException e) {
141
142 unwrapRuntimeException(e);
143 }
144 }
145 serve(imgProp, out);
146 }
147
148
149
150
151
152 protected NodeData fetchFromCache(ImageGenerator<ParameterProvider<P>> generator, ParameterProvider<P> parameterProvider) {
153 final String cachePath = cachingStrategy.getCachePath(generator, parameterProvider);
154 if (cachePath == null) {
155
156 return null;
157 }
158 try {
159 if (!hm.isExist(cachePath)) {
160 return null;
161 }
162 final Content imageNode = hm.getContent(cachePath);
163 final NodeData nodeData = imageNode.getNodeData(GENERATED_IMAGE_PROPERTY);
164 if (!nodeData.isExist()) {
165 return null;
166 }
167 InputStream in = null;
168 try {
169 in = nodeData.getStream();
170 } catch (Exception e) {
171
172
173 return null;
174 }
175 IOUtils.closeQuietly(in);
176
177 if (cachingStrategy.shouldRegenerate(nodeData, parameterProvider)) {
178 return null;
179 }
180 return nodeData;
181 } catch (RepositoryException e) {
182 throw new RuntimeException(e);
183 }
184 }
185
186
187 protected void serve(NodeData binary, OutputStream out) throws IOException {
188 final InputStream in = binary.getStream();
189 if (in == null) {
190 throw new IllegalStateException("Can't get InputStream from " + binary.getHandle());
191 }
192 IOUtils.copy(in, out);
193 IOUtils.closeQuietly(in);
194 IOUtils.closeQuietly(out);
195 }
196
197 protected NodeData generateAndStore(final ImageGenerator<ParameterProvider<P>> generator, final ParameterProvider<P> parameterProvider) throws IOException, ImagingException {
198
199 final ByteArrayOutputStream tempOut = new ByteArrayOutputStream();
200 delegate.serveImage(generator, parameterProvider, tempOut);
201
202
203 lock.lock();
204 try {
205 return MgnlContext.doInSystemContext(new MgnlContext.Op<NodeData, RepositoryException>() {
206 @Override
207 public NodeData exec() throws RepositoryException {
208 HierarchyManager systemHM = MgnlContext.getHierarchyManager(hm.getName());
209
210 final String cachePath = cachingStrategy.getCachePath(generator, parameterProvider);
211 final Content cacheNode = ContentUtil.createPath(systemHM, cachePath, false);
212 final NodeData imageData = NodeDataUtil.getOrCreate(cacheNode, GENERATED_IMAGE_PROPERTY, PropertyType.BINARY);
213
214
215 final ByteArrayInputStream tempIn = new ByteArrayInputStream(tempOut.toByteArray());
216 imageData.setValue(tempIn);
217
218 imageData.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, "image/" + generator.getOutputFormat(parameterProvider).getFormatName());
219 imageData.setAttribute(FileProperties.PROPERTY_LASTMODIFIED, Calendar.getInstance());
220
221
222
223 cacheNode.getMetaData().setModificationDate();
224
225
226 systemHM.save();
227 return imageData;
228 }
229 });
230 } catch (RepositoryException e) {
231 throw new ImagingException("Can't store rendered image: " + e.getMessage(), e);
232 } finally {
233 lock.unlock();
234 }
235 }
236
237
238
239
240
241
242
243 private void unwrapRuntimeException(Exception e) throws ImagingException, IOException {
244 final Throwable cause = e.getCause();
245 if (cause instanceof ImagingException) {
246 throw (ImagingException) cause;
247 } else if (cause instanceof IOException) {
248 throw (IOException) cause;
249 } else if (cause instanceof RuntimeException) {
250 unwrapRuntimeException((RuntimeException) cause);
251 } else if (cause == null) {
252
253 throw new IllegalStateException("Unexpected and unhandled exception: " + (e.getMessage() != null ? e.getMessage() : ""), e);
254 } else {
255
256 throw new ImagingException(e.getMessage(), cause);
257 }
258 }
259 }