View Javadoc
1   /*
2    * Copyright (C) 2003-2013 eXo Platform SAS.
3    *
4    * This program is free software: you can redistribute it and/or modify
5    * it under the terms of the GNU Affero General Public License as published by
6    * the Free Software Foundation, either version 3 of the License, or
7    * (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 Affero General Public License for more details.
13   *
14   * You should have received a copy of the GNU Affero General Public License
15   * along with this program. If not, see <http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.services.wcm.search.connector;
18  
19  import org.apache.commons.lang.LocaleUtils;
20  import org.codehaus.groovy.util.ListHashMap;
21  import org.exoplatform.commons.api.search.data.SearchContext;
22  import org.exoplatform.commons.api.search.data.SearchResult;
23  import org.exoplatform.commons.search.es.ElasticSearchServiceConnector;
24  import org.exoplatform.commons.search.es.client.ElasticSearchingClient;
25  import org.exoplatform.container.xml.InitParams;
26  import org.exoplatform.services.cms.documents.DocumentService;
27  import org.exoplatform.services.cms.drives.DriveData;
28  import org.exoplatform.services.cms.impl.Utils;
29  import org.exoplatform.services.jcr.RepositoryService;
30  import org.exoplatform.services.log.ExoLogger;
31  import org.exoplatform.services.log.Log;
32  import org.exoplatform.services.wcm.core.NodeLocation;
33  import org.exoplatform.services.wcm.search.base.EcmsSearchResult;
34  import org.exoplatform.services.wcm.utils.WCMCoreUtils;
35  import org.exoplatform.web.controller.QualifiedName;
36  import org.json.simple.JSONObject;
37  
38  import javax.jcr.Node;
39  import javax.jcr.RepositoryException;
40  import java.io.UnsupportedEncodingException;
41  import java.net.URLEncoder;
42  import java.time.Instant;
43  import java.time.ZoneId;
44  import java.time.format.DateTimeFormatter;
45  import java.time.format.FormatStyle;
46  import java.util.*;
47  import java.util.stream.Collectors;
48  
49  /**
50   * Search connector for files
51   */
52  public class FileSearchServiceConnector extends ElasticSearchServiceConnector {
53    
54    private static final Log LOG = ExoLogger.getLogger(FileSearchServiceConnector.class.getName());
55  
56    private RepositoryService repositoryService;
57  
58    private DocumentService documentService;
59  
60    public FileSearchServiceConnector(InitParams initParams, ElasticSearchingClient client, RepositoryService repositoryService, DocumentService documentService) {
61      super(initParams, client);
62      this.repositoryService = repositoryService;
63      this.documentService = documentService;
64    }
65  
66    @Override
67    protected String getSourceFields() {
68      List<String> fields = Arrays.asList("name",
69              "title",
70              "workspace",
71              "path",
72              "author",
73              "createdDate",
74              "lastUpdatedDate",
75              "fileType",
76              "fileSize");
77  
78      return fields.stream().map(field -> "\"" + field + "\"").collect(Collectors.joining(","));
79    }
80  
81    @Override
82    protected SearchResult buildHit(JSONObject jsonHit, SearchContext searchContext) {
83      SearchResult searchResult = super.buildHit(jsonHit, searchContext);
84  
85      JSONObject hitSource = (JSONObject) jsonHit.get("_source");
86      String workspace = (String) hitSource.get("workspace");
87      String nodePath = (String) hitSource.get("path");
88      String fileType = (String) hitSource.get("fileType");
89      String fileSize = (String) hitSource.get("fileSize");
90  
91      String driveName = "";
92      try {
93        DriveData driveOfNode = documentService.getDriveOfNode(nodePath);
94        if(driveOfNode != null) {
95          driveName = driveOfNode.getName() + " - ";
96        }
97      } catch (Exception e) {
98        LOG.warn("Cannot get drive of node " + nodePath, e);
99      }
100 
101     String lang = searchContext.getParamValue(SearchContext.RouterParams.LANG.create());
102     String detail = driveName + getFormattedFileSize(fileSize) + " - " + getFormattedDate(searchResult.getDate(), lang);
103 
104     SearchResult ecmsSearchResult = new EcmsSearchResult(getUrl(nodePath),
105             getPreviewUrl(jsonHit, searchContext),
106             searchResult.getTitle(),
107             searchResult.getExcerpt(),
108             detail,
109             getImageUrl(workspace, nodePath),
110             searchResult.getDate(),
111             searchResult.getRelevancy(),
112             fileType,
113             nodePath,
114             getBreadcrumb(nodePath));
115 
116     return ecmsSearchResult;
117   }
118 
119   protected String getUrl(String nodePath) {
120     String url = "";
121     try {
122       url = documentService.getLinkInDocumentsApp(nodePath);
123     } catch (Exception e) {
124       LOG.error("Cannot get url of document " + nodePath, e);
125     }
126     return url;
127   }
128 
129   protected String getFormattedDate(long createdDateTime, String lang) {
130     try {
131       Locale locale = LocaleUtils.toLocale(lang);
132       DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.SHORT).withLocale(locale).withZone(ZoneId.systemDefault());
133       return df.format(Instant.ofEpochMilli(createdDateTime));
134     } catch (Exception e) {
135       LOG.error("Cannot format date for timestamp " + createdDateTime, e);
136       return "";
137     }
138   }
139 
140   protected String getFormattedFileSize(String fileSize) {
141     try {
142       Long size = Long.parseLong(fileSize);
143       return Utils.formatSize(size);
144     } catch (Exception e) {
145       LOG.error("Cannot format file size " + fileSize, e);
146       return "";
147     }
148   }
149 
150   protected String getPreviewUrl(JSONObject jsonHit, SearchContext context) {
151     JSONObject hitSource = (JSONObject) jsonHit.get("_source");
152 
153     String id = (String) jsonHit.get("_id");
154     String author = (String) hitSource.get("author");
155     String title = (String) hitSource.get("title");
156     String workspace = (String) hitSource.get("workspace");
157     String nodePath = (String) hitSource.get("path");
158     String fileType = (String) hitSource.get("fileType");
159 
160     String restContextName =  WCMCoreUtils.getRestContextName();
161     String repositoryName = null;
162     try {
163       repositoryName = repositoryService.getCurrentRepository().getConfiguration().getName();
164     } catch (RepositoryException e) {
165       LOG.error("Cannot get repository name", e);
166     }
167 
168     StringBuffer downloadUrl = new StringBuffer();
169     downloadUrl.append('/').append(restContextName).append("/jcr/").
170             append(repositoryName).append('/').
171             append(workspace).append(nodePath);
172 
173     StringBuilder url = new StringBuilder("javascript:require(['SHARED/documentPreview'], function(documentPreview) {documentPreview.init({doc:{");
174     url.append("id:'").append(id).append("',");
175     url.append("fileType:'").append(fileType).append("',");
176     url.append("title:'").append(title).append("',");
177     String linkInDocumentsApp = "";
178     try {
179       linkInDocumentsApp = documentService.getLinkInDocumentsApp(nodePath);
180     } catch (Exception e) {
181       LOG.error("Cannot get link in document app for node " + nodePath, e);
182     }
183     url.append("path:'").append(nodePath)
184             .append("', repository:'").append(repositoryName)
185             .append("', workspace:'").append(workspace)
186             .append("', downloadUrl:'").append(downloadUrl.toString())
187             .append("', openUrl:'").append(linkInDocumentsApp)
188             .append("'}");
189     if(author != null) {
190       url.append(",author:{username:'").append(author).append("'}");
191     }
192     //add void(0) to make firefox execute js
193     url.append("})});void(0);");
194 
195     return url.toString();
196   }
197 
198   protected String getImageUrl(String workspace, String nodePath) {
199     try {
200       String path = nodePath.replaceAll("'", "\\\\'");
201       String encodedPath = URLEncoder.encode(path, "utf-8");
202       encodedPath = encodedPath.replaceAll ("%2F", "/");    //we won't encode the slash characters in the path
203       String restContextName = WCMCoreUtils.getRestContextName();
204       String repositoryName = null;
205       try {
206         repositoryName = repositoryService.getCurrentRepository().getConfiguration().getName();
207       } catch (RepositoryException e) {
208         LOG.error("Cannot get repository name", e);
209       }
210       String thumbnailImage = "/" + restContextName + "/thumbnailImage/medium/" +
211                               repositoryName + "/" + workspace + encodedPath;
212       return thumbnailImage;
213     } catch (UnsupportedEncodingException e) {
214       LOG.error("Cannot encode path " + nodePath, e);
215       return "";
216     }
217   }
218 
219   /**
220    * Build the breadcrumb of the file.
221    * The map keys contains the node path and the map value contains the node title and the node link.
222    * @param nodePath Path of the node to build the breadcrumb
223    * @return The breadcrumb
224    */
225   protected Map<String, List<String>> getBreadcrumb(String nodePath) {
226     Map<String, List<String>> uris = new ListHashMap<>();
227 
228     try {
229       if (nodePath.endsWith("/")) {
230         nodePath = nodePath.substring(0, nodePath.length() - 1);
231       }
232 
233       DriveData drive = documentService.getDriveOfNode(nodePath);
234 
235       String nodePathFromDrive = nodePath;
236 
237       String driveHomePath = drive.getResolvedHomePath();
238       if(nodePath.startsWith(driveHomePath)) {
239         nodePathFromDrive = nodePath.substring(driveHomePath.length());
240       }
241 
242       if (nodePathFromDrive.startsWith("/")) {
243         nodePathFromDrive = nodePathFromDrive.substring(1);
244       }
245 
246       String path = driveHomePath;
247       for (String nodeName : nodePathFromDrive.split("/")) {
248         path += "/" + nodeName;
249         try {
250           Node docNode = NodeLocation.getNodeByExpression(
251                   WCMCoreUtils.getRepository().getConfiguration().getName() + ":" +
252                           drive.getWorkspace() + ":" + path);
253           if(docNode != null) {
254             String nodeTitle = Utils.getTitle(docNode);
255 
256             String docLink = documentService.getLinkInDocumentsApp(path, drive);
257 
258             List<String> titleAndLink = new ArrayList<>();
259             titleAndLink.add(nodeTitle);
260             titleAndLink.add(docLink);
261 
262             uris.put(path, titleAndLink);
263           }
264         } catch (Exception e) {
265           LOG.error("Cannot get title and link of node " + nodeName, e);
266         }
267       }
268 
269     } catch (Exception e){
270       LOG.error("Error while building breadcrumb of file " + nodePath, e);
271     }
272 
273     return uris;
274   }
275 }