001package org.nuxeo.ecm.rating.operations; 002 003import static org.apache.commons.lang.StringUtils.isBlank; 004import static org.nuxeo.ecm.activity.ActivityHelper.getActivityId; 005import static org.nuxeo.ecm.activity.ActivityHelper.getDocumentId; 006import static org.nuxeo.ecm.activity.ActivityHelper.getUsername; 007 008import java.io.ByteArrayOutputStream; 009import java.io.IOException; 010import java.io.OutputStream; 011import java.util.ArrayList; 012import java.util.Date; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.regex.Matcher; 017import java.util.regex.Pattern; 018 019import net.sf.json.JSONArray; 020import net.sf.json.JSONObject; 021 022import org.apache.commons.lang.StringEscapeUtils; 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.codehaus.jackson.JsonGenerator; 026import org.nuxeo.ecm.activity.ActivitiesList; 027import org.nuxeo.ecm.activity.Activity; 028import org.nuxeo.ecm.activity.ActivityHelper; 029import org.nuxeo.ecm.activity.ActivityMessageHelper; 030import org.nuxeo.ecm.activity.ActivityStreamService; 031import org.nuxeo.ecm.automation.OperationContext; 032import org.nuxeo.ecm.automation.core.Constants; 033import org.nuxeo.ecm.automation.core.annotations.Context; 034import org.nuxeo.ecm.automation.core.annotations.Operation; 035import org.nuxeo.ecm.automation.core.annotations.OperationMethod; 036import org.nuxeo.ecm.automation.core.annotations.Param; 037import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper; 038import org.nuxeo.ecm.core.api.Blob; 039import org.nuxeo.ecm.core.api.Blobs; 040import org.nuxeo.ecm.core.api.CoreSession; 041import org.nuxeo.ecm.core.api.DocumentLocation; 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.ecm.core.api.IdRef; 044import org.nuxeo.ecm.core.api.PathRef; 045import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl; 046import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter; 047import org.nuxeo.ecm.core.io.registry.MarshallerRegistry; 048import org.nuxeo.ecm.core.io.registry.context.RenderingContext; 049import org.nuxeo.ecm.core.io.registry.context.RenderingContext.CtxBuilder; 050import org.nuxeo.ecm.platform.types.adapter.TypeInfo; 051import org.nuxeo.ecm.platform.url.DocumentViewImpl; 052import org.nuxeo.ecm.platform.url.api.DocumentView; 053import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; 054import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 055import org.nuxeo.ecm.rating.api.LikeService; 056import org.nuxeo.runtime.api.Framework; 057 058/** 059 * @author <a href="mailto:akervern@nuxeo.com">Arnaud Kervern</a> 060 */ 061@Operation(id = MostLiked.ID, category = Constants.CAT_SERVICES, label = "Like a document or an activity object") 062public class MostLiked { 063 private static final Log log = LogFactory.getLog(MostLiked.class); 064 065 public static final String ID = "Services.MostLiked"; 066 067 public static Pattern HTTP_URL_PATTERN = Pattern.compile("\\b(https?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])"); 068 069 @Context 070 protected CoreSession session; 071 072 @Context 073 protected LikeService likeService; 074 075 @Context 076 protected ActivityStreamService activityService; 077 078 @Context 079 protected OperationContext ctx; 080 081 @Param(name = "contextPath") 082 protected String contextPath; 083 084 @Param(name = "limit") 085 protected int limit; 086 087 @Param(name = "fromDt", required = false) 088 protected Date fromDt; 089 090 @Param(name = "toDt", required = false) 091 protected Date toDt; 092 093 @Param(name = "documentLinkBuilder", required = false) 094 protected String documentLinkBuilder; 095 096 @OperationMethod 097 public Blob run() throws IOException { 098 ActivitiesList mostLikedDocuments = likeService.getMostLikedActivities(session, limit, 099 session.getDocument(new PathRef(contextPath)), fromDt, toDt); 100 101 final List<JSONObject> docsWithRate = new ArrayList<JSONObject>(); 102 for (Activity activity : mostLikedDocuments) { 103 if (ActivityHelper.isDocument(activity.getTarget())) { 104 docsWithRate.add(buildFromDocument(activity)); 105 } else if (ActivityHelper.isActivity(activity.getTarget())) { 106 docsWithRate.add(buildFromActivity(activity)); 107 } else { 108 log.info("Unable to check activity type ..."); 109 } 110 } 111 112 Map<String, Object> jsonObj = new HashMap<String, Object>(); 113 jsonObj.put("items", JSONArray.fromObject(docsWithRate)); 114 JSONObject json = JSONObject.fromObject(jsonObj); 115 return Blobs.createBlob(json.toString(), "application/json"); 116 } 117 118 protected JSONObject buildFromActivity(Activity activity) { 119 Activity miniMessage = activityService.getActivity(Long.valueOf(getActivityId(activity.getTarget()))); 120 String message = MostLiked.replaceURLsByLinks(miniMessage.getObject()); 121 Integer rating = Integer.valueOf(activity.getObject()); 122 Integer hasRated = Integer.valueOf(activity.getContext()); 123 124 Map<String, Object> value = new HashMap<String, Object>(); 125 value.put("type", "minimessage"); 126 value.put("message", message); 127 value.put("rating", rating); 128 value.put("actor", getUsername(miniMessage.getActor())); 129 value.put("profile", 130 ActivityMessageHelper.getUserProfileLink(miniMessage.getActor(), miniMessage.getDisplayActor())); 131 value.put("hasUserLiked", hasRated); 132 133 return JSONObject.fromObject(value); 134 } 135 136 protected JSONObject buildFromDocument(Activity activity) throws IOException { 137 DocumentModel doc = session.getDocument(new IdRef(getDocumentId(activity.getTarget()))); 138 Integer rating = Integer.valueOf(activity.getObject()); 139 Integer hasRated = Integer.valueOf(activity.getContext()); 140 141 OutputStream out = new ByteArrayOutputStream(); 142 JsonGenerator jg = JsonHelper.createJsonGenerator(out); 143 144 writeDocument(doc, jg); 145 146 Map<String, Object> value = new HashMap<String, Object>(); 147 value.put("rating", rating); 148 value.put("document", JSONObject.fromObject(out.toString())); 149 value.put("url", getDocumentUrl(doc)); 150 value.put("hasUserLiked", hasRated); 151 value.put("type", "document"); 152 153 return JSONObject.fromObject(value); 154 } 155 156 private static DocumentModelJsonWriter documentModelWriter; 157 158 private static void writeDocument(DocumentModel doc, JsonGenerator jg) throws IOException { 159 if (documentModelWriter == null) { 160 RenderingContext ctx = CtxBuilder.properties("dublincore", "common").get(); 161 MarshallerRegistry registry = Framework.getService(MarshallerRegistry.class); 162 documentModelWriter = registry.getUniqueInstance(ctx, DocumentModelJsonWriter.class); 163 } 164 documentModelWriter.write(doc, jg); 165 jg.flush(); 166 } 167 168 protected String getDocumentUrl(DocumentModel doc) { 169 if (Framework.isTestModeSet()) { 170 return "http://dummyurl.com"; 171 } 172 173 DocumentViewCodecManager documentViewCodecManager = Framework.getLocalService(DocumentViewCodecManager.class); 174 String codecName = isBlank(documentLinkBuilder) ? documentViewCodecManager.getDefaultCodecName() 175 : documentLinkBuilder; 176 177 DocumentLocation docLoc = new DocumentLocationImpl(session.getRepositoryName(), doc.getRef()); 178 DocumentView docView = new DocumentViewImpl(docLoc, doc.getAdapter(TypeInfo.class).getDefaultView()); 179 return VirtualHostHelper.getContextPathProperty() + "/" 180 + documentViewCodecManager.getUrlFromDocumentView(codecName, docView, false, null); 181 } 182 183 protected static String replaceURLsByLinks(String message) { 184 String escapedMessage = StringEscapeUtils.escapeHtml(message); 185 Matcher m = HTTP_URL_PATTERN.matcher(escapedMessage); 186 StringBuffer sb = new StringBuffer(escapedMessage.length()); 187 while (m.find()) { 188 String url = m.group(1); 189 m.appendReplacement(sb, computeLinkFor(url)); 190 } 191 m.appendTail(sb); 192 return sb.toString(); 193 } 194 195 protected static String computeLinkFor(String url) { 196 return "<a href=\"" + url + "\" target=\"_top\">" + url + "</a>"; 197 } 198}