org.alfresco.repo.web.scripts.calendar.AbstractCalendarWebScript.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.web.scripts.calendar.AbstractCalendarWebScript.java

Source

/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.web.scripts.calendar;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

import org.alfresco.query.PagingRequest;
import org.alfresco.repo.calendar.CalendarModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.calendar.CalendarEntry;
import org.alfresco.service.cmr.calendar.CalendarService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.ISO8601DateFormat;
import org.alfresco.util.ScriptPagingDetails;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.json.JSONException;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.json.JSONWriter;

/**
 * @author Nick Burch
 * @since 4.0
 */
public abstract class AbstractCalendarWebScript extends DeclarativeWebScript {
    private static final String ALL_DAY_DATETIME_PATTERN = "yyyy-MM-dd'T00:00:00.000'";

    private static final DateTimeFormatter ALL_DAY_DATETIME_FORMATTER = DateTimeFormat
            .forPattern("yyyy-MM-dd'T00:00:00.000'");

    public static final String CALENDAR_SERVICE_ACTIVITY_APP_NAME = "calendar";

    protected static final String MSG_EVENT_NOT_FOUND = "calendar.err.event.not.found";
    protected static final String MSG_INVALID_JSON = "calendar.err.invalid.json";

    protected static final String PARAM_TIMEZONE = "timeZone";
    protected static final String PARAM_START_AT = "startAt";
    protected static final String PARAM_END_AT = "endAt";
    protected static final String PARAM_ISO8601 = "iso8601";

    private static Log logger = LogFactory.getLog(AbstractCalendarWebScript.class);

    /**
     * When no maximum or paging info is given, what should we use?
     */
    protected static final int MAX_QUERY_ENTRY_COUNT = 1000;

    // Injected services
    protected NodeService nodeService;
    protected SiteService siteService;
    protected ActivityService activityService;
    protected CalendarService calendarService;

    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setSiteService(SiteService siteService) {
        this.siteService = siteService;
    }

    public void setActivityService(ActivityService activityService) {
        this.activityService = activityService;
    }

    public void setCalendarService(CalendarService calendarService) {
        this.calendarService = calendarService;
    }

    /**
     * Gets the date from the String, trying the various formats
     *  (New and Legacy) until one works...
     */
    protected Date parseDate(String date) {
        // Is there one at all?
        if (date == null || date.length() == 0) {
            return null;
        }

        // Today's Date - special case 
        if (date.equalsIgnoreCase("NOW")) {
            // We want all of today, so go back to midnight
            Calendar c = Calendar.getInstance();
            c.set(Calendar.HOUR_OF_DAY, 0);
            c.set(Calendar.MINUTE, 0);
            c.set(Calendar.SECOND, 0);
            c.set(Calendar.MILLISECOND, 0);
            return c.getTime();
        }

        // Try as ISO8601
        try {
            return ISO8601DateFormat.parse(date);
        } catch (Exception e) {
        }

        // Try YYYY/MM/DD
        SimpleDateFormat slashtime = new SimpleDateFormat("yyyy/MM/dd HH:mm");
        SimpleDateFormat slash = new SimpleDateFormat("yyyy/MM/dd");
        try {
            return slashtime.parse(date);
        } catch (ParseException e) {
        }
        try {
            return slash.parse(date);
        } catch (ParseException e) {
        }

        // Try YYYY-MM-DD
        SimpleDateFormat dashtime = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        SimpleDateFormat dash = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return dashtime.parse(date);
        } catch (ParseException e) {
        }
        try {
            return dash.parse(date);
        } catch (ParseException e) {
        }

