001/* 002 * (C) Copyright 2012-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Arnaud Kervern 018 */ 019package org.nuxeo.ecm.rating.operations; 020 021import static org.apache.commons.lang3.StringUtils.isBlank; 022import static org.nuxeo.ecm.activity.ActivityHelper.getActivityId; 023import static org.nuxeo.ecm.activity.ActivityHelper.getDocumentId; 024import static org.nuxeo.ecm.activity.ActivityHelper.getUsername; 025 026import java.io.ByteArrayOutputStream; 027import java.io.IOException; 028import java.io.OutputStream; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.Date; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.regex.Matcher; 036import java.util.regex.Pattern; 037 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.apache.commons.text.StringEscapeUtils; 041import org.json.JSONException; 042import org.nuxeo.ecm.activity.ActivitiesList; 043import org.nuxeo.ecm.activity.Activity; 044import org.nuxeo.ecm.activity.ActivityHelper; 045import org.nuxeo.ecm.activity.ActivityMessageHelper; 046import org.nuxeo.ecm.activity.ActivityStreamService; 047import org.nuxeo.ecm.automation.OperationContext; 048import org.nuxeo.ecm.automation.core.Constants; 049import org.nuxeo.ecm.automation.core.annotations.Context; 050import org.nuxeo.ecm.automation.core.annotations.Operation; 051import org.nuxeo.ecm.automation.core.annotations.OperationMethod; 052import org.nuxeo.ecm.automation.core.annotations.Param; 053import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper; 054import org.nuxeo.ecm.core.api.Blob; 055import org.nuxeo.ecm.core.api.Blobs; 056import org.nuxeo.ecm.core.api.CoreSession; 057import org.nuxeo.ecm.core.api.DocumentLocation; 058import org.nuxeo.ecm.core.api.DocumentModel; 059import org.nuxeo.ecm.core.api.IdRef; 060import org.nuxeo.ecm.core.api.PathRef; 061import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl; 062import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter; 063import org.nuxeo.ecm.core.io.registry.MarshallerRegistry; 064import org.nuxeo.ecm.core.io.registry.context.RenderingContext; 065import org.nuxeo.ecm.core.io.registry.context.RenderingContext.CtxBuilder; 066import org.nuxeo.ecm.platform.types.adapter.TypeInfo; 067import org.nuxeo.ecm.platform.url.DocumentViewImpl; 068import org.nuxeo.ecm.platform.url.api.DocumentView; 069import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; 070import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 071import org.nuxeo.ecm.rating.api.LikeService; 072import org.nuxeo.runtime.api.Framework; 073 074import com.fasterxml.jackson.core.JsonGenerator; 075import com.fasterxml.jackson.databind.ObjectMapper; 076 077/** 078 * @author <a href="mailto:[email protected]">Arnaud Kervern</a> 079 */ 080@Operation(id = MostLiked.ID, category = Constants.CAT_SERVICES, label = "Like a document or an activity object") 081public class MostLiked { 082 private static final Log log = LogFactory.getLog(MostLiked.class); 083 084 public static final String ID = "Services.MostLiked"; 085 086 public static final Pattern HTTP_URL_PATTERN = Pattern.compile( 087 "\\b(https?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])"); 088 089 @Context 090 protected CoreSession session; 091 092 @Context 093 protected LikeService likeService; 094 095 @Context 096 protected ActivityStreamService activityService; 097 098 @Context 099 protected OperationContext ctx; 100 101 @Param(name = "contextPath") 102 protected String contextPath; 103 104 @Param(name = "limit") 105 protected int limit; 106 107 @Param(name = "fromDt", required = false) 108 protected Date fromDt; 109 110 @Param(name = "toDt", required = false) 111 protected Date toDt; 112 113 @Param(name = "documentLinkBuilder", required = false) 114 protected String documentLinkBuilder; 115 116 private ObjectMapper objectMapper = new ObjectMapper(); 117 118 @OperationMethod 119 public Blob run() throws IOException, JSONException { 120 ActivitiesList mostLikedDocuments = likeService.getMostLikedActivities(session, limit, 121 session.getDocument(new PathRef(contextPath)), fromDt, toDt); 122 123 final List<Map<String, Object>> docsWithRate = new ArrayList<>(); 124 for (Activity activity : mostLikedDocuments) { 125 if (ActivityHelper.isDocument(activity.getTarget())) { 126 docsWithRate.add(buildFromDocument(activity)); 127 } else if (ActivityHelper.isActivity(activity.getTarget())) { 128 docsWithRate.add(buildFromActivity(activity)); 129 } else { 130 log.info("Unable to check activity type ..."); 131 } 132 } 133 134 return Blobs.createJSONBlobFromValue(Collections.singletonMap("items", docsWithRate)); 135 } 136 137 protected Map<String, Object> buildFromActivity(Activity activity) { 138 Activity miniMessage = activityService.getActivity(Long.valueOf(getActivityId(activity.getTarget()))); 139 String message = MostLiked.replaceURLsByLinks(miniMessage.getObject()); 140 Integer rating = Integer.valueOf(activity.getObject()); 141 Integer hasRated = Integer.valueOf(activity.getContext()); 142 143 Map<String, Object> value = new HashMap<>(); 144 value.put("type", "minimessage"); 145 value.put("message", message); 146 value.put("rating", rating); 147 value.put("actor", getUsername(miniMessage.getActor())); 148 value.put("profile", 149 ActivityMessageHelper.getUserProfileLink(miniMessage.getActor(), miniMessage.getDisplayActor())); 150 value.put("hasUserLiked", hasRated); 151 152 return value; 153 } 154 155 protected Map<String, Object> buildFromDocument(Activity activity) throws IOException, JSONException { 156 DocumentModel doc = session.getDocument(new IdRef(getDocumentId(activity.getTarget()))); 157 Integer rating = Integer.valueOf(activity.getObject()); 158 Integer hasRated = Integer.valueOf(activity.getContext()); 159 160 try (OutputStream out = new ByteArrayOutputStream(); JsonGenerator jg = JsonHelper.createJsonGenerator(out)) { 161 162 writeDocument(doc, jg); 163 164 @SuppressWarnings("unchecked") 165 Map<String, Object> docAsMap = objectMapper.readValue(out.toString(), Map.class); 166 167 Map<String, Object> value = new HashMap<>(); 168 value.put("rating", rating); 169 value.put("document", docAsMap); 170 value.put("url", getDocumentUrl(doc)); 171 value.put("hasUserLiked", hasRated); 172 value.put("type", "document"); 173 174 return value; 175 } 176 } 177 178 private static DocumentModelJsonWriter documentModelWriter; 179 180 private static void writeDocument(DocumentModel doc, JsonGenerator jg) throws IOException { 181 if (documentModelWriter == null) { 182 RenderingContext ctx = CtxBuilder.properties("dublincore", "common").get(); 183 MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class); 184 documentModelWriter = registry.getUniqueInstance(ctx, DocumentModelJsonWriter.class); 185 } 186 documentModelWriter.write(doc, jg); 187 jg.flush(); 188 } 189 190 protected String getDocumentUrl(DocumentModel doc) { 191 if (Framework.isTestModeSet()) { 192 return "http://dummyurl.com"; 193 } 194 195 DocumentViewCodecManager documentViewCodecManager = Framework.getService(DocumentViewCodecManager.class); 196 String codecName = isBlank(documentLinkBuilder) ? documentViewCodecManager.getDefaultCodecName() 197 : documentLinkBuilder; 198 199 DocumentLocation docLoc = new DocumentLocationImpl(session.getRepositoryName(), doc.getRef()); 200 DocumentView docView = new DocumentViewImpl(docLoc, doc.getAdapter(TypeInfo.class).getDefaultView()); 201 return VirtualHostHelper.getContextPathProperty() + "/" 202 + documentViewCodecManager.getUrlFromDocumentView(codecName, docView, false, null); 203 } 204 205 protected static String replaceURLsByLinks(String message) { 206 String escapedMessage = StringEscapeUtils.escapeHtml4(message); 207 Matcher m = HTTP_URL_PATTERN.matcher(escapedMessage); 208 StringBuffer sb = new StringBuffer(escapedMessage.length()); 209 while (m.find()) { 210 String url = m.group(1); 211 m.appendReplacement(sb, computeLinkFor(url)); 212 } 213 m.appendTail(sb); 214 return sb.toString(); 215 } 216 217 protected static String computeLinkFor(String url) { 218 return "<a href=\"" + url + "\" target=\"_top\">" + url + "</a>"; 219 } 220}