View Javadoc
1   /*
2    * Copyright (C) 2003-2007 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.wcm.connector.collaboration;
18  
19  import java.awt.image.BufferedImage;
20  import java.io.BufferedInputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.InputStream;
24  import java.text.DateFormat;
25  import java.text.SimpleDateFormat;
26  import java.util.ArrayList;
27  import java.util.Date;
28  
29  import javax.jcr.*;
30  import javax.ws.rs.GET;
31  import javax.ws.rs.HeaderParam;
32  import javax.ws.rs.Path;
33  import javax.ws.rs.PathParam;
34  import javax.ws.rs.core.Response;
35  
36  import org.exoplatform.container.component.ComponentPlugin;
37  import org.exoplatform.ecm.utils.text.Text;
38  import org.exoplatform.services.cms.link.LinkManager;
39  import org.exoplatform.services.cms.link.NodeFinder;
40  import org.exoplatform.services.cms.thumbnail.ThumbnailPlugin;
41  import org.exoplatform.services.cms.thumbnail.ThumbnailService;
42  import org.exoplatform.services.cms.thumbnail.impl.ThumbnailUtils;
43  import org.exoplatform.services.jcr.RepositoryService;
44  import org.exoplatform.services.jcr.core.ManageableRepository;
45  import org.exoplatform.services.jcr.impl.core.NodeImpl;
46  import org.exoplatform.services.log.ExoLogger;
47  import org.exoplatform.services.log.Log;
48  import org.exoplatform.services.rest.resource.ResourceContainer;
49  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
50  
51  /**
52   * Returns a responding data as a thumbnail image.
53   * {{{{repoName}}}}: The name of repository.
54   * {{{{workspaceName}}}}: The name of workspace.
55   * {{{{nodePath}}}}: The node path.
56   *
57   * {{{{portalname}}}}: The name of the portal.
58   * {{{{restcontextname}}}}: The context name of REST web application which is deployed to the "{{{{portalname}}}}" portal.
59   *
60   * @LevelAPI Provisional
61   * @anchor ThumbnailRESTService
62   */
63  @Path("/thumbnailImage/")
64  public class ThumbnailRESTService implements ResourceContainer {
65  
66    /** The log **/
67    private static final Log LOG  = ExoLogger.getLogger(ThumbnailRESTService.class.getName());
68    
69    /** The Constant LAST_MODIFIED_PROPERTY. */
70    private static final String LAST_MODIFIED_PROPERTY = "Last-Modified";
71  
72    /** The Constant IF_MODIFIED_SINCE_DATE_FORMAT. */
73    private static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
74  
75    private final RepositoryService repositoryService_;
76    private final ThumbnailService thumbnailService_;
77    private final NodeFinder nodeFinder_;
78    private final LinkManager linkManager_;
79  
80    public ThumbnailRESTService(RepositoryService repositoryService,
81                                ThumbnailService thumbnailService,
82                                NodeFinder nodeFinder,
83                                LinkManager linkManager) {
84      repositoryService_ = repositoryService;
85      thumbnailService_ = thumbnailService;
86      nodeFinder_ = nodeFinder;
87      linkManager_ = linkManager;
88    }
89  
90  /**
91   * Returns an image with a medium size (64x64).
92   * For example: /portal/rest/thumbnailImage/medium/repository/collaboration/test.gif/
93   *
94   * @param repoName The repository name.
95   * @param workspaceName The workspace name.
96   * @param nodePath The node path.
97   * @return Response inputstream.
98   * @throws Exception
99   *
100  * @anchor ThumbnailRESTService.getThumbnailImage
101  */
102   @Path("/medium/{repoName}/{workspaceName}/{nodePath:.*}/")
103   @GET
104   public Response getThumbnailImage(@PathParam("repoName") String repoName,
105                                     @PathParam("workspaceName") String workspaceName,
106                                     @PathParam("nodePath") String nodePath,
107                                     @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
108     return getThumbnailByType(workspaceName,
109                               nodePath,
110                               ThumbnailService.MEDIUM_SIZE,
111                               ifModifiedSince);
112   }
113 
114 /**
115  * Returns an image with a big size.
116  *
117  * @param repoName The repository name.
118  * @param workspaceName The workspace name.
119  * @param nodePath The node path.
120  * @return Response inputstream.
121  * @throws Exception
122  *
123  * @anchor ThumbnailRESTService.getCoverImage
124  */
125   @Path("/big/{repoName}/{workspaceName}/{nodePath:.*}/")
126   @GET
127   public Response getCoverImage(@PathParam("repoName") String repoName,
128                                 @PathParam("workspaceName") String workspaceName,
129                                 @PathParam("nodePath") String nodePath,
130                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
131     return getThumbnailByType(workspaceName, nodePath, ThumbnailService.BIG_SIZE, ifModifiedSince);
132   }
133 
134 /**
135  * Returns an image with a large size (300x300).
136  *
137  * @param repoName The repository name.
138  * @param workspaceName The workspace name.
139  * @param nodePath The node path.
140  * @return Response inputstream.
141  * @throws Exception
142  *
143  * @anchor ThumbnailRESTService.getLargeImage
144  */
145   @Path("/large/{repoName}/{workspaceName}/{nodePath:.*}/")
146   @GET
147   public Response getLargeImage(@PathParam("repoName") String repoName,
148                                 @PathParam("workspaceName") String workspaceName,
149                                 @PathParam("nodePath") String nodePath,
150                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
151     return getThumbnailByType(workspaceName, nodePath, ThumbnailService.BIG_SIZE, ifModifiedSince);
152   }
153 
154 /**
155  * Returns an image with a small size (32x32).
156  *
157  * @param repoName The repository name.
158  * @param workspaceName The workspace name.
159  * @param nodePath The node path.
160  * @return Response inputstream.
161  * @throws Exception
162  *
163  * @anchor ThumbnailRESTService.getSmallImage
164  */
165   @Path("/small/{repoName}/{workspaceName}/{nodePath:.*}/")
166   @GET
167   public Response getSmallImage(@PathParam("repoName") String repoName,
168                                 @PathParam("workspaceName") String workspaceName,
169                                 @PathParam("nodePath") String nodePath,
170                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
171     return getThumbnailByType(workspaceName, nodePath, ThumbnailService.SMALL_SIZE, ifModifiedSince);
172   }
173 
174 /**
175  * Returns an image with a custom size.
176  *
177  * @param size The customized size of the image.
178  * @param repoName The repository name.
179  * @param workspaceName The workspace name.
180  * @param nodePath The node path.
181  * @return Response inputstream.
182  * @throws Exception
183  *
184  * @anchor ThumbnailRESTService.getCustomImage
185  */
186   @Path("/custom/{size}/{repoName}/{workspaceName}/{nodePath:.*}/")
187   @GET
188   public Response getCustomImage(@PathParam("size") String size,
189                                 @PathParam("repoName") String repoName,
190                                 @PathParam("workspaceName") String workspaceName,
191                                 @PathParam("nodePath") String nodePath,
192                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
193     return getThumbnailByType(workspaceName, nodePath, "exo:"+size, ifModifiedSince);
194   }
195 
196   /**
197    * Returns an image with an original size.
198    *
199    * @param repoName The repository name.
200    * @param workspaceName The workspace name.
201    * @param nodePath The node path.
202    * @return Response data stream.
203    * @throws Exception
204    *
205    * @anchor ThumbnailRESTService.getOriginImage
206    */
207   @Path("/origin/{repoName}/{workspaceName}/{nodePath:.*}/")
208   @GET
209   public Response getOriginImage(@PathParam("repoName") String repoName,
210                                  @PathParam("workspaceName") String workspaceName,
211                                  @PathParam("nodePath") String nodePath,
212                                  @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
213     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
214     if (!thumbnailService_.isEnableThumbnail())
215       return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
216     Node showingNode = getShowingNode(workspaceName, nodePath);
217     Node targetNode = getTargetNode(showingNode);
218     if (targetNode.isNodeType("nt:file") || targetNode.isNodeType("nt:resource")) {
219       Node content = targetNode;
220       if (targetNode.isNodeType("nt:file"))
221         content = targetNode.getNode("jcr:content");
222       if (ifModifiedSince != null) {
223         // get last-modified-since from header
224         Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);
225 
226         // get last modified date of node
227         Date lastModifiedDate = content.getProperty("jcr:lastModified").getDate().getTime();
228 
229         // Check if cached resource has not been modifed, return 304 code
230         if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
231           return Response.notModified().build();
232         }
233       }
234 
235       String mimeType = content.getProperty("jcr:mimeType").getString();
236       for (ComponentPlugin plugin : thumbnailService_.getComponentPlugins()) {
237         if (plugin instanceof ThumbnailPlugin) {
238           ThumbnailPlugin thumbnailPlugin = (ThumbnailPlugin) plugin;
239           if (thumbnailPlugin.getMimeTypes().contains(mimeType)) {
240             InputStream inputStream = content.getProperty("jcr:data").getStream();
241             return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
242                                                             dateFormat.format(new Date())).build();
243           }
244         }
245       }
246     }
247 
248     return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
249   }
250   
251   /**
252    * Return an image at an original size from local machine.
253    *
254    * @param nodePath The node path.
255    * @return Response data stream.
256    * @throws Exception
257    *
258    * @anchor ThumbnailRESTService.getOriginImage
259    */
260   @Path("/originImage/{nodePath:.*}/")
261   @GET
262   public Response getLocalImage(@PathParam("nodePath") String nodePath,
263                                 @HeaderParam("If-Modified-Since") String ifModifiedSince) throws Exception {
264     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
265     if (!thumbnailService_.isEnableThumbnail())
266       return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
267 
268     
269     try {
270       File file = new File(nodePath);
271       FileInputStream inputStream = new FileInputStream(file);
272       BufferedInputStream buf = new BufferedInputStream(inputStream);
273       
274       return Response.ok(buf, "image").header(LAST_MODIFIED_PROPERTY,
275                                                           dateFormat.format(new Date())).build();
276     } catch (Exception e) {
277       if (LOG.isErrorEnabled()) {
278         LOG.error(e);
279       }
280     }
281     return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
282   }
283 
284   private Response getThumbnailByType(String workspaceName, String nodePath,
285       String propertyName, String ifModifiedSince) throws Exception {
286     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
287     if (!thumbnailService_.isEnableThumbnail())
288       return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
289     Node showingNode = getShowingNode(workspaceName, nodePath);
290     String identifier = ((NodeImpl) showingNode).getInternalIdentifier();
291     Node targetNode = getTargetNode(showingNode);
292     Node parentNode = null;
293     try {
294       parentNode = showingNode.getParent();
295     } catch (AccessDeniedException e) {
296       Session session = WCMCoreUtils.getSystemSessionProvider().getSession(workspaceName,
297                                                                            repositoryService_.getCurrentRepository());
298       parentNode = session.getItem(showingNode.getPath()).getParent();
299     }
300     if(targetNode.isNodeType("nt:file")) {
301       Node content = targetNode.getNode("jcr:content");
302       String mimeType = content.getProperty("jcr:mimeType").getString();
303       for(ComponentPlugin plugin : thumbnailService_.getComponentPlugins()) {
304         if(plugin instanceof ThumbnailPlugin) {
305           ThumbnailPlugin thumbnailPlugin = (ThumbnailPlugin) plugin;
306           if(thumbnailPlugin.getMimeTypes().contains(mimeType)) {
307             Node thumbnailFolder = ThumbnailUtils.getThumbnailFolder(parentNode);
308 
309             Node thumbnailNode = ThumbnailUtils.getThumbnailNode(thumbnailFolder, identifier);
310 
311             if(!thumbnailNode.hasProperty(propertyName)) {
312               try {
313                 BufferedImage image = thumbnailPlugin.getBufferedImage(content, targetNode.getPath());
314                 thumbnailService_.addThumbnailImage(thumbnailNode, image, propertyName);
315               } catch (Exception e) {
316             	throw new Exception("Failed to get image.", e);            	
317               }
318             }
319 
320             if(ifModifiedSince != null && thumbnailNode.hasProperty(ThumbnailService.THUMBNAIL_LAST_MODIFIED)) {
321               // get last-modified-since from header
322               Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);
323 
324               // get last modified date of node
325               Date lastModifiedDate = thumbnailNode.getProperty(ThumbnailService.THUMBNAIL_LAST_MODIFIED)
326                                                    .getDate()
327                                                    .getTime();
328 
329               // Check if cached resource has not been modifed, return 304 code
330               if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
331                 return Response.notModified().build();
332               }
333             }
334             InputStream inputStream = null;
335             if(thumbnailNode.hasProperty(propertyName)) {
336               inputStream = thumbnailNode.getProperty(propertyName).getStream();
337             }
338             return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
339                                                             dateFormat.format(new Date())).build();
340           }
341         }
342       }
343     }
344     return getThumbnailRes(parentNode, identifier, propertyName, ifModifiedSince);
345   }
346 
347   private Response getThumbnailRes(Node parentNode,
348                                    String identifier,
349                                    String propertyName,
350                                    String ifModifiedSince) throws Exception {
351 
352     DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
353     if(parentNode.hasNode(ThumbnailService.EXO_THUMBNAILS_FOLDER)) {
354       Node thumbnailFolder = parentNode.getNode(ThumbnailService.EXO_THUMBNAILS_FOLDER);
355       if(thumbnailFolder.hasNode(identifier)) {
356         Node thumbnailNode = thumbnailFolder.getNode(identifier);
357         if (ifModifiedSince != null && thumbnailNode.hasProperty("exo:dateModified")) {
358           // get last-modified-since from header
359           Date ifModifiedSinceDate = dateFormat.parse(ifModifiedSince);
360 
361           // get last modified date of node
362           Date lastModifiedDate = thumbnailNode.getProperty("exo:dateModified").getDate().getTime();
363 
364           // Check if cached resource has not been modified, return 304 code
365           if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
366             return Response.notModified().build();
367           }
368         }
369 
370         if(thumbnailNode.hasProperty(propertyName)) {
371           InputStream inputStream = thumbnailNode.getProperty(propertyName).getStream();
372           return Response.ok(inputStream, "image").header(LAST_MODIFIED_PROPERTY,
373                                                           dateFormat.format(new Date())).build();
374         }
375       }
376     }
377     return Response.ok().header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date())).build();
378   }
379 
380   private String getNodePath(String nodePath) throws Exception {
381     ArrayList<String> encodeNameArr = new ArrayList<String>();
382     if(!nodePath.equals("/")) {
383       for(String name : nodePath.split("/")) {
384         if(name.length() > 0) {
385           encodeNameArr.add(Text.escapeIllegalJcrChars(name));
386         }
387       }
388       StringBuilder encodedPath = new StringBuilder();
389       for(String encodedName : encodeNameArr) {
390         encodedPath.append("/").append(encodedName);
391       }
392       nodePath = encodedPath.toString();
393     }
394     return nodePath;
395   }
396 
397   private Node getTargetNode(Node showingNode) throws Exception {
398     Node targetNode = null;
399     if (linkManager_.isLink(showingNode)) {
400       try {
401         targetNode = linkManager_.getTarget(showingNode);
402       } catch (ItemNotFoundException e) {
403         targetNode = showingNode;
404       }
405     } else {
406       targetNode = showingNode;
407     }
408     return targetNode;
409   }
410 
411   private Node getShowingNode(String workspaceName, String nodePath) throws Exception {
412     ManageableRepository repository = repositoryService_.getCurrentRepository();
413     Session session = WCMCoreUtils.getUserSessionProvider().getSession(workspaceName, repository);
414     Node showingNode = null;
415     if(nodePath.equals("/")) {
416       showingNode = session.getRootNode();
417     } else {
418       if (!nodePath.startsWith("/")) {
419         nodePath = "/" + nodePath;
420       }
421       try {
422         showingNode = (Node) nodeFinder_.getItem(session, nodePath);
423       } catch (PathNotFoundException e) {
424         showingNode = (Node) nodeFinder_.getItem(session, getNodePath(nodePath));
425       }
426     }
427     return showingNode;
428   }
429 
430 }