FileSearchServiceConnector.java

/*
 * Copyright (C) 2003-2013 eXo Platform SAS.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.wcm.search.connector;

import org.apache.commons.lang.LocaleUtils;
import org.codehaus.groovy.util.ListHashMap;
import org.exoplatform.commons.api.search.data.SearchContext;
import org.exoplatform.commons.api.search.data.SearchResult;
import org.exoplatform.commons.search.es.ElasticSearchServiceConnector;
import org.exoplatform.commons.search.es.client.ElasticSearchingClient;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.services.cms.documents.DocumentService;
import org.exoplatform.services.cms.drives.DriveData;
import org.exoplatform.services.cms.impl.Utils;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.services.wcm.search.base.EcmsSearchResult;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.web.controller.QualifiedName;
import org.json.simple.JSONObject;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Search connector for files
 */
public class FileSearchServiceConnector extends ElasticSearchServiceConnector {
  
  private static final Log LOG = ExoLogger.getLogger(FileSearchServiceConnector.class.getName());

  private RepositoryService repositoryService;

  private DocumentService documentService;

  public FileSearchServiceConnector(InitParams initParams, ElasticSearchingClient client, RepositoryService repositoryService, DocumentService documentService) {
    super(initParams, client);
    this.repositoryService = repositoryService;
    this.documentService = documentService;
  }

  @Override
  protected String getSourceFields() {
    List<String> fields = Arrays.asList("name",
            "title",
            "workspace",
            "path",
            "author",
            "createdDate",
            "lastUpdatedDate",
            "fileType",
            "fileSize");

    return fields.stream().map(field -> "\"" + field + "\"").collect(Collectors.joining(","));
  }

  @Override
  protected SearchResult buildHit(JSONObject jsonHit, SearchContext searchContext) {
    SearchResult searchResult = super.buildHit(jsonHit, searchContext);

    JSONObject hitSource = (JSONObject) jsonHit.get("_source");
    String workspace = (String) hitSource.get("workspace");
    String nodePath = (String) hitSource.get("path");
    String fileType = (String) hitSource.get("fileType");
    String fileSize = (String) hitSource.get("fileSize");

    String driveName = "";
    try {
      DriveData driveOfNode = documentService.getDriveOfNode(nodePath);
      if(driveOfNode != null) {
        driveName = driveOfNode.getName() + " - ";
      }
    } catch (Exception e) {
      LOG.warn("Cannot get drive of node " + nodePath, e);
    }

    String lang = searchContext.getParamValue(SearchContext.RouterParams.LANG.create());
    String detail = driveName + getFormattedFileSize(fileSize) + " - " + getFormattedDate(searchResult.getDate(), lang);

    SearchResult ecmsSearchResult = new EcmsSearchResult(getUrl(nodePath),
            getPreviewUrl(jsonHit, searchContext),
            searchResult.getTitle(),
            searchResult.getExcerpt(),
            detail,
            getImageUrl(workspace, nodePath),
            searchResult.getDate(),
            searchResult.getRelevancy(),
            fileType,
            nodePath,
            getBreadcrumb(nodePath));

    return ecmsSearchResult;
  }

  protected String getUrl(String nodePath) {
    String url = "";
    try {
      url = documentService.getLinkInDocumentsApp(nodePath);
    } catch (Exception e) {
      LOG.error("Cannot get url of document " + nodePath, e);
    }
    return url;
  }