        // We don't know what it is, object
        throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid date '" + date + "'");
    }

    /**
     * Extracts the Start and End details, along with the All Day flag
     *  from the JSON, and returns if the event is all day or not
     */
    protected boolean extractDates(CalendarEntry entry, JSONObject json) throws JSONException {
        boolean isAllDay = false;
        if (json.containsKey("allday")) {
            isAllDay = true;
        }

        if (json.containsKey(PARAM_START_AT) && json.containsKey(PARAM_END_AT)) {
            // New style ISO8601 based dates and times
            Object startAtO = json.get(PARAM_START_AT);
            Object endAtO = json.get(PARAM_END_AT);

            // Grab the details
            String startAt;
            String endAt;
            String timezoneName = null;
            if (startAtO instanceof JSONObject) {
                // "startAt": { "iso8601":"2011-...." }
                JSONObject startAtJSON = (JSONObject) startAtO;
                JSONObject endAtJSON = (JSONObject) endAtO;
                startAt = (String) startAtJSON.get(PARAM_ISO8601);
                endAt = (String) endAtJSON.get(PARAM_ISO8601);

                if (startAtJSON.containsKey(PARAM_TIMEZONE)) {
                    timezoneName = (String) startAtJSON.get(PARAM_TIMEZONE);
                    if (endAtJSON.containsKey(PARAM_TIMEZONE)) {
                        String endTZ = (String) endAtJSON.get(PARAM_TIMEZONE);
                        if (!endTZ.equals(timezoneName)) {
                            throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Timezones must match");
                        }
                    }
                }
            } else {
                // "startAt": "2011-...."
                startAt = (String) json.get(PARAM_START_AT);
                endAt = (String) json.get(PARAM_END_AT);
            }
            if (json.containsKey(PARAM_TIMEZONE)) {
                timezoneName = (String) json.get(PARAM_TIMEZONE);
            }

            // Is this an all day event?
            if (json.containsKey("allday")) {
                // Store it as UTC midnight to midnight
                // Reset the time part to ensure that
                String utcMidnight = "T00:00:00Z";
                startAt = startAt.substring(0, 10) + utcMidnight;
                endAt = endAt.substring(0, 10) + utcMidnight;
                entry.setStart(ISO8601DateFormat.parse(startAt));
                entry.setEnd(ISO8601DateFormat.parse(endAt));
            } else {
                // Regular event start and end rules

                // Do we have explicit timezone information?
                if (timezoneName != null) {
                    // Get the specified timezone
                    TimeZone tz = TimeZone.getTimeZone(timezoneName);

                    // Grab the dates and times in the specified timezone
                    entry.setStart(ISO8601DateFormat.parse(startAt, tz));
                    entry.setEnd(ISO8601DateFormat.parse(endAt, tz));
                } else {
                    // Offset info is either in the date, or we just have to guess
                    entry.setStart(parseDate(startAt));
                    entry.setEnd(parseDate(endAt));
                }
            }
        } else if (json.containsKey("allday")) {
            // Old style all-day event
            Date start = parseDate(getOrNull(json, "from"));
            Date end = parseDate(getOrNull(json, "to"));
            // Store it as UTC midnight to midnight
            // Reset the time part to ensure that
            String isoStartAt = ISO8601DateFormat.format(start);
            String isoEndAt = ISO8601DateFormat.format(end);
            String utcMidnight = "T00:00:00Z";
            isoStartAt = isoStartAt.substring(0, 10) + utcMidnight;
            isoEndAt = isoEndAt.substring(0, 10) + utcMidnight;
            entry.setStart(ISO8601DateFormat.parse(isoStartAt));
            entry.setEnd(ISO8601DateFormat.parse(isoEndAt));

        } else {
            // Old style regular event
            entry.setStart(parseDate((String) json.get("from") + " " + (String) json.get("start")));
            entry.setEnd(parseDate((String) json.get("to") + " " + (String) json.get("end")));
        }

        return isAllDay;
    }

    protected String getOrNull(JSONObject json, String key) throws JSONException {
        if (json.containsKey(key)) {
            return (String) json.get(key);
        }
        return null;
    }

    /**
     * Builds up a listing Paging request, either using the defaults or
     *  the paging options specified
     */
    protected PagingRequest buildPagingRequest(WebScriptRequest req) {
        return new ScriptPagingDetails(req, MAX_QUERY_ENTRY_COUNT);
    }

    /**
     * Normally the Calendar webscripts return a 200 with JSON
     *  containing the error message. Override this to switch to
     *  using HTTP status codes instead
     */
    protected boolean useJSONErrors() {
        return true;
    }

    /**
     * Equivalent of <i>jsonError</i> in the old JavaScript controllers
     */
    protected Map<String, Object> buildError(String message) {
        HashMap<String, Object> result = new HashMap<String, Object>();
        result.put("error", message);

        HashMap<String, Object> model = new HashMap<String, Object>();
        model.put("error", message);
        model.put("result", result);

        return model;
    }

    /**
     * Generates an activity entry for the entry
     */
    protected String addActivityEntry(String event, CalendarEntry entry, SiteInfo site, WebScriptRequest req,
            JSONObject json) {
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
        String dateOpt = "?date=" + fmt.format(entry.getStart());

        // What page is this for?
        String page = req.getParameter("page");
        if (page == null && json != null) {
            if (json.containsKey("page")) {
                page = (String) json.get("page");
            }
        }
        if (page == null) {
            // Default
            page = "calendar";
        }

        try {
            StringWriter activityJson = new StringWriter();
            JSONWriter activity = new JSONWriter(activityJson);
            activity.startObject();
            activity.writeValue("title", entry.getTitle());
            activity.writeValue("page", page + dateOpt);
            activity.endObject();

            activityService.postActivity("org.alfresco.calendar.event-" + event, site.getShortName(),
                    CALENDAR_SERVICE_ACTIVITY_APP_NAME, activityJson.toString());
        } catch (Exception e) {
            // Warn, but carry on
            logger.warn("Error adding event " + event + " to activities feed", e);
        }

        // Return the date we used
        return dateOpt;
    }

    /**
     * For an event that is a recurring event, have an ignored child event
     *  generated for it
     */
    protected NodeRef createIgnoreEvent(WebScriptRequest req, CalendarEntry parent) {
        // Get the date to be ignored
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();
        Date date = parseDate(req.getParameter("date"));
        props.put(CalendarModel.PROP_IGNORE_EVENT_DATE, date);

        // Create a child node of the event
        NodeRef ignored = nodeService.createNode(parent.getNodeRef(), CalendarModel.ASSOC_IGNORE_EVENT_LIST,
                QName.createQName(GUID.generate()), CalendarModel.TYPE_IGNORE_EVENT, props).getChildRef();

        // No further setup is needed
        return ignored;
    }

    @Override
    protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) {
        Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
        if (templateVars == null) {
            String error = "No parameters supplied";
            if (useJSONErrors()) {
                return buildError(error);
            } else {
                throw new WebScriptException(Status.STATUS_BAD_REQUEST, error);
            }
        }

        // Parse the JSON, if supplied
        JSONObject json = null;
        String contentType = req.getContentType();
        if (contentType != null && contentType.indexOf(';') != -1) {
            contentType = contentType.substring(0, contentType.indexOf(';'));
        }
        if (MimetypeMap.MIMETYPE_JSON.equals(contentType)) {
            JSONParser parser = new JSONParser();
            try {
                json = (JSONObject) parser.parse(req.getContent().getContent());
            } catch (IOException io) {
                return buildError("Invalid JSON: " + io.getMessage());
            } catch (org.json.simple.parser.ParseException je) {
                return buildError("Invalid JSON: " + je.getMessage());
            }
        }

        // Get the site short name. Try quite hard to do so...
        String siteName = templateVars.get("siteid");
        if (siteName == null) {
            siteName = templateVars.get("site");
        }
        if (siteName == null) {
            siteName = req.getParameter("site");
        }
        if (siteName == null && json != null) {
            if (json.containsKey("siteid")) {
                siteName = (String) json.get("siteid");
            } else if (json.containsKey("site")) {
                siteName = (String) json.get("site");
            }
        }
        if (siteName == null) {
            String error = "No site given";
            if (useJSONErrors()) {
                return buildError("No site given");
            } else {
                throw new WebScriptException(Status.STATUS_BAD_REQUEST, error);
            }
        }

        // Grab the requested site
        SiteInfo site = siteService.getSite(siteName);
        if (site == null) {
            String error = "Could not find site: " + siteName;
            if (useJSONErrors()) {
                return buildError(error);
            } else {
                throw new WebScriptException(Status.STATUS_NOT_FOUND, error);
            }
        }

        // Event name is optional
        String eventName = templateVars.get("eventname");

        // Have the real work done
        return executeImpl(site, eventName, req, json, status, cache);
    }

    /**
     * Removes the time zone for a given date if the Calendar Entry is an all day event
     * 
     * @return ISO 8601 formatted date String
     */
    protected String removeTimeZoneIfRequired(Date date, Boolean isAllDay, Boolean removeTimezone) {
        return removeTimeZoneIfRequired(date, isAllDay, removeTimezone, null);
    }

    /**
     * Removes the time zone for a given date if the Calendar Entry is an all day event
     * 
     * @return ISO 8601 formatted date String if datePattern is null
     */
    protected String removeTimeZoneIfRequired(Date date, Boolean isAllDay, Boolean removeTimezone,
            String datePattern) {
        if (removeTimezone) {
            DateTime dateTime = new DateTime(date, DateTimeZone.UTC);

            if (null == datePattern) {
                return dateTime
                        .toString((isAllDay) ? (ALL_DAY_DATETIME_FORMATTER) : (ISODateTimeFormat.dateTime()));
            } else {
                // For Legacy Dates and Times.
                return dateTime.toString(DateTimeFormat.forPattern(datePattern));
            }
        }

        // This is for all other cases, including the case, when UTC time zone is configured

        if (!isAllDay && (null == datePattern)) {
            return ISO8601DateFormat.format(date);
        }

        DateFormat simpleDateFormat = new SimpleDateFormat(
                (null != datePattern) ? (datePattern) : (ALL_DAY_DATETIME_PATTERN));

        return simpleDateFormat.format(date);
    }

    protected abstract Map<String, Object> executeImpl(SiteInfo site, String eventName, WebScriptRequest req,
            JSONObject json, Status status, Cache cache);
}