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}