  protected String getFormattedDate(long createdDateTime, String lang) {
    try {
      Locale locale = LocaleUtils.toLocale(lang);
      DateTimeFormatter df = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.SHORT).withLocale(locale).withZone(ZoneId.systemDefault());
      return df.format(Instant.ofEpochMilli(createdDateTime));
    } catch (Exception e) {
      LOG.error("Cannot format date for timestamp " + createdDateTime, e);
      return "";
    }
  }

  protected String getFormattedFileSize(String fileSize) {
    try {
      Long size = Long.parseLong(fileSize);
      return Utils.formatSize(size);
    } catch (Exception e) {
      LOG.error("Cannot format file size " + fileSize, e);
      return "";
    }
  }

  protected String getPreviewUrl(JSONObject jsonHit, SearchContext context) {
    JSONObject hitSource = (JSONObject) jsonHit.get("_source");

    String id = (String) jsonHit.get("_id");
    String author = (String) hitSource.get("author");
    String title = (String) hitSource.get("title");
    String workspace = (String) hitSource.get("workspace");
    String nodePath = (String) hitSource.get("path");
    String fileType = (String) hitSource.get("fileType");

    String restContextName =  WCMCoreUtils.getRestContextName();
    String repositoryName = null;
    try {
      repositoryName = repositoryService.getCurrentRepository().getConfiguration().getName();
    } catch (RepositoryException e) {
      LOG.error("Cannot get repository name", e);
    }

    StringBuffer downloadUrl = new StringBuffer();
    downloadUrl.append('/').append(restContextName).append("/jcr/").
            append(repositoryName).append('/').
            append(workspace).append(nodePath);

    StringBuilder url = new StringBuilder("javascript:require(['SHARED/documentPreview'], function(documentPreview) {documentPreview.init({doc:{");
    url.append("id:'").append(id).append("',");
    url.append("fileType:'").append(fileType).append("',");
    url.append("title:'").append(title).append("',");
    String linkInDocumentsApp = "";
    try {
      linkInDocumentsApp = documentService.getLinkInDocumentsApp(nodePath);
    } catch (Exception e) {
      LOG.error("Cannot get link in document app for node " + nodePath, e);
    }
    url.append("path:'").append(nodePath)
            .append("', repository:'").append(repositoryName)
            .append("', workspace:'").append(workspace)
            .append("', downloadUrl:'").append(downloadUrl.toString())
            .append("', openUrl:'").append(linkInDocumentsApp)
            .append("'}");
    if(author != null) {
      url.append(",author:{username:'").append(author).append("'}");
    }
    //add void(0) to make firefox execute js
    url.append("})});void(0);");

    return url.toString();
  }

  protected String getImageUrl(String workspace, String nodePath) {
    try {
      String path = nodePath.replaceAll("'", "\\\\'");
      String encodedPath = URLEncoder.encode(path, "utf-8");
      encodedPath = encodedPath.replaceAll ("%2F", "/");    //we won't encode the slash characters in the path
      String restContextName = WCMCoreUtils.getRestContextName();
      String repositoryName = null;
      try {
        repositoryName = repositoryService.getCurrentRepository().getConfiguration().getName();
      } catch (RepositoryException e) {
        LOG.error("Cannot get repository name", e);
      }
      String thumbnailImage = "/" + restContextName + "/thumbnailImage/medium/" +
                              repositoryName + "/" + workspace + encodedPath;
      return thumbnailImage;
    } catch (UnsupportedEncodingException e) {
      LOG.error("Cannot encode path " + nodePath, e);
      return "";
    }
  }

  /**
   * Build the breadcrumb of the file.
   * The map keys contains the node path and the map value contains the node title and the node link.
   * @param nodePath Path of the node to build the breadcrumb
   * @return The breadcrumb
   */
  protected Map<String, List<String>> getBreadcrumb(String nodePath) {
    Map<String, List<String>> uris = new ListHashMap<>();

    try {
      if (nodePath.endsWith("/")) {
        nodePath = nodePath.substring(0, nodePath.length() - 1);
      }

      DriveData drive = documentService.getDriveOfNode(nodePath);

      String nodePathFromDrive = nodePath;

      String driveHomePath = drive.getResolvedHomePath();
      if(nodePath.startsWith(driveHomePath)) {
        nodePathFromDrive = nodePath.substring(driveHomePath.length());
      }

      if (nodePathFromDrive.startsWith("/")) {
        nodePathFromDrive = nodePathFromDrive.substring(1);
      }

      String path = driveHomePath;
      for (String nodeName : nodePathFromDrive.split("/")) {
        path += "/" + nodeName;
        try {
          Node docNode = NodeLocation.getNodeByExpression(
                  WCMCoreUtils.getRepository().getConfiguration().getName() + ":" +
                          drive.getWorkspace() + ":" + path);
          if(docNode != null) {
            String nodeTitle = Utils.getTitle(docNode);

            String docLink = documentService.getLinkInDocumentsApp(path, drive);

            List<String> titleAndLink = new ArrayList<>();
            titleAndLink.add(nodeTitle);
            titleAndLink.add(docLink);

            uris.put(path, titleAndLink);
          }
        } catch (Exception e) {
          LOG.error("Cannot get title and link of node " + nodeName, e);
        }
      }

    } catch (Exception e){
      LOG.error("Error while building breadcrumb of file " + nodePath, e);
    }

    return uris;
  }
}