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.ui.contentapp.preview;
35
36 import info.magnolia.cms.beans.runtime.FileProperties;
37 import info.magnolia.jcr.util.NodeTypes.LastModified;
38 import info.magnolia.jcr.util.NodeUtil;
39 import info.magnolia.link.LinkUtil;
40 import info.magnolia.ui.contentapp.browser.preview.PreviewProvider;
41 import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
42
43 import java.io.InputStream;
44 import java.util.Calendar;
45 import java.util.Optional;
46 import java.util.Set;
47
48 import javax.inject.Provider;
49 import javax.jcr.Item;
50 import javax.jcr.Node;
51 import javax.jcr.RepositoryException;
52 import javax.servlet.http.HttpServletRequest;
53
54 import org.apache.jackrabbit.JcrConstants;
55 import org.apache.jackrabbit.util.Text;
56
57 import com.google.common.collect.ImmutableSet;
58 import com.google.common.net.MediaType;
59 import com.vaadin.server.ExternalResource;
60 import com.vaadin.server.Resource;
61 import com.vaadin.server.StreamResource;
62 import com.vaadin.server.StreamResource.StreamSource;
63
64 import lombok.Getter;
65 import lombok.Setter;
66 import lombok.SneakyThrows;
67
68
69
70
71
72
73
74
75 @Setter
76 @Getter
77 public class JcrPreviewProvider implements PreviewProvider<Item> {
78
79 private static final Set<String> UNSUPPORTED_IMAGE_TYPES = ImmutableSet.of(
80 "image/x-icon",
81 "image/x-portable-bitmap",
82 "image/x-xpixmap",
83 "image/x-portable-graymap",
84 "image/x-cmu-raster",
85 "image/x-portable-pixmap",
86 "image/x-xbitmap",
87 "image/x-rgb",
88 "image/x-xwindowdump",
89 "image/x-cmx",
90 "image/cis-cod",
91 "image/ief"
92 );
93
94 private static final Set<String> SUPPORTED_AUDIO_TYPES = ImmutableSet.of(
95 MediaType.MPEG_AUDIO.toString(),
96 MediaType.WEBM_AUDIO.toString(),
97 MediaType.OGG_AUDIO.toString(),
98 MediaType.MP4_AUDIO.toString(),
99 MediaType.AAC_AUDIO.toString(),
100 "audio/aacp",
101 "audio/flac",
102 "audio/wav",
103 "audio/x-wav",
104 "audio/mp3"
105 );
106
107 private static final Set<String> SUPPORTED_VIDEO_TYPES = ImmutableSet.of(
108 MediaType.MPEG_VIDEO.toString(),
109 MediaType.WEBM_VIDEO.toString(),
110 MediaType.MP4_VIDEO.toString(),
111 MediaType.OGG_VIDEO.toString()
112 );
113
114 private final Provider<HttpServletRequest> requestProvider;
115 private final JcrPreviewDefinition previewDefinition;
116
117 public JcrPreviewProvider(JcrDatasourceDefinition datasourceDefinition, Provider<HttpServletRequest> requestProvider) {
118 this.requestProvider = requestProvider;
119 this.previewDefinition = datasourceDefinition.getPreviewDefinition();
120 }
121
122 @Override
123 public Optional<Resource> getResource(Item item) {
124 return getPreviewNode(item).map(node -> {
125 String mimeType = getMimeType(node);
126 if (mimeType != null) {
127 if (mimeType.matches("image.*")) {
128 return imageResource(mimeType, (Node) item, node);
129 } else if (SUPPORTED_AUDIO_TYPES.contains(mimeType)) {
130 return new ExternalResource(LinkUtil.createAbsoluteLink(node), mimeType);
131 } else if (SUPPORTED_VIDEO_TYPES.contains(mimeType)) {
132 return new ExternalResource(LinkUtil.createAbsoluteLink(node), mimeType);
133 }
134 }
135 return null;
136 });
137 }
138
139 @SneakyThrows(RepositoryException.class)
140 private Optional<Node> getPreviewNode(Item item) {
141 if (item.isNode() && ((Node) item).hasNode(previewDefinition.getNodeName())) {
142 return Optional.of(((Node) item).getNode(previewDefinition.getNodeName()));
143 }
144 return Optional.empty();
145 }
146
147 @SneakyThrows(RepositoryException.class)
148 private Resource imageResource(String mimeType, Node item, Node imageNode) {
149 if (MediaType.SVG_UTF_8.is(MediaType.parse(mimeType)) || MediaType.ICO.is(MediaType.parse(mimeType))) {
150 ImageStreamSource iss = new ImageStreamSource(imageNode.getIdentifier(), imageNode.getSession().getWorkspace().getName());
151
152 String filename = imageNode.getIdentifier() + LastModified.getLastModified(imageNode).getTimeInMillis();
153 StreamResource streamResource = new StreamResource(iss, filename);
154 streamResource.setMIMEType(mimeType);
155 return streamResource;
156 } else if (!UNSUPPORTED_IMAGE_TYPES.contains(mimeType)) {
157 return resolveImagePath(item, imageNode, previewDefinition.getGenerator()).map(path -> new ExternalResource(path, MediaType.PNG.toString())).orElse(null);
158 }
159 return null;
160 }
161
162 private Optional<String> resolveImagePath(Node node, Node imageNode, String generator) throws RepositoryException {
163 String imageName;
164 if (imageNode.hasProperty(FileProperties.PROPERTY_FILENAME)) {
165 imageName = imageNode.getProperty(FileProperties.PROPERTY_FILENAME).getString();
166 } else {
167 imageName = node.getName();
168 }
169
170
171
172
173 String imagePath = Text.escapePath(requestProvider.get().getContextPath() + "/" + previewDefinition.getServletPath() + "/" + generator + "/" + node.getSession().getWorkspace().getName() + "/" + imageNode.getIdentifier() + "/" + imageName + "." + previewDefinition.getExtension());
174
175 Calendar lastModified = LastModified.getLastModified(node);
176 imagePath = LinkUtil.addFingerprintToLink(imagePath, lastModified);
177 return Optional.ofNullable(imagePath);
178 }
179
180 @SneakyThrows(RepositoryException.class)
181 private String getMimeType(Node imageNode) {
182 return imageNode.getProperty(JcrConstants.JCR_MIMETYPE).getString();
183 }
184
185 private static class ImageStreamSource implements StreamSource {
186 private final String id;
187 private final String workspace;
188
189 private ImageStreamSource(String id, String workspace) {
190 this.id = id;
191 this.workspace = workspace;
192 }
193
194 @SneakyThrows(RepositoryException.class)
195 @Override
196 public InputStream getStream() {
197 Node node = NodeUtil.getNodeByIdentifier(workspace, id);
198 if (node != null && node.hasProperty(JcrConstants.JCR_DATA)) {
199 return node.getProperty(JcrConstants.JCR_DATA).getBinary().getStream();
200 }
201 return null;
202 }
203 }
204 }