CalendarRestApi.java
/*
* Copyright (C) 2003-2014 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.calendar.ws;
import com.sun.syndication.feed.synd.*;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Method;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Version;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.exoplatform.calendar.model.Event;
import org.exoplatform.calendar.service.*;
import org.exoplatform.calendar.service.Calendar;
import org.exoplatform.calendar.service.Calendar.Type;
import org.exoplatform.calendar.service.impl.MailNotification;
import org.exoplatform.calendar.ws.bean.*;
import org.exoplatform.calendar.ws.common.Resource;
import org.exoplatform.calendar.ws.common.RestAPIConstants;
import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.commons.utils.DateUtils;
import org.exoplatform.commons.utils.ISO8601;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.commons.utils.MimeTypeResolver;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ValueParam;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.mail.MailService;
import org.exoplatform.services.organization.Group;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Identity;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.social.core.profile.ProfileFilter;
import org.exoplatform.upload.UploadResource;
import org.exoplatform.upload.UploadService;
import org.exoplatform.webservice.cs.bean.End;
import org.exoplatform.ws.frameworks.json.impl.JsonGeneratorImpl;
import org.exoplatform.ws.frameworks.json.value.JsonValue;
import org.json.JSONException;
import org.json.JSONObject;
import javax.annotation.security.RolesAllowed;
import javax.jcr.query.Query;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URI;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// Swagger //
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
/**
* This rest service class provides entry point for calendar resources.
*/
@Path(CalendarRestApi.CAL_BASE_URI)
@Api(value = CalendarRestApi.CAL_BASE_URI, description = "Entry point for calendar resources")
public class CalendarRestApi implements ResourceContainer {
public final static String CAL_BASE_URI = RestAPIConstants.BASE_VERSION_URI + "/calendar";
public static final String TEXT_ICS = "text/calendar";
public static final MediaType TEXT_ICS_TYPE = new MediaType("text","calendar");
// TODO: Why /cs/calendar is still being used here ?
public final static String BASE_URL = "/cs/calendar";
public final static String BASE_EVENT_URL = BASE_URL + "/event";
public final static String CALENDAR_URI = "/calendars/";
public final static String EVENT_URI = "/events/";
public final static String TASK_URI = "/tasks/";
public final static String ICS_URI = "/ics";
public final static String ATTACHMENT_URI = "/attachments/";
public final static String OCCURRENCE_URI = "/occurrences";
public final static String CATEGORY_URI = "/categories/";
public final static String PARTICIPANT_URI = "/participants/";
public final static String AVAILABILITY_URI = "/availabilities/";
public final static String FEED_URI = "/feeds/";
public final static String RSS_URI = "/rss";
public final static String INVITATION_URI ="/invitations/";
public static final String HEADER_LINK = "Link";
public static final String HEADER_LOCATION = "Location";
private OrganizationService orgService;
private IdentityManager identityManager;
private UploadService uploadService;
private MailService mailService;
private int defaultLimit = 10;
private int hardLimit = 100;
private SubResourceHrefBuilder subResourcesBuilder = new SubResourceHrefBuilder(this);
private final static CacheControl nc = new CacheControl();
public static final String DEFAULT_CAL_NAME = "calendar";
public static final String DEFAULT_EVENT_NAME = "default";
public static final String[] RP_WEEKLY_BYDAY = CalendarEvent.RP_WEEKLY_BYDAY.clone();
public static final String[] EVENT_AVAILABILITY = {CalendarEvent.ST_AVAILABLE, CalendarEvent.ST_BUSY, CalendarEvent.ST_OUTSIDE};
public static final String[] REPEATTYPES = CalendarEvent.REPEATTYPES.clone();
public final static String RP_END_BYDATE = "endByDate";
public final static String RP_END_AFTER = "endAfter";
public final static String RP_END_NEVER = "neverEnd";
public static final String[] PRIORITY = CalendarEvent.PRIORITY.clone();
public static final String[] TASK_STATUS = CalendarEvent.TASK_STATUS.clone();
private static final String[] INVITATION_STATUS = {"", "maybe", "yes", "no"};
public static enum RecurringUpdateType {
ALL, FOLLOWING, ONE
}
static {
Arrays.sort(RP_WEEKLY_BYDAY);
Arrays.sort(EVENT_AVAILABILITY);
Arrays.sort(REPEATTYPES);
Arrays.sort(PRIORITY);
Arrays.sort(TASK_STATUS);
Arrays.sort(INVITATION_STATUS);
}
private final CacheControl cc = new CacheControl();
static {
nc.setNoCache(true);
nc.setNoStore(true);
}
private static final Log log = ExoLogger.getExoLogger(CalendarRestApi.class);
/**
* Constructor helps to configure the rest service with parameters.
*
* Here is the configuration parameters:
* - default.limit default number of objects returned by a collection query, default value: 10.
* - hard.limit maximum number of objects returned by a collection query, default value: 100.
* - cache_maxage time in milliseconds returned in the cache-control header, default value: 604800.
*
* @param orgService
* eXo organization service implementation.
*
* @param params
* Object contains the configuration parameters.
*/
public CalendarRestApi(OrganizationService orgService, IdentityManager identityManager, UploadService uploadService, MailService mailService, InitParams params) {
this.orgService = orgService;
this.identityManager = identityManager;
this.uploadService = uploadService;
this.mailService = mailService;
int maxAge = 604800;
if (params != null) {
if (params.getValueParam("default.limit") != null) {
defaultLimit = Integer.parseInt(params.getValueParam("default.limit").getValue());
}
if (params.getValueParam("hard.limit") != null) {
hardLimit = Integer.parseInt(params.getValueParam("hard.limit").getValue());
}
ValueParam cacheConfig = params.getValueParam("cache_maxage");
if (cacheConfig != null) {
try {
maxAge = Integer.parseInt(cacheConfig.getValue());
} catch (Exception ex) {
log.warn("Can't parse {} to maxAge, use the default value {}", cacheConfig, maxAge);
}
}
}
cc.setPrivate(true);
cc.setMaxAge(maxAge);
cc.setSMaxAge(maxAge);
}
/**
* Returns all the available sub-resources of this API in JSON.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar
*
* @format JSON
*
* @response
* {
* "subResourcesHref": [
* "http://localhost:8080/rest/private/v1/calendar/calendars",
* "http://localhost:8080/rest/private/v1/calendar/events",
* "http://localhost:8080/rest/private/v1/calendar/tasks"
* ]
* }
*
* @return URLs of all REST services provided by this API, in absolute form.
*
* @authentication
*
* @anchor CalendarRestApi.getSubResources
*/
@GET
@RolesAllowed("users")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns all the available subresources as json",
notes = "Returns all the available subresources as json, in order to navigate easily in the REST API.")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all available subresources")
})
public Response getSubResources(@Context UriInfo uri) {
Map<String, String[]> subResources = new HashMap<String, String[]>();
subResources.put("subResourcesHref", subResourcesBuilder.buildResourceMap(uri));
return Response.ok(subResources, MediaType.APPLICATION_JSON).cacheControl(nc).build();
}
/**
* Searches for calendars by a type (personal/group/shared), returns calendars that the user has access permission.
*
* @param type Type of calendar, can be *personal*, *group* or *shared*. If omitted or unknown, it searches for *all* types.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of calendars returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched calendars will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective calendar attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request {@code GET: http://localhost:8080/rest/private/v1/calendar/calendars?type=personal&fields=id,name}
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* {
* "editPermission": "/platform/users/:*.*;",
* "viewPermission": "*.*;",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/calendar8b8f65e77f00010122b425ec81b80da9/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "Users",
* "type": "0",
* "owner": null,
* "groups": [
* "/platform/users"
* ],
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/calendar8b8f65e77f00010122b425ec81b80da9",
* "id": "calendar8b8f65e77f00010122b425ec81b80da9"
* }
* ],
* "size": -1,
* "offset": 0
* }
*
* @return List of calendars in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getCalendars
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@GET
@RolesAllowed("users")
@Path("/calendars/")
@Produces({MediaType.APPLICATION_JSON})
@ApiOperation(
value = "Returns all user-related calendars",
notes = "This method lists all the calendars a specific user can see.")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all user-related calendars"),
@ApiResponse(code = 404, message = "Bad Request, or no calendars associated to the user"),
@ApiResponse(code = 503, message = "Can't generate JSON file") })
public Response getCalendars(
@ApiParam(value = "The calendar type to search for. It can be one of \"personal, group, shared\"", required = false, allowableValues = "personal, group, shared") @QueryParam("type") String type,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "Tell the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@ApiParam(value = "This is a list of comma-separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uri) {
try {
limit = parseLimit(limit);
Type calType = Calendar.Type.UNDEFINED;
if (type != null) {
try {
calType = Calendar.Type.valueOf(type.toUpperCase());
} catch (IllegalArgumentException ex) {
// Use default Type.UNDEFINED in any case of exception
log.debug(ex);
}
}
CalendarCollection<Calendar> cals = calendarServiceInstance().getAllCalendars(currentUserId(), calType.type(), offset, limit);
if(cals == null || cals.isEmpty()) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
String basePath = getBasePath(uri);
Collection data = new LinkedList();
Iterator<Calendar> calIterator = cals.iterator();
while (calIterator.hasNext()) {
Calendar cal = calIterator.next();
setCalType(cal);
data.add(extractObject(new CalendarResource(cal, basePath), fields));
}
CollectionResource calData = new CollectionResource(data, returnSize ? cals.getFullSize() : -1);
calData.setOffset(offset);
calData.setLimit(limit);
ResponseBuilder okResult;
if (jsonp != null) {
JsonValue value = new JsonGeneratorImpl().createJsonObject(calData);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(value).append(");");
okResult = Response.ok(sb.toString(), new MediaType("text", "javascript"));
} else {
okResult = Response.ok(calData, MediaType.APPLICATION_JSON);
}
if (returnSize) {
okResult.header(HEADER_LINK, buildFullUrl(uri, offset, limit, calData.getSize()));
}
//
return okResult.cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Creates a calendar based on calendar attributes sent in the request body. Can be personal or group calendar.
*
* This accepts HTTP POST request, with JSON object (cal) represents a CalendarResource. Example:
* {
* name: "Calendar name",
* description: "Description of the calendar"
* }
*
* @param cal JSON object contains attributes of calendar object to create.
* All attributes are optional. If specified explicitly, calendar name must not empty,
* contains only letter, digit, space, "-", "_" characters. Default value of calendar name is: calendar.
*
* @request POST: http://localhost:8080/rest/private/v1/calendar/calendars
*
* @response HTTP status code:
* 201 if created successfully, and HTTP header *location* href that points to the newly created calendar.
* 401 if the user does not have create permission, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.createCalendar
*/
@POST
@RolesAllowed("users")
@Path("/calendars/")
@ApiOperation(
value = "Creates a calendar",
notes = "Creates a calendar if: <br/>"
+ "- this is a personal calendar and the user is authenticated<br/>"
+ "- this is a group calendar and the user is authenticated and belongs to the group."
)
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Calendar successfully created"),
@ApiResponse(code = 401, message = "The User isn't authorized to create a calendar there")
})
public Response createCalendar(CalendarResource cal, @Context UriInfo uriInfo) {
Calendar calendar = new Calendar();
if (cal.getName() == null) {
cal.setName(DEFAULT_CAL_NAME);
}
if (cal.getOwner() == null) {
cal.setOwner(currentUserId());
}
Response error = buildCalendar(calendar, cal);
if (error != null) {
return error;
}
if (cal.getGroups() != null && cal.getGroups().length > 0) {
// Create a group calendar
if (isInGroups(cal.getGroups())) {
calendarServiceInstance().savePublicCalendar(calendar, true);
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} else {
if (cal.getOwner() != null && !cal.getOwner().equals(currentUserId())) {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
} else {
// Create a personal calendar
final String username = currentUserId();
calendarServiceInstance().saveUserCalendar(username, calendar, true);
// Share calendar if user set edit or view permission
String[] viewPermissions = calendar.getViewPermission();
if (viewPermissions != null && viewPermissions.length > 0) {
Set<String> sharedUsers = new HashSet<String>();
Set<String> sharedGroups = new HashSet<String>();
for (String permission : viewPermissions) {
PermissionOwner perm = PermissionOwner.createPermissionOwnerFrom(permission);
if (PermissionOwner.USER_OWNER.equals(perm.getOwnerType())) {
sharedUsers.add(perm.getId());
} else if (PermissionOwner.GROUP_OWNER.equals(perm.getOwnerType())) {
sharedGroups.add(perm.getGroupId());
} else if (PermissionOwner.MEMBERSHIP_OWNER.equals(perm.getOwnerType())) {
try {
sharedUsers.addAll(Utils.getUserByMembershipId(perm.getMembership(), perm.getGroupId()));
} catch (Exception ex) {
log.warn("Can not share calendar to Membership: " + permission, ex);
}
}
}
if (sharedGroups.size() > 0) {
try {
calendarServiceInstance().shareCalendarByRunJob(username, calendar.getId(), new ArrayList<String>(sharedGroups));
} catch (Exception ex) {
log.warn("Exception while share calendar to groups", ex);
}
}
if (sharedUsers.size() > 0) {
try {
calendarServiceInstance().shareCalendar(username, calendar.getId(), new ArrayList<String>(sharedUsers));
} catch (Exception ex) {
log.warn("Exception while share calendar to users", ex);
}
}
}
}
}
StringBuilder location = new StringBuilder(getBasePath(uriInfo));
location.append(CALENDAR_URI);
location.append(calendar.getId());
return Response.status(HTTPStatus.CREATED).header(HEADER_LOCATION, location).cacheControl(nc).build();
}
/**
* Search for a calendar by its id, in one of conditions:
* The authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the calendar to be retrieved.
*
* @param fields Comma-separated list of selective calendar attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/calendars/{id}
*
* @format JSON
*
* @response
* {
* "editPermission": "/platform/users/:*.*;",
* "viewPermission": "*.*;",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/calendar8b8f65e77f00010122b425ec81b80da9/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "Users",
* "type": "2",
* "owner": null,
* "groups": [
* "/platform/users"
* ],
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/calendar8b8f65e77f00010122b425ec81b80da9",
* "id": "calendar8b8f65e77f00010122b425ec81b80da9"
* }
* @return Calendar as JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getCalendarById
*/
@GET
@RolesAllowed("users")
@Path("/calendars/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Finds a calendar by ID",
notes = "Returns the calendar with the specified id parameter if:<br/>"
+ "- The authenticated user is the owner of the calendar<br/>"
+ "- The authenticated user belongs to the group of the calendar<br/>"
+ "- The calendar has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "Can't generate JSON file")
})
public Response getCalendarById(
@ApiParam(value = "Identity of the calendar to retrieve", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma-separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
CalendarService service = calendarServiceInstance();
Calendar cal = service.getCalendarById(id);
if(cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
cal.setCalType(service.getTypeOfCalendar(currentUserId(), cal.getId()));
Date lastModified = new Date(cal.getLastModified());
ResponseBuilder preCondition = request.evaluatePreconditions(lastModified);
if (preCondition != null) {
return preCondition.build();
}
CalendarResource calData = null;
if (this.hasViewCalendarPermission(cal, currentUserId())) {
setCalType(cal);
calData = new CalendarResource(cal, getBasePath(uriInfo));
}
if (calData == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Object resource = extractObject(calData, fields);
if (jsonp != null) {
String json = null;
if (resource instanceof Map) json = new JSONObject(resource).toString();
else {
JsonGeneratorImpl generatorImpl = new JsonGeneratorImpl();
json = generatorImpl.createJsonObject(resource).toString();
}
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(json).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript")).cacheControl(cc).lastModified(lastModified).build();
}
//
return Response.ok(resource, MediaType.APPLICATION_JSON).cacheControl(cc).lastModified(lastModified).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Update a calendar specified by id, in one of conditions:
* the authenticated user is the owner of the calendar,
* OR for group calendars, the authenticated user has edit permission on the calendar.
*
* This accepts HTTP PUT request, with JSON object (calObj) in the request body, and calendar id in the path.
* All the attributes of JSON object are optional, any absent/invalid ones will be ignored.
* *id*, *href* and URLs attributes are Read-only.
*
* @param id Identity of the updated calendar.
*
* @param calObj JSON object contains attributes of calendar object to update, all the attributes are optional.
*
* @request PUT: http://localhost:8080/rest/private/v1/calendar/calendars/demo-defaultCalendarId
*
* @response HTTP status code: 200 if updated successfully, 404 if calendar not found,
* 401 if the user does not have edit permission, 403 if user submit invalid data to update
* 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.updateCalendarById
*/
@PUT
@RolesAllowed("users")
@Path("/calendars/{id}")
@ApiOperation(
value = "Updates a calendar",
notes = "Update the calendar with specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Calendar successfully updated"),
@ApiResponse(code = 401, message = "User unauthorized to update the calendar"),
@ApiResponse(code = 403, message = "If user try to update invalid data to the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response updateCalendarById(
@ApiParam(value = "Identity of the calendar to update", required = true) @PathParam("id") String id,
CalendarResource calObj) {
try {
Calendar cal = calendarServiceInstance().getCalendarById(id);
if(cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
//Only allow to edit if user is owner of calendar, or have edit permission on group calendar
//don't allow to edit shared calendar, or remote calendar
if ((currentUserId().equals(cal.getCalendarOwner()) || cal.getGroups() != null) &&
Utils.isCalendarEditable(currentUserId(), cal)) {
final List<String> oldViewPermissions;
if (cal.getViewPermission() != null) {
oldViewPermissions = Collections.unmodifiableList(Arrays.<String>asList(cal.getViewPermission()));
} else {
oldViewPermissions = Collections.<String>emptyList();
}
Response error = buildCalendar(cal, calObj);
if (error != null) {
return error;
} else {
int type = calendarServiceInstance().getTypeOfCalendar(currentUserId(), cal.getId());
if (type == Calendar.TYPE_PRIVATE) {
if (!currentUserId().equals(cal.getCalendarOwner())) {
return Response.status(HTTPStatus.FORBIDDEN).entity("Can not change owner of personal calendar").cacheControl(nc).build();
}
if (cal.getGroups() != null && cal.getGroups().length > 0) {
return Response.status(HTTPStatus.FORBIDDEN).entity("Can not update groups of personal calendar").cacheControl(nc).build();
}
}
calendarServiceInstance().saveCalendar(cal.getCalendarOwner(), cal, type, false);
if (type == Calendar.TYPE_PRIVATE) {
final List<String> viewPermissions;
if (cal.getViewPermission() != null) {
viewPermissions = Arrays.asList(cal.getViewPermission());
} else {
viewPermissions = Collections.emptyList();
}
//. Only update when new viewPermission is different with old viewPermission
boolean needUpdateShare = false;
if (oldViewPermissions.size() != viewPermissions.size()) {
needUpdateShare = true;
} else {
for (String p : oldViewPermissions) {
if (!viewPermissions.contains(p)) {
needUpdateShare = true;
break;
}
}
}
if (needUpdateShare) {
final String username = currentUserId();
final String calendarId = cal.getId();
Set<String> newSharedUsers = new HashSet<String>();
Set<String> newSharedGroups = new HashSet<String>();
for (String p : viewPermissions) {
PermissionOwner perm = PermissionOwner.createPermissionOwnerFrom(p);
String ownerType = perm.getOwnerType();
if (PermissionOwner.USER_OWNER.equals(ownerType)) {
newSharedUsers.add(perm.getId());
} else if (PermissionOwner.GROUP_OWNER.equals(ownerType)) {
newSharedGroups.add(perm.getGroupId());
} else if (PermissionOwner.MEMBERSHIP_OWNER.equals(ownerType)) {
try {
newSharedUsers.addAll(Utils.getUserByMembershipId(perm.getMembership(), perm.getGroupId()));
} catch (Exception ex) {
log.warn("Exception while try to share calendar to Membership: " + p, ex);
}
}
}
Set<String> removeShareUsers = new HashSet<String>();
Set<String> removeShareGroups = new HashSet<String>();
if (oldViewPermissions.size() > 0) {
for (String p : oldViewPermissions) {
if (viewPermissions.contains(p)) {
continue;
}
PermissionOwner perm = PermissionOwner.createPermissionOwnerFrom(p);
String ownerType = perm.getOwnerType();
if (PermissionOwner.USER_OWNER.equals(ownerType)) {
removeShareUsers.add(perm.getId());
} else if (PermissionOwner.GROUP_OWNER.equals(ownerType)) {
removeShareGroups.add(perm.getGroupId());
} else if (PermissionOwner.MEMBERSHIP_OWNER.equals(ownerType)) {
try {
removeShareUsers.addAll(Utils.getUserByMembershipId(perm.getMembership(), perm.getGroupId()));
} catch (Exception ex) {
log.error("Exception when try unshare calendar to Membership: " + p, ex);
}
}
}
}
// Remove all who shared before but not share any more
for (String user : removeShareUsers) {
calendarServiceInstance().removeSharedCalendar(user, calendarId);
}
if (removeShareGroups.size() > 0) {
calendarServiceInstance().removeSharedCalendarByJob(username,
new ArrayList<String>(removeShareGroups), calendarId);
}
// Share to new user or group
if (newSharedUsers.size() > 0) {
newSharedUsers.remove(username);
calendarServiceInstance().shareCalendar(username, calendarId, new ArrayList<String>(newSharedUsers));
}
if (newSharedGroups.size() > 0) {
calendarServiceInstance().shareCalendarByRunJob(username,
calendarId, new ArrayList<String>(newSharedGroups));
}
}
}
return Response.ok().cacheControl(nc).build();
}
}
//
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
} catch (Exception e) {
log.error(e);
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Deletes a calendar specified by id, in one of conditions:
* - the authenticated user is the owner of the calendar.
* - for group calendars, the authenticated user has edit permission on the calendar.
* - if it is a shared calendar, the calendar is not shared anymore (but not deleted).
*
* @param id Identity of the calendar to be deleted.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/calendars/demo-defaultCalendarId
*
* @response HTTP status code: 200 if updated successfully, 404 if calendar not found,
* 401 if the user does not have permissions, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.deleteCalendarById
*/
@DELETE
@RolesAllowed("users")
@Path("/calendars/{id}")
@ApiOperation(
value = "Deletes a calendar",
notes = "Delete the calendar with the specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar.<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar.<br/>"
+ "- If it is a shared calendar the calendar is not shared anymore (but the original calendar is not deleted).")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Calendar successfully deleted"),
@ApiResponse(code = 401, message = "User unauthorized to delete the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response deleteCalendarById(
@ApiParam(value = "Identity of the calendar to delete", required = true) @PathParam("id") String id) {
try {
Calendar cal = calendarServiceInstance().getCalendarById(id);
if(cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
cal.setCalType(calendarServiceInstance().getTypeOfCalendar(currentUserId(), id));
if (Utils.isCalendarEditable(currentUserId(), cal) || cal.getCalType() == Calendar.TYPE_SHARED) {
switch (cal.getCalType()) {
case Calendar.TYPE_PRIVATE:
calendarServiceInstance().removeUserCalendar(cal.getCalendarOwner(), id);
break;
case Calendar.TYPE_PUBLIC:
calendarServiceInstance().removePublicCalendar(id);
break;
case Calendar.TYPE_SHARED:
if (this.hasViewCalendarPermission(cal, currentUserId())) {
calendarServiceInstance().removeSharedCalendar(currentUserId(),id);
break;
}
}
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Exports a calendar specified by id into iCal formated file, with one of conditions:
* The calendar is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the exported calendar.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/calendars/demo-defaultCalendarId/ics
*
* @format text/calendar
*
* @response ICS file on success, or HTTP status code 404 on failure.
*
* @return ICS file on success, or HTTP status code 404 on failure.
*
* @authentication
*
* @anchor CalendarRestApi.exportCalendarToIcs
*/
@GET
@RolesAllowed("users")
@Path("/calendars/{id}/ics")
@Produces(TEXT_ICS)
@ApiOperation(
value = "Exports a calendar to iCal",
notes = "Returns an iCalendar formated file which is exported from the calendar with specified id if:<br/>"
+ "- the calendar is public<br/>"
+ "- the authenticated user is the owner of the calendar<br/>"
+ "- the authenticated user belongs to the group of the calendar<br/>"
+ "- the calendar has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Calendar successfully exported to ICS"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred")
})
public Response exportCalendarToIcs(
@ApiParam(value = "Identity of the calendar to retrieve ICS file", required = true) @PathParam("id") String id,
@Context Request request) {
try {
Calendar cal = calendarServiceInstance().getCalendarById(id);
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId())) {
int type = calendarServiceInstance().getTypeOfCalendar(currentUserId(),id);
String username = currentUserId();
if (type == -1) {
//this is a workaround
//calendarService can't find type of calendar correctly
type = Calendar.TYPE_PRIVATE;
username = cal.getCalendarOwner();
}
CalendarImportExport iCalExport = calendarServiceInstance().getCalendarImportExports(CalendarService.ICALENDAR);
ArrayList<String> calIds = new ArrayList<String>();
calIds.add(id);
OutputStream out = iCalExport.exportCalendar(username, calIds, String.valueOf(type), Utils.UNLIMITED);
// In case calendar hasn't got any event/task, CalendarImportExport service will return NULL
// But in this case, we still should return an ICS file without any event/task (like Google do)
// This is workaround to reach it without update in CalendarImportExport. (Because CalendarImportExport is used in some other business that need NULL value returned).
if (out == null) {
net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
calendar.getProperties().add(Version.VERSION_2_0);
calendar.getProperties().add(CalScale.GREGORIAN);
calendar.getProperties().add(Method.REQUEST);
out = new ByteArrayOutputStream();
CalendarOutputter output = new CalendarOutputter(false);
output.output(calendar, out);
}
byte[] data = out.toString().getBytes();
byte[] hashCode = digest(data).getBytes();
EntityTag tag = new EntityTag(new String(hashCode));
ResponseBuilder preCondition = request.evaluatePreconditions(tag);
if (preCondition != null) {
return preCondition.build();
}
InputStream in = new ByteArrayInputStream(data);
return Response.ok(in, TEXT_ICS_TYPE)
.header("Content-Disposition", "attachment;filename=\"" + cal.getName() + Utils.ICS_EXT)
.cacheControl(cc).tag(tag).build();
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Searches for an event by id, in one of conditions:
* The calendar of the event is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event,
* OR the calendar of the event has been shared with the user or with a group of the user.
*
* @param id Identity of the event.
*
* @param fields Comma-separated list of selective event attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of property names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/events/Event123
*
* @format JSON
*
* @response
* {
* "to": "2015-07-24T01:30:00.000Z",
* "attachments": [],
* "from": "2015-07-24T01:00:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "availability": "busy",
* "repeat": {},
* "reminder": [],
* "privacy": "private",
* "recurrenceId": null,
* "participants": [
* "john"
* ],
* "originalEvent": null,
* "description": null,
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "subject": "event123",
* "location": null,
* "priority": "none",
* "href": "http://localhost:8080/rest/private/v1/calendar/events/Eventa9c5b87b7f00010178ce661a6beb020d",
* "id": "Eventa9c5b87b7f00010178ce661a6beb020d"
* }
*
* @return Event as JSON object.
*
* @authentication
*
* @anchor CalendarRestApi.getEventById
*/
@GET
@RolesAllowed("users")
@Path("/events/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns an event by ID",
notes = "Returns an event with specified id parameter if:<br/>"
+ "- the calendar of the event is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user"
)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred")
})
public Response getEventById(
@ApiParam(value = "Identity of the event to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
CalendarService service = calendarServiceInstance();
CalendarEvent ev = service.getEventById(id);
if(ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Date lastModified = new Date(ev.getLastModified());
ResponseBuilder preCondition = request.evaluatePreconditions(lastModified);
if (preCondition != null) {
return preCondition.build();
}
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
boolean inParticipant = false;
String[] participant = ev.getParticipant();
if (participant != null) {
Arrays.sort(participant);
if (Arrays.binarySearch(participant, currentUserId()) > -1) inParticipant = true;
}
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId()) || inParticipant) {
Object resource = buildEventResource(ev, uriInfo, expand, fields);
return buildJsonP(resource, jsonp).cacheControl(cc).lastModified(lastModified).build();
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Updates an event specified by id, in one of conditions:
* The authenticated user is the owner of the calendar of the event,
* OR for group calendars, the user has edit permission on the calendar,
* OR the calendar has been shared with the user, with edit permission,
* OR the calendar has been shared with a group of the user, with edit permission.
*
* This accepts HTTP PUT request, with JSON object (evObject) in the request body, and event id in the path.
* All the attributes of JSON object are optional, any absent/invalid ones will be ignored.
* Read-only attributes: *id* and *href*, *originalEvent*, *calendar*, *recurrentId* will be ignored too.
*
* @param id Identity of the updated event.
*
* @param evObject JSON object contains event attributes, all are optional.
* If provided explicitly (not null), attributes are checked with some rules:
* 1. *subject* must not be empty.
* 2. *availability* can only be one of "available", "busy", "outside".
* 3. *repeat.repeatOn* can only be one of "MO", "TU", "WE", "TH", "FR", "SA", "SU".
* 4. *repeat.repeatBy* {@literal must be >= 1 and <= 31}.
* 5. *repeat.repeatType* must be one of "norepeat", "daily", "weekly", "monthly", "yearly".
* 6. *from* date must be earlier than *to* date.
* 7. *priority* must be one of "none", "high", "normal", "low".
* 8. *privacy* can only be "public" or "private".
*
* @request PUT: http://localhost:8080/rest/private/v1/calendar/events/Event123
*
* @response HTTP status code: 200 if updated successfully, 400 if parameters are not valid, 404 if event does not exist,
* 401 if the user does not have edit permission, 503 if any error during save process.
*
* @return HTTP status code
*
* @authentication
*
* @anchor CalendarRestApi.updateEventById
*/
@PUT
@RolesAllowed("users")
@Path("/events/{id}")
@ApiOperation(
value = "Updates an event identified by its ID",
notes = "Updates the event with specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Event successfully updated"),
@ApiResponse(code = 400, message = "Bad Request, parameters not valid"),
@ApiResponse(code = 401, message = "User unauthorized to update the event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "Error during the saving process")
})
public Response updateEventById(
@ApiParam(value = "Identity of the event to update", required = true) @PathParam("id") String id,
@ApiParam(value = "Recurring update type, can be ALL, FOLLOWING or ONE, by default, it's ONE", required = false) @QueryParam("recurringUpdateType") RecurringUpdateType recurringUpdateType,
EventResource evObject) {
try {
CalendarEvent event = calendarServiceInstance().getEventById(id);
if(event == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
if (recurringUpdateType != null) {
//clone the event to build occurrent event
event = CalendarEvent.build(event);
}
Calendar moveToCal = null;
if (evObject.getCalendarId() != null && !event.getCalendarId().equals(evObject.getCalendarId())) {
moveToCal = calendarServiceInstance().getCalendarById(evObject.getCalendarId());
}
Calendar cal = calendarServiceInstance().getCalendarById(event.getCalendarId());
int fromType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), cal.getId());
if (Utils.isCalendarEditable(currentUserId(), cal) && (moveToCal == null || Utils.isCalendarEditable(currentUserId(), moveToCal))) {
Response error = buildEvent(event, evObject, moveToCal);
if (error != null) {
return error;
}
int toType;
if(moveToCal != null) {
toType = moveToCal.getCalType();
} else {
toType = fromType;
}
moveToCal = moveToCal == null ? cal : moveToCal;
saveEvent(cal.getId(), moveToCal.getId(), fromType, toType, event, recurringUpdateType, false);
return Response.ok().cacheControl(nc).build();
}
//
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Deletes an event specified by id, in one of conditions:
* The authenticated user is the owner of the calendar of the event,
* OR for group calendars, the user has edit permission on the calendar,
* OR the calendar has been shared with the user, with edit permission,
* OR the calendar has been shared with a group of the user, with edit permission.
*
* @param id Identity of the event.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/events/Event123
*
* @response HTTP status code: 200 if deleted successfully, 404 if event not found,
* 401 if the user does not have edit permission, 503 if any error during save process.
*
* @return HTTP status code
*
* @authentication
*
* @anchor CalendarRestApi.deleteEventById
*/
@DELETE
@RolesAllowed("users")
@Path("/events/{id}")
@ApiOperation(
value = "Deletes an event identified by its ID",
notes = "Delete an event with specified id parameter if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Event deleted successfully"),
@ApiResponse(code = 401, message = "User unauthorized to delete this event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response deleteEventById(
@ApiParam(value = "identity of the event to delete", required = true) @PathParam("id") String id) {
try {
CalendarEvent ev = calendarServiceInstance().getEventById(id);
if(ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = Calendar.TYPE_ALL;
try {
calType = Integer.parseInt(ev.getCalType());
} catch (NumberFormatException e) {
calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), ev.getCalendarId());
}
switch (calType) {
case Calendar.TYPE_PRIVATE:
calendarServiceInstance().removeUserEvent(currentUserId(), ev.getCalendarId(), id);
break;
case Calendar.TYPE_PUBLIC:
calendarServiceInstance().removePublicEvent(ev.getCalendarId(),id);
break;
case Calendar.TYPE_SHARED:
calendarServiceInstance().removeSharedEvent(currentUserId(), ev.getCalendarId(), id);
break;
default:
break;
}
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns attachments of an event specified by event id, in one of conditions:
* The calendar of the event is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the event.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of attachments returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param fields Comma-separated list of selective attachment attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/events/Event123/attachments
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "mimeType": "image/jpeg",
* "weight": 31249,
* "name": "test.jpg",
* "href": "...",
* "id": "..."
* }
* ],
* "size": 1,
* "offset": 0
* }
*
* @return Attachments in JSON, or HTTP status 404 if event not found.
*
* @authentication
*
* @anchor CalendarRestApi.getAttachmentsFromEvent
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/events/{id}/attachments")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns the attachments of an event identified by its ID",
notes = "Returns attachments of an event with specified id if:<br/>"
+ "- the calendar of the event is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all attachments"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occured during the saving process")
})
public Response getAttachmentsFromEvent(
@ApiParam(value = "Identity of an event to query for attachments", required = true) @PathParam("id") String id,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used*", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "This is a list of comma-separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo) {
try {
limit = parseLimit(limit);
CalendarEvent ev = calendarServiceInstance().getEventById(id);
if(ev == null || ev.getAttachment() == null) {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
} else {
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
boolean inParticipant = false;
if (ev.getParticipant() != null) {
String[] participant = ev.getParticipant();
Arrays.sort(participant);
int i = Arrays.binarySearch(participant, currentUserId());
if (i > -1) inParticipant = true;
}
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId()) || inParticipant) {
Iterator<Attachment> it = ev.getAttachment().iterator();
List attResource = new ArrayList();
Utils.skip(it, offset);
int counter = 0;
String basePath = getBasePath(uriInfo);
while (it.hasNext()) {
Attachment a = it.next();
attResource.add(extractObject(new AttachmentResource(a, basePath), fields));
if(++counter == limit) break;
}
CollectionResource evData = new CollectionResource(attResource, ev.getAttachment().size());
evData.setOffset(offset);
evData.setLimit(limit);
if (jsonp != null) {
JsonValue value = new JsonGeneratorImpl().createJsonObject(evData);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(value).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript")).cacheControl(nc).header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, evData.getSize())).build();
}
//
return Response.ok(evData, MediaType.APPLICATION_JSON).header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, evData.getSize())).cacheControl(nc).build();
}
//
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Creates attachments for an event specified by id, in one of conditions:
* The authenticated user is the owner of the calendar of the event,
* OR for group calendars, the user has edit permission on the calendar,
* OR the calendar has been shared with the user, with edit permission,
* OR the calendar has been shared with a group of the user, with edit permission.
*
* This accepts HTTP POST request, with files in HTTP form submit request, and the event id in path.
*
* @param id Identity of the event.
*
* @param iter Iterator of org.apache.commons.fileupload.FileItem objects.
* (eXo Rest framework uses Apache file upload to parse the input stream of HTTP form submit request, and inject FileItem objects).
*
* @request POST: http://localhost:8080/rest/private/v1/calendar/events/Event123/attachments
*
* @response HTTP status code:
* 201 if created successfully, and HTTP header *location* href that points to the newly created attachments.
* 404 if event not found, 401 if the user does not have create permission, 503 if any error during save process.
*
* @return HTTP status code
*
* @authentication
*
* @anchor CalendarRestApi.createAttachmentForEvent
*/
@POST
@RolesAllowed("users")
@Path("/events/{id}/attachments")
@Consumes("multipart/*")
@ApiOperation(
value = "Creates attachments for an event identified by its ID",
notes = "Creates attachments for an event with specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Attachment successfully created"),
@ApiResponse(code = 401, message = "User unauthorized to create an attachment to this event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response createAttachmentForEvent(
@Context UriInfo uriInfo,
@ApiParam(value = "Identity of an event where the attachment is created", required = true) @PathParam("id") String id,
Iterator<FileItem> iter) {
try {
CalendarEvent event = calendarServiceInstance().getEventById(id);
if (event == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(event.getCalendarId());
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = Calendar.TYPE_ALL;
List<Attachment> attachment = new ArrayList<Attachment>();
try {
calType = Integer.parseInt(event.getCalType());
} catch (NumberFormatException e) {
calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), event.getCalendarId());
}
attachment.addAll(event.getAttachment());
while (iter.hasNext()) {
FileItem file = iter.next();
String fileName = file.getName();
if(fileName != null) {
String mimeType = new MimeTypeResolver().getMimeType(fileName.toLowerCase());
Attachment at = new Attachment();
at.setMimeType(mimeType);
at.setSize(file.getSize());
at.setName(file.getName());
at.setInputStream(file.getInputStream());
attachment.add(at);
}
}
event.setAttachment(attachment);
saveEvent(calType, event, false);
StringBuilder attUri = new StringBuilder(getBasePath(uriInfo));
attUri.append("/").append(event.getId());
attUri.append(ATTACHMENT_URI);
return Response.status(HTTPStatus.CREATED).header(HEADER_LOCATION, attUri.toString()).cacheControl(nc).build();
}
//
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns events of a calendar specified by id, in one of conditions:
* The calendar is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of a calendar to search for events.
*
* @param start Date that complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date.
* Default: current server time.
*
* @param end Date that complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date.
* Default: current server time + 1 week.
*
* @param category Search for this category only. If not specified, search events of all categories.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of events returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched calendars will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective event properties to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of event attribute names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request {@code GET: http://localhost:8080/rest/private/v1/calendar/calendars/myCalId/events?category=meeting&expand=calendar,categories(1,5)}
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "to": "2015-07-24T01:30:00.000Z",
* "attachments": [],
* "from": "2015-07-24T01:00:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "availability": "busy",
* "repeat": {},
* "reminder": [],
* "privacy": "private",
* "recurrenceId": null,
* "participants": [
* "john"
* ],
* "originalEvent": null,
* "description": null,
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "subject": "event123",
* "location": null,
* "priority": "none",
* "href": "http://localhost:8080/rest/private/v1/calendar/events/Eventa9c5b87b7f00010178ce661a6beb020d",
* "id": "Eventa9c5b87b7f00010178ce661a6beb020d"
* }
* ],
* "size": -1,
* "offset": 0
* }
*
* @return List of events in JSON, or HTTP status 404 if the calendar is not found.
*
* @authentication
*
* @anchor CalendarRestApi.getEventsByCalendar
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/calendars/{id}/events")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns the events of a calendar identified by its ID",
notes = "Returns events of an calendar with specified id when:<br/>"
+ "- the calendar is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all events from the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found")
})
public Response getEventsByCalendar(
@ApiParam(value = "Identity of a calendar to search for events", required = true) @PathParam("id") String id,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date", required = false, defaultValue = "Current server time") @QueryParam("startTime") String start,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date", required = false, defaultValue = "Current server time + 1 week") @QueryParam("endTime") String end,
@ApiParam(value = "Search for this category only. If not specified, search event of any category", required = false) @QueryParam("category") String category,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "Tells the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@Context UriInfo uri) throws Exception {
limit = parseLimit(limit);
String username = currentUserId();
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
long fullSize = returnSize ? 0 : -1;
List data = new LinkedList();
Calendar calendar = service.getCalendarById(id);
if (calendar != null) {
if (calendar.hasChildren()) {
String participant = null;
if (calendar.getPublicUrl() == null && !hasViewCalendarPermission(calendar, username)) {
participant = username;
}
EventQuery eventQuery = buildEventQuery(start, end, category, Arrays.asList(calendar),
id, participant, CalendarEvent.TYPE_EVENT);
ListAccess<CalendarEvent> events = evtDAO.findEventsByQuery(eventQuery);
//
for (CalendarEvent event : events.load(offset, limit)) {
data.add(buildEventResource(event, uri, expand, fields));
}
if (returnSize) {
fullSize = events.getSize();
}
}
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
//
CollectionResource evData = new CollectionResource(data, fullSize);
evData.setOffset(offset);
evData.setLimit(limit);
ResponseBuilder response = buildJsonP(evData, jsonp);
if (returnSize) {
response.header(HEADER_LINK, buildFullUrl(uri, offset, limit, fullSize));
}
//
return response.build();
}
/**
* Returns events of the authenticated user.
*
* @param start Date that complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date.
* Default: current server time.
*
* @param end Date that complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date.
* Default: current server time + 1 week.
*
* @param category Search for this category only. If not specified, search events of all categories.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of events returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched calendars will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective event properties to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of event attribute names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *query_limit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request {@code GET: http://localhost:8080/rest/private/v1/calendar/calendars/myCalId/events?category=meeting&expand=calendar,categories(1,5)}
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "to": "2015-07-24T01:30:00.000Z",
* "attachments": [],
* "from": "2015-07-24T01:00:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "availability": "busy",
* "repeat": {},
* "reminder": [],
* "privacy": "private",
* "recurrenceId": null,
* "participants": [
* "john"
* ],
* "originalEvent": null,
* "description": null,
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "subject": "event123",
* "location": null,
* "priority": "none",
* "href": "http://localhost:8080/rest/private/v1/calendar/events/Eventa9c5b87b7f00010178ce661a6beb020d",
* "id": "Eventa9c5b87b7f00010178ce661a6beb020d"
* }
* ],
* "size": -1,
* "offset": 0
* }
*
* @return List of events in JSON, or HTTP status 404 if the calendar is not found.
*
* @authentication
*
* @anchor CalendarRestApi.getEventsByCalendar
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/events")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns the events of a calendar identified by its ID",
notes = "Returns events of an calendar with specified id when:<br/>"
+ "- the calendar is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all events from the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found")
})
public Response getEvents(
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date", required = false, defaultValue = "Current server time") @QueryParam("startTime") String start,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date", required = false, defaultValue = "Current server time + 1 week") @QueryParam("endTime") String end,
@ApiParam(value = "Search for this category only. If not specified, search event of any category", required = false) @QueryParam("category") String category,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities. If not specified or exceed the *query_limit* configuration of calendar rest service, it will use the *query_limit*", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "Tells the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@Context UriInfo uri) throws Exception {
limit = parseLimit(limit);
String username = currentUserId();
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
long fullSize = returnSize ? 0 : -1;
List data = new LinkedList();
List<Calendar> calendarList;
try {
calendarList = getCalendarsOfUser(service, username);
} catch (Exception e) {
log.error("Cannot find calendars of user " + username, e);
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
EventQuery eventQuery = buildEventQuery(start, end, category, calendarList,
null, username, CalendarEvent.TYPE_EVENT);
ListAccess<CalendarEvent> events = evtDAO.findEventsByQuery(eventQuery);
//
for (CalendarEvent event : events.load(offset, limit)) {
data.add(buildEventResource(event, uri, expand, fields));
}
if (returnSize) {
fullSize = events.getSize();
}
CollectionResource evData = new CollectionResource(data, fullSize);
evData.setOffset(offset);
evData.setLimit(limit);
ResponseBuilder response = buildJsonP(evData, jsonp);
if (returnSize) {
response.header(HEADER_LINK, buildFullUrl(uri, offset, limit, fullSize));
}
//
return response.build();
}
private List<Calendar> getCalendarsOfUser(CalendarService service, String username) throws Exception {
List<Calendar> list = new ArrayList<>();
List<GroupCalendarData> listgroupCalendar = service.getGroupCalendars(getUserGroups(username), true, username);
for (GroupCalendarData group : listgroupCalendar) {
Optional.ofNullable(group.getCalendars()).ifPresent(list::addAll);
}
Optional.ofNullable(service.getUserCalendars(username, true)).ifPresent(list::addAll);
return list;
}
private String[] getUserGroups(String username) throws Exception {
String [] groupsList;
Object[] objs = orgService.getGroupHandler().findGroupsOfUser(username).toArray();
groupsList = new String[objs.length];
for (int i = 0; i < objs.length; i++) {
groupsList[i] = ((Group) objs[i]).getId();
}
return groupsList;
}
/**
* Creates an event in a calendar specified by id, in one of conditions:
* The authenticated user is the owner of the calendar,
* OR for group calendars, the user has edit permission on the calendar,
* OR the calendar has been shared with the user, with edit permission,
* OR the calendar has been shared with a group of the user, with edit permission.
*
* This accepts HTTP POST request, with JSON object (evObject) in the request body.
*
* @param evObject JSON object contains attributes of event.
* All attributes are optional. If provided explicitly (not null), attributes are checked with some rules:
* 1. *subject* must not be empty, default value is: default.
* 2. *availability* can only be one of "available", "busy", "outside".
* 3. *repeat.repeatOn* can only be one of "MO", "TU", "WE", "TH", "FR", "SA", "SU".
* 4. *repeat.repeatBy* {@literal must be >= 1 and <= 31}.
* 5. *repeat.repeatType* must be one of "norepeat", "daily", "weekly", "monthly", "yearly".
* 6. *from* date must be earlier than *to* date.
* 7. *priority* must be one of "none", "high", "normal", "low".
* 8. *privacy* can only be public or private.
*
* @param id Identity of the calendar.
*
* @request POST: http://localhost:8080/rest/private/v1/calendar/calendars/myCalId/events
*
* @response HTTP status code:
* 201 if created successfully, and HTTP header *location* href that points to the newly created event.
* 400 if provided attributes are not valid (not pass the rule of evObject).
* 404 if calendar not found.
* 401 if the user does not have create permission.
* 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.createEventForCalendar
*/
@POST
@RolesAllowed("users")
@Path("/calendars/{id}/events")
@ApiOperation(
value = "Creates an event in a Calendar identified by its ID",
notes = "Creates an event in a calendar with specified id only if:<br/>"
+ "- the authenticated user is the owner of the calendar<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Event successfully created in the Calendar"),
@ApiResponse(code = 400, message = "Bad Request: Provided attributes are not valid (not following the rules of evObject)"),
@ApiResponse(code = 401, message = "User unauthorized to create an event in this calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response createEventForCalendar(
@ApiParam(value = "Identity of the calendar where the event is created", required = true) @PathParam("id") String id,
EventResource evObject,
@Context UriInfo uriInfo) {
try {
Calendar cal = calendarServiceInstance().getCalendarById(id);
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
CalendarEvent newEvent = new CalendarEvent();
if (evObject.getSubject() == null) {
evObject.setSubject(DEFAULT_EVENT_NAME);
}
if (evObject.getCategoryId() == null) {
evObject.setCategoryId(CalendarService.DEFAULT_EVENTCATEGORY_ID_ALL);
}
Response error = buildEvent(newEvent, evObject, null);
if (error != null) {
return error;
}
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), id);
newEvent.setCalendarId(id);
saveEvent(calType, newEvent, true);
String username = ConversationState.getCurrent().getIdentity().getUserId();
MailNotification mail = new MailNotification(mailService, orgService, calendarServiceInstance());
mail.sendEmail(newEvent,username);
String location = new StringBuilder(getBasePath(uriInfo)).append(EVENT_URI).append(newEvent.getId()).toString();
return Response.status(HTTPStatus.CREATED).header(HEADER_LOCATION, location).cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns occurrences of a recurring event specified by id, in one of conditions:
* the calendar of the event is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the event.
*
* @param start Date complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for occurrences *from* this date.
* Default: current server time.
*
* @param end Date complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for occurrences *to* this date.
* Default: current server time + 1 week.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of occurrences returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched calendars will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of property names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request {@code GET: http://localhost:8080/rest/private/v1/calendar/events/Event123/occurences?offset=1&limit=5}
*
* @format JSON
*
* @response
* {
* "limit": 0,
* "data": [
* {
* "to": "2015-07-24T02:30:00.000Z",
* "attachments": [],
* "from": "2015-07-24T02:00:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "availability": "busy",
* "repeat": {},
* "reminder": [],
* "privacy": "private",
* "recurrenceId": null,
* "participants": [],
* "originalEvent": null,
* "description": null,
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "subject": "rep123",
* "location": null,
* "priority": "none",
* "href": "http://localhost:8080/rest/private/v1/calendar/events/Eventaa3c68ee7f00010171ac205a54bc1419",
* "id": "Eventaa3c68ee7f00010171ac205a54bc1419"
* },
* {}
* ],
* "size": -1,
* "offset": 10
* }
*
* @return List of occurrences.
*
* @authentication
*
* @anchor CalendarRestApi.getOccurrencesFromEvent
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@GET
@RolesAllowed("users")
@Path("/events/{id}/occurrences")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns occurrences of a recurring event identified by its ID",
notes = "Returns occurrences of a recurring event with specified id when :<br/>"
+ "- the calendar of the event is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all occurrences of the event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getOccurrencesFromEvent(
@ApiParam(value = "Identity of the recurrent event", required = true) @PathParam("id") String id,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date.", required = false, defaultValue = "current server time") @QueryParam("start") String start,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date.", required = false, defaultValue = "current server time + 1 week") @QueryParam("end") String end,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link. This is a list of comma-separated property's names", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "Tells the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@Context UriInfo uriInfo) {
try {
limit = parseLimit(limit);
java.util.Calendar[] dates = parseDate(start, end);
CalendarEvent recurEvent = calendarServiceInstance().getEventById(id);
if (recurEvent == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
TimeZone tz = java.util.Calendar.getInstance().getTimeZone();
String timeZone = tz.getID();
Map<String,CalendarEvent> occMap = calendarServiceInstance().getOccurrenceEvents(recurEvent, dates[0], dates[1], timeZone);
if(occMap == null || occMap.isEmpty()) {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
Calendar cal = calendarServiceInstance().getCalendarById(recurEvent.getCalendarId());
boolean inParticipant = false;
if (recurEvent.getParticipant() != null) {
String[] participant = recurEvent.getParticipant();
Arrays.sort(participant);
int i = Arrays.binarySearch(participant, currentUserId());
if (i > -1) inParticipant = true;
}
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId()) || inParticipant) {
Collection data = new ArrayList();
Iterator<CalendarEvent> evIter = occMap.values().iterator();
Utils.skip(evIter, offset);
int counter =0;
while (evIter.hasNext()) {
data.add(buildEventResource(evIter.next(), uriInfo, expand, fields));
if(++counter == limit) break;
}
int fullSize = returnSize ? occMap.values().size() : -1;
CollectionResource evData = new CollectionResource(data, fullSize);
evData.setOffset(offset);
evData.setLimit(limit);
//
ResponseBuilder response = buildJsonP(evData, jsonp);
if (returnSize) {
response.header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, evData.getSize()));
}
return response.build();
}
//
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns tasks of a calendar specified by id, in one of conditions:
* The calendar is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the task is delegated to the user,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the calendar.
*
* @param start Date complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for tasks *from* this date.
* Default: current server time.
*
* @param end Date complies ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for tasks *to* this date.
* Default: current server time + 1 week.
*
* @param category Filter the tasks by this category if specified.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of tasks returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched calendars will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective task attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of property names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request GET: {@code http://localhost:8080/rest/private/v1/calendar/myCalId/tasks?category=meeting&expand=calendar,categories(1,5)}
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "to": "2015-07-18T12:00:00.000Z",
* "attachments": [],
* "from": "2015-07-18T11:30:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "reminder": [],
* "delegation": [
* "john"
* ],
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "name": "caramazov",
* "priority": "none",
* "note": null,
* "status": "",
* "href": "http://localhost:8080/rest/private/v1/calendar/tasks/Event99f63db07f0001016dd7a4b4e0e7125c",
* "id": "Event99f63db07f0001016dd7a4b4e0e7125c"
* }
* ],
* "size": -1,
* "offset": 0
* }
*
* @return List of tasks in JSON, or HTTP status 404 if calendar not found.
*
* @authentication
*
* @anchor CalendarRestApi.getTasksByCalendar
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/calendars/{id}/tasks")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns tasks of a calendar identified by its ID",
notes = "Returns tasks of a calendar with specified id when:<br/>"
+ "- the calendar is public<br/>"
+ "- the authenticated user is the owner of the calendar of the task<br/>"
+ "- the authenticated user belongs to the group of the calendar of the task<br/>"
+ "- the authenticated user is delegated by the task<br/>"
+ "- the calendar of the task has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all tasks from the calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found")
})
public Response getTasksByCalendar(
@ApiParam(value = "Identity of a calendar to search for tasks", required = true) @PathParam("id") String id,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *from* this date.", required = false, defaultValue = "current server time")@QueryParam("startTime") String start,
@ApiParam(value = "Date follow ISO8601 (YYYY-MM-DDThh:mm:ssTZD). Search for events *to* this date.", required = false, defaultValue = "current server time + 1 week") @QueryParam("endTime") String end,
@ApiParam(value = "Search for this category only", required = false, defaultValue = "If not specified, search task of any category") @QueryParam("category") String category,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "used to ask for a full representation of a subresource, instead of only its link", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "Tells the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@Context UriInfo uri) throws Exception {
limit = parseLimit(limit);
String username = currentUserId();
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
long fullSize = returnSize ? 0 : -1;
List data = new LinkedList();
Calendar calendar = service.getCalendarById(id);
if (calendar != null) {
String participant = null;
if (calendar.getPublicUrl() == null && !hasViewCalendarPermission(calendar, username)) {
participant = username;
}
EventQuery eventQuery = buildEventQuery(start, end, category, Arrays.asList(calendar),
id, participant, CalendarEvent.TYPE_TASK);
ListAccess<CalendarEvent> events = evtDAO.findEventsByQuery(eventQuery);
//
for (CalendarEvent event : events.load(offset, limit)) {
data.add(buildTaskResource(event, uri, expand, fields));
}
if (returnSize) {
fullSize = events.getSize();
}
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
//
CollectionResource evData = new CollectionResource(data, fullSize);
evData.setOffset(offset);
evData.setLimit(limit);
ResponseBuilder response = buildJsonP(evData, jsonp);
if (returnSize) {
response.header(HEADER_LINK, buildFullUrl(uri, offset, limit, fullSize));
}
//
return response.build();
}
/**
* Creates a task for a calendar specified by id, in one of conditions:
* The user is the owner of the calendar,
* OR for group calendars, the user has edit permission on the calendar,
* OR the calendar has been shared with the user, with edit permission,
* OR the calendar has been shared with a group of the user, with edit permission.
*
* This accepts HTTP POST request, with JSON object (evObject) in the request body. Example:
* {
* "name": "...", "note": "...",
* "categoryId": "",
* "from": "...", "to": "...",
* "delegation": ["...", ""], "priority": "",
* "reminder": [],
* "status": ""
* }
*
* @param evObject JSON object contains attributes of task.
* All attributes are optional. If provided explicitly (not null), attributes are checked with some rules:
* 1. *name* must not be empty, default value is: "default".
* 2. *from* date must be earlier than *to* date.
* 3. *priority* must be one of "none", "high", "normal", "low".
* 4. *status* must be one of "needs-action", "completed", "in-progress", "canceled".
*
* @param id Identity of the calendar.
*
* @request POST: http://localhost:8080/rest/private/v1/calendar/calendars/myCalId/tasks
*
* @response HTTP status code:
* 201 if created successfully, and HTTP header *location* href that points to the newly created task.
* 400 if attributes are invalid (not pass the rule of evObject).
* 404 if calendar not found.
* 401 if the user does not have create permission.
* 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.createTaskForCalendar
*/
@POST
@RolesAllowed("users")
@Path("/calendars/{id}/tasks")
@ApiOperation(
value = "Creates a task for a calendar identified by its ID",
notes = "Creates a task for a calendar with specified id only if:<br/>"
+ "- the authenticated user is the owner of the calendar<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar has been shared with a group of the authenticated user, with modification rights<br/>")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Task successfully created"),
@ApiResponse(code = 400, message = "Bad Request: Provided attributes are not valid (not following the rules of evObject)"),
@ApiResponse(code = 401, message = "User unauthorized to create a task for this calendar"),
@ApiResponse(code = 404, message = "Calendar with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during saving process")
})
public Response createTaskForCalendar(
@ApiParam(value = "Identity of the calendar where the task is created", required = true) @PathParam("id") String id,
TaskResource evObject,
@Context UriInfo uriInfo) {
try {
Calendar cal = calendarServiceInstance().getCalendarById(id);
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
CalendarEvent newEvent = new CalendarEvent();
newEvent.setEventType(CalendarEvent.TYPE_TASK);
if (evObject.getName() == null) {
evObject.setName(DEFAULT_EVENT_NAME);
}
if (evObject.getCategoryId() == null) {
evObject.setCategoryId(CalendarService.DEFAULT_EVENTCATEGORY_ID_ALL);
}
Response error = buildEventFromTask(newEvent, evObject);
if (error != null) {
return error;
}
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), id);
newEvent.setCalendarId(id);
saveEvent(calType, newEvent, true);
String location = new StringBuilder(getBasePath(uriInfo)).append(TASK_URI).append(newEvent.getId()).toString();
return Response.status(HTTPStatus.CREATED).header(HEADER_LOCATION, location).cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
*
* Returns a task specified by id, in one of conditions:
* the calendar of the task is public;
* OR the authenticated user is the owner of the calendar;
* OR the user belongs to the group of the calendar;
* OR the task is delegated to the user;
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the task.
*
* @param fields Comma-separated list of selective task properties to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of task attributes names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* }
* It returns:
* {
* "calendar": {
* "editPermission": "",
* "viewPermission": "",
* "privateURL": null,
* "publicURL": null,
* "icsURL": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId/ics",
* "description": null,
* "color": "asparagus",
* "timeZone": "Europe/Brussels",
* "name": "John Smith",
* "type": "0",
* "owner": "john",
* "groups": null,
* "href": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "id": "john-defaultCalendarId"
* },
* }
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/tasks/Task123?fields=id,name
*
* @format JSON
*
* @response
* {
* "to": "2015-07-18T12:00:00.000Z",
* "attachments": [],
* "from": "2015-07-18T11:30:00.000Z",
* "categories": [
* "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll"
* ],
* "categoryId": "defaultEventCategoryIdAll",
* "reminder": [],
* "delegation": [
* "john"
* ],
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId",
* "name": "caramazov",
* "priority": "none",
* "note": null,
* "status": "",
* "href": "http://localhost:8080/rest/private/v1/calendar/tasks/Event99f63db07f0001016dd7a4b4e0e7125c",
* "id": "Event99f63db07f0001016dd7a4b4e0e7125c"
* }
*
* @return Task in JSON format, or HTTP status 404 if task not found, or 503 if any other failure.
*
* @authentication
*
* @anchor CalendarRestApi.getTaskById
*/
@GET
@RolesAllowed("users")
@Path("/tasks/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns a task identified by its ID",
notes = "Returns a task with specified id if:<br/>"
+ "- the calendar of the task is public<br/>"
+ "- the authenticated user is the owner of the calendar of the task<br/>"
+ "- the authenticated user belongs to the group of the calendar of the task<br/>"
+ "- the authenticated user is a participant of the task<br/>"
+ "- the calendar of the task has been shared with the authenticated user or with a group of the authenticated user"
)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the task"),
@ApiResponse(code = 404, message = "Task with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getTaskById(
@ApiParam(value = "Identity of the task to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link. This is a list of comma-separated property's names", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
CalendarEvent ev = calendarServiceInstance().getEventById(id);
if(ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Date lastModified = new Date(ev.getLastModified());
ResponseBuilder preCondition = request.evaluatePreconditions(lastModified);
if (preCondition != null) {
return preCondition.build();
}
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
boolean inParticipant = false;
if (ev.getParticipant() != null) {
String[] participant = ev.getParticipant();
Arrays.sort(participant);
if (Arrays.binarySearch(participant, currentUserId()) > -1) inParticipant = true;;
}
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId()) || inParticipant) {
Object resource = buildTaskResource(ev, uriInfo, expand, fields);
return buildJsonP(resource, jsonp).cacheControl(cc).lastModified(lastModified).build();
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
*
* Updates a task specified by id, in one of conditions:
* the calendar of the task is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the task is delegated to the user,
* OR the calendar has been shared with the user or with a group of the user.
*
* This accepts HTTP PUT request, with JSON object (evObject) in the request body, and task id in the path.
* All the attributes are optional, any absent/invalid attributes will be ignored.
* *id*, *href*, *calendar* are Read-only.
* For example:
* {
* "name": "...", "note": "...",
* "categoryId": "",
* "from": "...", "to": "...",
* "delegation": ["...", ""], "priority": "",
* "reminder": [],
* "status": ""
* }
*
* @param id Identity of the task.
*
* @param evObject JSON object contains attributes of the task to be updated, all attributes are optional.
* If provided explicitly (not null), attributes are checked with some rules:
* 1. *name* must not be empty.
* 2. *from* date must be earlier than *to* date.
* 3. *priority* must be one of "none", "high", "normal", "low".
* 4. *status* must be one of "needs-action", "completed", "in-progress", "canceled".
*
* @request PUT: http://localhost:8080/rest/private/v1/calendar/tasks/Task123
*
* @response HTTP status code:
* 200 if updated successfully,
* 404 if task not found,
* 400 if attributes are invalid,
* 401 if the user does not have edit permission,
* 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.updateTaskById
*/
@PUT
@RolesAllowed("users")
@Path("/tasks/{id}")
@ApiOperation(
value = "Updates a task identified by its ID",
notes = "Updates a task with the specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Task successfully updated"),
@ApiResponse(code = 400, message = "Bad Request: Provided attributes are not valid (not following the rules of evObject)"),
@ApiResponse(code = 401, message = "User unauthorized to update this task"),
@ApiResponse(code = 404, message = "Task with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response updateTaskById(
@ApiParam(value = "Identity of the task to update", required = true) @PathParam("id") String id,
TaskResource evObject) {
try {
CalendarEvent event = calendarServiceInstance().getEventById(id);
if (event == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(event.getCalendarId());
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = -1;
try {
calType = Integer.parseInt(event.getCalType());
}catch (NumberFormatException e) {
calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), event.getCalendarId());
}
buildEventFromTask(event, evObject);
saveEvent(calType, event, false);
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Deletes a task specified by id, in one of conditions:
* the calendar of the task is public;
* OR the authenticated user is the owner of the calendar;
* OR the user belongs to the group of the calendar;
* OR the task is delegated to the user;
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the task.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/tasks/Task123
*
* @response HTTP status code:
* 200 if deleted successfully, 404 if task not found,
* 401 if the user does not have permission, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.deleteTaskById
*/
@DELETE
@RolesAllowed("users")
@Path("/tasks/{id}")
@ApiOperation(
value = "Deletes a task identified by its ID",
notes = "Deletes a task with specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Task successfully deleted"),
@ApiResponse(code = 401, message = "User unauthorized to delete this task"),
@ApiResponse(code = 404, message = "Task with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response deleteTaskById(
@ApiParam(value = "Identity of the task to delete", required = true) @PathParam("id") String id) {
try {
CalendarEvent ev = calendarServiceInstance().getEventById(id);
if (ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
if (Utils.isCalendarEditable(currentUserId(), cal)) {
int calType = Calendar.TYPE_ALL;
try {
calType = Integer.parseInt(ev.getCalType());
} catch (NumberFormatException e) {
calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), ev.getCalendarId());
}
switch (calType) {
case Calendar.TYPE_PRIVATE:
calendarServiceInstance().removeUserEvent(currentUserId(), ev.getCalendarId(), id);
break;
case Calendar.TYPE_PUBLIC:
calendarServiceInstance().removePublicEvent(ev.getCalendarId(),id);
break;
case Calendar.TYPE_SHARED:
calendarServiceInstance().removeSharedEvent(currentUserId(), ev.getCalendarId(), id);
break;
default:
break;
}
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Queries an attachment (of an event/task) by attachment id, in one of conditions:
* The calendar of the event/task is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event or is delegated to the task,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the attachment.
*
* @param fields Comma-separated list of selective attachment attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/attachments/att123?fields=id,name
*
* @format JSON
*
* @response
* {
* "weight": 38569,
* "mimeType": "image/png",
* "name": "test.png",
* "href": "...",
* "id": "..."
* }
*
* @return Attachment info in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getAttachmentById
*/
@GET
@RolesAllowed("users")
@Path("/attachments/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns an attachment identified by its ID",
notes = "Returns an attachment with specified id if:<br/>"
+ "- the calendar of the event is public<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- the authenticated user belongs to the group of the calendar of the event<br/>"
+ "- the authenticated user is a participant of the event<br/>"
+ "- the calendar of the event has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the attachment"),
@ApiResponse(code = 404, message = "Attachment with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getAttachmentById(
@ApiParam(value = "Identity of the attachment to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
id = AttachmentResource.decode(id);
CalendarEvent ev = this.findEventAttachment(id);
if (ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Attachment att = calendarServiceInstance().getAttachmentById(id);
if(att == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Date lastModified = new Date(att.getLastModified());
ResponseBuilder preCondition = request.evaluatePreconditions(lastModified);
if (preCondition != null) {
return preCondition.build();
}
boolean inParticipant = false;
if (ev.getParticipant() != null) {
String[] participant = ev.getParticipant();
Arrays.sort(participant);
int i = Arrays.binarySearch(participant, currentUserId());
if (i > -1) inParticipant = true;
}
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, currentUserId()) || inParticipant) {
AttachmentResource evData = new AttachmentResource(att, getBasePath(uriInfo));
Object resource = extractObject(evData, fields);
if (jsonp != null) {
String json = null;
if (resource instanceof Map) json = new JSONObject(resource).toString();
else {
JsonGeneratorImpl generatorImpl = new JsonGeneratorImpl();
json = generatorImpl.createJsonObject(resource).toString();
}
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(json).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript")).cacheControl(cc).lastModified(lastModified).build();
}
//
return Response.ok(resource, MediaType.APPLICATION_JSON).cacheControl(cc).lastModified(lastModified).build();
}
//
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Deletes an attachment (of an event/task) specified by attachment id, in one of conditions:
* The calendar of the event/task is public,
* OR the authenticated user is the owner of the calendar,
* OR the user belongs to the group of the calendar,
* OR the user is a participant of the event or is delegated to the task,
* OR the calendar has been shared with the user or with a group of the user.
*
* @param id Identity of the attachment.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/attachments/att123
*
* @response HTTP status code:
* 200 if deleted successfully, 404 if attachment not found,
* 401 if the user does not have permission, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.deleteAttachmentById
*/
@DELETE
@RolesAllowed("users")
@Path("/attachments/{id}")
@ApiOperation(
value = "Deletes an attachment identified by its ID",
notes = "Deletes an attachment with specified id if:<br/>"
+ "- the authenticated user is the owner of the calendar of the event<br/>"
+ "- for group calendars, the authenticated user has edit rights on the calendar<br/>"
+ "- the calendar of the event has been shared with the authenticated user, with modification rights<br/>"
+ "- the calendar of the event has been shared with a group of the authenticated user, with modification rights")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Attachment successfully deleted"),
@ApiResponse(code = 401, message = "User unauthorized to delete this attachment"),
@ApiResponse(code = 404, message = "Attachment with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occured during the saving process")
})
public Response deleteAttachmentById(
@ApiParam(value = "Identity of the attachment to delete", required = true) @PathParam("id") String id) {
try {
id = AttachmentResource.decode(id);
CalendarEvent ev = this.findEventAttachment(id);
if (ev == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Calendar cal = calendarServiceInstance().getCalendarById(ev.getCalendarId());
if (cal == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
if (Utils.isCalendarEditable(currentUserId(), cal)) {
calendarServiceInstance().removeAttachmentById(id);
return Response.ok().cacheControl(nc).build();
}
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns the categories (common and personal categories).
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of categories returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param fields Comma-separated list of selective category attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/categories?fields=id,name
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "name": "defaultEventCategoryNameAll",
* "href": "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll",
* "id": "defaultEventCategoryIdAll"
* },
* {...}
* ],
* "size": 6,
* "offset": 0
* }
*
* @return List of categories.
*
* @authentication
*
* @anchor CalendarRestApi.getEventCategories
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/categories")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns the categories accessible to the user",
notes = "Returns the categories if a user is authenticated (the common categories + the personal categories)")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all event categories"),
@ApiResponse(code = 404, message = "No categories Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getEventCategories(
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo) {
limit = parseLimit(limit);
try {
List<EventCategory> ecData = calendarServiceInstance().getEventCategories(currentUserId(), offset, limit);
if(ecData == null || ecData.isEmpty()) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Collection data = new ArrayList();
String basePath = getBasePath(uriInfo);
for(EventCategory ec:ecData) {
data.add(extractObject(new CategoryResource(ec, basePath), fields));
}
CollectionResource resource = new CollectionResource(data, ecData.size());
resource.setOffset(offset);
resource.setLimit(limit);
if (jsonp != null) {
JsonValue json = new JsonGeneratorImpl().createJsonObject(resource);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(json).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript")).header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, resource.getSize())).cacheControl(nc).build();
}
//
return Response.ok(resource, MediaType.APPLICATION_JSON).header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, resource.getSize())).cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns a category specified by id if it is a common category or is a personal category of the user.
*
* @param id Identity of the category.
*
* @param fields Comma-separated list of selective category attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/categories/cat123?fields=id,name
*
* @format JSON
*
* @response
* {
* "name": "defaultEventCategoryNameAll",
* "href": "http://localhost:8080/rest/private/v1/calendar/categories/defaultEventCategoryIdAll",
* "id": "defaultEventCategoryIdAll"
* }
*
* @return A category in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getEventCategoryById
*/
@GET
@RolesAllowed("users")
@Path("/categories/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns an event category identified by its ID",
notes = "Returns the event category by id if it belongs to the user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the event category"),
@ApiResponse(code = 404, message = "Event category with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getEventCategoryById(
@ApiParam(value = "Identity of the event category to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
List<EventCategory> data = calendarServiceInstance().getEventCategories(currentUserId());
if(data == null || data.isEmpty()) {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
EventCategory category = null;
for (int i = 0; i < data.size(); i++) {
if(id.equals(data.get(i).getId())) {
category = data.get(i);
break;
}
}
if(category == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
Date lastModified = new Date(category.getLastModified());
ResponseBuilder preCondition = request.evaluatePreconditions(lastModified);
if (preCondition != null) {
return preCondition.build();
}
CategoryResource categoryR = new CategoryResource(category, getBasePath(uriInfo));
Object resource = extractObject(categoryR, fields);
if (jsonp != null) {
String json = null;
if (resource instanceof Map) json = new JSONObject((Map<?, ?>)resource).toString();
else {
JsonGeneratorImpl generatorImpl = new JsonGeneratorImpl();
json = generatorImpl.createJsonObject(resource).toString();
}
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(json).append(");");
return Response.ok(sb.toString(), new MediaType("text", "javascript")).cacheControl(cc).lastModified(lastModified).build();
}
//
return Response.ok(resource, MediaType.APPLICATION_JSON).cacheControl(cc).lastModified(lastModified).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
*
* Gets a feed with the given id. The user must be the owner of the feed.
*
* @param id The title of the feed.
*
* @param fields Comma-separated list of selective feed attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Used to ask for more attributes of a sub-resource, instead of its link only.
* This is a comma-separated list of attribute names. For example: expand=calendar,categories. In case of collections,
* you can specify offset (default: 0), limit (default: *defaultLimit*). For example, expand=categories(1,5).
* Instead of:
* {
* "id": "...",
* "calendar": "http://localhost:8080/rest/private/v1/calendar/calendars/demo-defaultCalendarId"
* ...
* }
* It returns:
* {
* "id": "...",
* "calendar":
* {
* "id": "...",
* "name":"demo-defaultId",
* ...
* }
* ...
* }
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/feeds/feed123?fields=id,name
*
* @format JSON
*
* @response
* {
* "calendars": [
* "http://localhost:8080/rest/private/v1/calendar/calendars/john-defaultCalendarId"
* ],
* "calendarIds": [
* "john-defaultCalendarId"
* ],
* "rss": "/v1/calendar/feeds/Calendar_Feed/rss",
* "name": "Calendar_Feed",
* "href": "http://localhost:8080/rest/private/v1/calendar/feeds/Calendar_Feed",
* "id": "Calendar_Feed"
* }
*
* @return Feed in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getFeedById
*/
@GET
@RolesAllowed("users")
@Path("/feeds/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns a feed identified by its ID",
notes = "Returns the feed with the given ID if the authenticated user is the owner of the feed")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the feed"),
@ApiResponse(code = 404, message = "Feed with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response getFeedById(
@ApiParam(value = "Title of the feed to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link. This is a list of comma-separated property's names", required = false) @QueryParam("expand") String expand,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uriInfo,
@Context Request request) {
try {
FeedData feed = null;
for (FeedData feedData : calendarServiceInstance().getFeeds(currentUserId())) {
if (feedData.getTitle().equals(id)) {
feed = feedData;
break;
}
}
if(feed == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
byte[] data = feed.getContent();
byte[] hashCode = digest(data).getBytes();
EntityTag tag = new EntityTag(new String(hashCode));
ResponseBuilder preCondition = request.evaluatePreconditions(tag);
if (preCondition != null) {
return preCondition.build();
}
SyndFeedInput input = new SyndFeedInput();
SyndFeed syndFeed = input.build(new XmlReader(new ByteArrayInputStream(data)));
List<SyndEntry> entries = new ArrayList<SyndEntry>(syndFeed.getEntries());
List<String> calIds = new ArrayList<String>();
for (SyndEntry entry : entries) {
String calendarId = entry.getLink().substring(entry.getLink().lastIndexOf("/")+1) ;
calIds.add(calendarId);
}
Object resource = buildFeedResource(feed, calIds, uriInfo, expand, fields);
return buildJsonP(resource, jsonp).cacheControl(cc).tag(tag).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Updates a feed with the given id. The user must be the owner of the feed.
*
* This accepts HTTP PUT request, with JSON object (feedResource) in the request body, and feed id in the path.
* All the feed attributes are optional, any absent/invalid attributes will be ignored.
* *id* and *href* are auto-generated and cannot be edited by the users.
* For example:
* {
* "name": "..",
* "calendarIds": ["...", "..."]
* }
*
* @param id The title of the feed.
*
* @param feedResource JSON object contains attributes of the feed to be updated, all the attributes are optional.
*
* @request PUT: http://localhost:8080/rest/private/v1/calendar/feeds/feed123
*
* @response HTTP status code:
* 200 if updated successfully, 404 if feed not found, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.updateFeedById
*/
@PUT
@RolesAllowed("users")
@Path("/feeds/{id}")
@ApiOperation(
value = "Updates a feed identified by its ID",
notes = "Updates the feed with the given ID if the authenticated user is the owner of the feed")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Feed successfully updated"),
@ApiResponse(code = 404, message = "Feed with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response updateFeedById(
@ApiParam(value = "Title of the feed to update", required = true) @PathParam("id") String id,
FeedResource feedResource) {
try {
FeedData feed = null;
for (FeedData feedData : calendarServiceInstance().getFeeds(currentUserId())) {
if (feedData.getTitle().equals(id)) {
feed = feedData;
break;
}
}
if (feed == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
LinkedHashMap<String, Calendar> calendars = new LinkedHashMap<String, Calendar>();
if (feedResource.getCalendarIds() != null) {
for (String calendarId : feedResource.getCalendarIds()) {
Calendar calendar = calendarServiceInstance().getCalendarById(calendarId);
int calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), calendarId);
switch (calType) {
case Calendar.TYPE_PRIVATE:
calendars.put(Calendar.TYPE_PRIVATE + Utils.COLON + calendarId, calendar);
break;
case Calendar.TYPE_PUBLIC:
calendars.put(Calendar.TYPE_PUBLIC + Utils.COLON + calendarId, calendar);
break;
case Calendar.TYPE_SHARED:
calendars.put(Calendar.TYPE_SHARED + Utils.COLON + calendarId, calendar);
break;
default:
break;
}
}
}
//
calendarServiceInstance().removeFeedData(currentUserId(),id);
RssData rssData = new RssData();
if (feedResource.getName() != null) {
rssData.setName(feedResource.getName() + Utils.RSS_EXT) ;
rssData.setTitle(feedResource.getName()) ;
rssData.setDescription(feedResource.getName());
}
rssData.setUrl(feed.getUrl()) ;
rssData.setLink(feed.getUrl());
rssData.setVersion("rss_2.0") ;
//
calendarServiceInstance().generateRss(currentUserId(), calendars, rssData);
return Response.ok().cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Deletes a feed with the given id. The user must be the owner of the feed.
*
* @param id The title of the feed.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/feeds/feed123
*
* @response HTTP status code:
* 200 if delete successfully, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.deleteFeedById
*/
@DELETE
@RolesAllowed("users")
@Path("/feeds/{id}")
@ApiOperation(
value = "Deletes a feed identified by its ID",
notes = "Deletes the feed with the given ID if the authenticated user is the owner of the feed")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Feed successfully deleted"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response deleteFeedById(
@ApiParam(value = "Title of the feed to delete", required = true) @PathParam("id") String id) {
try {
calendarServiceInstance().removeFeedData(currentUserId(),id);
return Response.ok().cacheControl(nc).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Gets the RSS stream of a feed with the given id. The user must be the owner of the feed.
*
* @param id The title of the feed.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/feeds/feed123/rss
*
* @format application/xml
*
* @response RSS
*
* @return Calendar RSS.
*
* @authentication
*
* @anchor CalendarRestApi.getRssFromFeed
*/
@GET
@RolesAllowed("users")
@Path("/feeds/{id}/rss")
@Produces(MediaType.APPLICATION_XML)
@ApiOperation(
value = "Gets the RSS stream of the feed with the given ID",
notes = "Returns the RSS stream if:<br/>"
+ "- the calendar is public<br/>"
+ "- the authenticated user is the owner of the calendar<br/>"
+ "- the authenticated user belongs to the group of the calendar<br/>"
+ "- the calendar has been shared with the authenticated user or with a group of the authenticated user")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of RSS stream from the feed"),
@ApiResponse(code = 404, message = "Feed with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurrred during the saving process")
})
public Response getRssFromFeed(
@ApiParam(value = "Title of the feed", required = true) @PathParam("id") String id,
@Context UriInfo uri,
@Context Request request) {
try {
String username = currentUserId();
String feedname = id;
FeedData feed = null;
for (FeedData feedData : calendarServiceInstance().getFeeds(username)) {
if (feedData.getTitle().equals(feedname)) {
feed = feedData;
break;
}
}
if (feed == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
SyndFeedInput input = new SyndFeedInput();
SyndFeed syndFeed = input.build(new XmlReader(new ByteArrayInputStream(feed.getContent())));
List<SyndEntry> entries = new ArrayList<SyndEntry>(syndFeed.getEntries());
List<CalendarEvent> events = new ArrayList<CalendarEvent>();
List<Calendar> calendars = new ArrayList<Calendar>();
for (SyndEntry entry : entries) {
String calendarId = entry.getLink().substring(entry.getLink().lastIndexOf("/")+1) ;
calendars.add(calendarServiceInstance().getCalendarById(calendarId));
}
for (Calendar cal : calendars) {
if (cal.getPublicUrl() != null || this.hasViewCalendarPermission(cal, username)) {
int calType = calendarServiceInstance().getTypeOfCalendar(username, cal.getId());
switch (calType) {
case Calendar.TYPE_PRIVATE:
events.addAll(calendarServiceInstance().getUserEventByCalendar(username, Arrays.asList(cal.getId())));
break;
case Calendar.TYPE_SHARED:
events.addAll(calendarServiceInstance().getSharedEventByCalendars(username, Arrays.asList(cal.getId())));
break;
case Calendar.TYPE_PUBLIC:
EventQuery eventQuery = new EventQuery();
eventQuery.setCalendarId(new String[] { cal.getId() });
events.addAll(calendarServiceInstance().getPublicEvents(eventQuery));
break;
default:
break;
}
}
}
if(events.size() == 0) {
return Response.status(HTTPStatus.NOT_FOUND).entity("Feed " + feedname + "is removed").cacheControl(nc).build();
}
String xml = makeFeed(username, events, feed, uri);
byte[] hashCode = digest(xml.getBytes()).getBytes();
EntityTag tag = new EntityTag(new String(hashCode));
ResponseBuilder preCondition = request.evaluatePreconditions(tag);
if (preCondition != null) {
return preCondition.build();
}
return Response.ok(xml, MediaType.APPLICATION_XML).cacheControl(cc).tag(tag).build();
} catch (Exception e) {
if(log.isDebugEnabled()) log.debug(e.getMessage());
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Returns an invitation with specified id if one of conditions:
* The authenticated user is the participant of the invitation,
* OR the user has edit permission on the calendar of the event of the invitation.
*
* @param id Identity of the invitation.
*
* @param fields Comma-separated list of selective invitation attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Use expand=event to get more event attributes instead of only link.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/invitations/evt123:root
*
* @format JSON
*
* @response
* {
* "participant": "root",
* "event": "http://localhost:8080/rest/private/v1/calendar/events/Event9b014f9e7f00010166296f35cf2af06b",
* "status": "",
* "href": "http://localhost:8080/rest/private/v1/calendar/invitations/Event9b014f9e7f00010166296f35cf2af06b:root",
* "id": "Event9b014f9e7f00010166296f35cf2af06b:root"
* }
*
* @return Invitation as JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getInvitationById
*/
@GET
@RolesAllowed("users")
@Path("/invitations/{id}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns an invitation identified by its ID",
notes = "Returns an invitation with specified id if:<br/>"
+ "- the authenticated user is the participant of the invitation<br/>"
+ "- the authenticated user has edit rights on the calendar of the event of the invitation")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of the invitation"),
@ApiResponse(code = 404, message = "Invitation with provided ID Not Found")
})
public Response getInvitationById(
@ApiParam(value = "Identity of the invitation to find", required = true) @PathParam("id") String id,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link. This is a list of comma-separated property's names", required = false) @QueryParam("expand") String expand,
@Context UriInfo uriInfo,
@Context Request request) throws Exception {
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
String username = currentUserId();
Invitation invitation = evtDAO.getInvitationById(id);
if (invitation == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
EntityTag tag = new EntityTag(String.valueOf(invitation.hashCode()));
ResponseBuilder preCondition = request.evaluatePreconditions(tag);
if (preCondition != null) {
return preCondition.build();
}
//dont return invitation if user is not participant and not have edit permission
if (!username.equals(invitation.getParticipant())) {
CalendarEvent event = service.getEventById(invitation.getEventId());
Calendar calendar = service.getCalendarById(event.getCalendarId());
if (!Utils.isCalendarEditable(username, calendar)) {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
}
Object resource = buildInvitationResource(invitation, uriInfo, expand, fields);
return buildJsonP(resource, jsonp).cacheControl(cc).tag(tag).build();
}
/**
* Replies to an invitation specified by id. The user must be the invitee.
*
* @param id Identity of the invitation.
*
* @param status New status to update ("", "maybe", "yes", "no").
*
* @request PUT: http://localhost:8080/rest/private/v1/calendar/invitations/evt123:root
*
* @response HTTP status code:
* 200 if updated successfully, 404 if invitation not found, 400 if status is invalid,
* 401 if the user does not have permission, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.updateInvitationById
*/
@PUT
@RolesAllowed("users")
@Path("/invitations/{id}")
@ApiOperation(
value = "Updates an invitation identified by its ID",
notes = "Update the invitation if the authenticated user is the participant of the invitation.<br/>"
+ "This entry point only allow http PUT request, with id of invitation in the path, and the status")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Invitation successfully updated"),
@ApiResponse(code = 400, message = "Bad Request: Status invalid"),
@ApiResponse(code = 401, message = "User unauthorized to update the invitation"),
@ApiResponse(code = 404, message = "Invitation with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response updateInvitationById(
@ApiParam(value = "Identity of the invitation to update", required = true) @PathParam("id") String id,
@ApiParam(value = "New status to update", allowableValues = "['', 'maybe', 'yes', 'no']", required = true) @QueryParam("status") String status) {
if (Arrays.binarySearch(INVITATION_STATUS, status) < 0) {
return buildBadResponse(new ErrorResource("status must be one of: " + StringUtils.join(INVITATION_STATUS, ","), "status"));
}
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
String username = currentUserId();
Invitation invitation = evtDAO.getInvitationById(id);
if (invitation != null) {
//Update only if user is participant
if (invitation.getParticipant().equals(username)) {
evtDAO.updateInvitation(id, status);
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
}
/**
* Deletes an invitation with specified id. The authenticated user must have edit permission on the calendar.
*
* @param id Identity of the invitation.
*
* @request DELETE: http://localhost:8080/rest/private/v1/calendar/invitations/evt123:root
*
* @response HTTP status code:
* 200 if deleted successfully, 404 if invitation not found,
* 401 if the user does not have permission, 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.deleteInvitationById
*/
@DELETE
@RolesAllowed("users")
@Path("/invitations/{id}")
@ApiOperation(
value = "Deletes an invitation identified by its ID",
notes = "Deletes an invitation with specified id if the authenticated user has edit rights on the calendar of the event of the invitation")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Invitation deleted successfully"),
@ApiResponse(code = 401, message = "User unauthorized to delete this invitation"),
@ApiResponse(code = 404, message = "Invitation with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response deleteInvitationById(
@ApiParam(value = "Identity of the invitation to delete", required = true) @PathParam("id") String id) throws Exception {
CalendarService calService = calendarServiceInstance();
EventDAO evtDAO = calService.getEventDAO();
String username = currentUserId();
Invitation invitation = evtDAO.getInvitationById(id);
if (invitation == null) return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
CalendarEvent event = calService.getEventById(invitation.getEventId());
Calendar calendar = calService.getCalendarById(event.getCalendarId());
if (Utils.isCalendarEditable(username, calendar)) {
evtDAO.removeInvitation(id);
return Response.ok().cacheControl(nc).build();
} else {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
}
/**
* Returns invitations of an event specified by id when:
* the authenticated user is the participant of the invitation,
* OR the authenticated user has edit permission on the calendar of the event.
*
* @param id Identity of the event.
*
* @param status Filter the invitations by this status if specified.
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of invitations returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched invitations will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective invitation attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @param expand Use expand=event to get more event attributes instead of only link.
*
* @request GET: http://localhost:8080/rest/private/v1/calendar/events/evt123/invitations
*
* @format JSON
*
* @response
* {
* "limit": 10,
* "data": [
* {
* "participant": "john",
* "event": "http://localhost:8080/rest/private/v1/calendar/events/Event9b014f9e7f00010166296f35cf2af06b",
* "status": "",
* "href": "http://localhost:8080/rest/private/v1/calendar/invitations/Event9b014f9e7f00010166296f35cf2af06b:john",
* "id": "Event9b014f9e7f00010166296f35cf2af06b:john"
* },
* {
* "participant": "root",
* "event": "http://localhost:8080/rest/private/v1/calendar/events/Event9b014f9e7f00010166296f35cf2af06b",
* "status": "",
* "href": "http://localhost:8080/rest/private/v1/calendar/invitations/Event9b014f9e7f00010166296f35cf2af06b:root",
* "id": "Event9b014f9e7f00010166296f35cf2af06b:root"
* }
* ],
* "size": -1,
* "offset": 0
* }
*
* @return Invitations as JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getInvitationsFromEvent
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@GET
@RolesAllowed("users")
@Path("/events/{id}/invitations/")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns the invitations of an event identified by its ID",
notes = "Returns invitations of an event with specified id when:<br/>"
+ "- the authenticated user is the participant of the invitation<br/>"
+ "- the authenticated user has edit rights on the calendar of the event of the invitation")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all invitations from the event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found")
})
public Response getInvitationsFromEvent(
@ApiParam(value = "Identity of the event to search for invitations", required = true) @PathParam("id") String id,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "Tells the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@ApiParam(value = "search for this status only", required = false, defaultValue = "If not specified, search invitation of any status ('', 'maybe', 'yes', 'no')") @QueryParam("status") String status,
@ApiParam(value = "This is a list of comma separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@ApiParam(value = "Used to ask for a full representation of a subresource, instead of only its link. This is a list of comma-separated property's names", required = false) @QueryParam("expand") String expand,
@Context UriInfo uriInfo) throws Exception {
limit = parseLimit(limit);
CalendarService calService = calendarServiceInstance();
CalendarEvent event = calService.getEventById(id);
String username = currentUserId();
List<Invitation> invitations = Collections.<Invitation>emptyList();
if (event != null) {
//All invitations in event
invitations = new LinkedList<Invitation>(Arrays.asList(event.getInvitations()));
//Only return user's invitation if calendar is not editable
Calendar calendar = calService.getCalendarById(event.getCalendarId());
if (!Utils.isCalendarEditable(username, calendar)) {
Iterator<Invitation> iter = invitations.iterator();
while(iter.hasNext()) {
if (!iter.next().getParticipant().equals(username)) {
iter.remove();
}
}
}
//Return only invitation with specific status
if (status != null) {
Iterator<Invitation> iter = invitations.iterator();
while(iter.hasNext()) {
if (!iter.next().getStatus().equals(status)) {
iter.remove();
}
}
}
}
List data = new LinkedList();
for (Invitation invitation : Utils.subList(invitations, offset, limit)) {
data.add(buildInvitationResource(invitation, uriInfo, expand, fields));
}
int fullSize = invitations.size();
CollectionResource ivData = new CollectionResource(data, returnSize ? fullSize : -1);
ivData.setOffset(offset);
ivData.setLimit(limit);
ResponseBuilder response = buildJsonP(ivData, jsonp);
if (returnSize) {
response.header(HEADER_LINK, buildFullUrl(uriInfo, offset, limit, fullSize));
}
return response.build();
}
/**
* Creates an invitation in an event specified by event id, in one of conditions:
* the authenticated user is the participant of the event,
* OR the authenticated user has edit permission on the calendar of the event.
* This accepts invitation resource in request body, for example : {"participant":"root","status":""}.
*
*
* @param id Identity of the event.
*
* @request POST: http://localhost:8080/rest/private/v1/calendar/events/evt123/invitations
*
* @response HTTP status code:
* 201 if created successfully, and HTTP header *location* href that points to the newly created invitation.
* 400 if participant is invalid.
* 404 if event not found.
* 401 if the authenticated user does not have permission.
* 503 if any error during save process.
*
* @return HTTP status code.
*
* @authentication
*
* @anchor CalendarRestApi.createInvitationForEvent
*/
@POST
@RolesAllowed("users")
@Path("/events/{id}/invitations/")
@ApiOperation(
value = "Creates an invitation in the event with the given id",
notes = "Creates the invitation only if:<br/>"
+ "- the authenticated user is the participant of the invitation<br/>"
+ "- the authenticated user has edit rights on the calendar of the event of the invitation")
@ApiResponses(value = {
@ApiResponse(code = 201, message = "Invitation created successfully"),
@ApiResponse(code = 400, message = "Bad Request: invalid participant or status"),
@ApiResponse(code = 401, message = "User unauthorized to create an invitation for this event"),
@ApiResponse(code = 404, message = "Event with provided ID Not Found"),
@ApiResponse(code = 503, message = "An error occurred during the saving process")
})
public Response createInvitationForEvent(
@ApiParam(value = "Identity of the event where the invitation is created", required = true) @PathParam("id") String id,
InvitationResource invitation,
@Context UriInfo uriInfo) throws Exception {
if(invitation == null) {
return buildBadResponse(new ErrorResource("Invitation information must not null", "invitation"));
}
String participant = invitation.getParticipant();
String status = invitation.getStatus();
if (participant == null || participant.trim().isEmpty()) {
return buildBadResponse(new ErrorResource("participant must not null or empty", "participant"));
}
if (Arrays.binarySearch(INVITATION_STATUS, status) < 0) {
return buildBadResponse(new ErrorResource("status must be one of: " + StringUtils.join(INVITATION_STATUS, ","), "status"));
}
CalendarService service = calendarServiceInstance();
EventDAO evtDAO = service.getEventDAO();
String username = currentUserId();
CalendarEvent event = service.getEventById(id);
if (event != null) {
Calendar calendar = service.getCalendarById(event.getCalendarId());
if (!Utils.isCalendarEditable(username, calendar)) {
return Response.status(HTTPStatus.UNAUTHORIZED).cacheControl(nc).build();
}
Invitation invite = evtDAO.createInvitation(id, participant, status);
if (invite != null) {
String location = new StringBuilder(getBasePath(uriInfo)).append(INVITATION_URI).append(invite.getId()).toString();
return Response.status(HTTPStatus.CREATED).header(HEADER_LOCATION, location).cacheControl(nc).build();
} else {
return buildBadResponse(new ErrorResource(participant + " has already been participant, can't create more", "participant"));
}
} else {
return Response.status(HTTPStatus.NOT_FOUND).cacheControl(nc).build();
}
}
/**
* Suggest participant for specific user.
*
* @param name of participant
*
* @param offset The starting point when paging the result. Default is *0*.
*
* @param limit Maximum number of participants returned.
* If omitted or exceeds the *query limit* parameter configured for the class, *query limit* is used instead.
*
* @param returnSize Default is *false*. If set to *true*, the total number of matched participants will be returned in JSON,
* and a "Link" header is added. This header contains "first", "last", "next" and "previous" links.
*
* @param fields Comma-separated list of selective participant attributes to be returned. All returned if not specified.
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request {@code GET: http://localhost:8080/rest/private/v1/calendar/participants?name=mary&fields=id,name}
*
* @format JSON
*
* @response
*
* @return List of participants in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.suggestParticipants
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@GET
@RolesAllowed("users")
@Path("/participants/")
@Produces({MediaType.APPLICATION_JSON})
@ApiOperation(
value = "Returns all suggested participants",
notes = "This method lists all the participants a specific user can invite in a calendar.")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval of all participants"),
@ApiResponse(code = 503, message = "Can't generate JSON file") })
public Response suggestParticipants(
@ApiParam(value = "The participant name to search for", required = false) @QueryParam("name") String name,
@ApiParam(value = "The starting point when paging through a list of entities", required = false, defaultValue = "0") @QueryParam("offset") int offset,
@ApiParam(value = "The maximum number of results when paging through a list of entities, and do not exceed *hardLimit*. If not specified, *defaultLimit* will be used", required = false) @QueryParam("limit") int limit,
@ApiParam(value = "Tell the service if it must return the total size of the returned collection result, and the *link* http headers", required = false, defaultValue = "false") @QueryParam("returnSize") boolean returnSize,
@ApiParam(value = "This is a list of comma-separated property's names of response json object", required = false) @QueryParam("fields") String fields,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uri) {
try {
limit = parseLimit(limit);
name = name == null ? "" : name;
ProfileFilter filter = new ProfileFilter();
filter.setName(name);
filter.setCompany("");
filter.setPosition("");
filter.setSkills("");
filter.setExcludedIdentityList(Collections.emptyList());
ListAccess<org.exoplatform.social.core.identity.model.Identity> list = identityManager.getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, filter, true);
Collection data = new LinkedList();
if (list != null) {
for (org.exoplatform.social.core.identity.model.Identity identity : list.load(offset, limit)) {
data.add(extractObject(new ParticipantResource(identity), fields));
}
}
CollectionResource parData = new CollectionResource(data, returnSize ? list.getSize() : -1);
parData.setOffset(offset);
parData.setLimit(limit);
ResponseBuilder okResult;
if (jsonp != null) {
JsonValue value = new JsonGeneratorImpl().createJsonObject(parData);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(value).append(");");
okResult = Response.ok(sb.toString(), new MediaType("text", "javascript"));
} else {
okResult = Response.ok(parData, MediaType.APPLICATION_JSON);
}
if (returnSize) {
okResult.header(HEADER_LINK, buildFullUrl(uri, offset, limit, parData.getSize()));
}
//
return okResult.cacheControl(nc).build();
} catch (Exception e) {
log.error("Can not suggest participant", e);
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
/**
* Return availability (free/busy time) of users in period of time.
*
* @param usernames list of user name separated by comma
*
* @param fromDate count from this date
*
* @param toDate count to this date
*
* @param jsonp The name of a JavaScript function to be used as the JSONP callback.
* If not specified, only JSON object is returned.
*
* @request {@code GET: http://localhost:8080/rest/private/v1/events/?users=root,mary}
*
* @format JSON
*
* @response
*
* @return Map of availability time in JSON.
*
* @authentication
*
* @anchor CalendarRestApi.getAvailability
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@GET
@RolesAllowed("users")
@Path("/availabilities")
@Produces({MediaType.APPLICATION_JSON})
@ApiOperation(
value = "Return availability of users.",
notes = "(free/busy time)")
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successful retrieval"),
@ApiResponse(code = 503, message = "Can't generate JSON file") })
public Response getAvailabilities(
@ApiParam(value = "usernames to check availability", required = true) @QueryParam("usernames") String usernames,
@ApiParam(value = "usernames to check availability", required = true) @QueryParam("fromDate") Long fromDate,
@ApiParam(value = "usernames to check availability", required = true) @QueryParam("toDate") Long toDate,
@ApiParam(value = "The name of a JavaScript function to be used as the JSONP callback", required = false) @QueryParam("jsonp") String jsonp,
@Context UriInfo uri) {
try {
Map<String, String> parMap_ = new HashMap<String, String>() ;
parMap_.clear() ;
Map<String, String> tmpMap = new HashMap<String, String>() ;
List<String> newPars = new ArrayList<String>() ;
if(StringUtils.isNotBlank(usernames)) {
for(String par : usernames.split(",")) {
if(orgService.getUserHandler().findUserByName(par) != null) {
String vl = tmpMap.get(par) ;
parMap_.put(par.trim(), vl) ;
if(vl == null) newPars.add(par.trim()) ;
}
}
}
if(newPars.size() > 0) {
EventQuery eventQuery = new EventQuery() ;
CalendarSetting setting = calendarServiceInstance().getCalendarSetting(currentUserId());
TimeZone timeZone = DateUtils.getTimeZone(setting.getTimeZone());
java.util.Calendar from = buildTimeFrom(fromDate, timeZone);
eventQuery.setFromDate(from);
java.util.Calendar to = buildTimeFrom(toDate, timeZone);
eventQuery.setToDate(to);
//
eventQuery.setParticipants(newPars.toArray(new String[]{})) ;
eventQuery.setNodeType("exo:calendarPublicEvent") ;
Map<String, String> parsMap = calendarServiceInstance().checkFreeBusy(eventQuery);
parMap_.putAll(parsMap) ;
}
ResponseBuilder okResult;
if (jsonp != null) {
JsonValue value = new JsonGeneratorImpl().createJsonObject(parMap_);
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(value).append(");");
okResult = Response.ok(sb.toString(), new MediaType("text", "javascript"));
} else {
okResult = Response.ok(parMap_, MediaType.APPLICATION_JSON);
}
//
return okResult.cacheControl(nc).build();
} catch (Exception e) {
log.error("Can not check availability", e);
}
return Response.status(HTTPStatus.UNAVAILABLE).cacheControl(nc).build();
}
private java.util.Calendar buildTimeFrom(Long miliseconds, TimeZone timeZone) {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTimeZone(timeZone);
cal.setTimeInMillis(miliseconds);
return cal;
}
private Response buildBadResponse(ErrorResource error) {
return Response.status(HTTPStatus.BAD_REQUEST).entity(error).type(MediaType.APPLICATION_JSON)
.cacheControl(nc).build();
}
/**
* Parse date by ISO8601 standard
* if start is null, start is current time
* if end is null, end is current time plus 1 week
* @param start
* @param end
* @return array of start, end date
*/
private java.util.Calendar[] parseDate(String start, String end) {
java.util.Calendar from = GregorianCalendar.getInstance();
java.util.Calendar to = GregorianCalendar.getInstance();
if(Utils.isEmpty(start)) {
from = java.util.Calendar.getInstance();
from.set(java.util.Calendar.HOUR, 0);
from.set(java.util.Calendar.MINUTE, 0);
from.set(java.util.Calendar.SECOND, 0);
from.set(java.util.Calendar.MILLISECOND, 0);
} else {
from = ISO8601.parse(start);
}
if(Utils.isEmpty(end)) {
to.add(java.util.Calendar.WEEK_OF_MONTH, 1);
to.set(java.util.Calendar.HOUR, 0);
to.set(java.util.Calendar.MINUTE, 0);
to.set(java.util.Calendar.SECOND, 0);
to.set(java.util.Calendar.MILLISECOND, 0);
} else {
to = ISO8601.parse(end);
}
return new java.util.Calendar[] {from, to};
}
/**
* Use default value if limit is not set. And do not allow to exceed the hard limit 100.
*/
private int parseLimit(int limit) {
if (limit <= 0) {
return defaultLimit;
}
if (limit > hardLimit) {
return hardLimit;
}
return limit;
}
private String getBasePath(UriInfo uriInfo) {
StringBuilder path = new StringBuilder(uriInfo.getBaseUri().toString());
path.append(CAL_BASE_URI);
return path.toString();
}
private String buildFullUrl(UriInfo uriInfo, int offset, int limit, long fullSize) {
if (fullSize <= 0) {
return "";
}
offset = offset < 0 ? 0 : offset;
long prev = offset - limit;
prev = offset > 0 && prev < 0 ? 0 : prev;
long prevLimit = offset - prev;
//
StringBuilder sb = new StringBuilder();
if (prev >= 0) {
sb.append("<").append(uriInfo.getAbsolutePath()).append("?offset=");
sb.append(prev).append("&limit=").append(prevLimit).append(">").append(Utils.SEMICOLON).append("rel=\"previous\",");
}
long next = offset + limit;
//
if (next < fullSize) {
sb.append("<").append(uriInfo.getAbsolutePath()).append("?offset=");
sb.append(next).append("&limit=").append(limit).append(">").append(Utils.SEMICOLON).append("rel=\"next\",");
}
//first page
long firstLimit = limit > fullSize ? fullSize : limit;
sb.append("<").append(uriInfo.getAbsolutePath()).append("?offset=0&limit=").append(firstLimit).append(">");
sb.append(Utils.SEMICOLON).append("rel=\"first\",");
//last page
long lastIndex = fullSize - (fullSize % firstLimit);
if (lastIndex == fullSize) {
lastIndex = fullSize - firstLimit;
}
if (lastIndex > 0) {
sb.append("<").append(uriInfo.getAbsolutePath()).append("?offset=").append(lastIndex);
sb.append("&limit=").append(fullSize - lastIndex).append(">");
sb.append(Utils.SEMICOLON).append("rel=\"last\"");
}
if (sb.charAt(sb.length() - 1) == ',') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
private static CalendarService calendarServiceInstance() {
return (CalendarService)ExoContainerContext.getCurrentContainer()
.getComponentInstanceOfType(CalendarService.class);
}
/**
*
* @param author : the feed create
* @param events : list of event from data
* @return
* @throws Exception
*/
private String makeFeed(String author, List<CalendarEvent> events, FeedData feedData, UriInfo uri) throws Exception {
URI baseUri = uri.getBaseUri();
String baseURL = baseUri.getScheme() + "://" + baseUri.getHost() + ":" + Integer.toString(baseUri.getPort());
String baseRestURL = baseUri.toString();
SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0");
feed.setTitle(feedData.getTitle());
feed.setLink(baseURL + feedData.getUrl());
feed.setDescription(feedData.getTitle());
List<SyndEntry> entries = new ArrayList<SyndEntry>();
SyndEntry entry;
SyndContent description;
for(CalendarEvent event : events) {
if (Utils.EVENT_NUMBER > 0 && Utils.EVENT_NUMBER <= entries.size()) break;
entry = new SyndEntryImpl();
entry.setTitle(event.getSummary());
entry.setLink(baseRestURL + BASE_EVENT_URL + Utils.SLASH + author + Utils.SLASH + event.getId()
+ Utils.SPLITTER + event.getCalType() + Utils.ICS_EXT);
entry.setAuthor(author) ;
description = new SyndContentImpl();
description.setType(Utils.MIMETYPE_TEXTPLAIN);
description.setValue(event.getDescription());
entry.setDescription(description);
entries.add(entry);
entry.getEnclosures() ;
}
feed.setEntries(entries);
feed.setEncoding("UTF-8") ;
SyndFeedOutput output = new SyndFeedOutput();
String feedXML = output.outputString(feed);
feedXML = StringUtils.replace(feedXML,"&","&");
return feedXML;
}
private boolean isInGroups(String[] groups) {
Identity identity = ConversationState.getCurrent().getIdentity();
for (String group : groups) {
if (identity.isMemberOf(group)) {
return true;
}
}
return false;
}
private boolean hasViewCalendarPermission(Calendar cal, String username) throws Exception {
if (cal.getCalendarOwner() != null && cal.getCalendarOwner().equals(username)) return true;
else if (cal.getGroups() != null) {
return isInGroups(cal.getGroups());
} else if (cal.getViewPermission() != null) {
return Utils.hasPermission(orgService, cal.getViewPermission(), username);
}
return false;
}
private List<Calendar> findViewableCalendars(String username) throws Exception {
CalendarService service = calendarServiceInstance();
//private calendar
List<Calendar> uCals = service.getUserCalendars(username, true);
//group calendar
Set<String> groupIds = ConversationState.getCurrent().getIdentity().getGroups();
List<GroupCalendarData> gCals = service.getGroupCalendars(groupIds.toArray(new String[groupIds.size()]), true, username);
//shared calendar
GroupCalendarData sCals = service.getSharedCalendars(username, true);
if (sCals != null) {
gCals.add(sCals);
}
//public calendar
Calendar[] publicCals = service.getPublicCalendars().load(0, -1);
List<Calendar> results = new LinkedList<Calendar>();
results.addAll(Arrays.asList(publicCals));
for (GroupCalendarData data : gCals) {
if (data.getCalendars() != null) {
for (Calendar cal : data.getCalendars()) {
results.add(cal);
}
}
}
results.addAll(uCals);
return results;
}
private List<Calendar> findEditableCalendars(String username) throws Exception {
List<Calendar> calendars = findViewableCalendars(username);
Iterator<Calendar> iter = calendars.iterator();
while (iter.hasNext()) {
if (!Utils.isCalendarEditable(username, iter.next())) {
iter.remove();
}
}
return calendars;
}
private EventQuery buildEventQuery(String start, String end, String category, List<Calendar> calendars, String calendarPath,
String participant, String eventType) {
java.util.Calendar[] dates = parseDate(start, end);
//Find all invitations that user is participant
EventQuery uQuery = new RestEventQuery();
uQuery.setQueryType(Query.SQL);
if (calendarPath != null) {
uQuery.setCalendarPath(calendarPath);
}
List<String> calIds = new LinkedList<String>();
if (calendars != null) {
for (Calendar cal : calendars) {
calIds.add(cal.getId());
}
uQuery.setCalendarId(calIds.toArray(new String[calIds.size()]));
}
if (category != null) {
uQuery.setCategoryId(new String[] {category});
}
if (participant != null) {
uQuery.setParticipants(new String[] {participant});
}
uQuery.setEventType(eventType);
uQuery.setFromDate(dates[0]);
uQuery.setToDate(dates[1]);
uQuery.setOrderType(Utils.ORDER_TYPE_ASCENDING);
uQuery.setOrderBy(new String[]{Utils.ORDERBY_TITLE});
return uQuery;
}
private CalendarEvent findEventAttachment(String attachmentID) throws Exception {
int idx = attachmentID.indexOf("/calendars/");
if (idx != -1) {
int calendars = idx + "/calendars/".length();
int calendar = attachmentID.indexOf('/', calendars) + 1;
int event = attachmentID.indexOf('/', calendar);
if (calendar != -1 && event != -1) {
String eventId = attachmentID.substring(calendar, event);
return calendarServiceInstance().getEventById(eventId);
}
}
return null;
}
private Object extractObject(Resource iv, String fields) {
if (fields != null && iv != null) {
String[] f = fields.split(",");
if (f.length > 0) {
JSONObject obj = new JSONObject(iv);
Map<String, Object> map = new HashMap<String, Object>();
for (String name : f) {
try {
map.put(name, obj.get(name));
} catch (JSONException e) {
log.warn("Can't extract property {} from object {}", name, iv);
}
}
return map;
}
}
return iv;
}
private String currentUserId() {
return ConversationState.getCurrent().getIdentity().getUserId();
}
private Response buildEvent(CalendarEvent old, EventResource evObject, Calendar moveToCal) {
if (moveToCal != null) {
old.setCalendarId(moveToCal.getId());
try {
int calType = calendarServiceInstance().getTypeOfCalendar(currentUserId(), moveToCal.getId());
old.setCalType(String.valueOf(calType));
} catch (Exception ex) {
return buildBadResponse(new ErrorResource("Can not get type of calendar " + moveToCal.getId()));
}
}
try {
if (calendarServiceInstance().isRemoteCalendar(currentUserId(), old.getCalendarId())) {
return buildBadResponse(new ErrorResource("Can not add/update event in remote calendar", "cant-add-event-on-remote-calendar"));
}
} catch (Exception ex) {
return buildBadResponse(new ErrorResource("Can not check remote calendar " + moveToCal.getId(), "cant-check-remote"));
}
String catId = evObject.getCategoryId();
setEventCategory(old, catId);
if (evObject.getDescription() != null) {
old.setDescription(evObject.getDescription());
}
String eventState = evObject.getAvailability();
if (eventState != null) {
if (Arrays.binarySearch(EVENT_AVAILABILITY, eventState) < 0) {
return buildBadResponse(new ErrorResource("availability must be one of " + StringUtils.join(EVENT_AVAILABILITY, ","), "availability"));
} else {
old.setEventState(eventState);
}
}
if (evObject.getParticipants() != null) {
old.setParticipant(evObject.getParticipants());
List<String> parStatus = Stream.of(evObject.getParticipants()).map(par -> {
return par + ":";
}).collect(Collectors.toList());
old.setParticipantStatus(parStatus.toArray(new String[parStatus.size()]));
}
List<Attachment> attachments = new ArrayList<>();
if (old.getAttachment() != null && evObject.getUploadResources() != null) {
for (Attachment att : old.getAttachment()) {
for (org.exoplatform.calendar.ws.bean.UploadResource resource : evObject.getUploadResources()) {
if (att.getName().equals(resource.getName()) && StringUtils.isBlank(resource.getId())) {
attachments.add(att);
}
}
}
}
if (evObject.getUploadResources() != null) {
Stream.of(evObject.getUploadResources()).forEach((upload) -> {
String uploadId = upload.getId();
UploadResource uploadResource = uploadService.getUploadResource(uploadId);
try {
Attachment attachment = new Attachment();
attachment.setInputStream(new FileInputStream(uploadResource.getStoreLocation()));
attachment.setMimeType(uploadResource.getMimeType());
attachment.setName(uploadResource.getFileName());
long fileSize = ((long)uploadResource.getUploadedSize()) ;
attachment.setSize(fileSize);
attachment.setResourceId(uploadId);
attachments.add(attachment);
} catch (Exception ex) {
log.error("Can not save event with upload resource: " + uploadId, ex);
}
});
}
old.setAttachment(attachments);
if (evObject.getRepeat() != null) {
RepeatResource repeat = evObject.getRepeat();
old.setRepeatType(repeat.getType());
if (repeat.getExclude() != null) {
old.setExceptionIds(Arrays.asList(repeat.getExclude()));
}
if (repeat.getRepeatOn() != null) {
String[] reptOns = repeat.getRepeatOn().split(",");
for (String on : reptOns) {
if (Arrays.binarySearch(RP_WEEKLY_BYDAY, on) < 0) {
return buildBadResponse(new ErrorResource("repeatOn can only contains " + StringUtils.join(RP_WEEKLY_BYDAY, ","), "repeatOn"));
}
}
old.setRepeatByDay(reptOns);
}
if (repeat.getRepeateBy() != null) {
String[] repeatBy = repeat.getRepeateBy().split(",");
long[] by = new long[repeatBy.length];
for (int i = 0; i < repeatBy.length; i++) {
try {
by[i] = Integer.parseInt(repeatBy[i]);
if (by[i] < 1 || by[i] > 31) {
return buildBadResponse(new ErrorResource("repeatBy must be >= 1 and <= 31", "repeatBy"));
}
} catch (Exception e) {
log.debug(e);
}
}
old.setRepeatByMonthDay(by);
}
Date untilDate = null;
long count = 0;
if (repeat.getEnd() != null) {
End end = repeat.getEnd();
String val = end.getValue();
if (RP_END_AFTER.equals(end.getType())) {
try {
count = Long.parseLong(val);
} catch (Exception ex) {
log.debug("Can not set repeat count, count is invalid: {}", val);
}
} else if (RP_END_BYDATE.equals(end.getType())) {
try {
untilDate = ISO8601.parse(val).getTime();
} catch (Exception e) {
log.debug("Can not set repeat until date, date is invalid: {}", val);
}
}
}
old.setRepeatUntilDate(untilDate);
old.setRepeatCount(count);
int every = repeat.getEvery();
if (every < 1 || every > 30) {
every = 1;
}
old.setRepeatInterval(repeat.getEvery());
} else {
old.setRepeatType(Event.RP_NOREPEAT);
}
old.setRecurrenceId(evObject.getRecurrenceId());
java.util.Calendar[] fromTo = parseDate(evObject.getFrom(), evObject.getTo());
if (fromTo[0].after(fromTo[1]) || fromTo[0].equals(fromTo[1])) {
return buildBadResponse(new ErrorResource("\"from\" date must be before \"to\" date", "event-date-time-logic"));
}
old.setFromDateTime(fromTo[0].getTime());
if (evObject.getLocation() != null) {
old.setLocation(evObject.getLocation());
}
String priority = evObject.getPriority();
if (priority != null) {
if (Arrays.binarySearch(PRIORITY, priority) < 0) {
return buildBadResponse(new ErrorResource("priority must be one of " + StringUtils.join(PRIORITY, ","), "priority"));
} else {
old.setPriority(evObject.getPriority());
}
}
if (evObject.getReminder() != null) {
Arrays.stream(evObject.getReminder()).forEach(reminder -> {
if (reminder.getReminderOwner() == null) {
reminder.setReminderOwner(currentUserId());
}
try {
if (reminder.getReminderType().equals(Reminder.TYPE_EMAIL) &&
reminder.getEmailAddress() == null) {
String email = orgService.getUserHandler().findUserByName(currentUserId()).getEmail();
reminder.setEmailAddress(email);
}
} catch (Exception ex) {
log.error("Can not set default email for reminder of user " + currentUserId(), ex);
}
if (reminder.getFromDateTime() == null) {
reminder.setFromDateTime(old.getFromDateTime());
}
});
old.setReminders(Arrays.asList(evObject.getReminder()));
}
String privacy = evObject.getPrivacy();
if (privacy != null) {
if (!CalendarEvent.IS_PRIVATE.equals(privacy) && !CalendarEvent.IS_PUBLIC.equals(privacy)) {
return buildBadResponse(new ErrorResource("privacy can only be public or private", "privacy"));
} else {
old.setPrivate(CalendarEvent.IS_PRIVATE.equals(privacy));
}
}
String subject = evObject.getSubject();
if (subject != null) {
subject = subject.trim();
if (subject.isEmpty()) {
return buildBadResponse(new ErrorResource("subject must not be empty", "subject"));
} else {
old.setSummary(subject);
}
}
old.setToDateTime(fromTo[1].getTime());
return null;
}
private Response buildEventFromTask(CalendarEvent old, TaskResource evObject) {
String catId = evObject.getCategoryId();
setEventCategory(old, catId);
if (evObject.getNote() != null) {
old.setDescription(evObject.getNote());
}
java.util.Calendar[] fromTo = parseDate(evObject.getFrom(), evObject.getTo());
if (fromTo[0].after(fromTo[1]) || fromTo[0].equals(fromTo[1])) {
return buildBadResponse(new ErrorResource("\"from\" date must be before \"to\" date", "from"));
}
old.setFromDateTime(fromTo[0].getTime());
String priority = evObject.getPriority();
if (priority != null) {
if (Arrays.binarySearch(PRIORITY, priority) < 0) {
return buildBadResponse(new ErrorResource("priority must be one of " + StringUtils.join(PRIORITY, ","), "priority"));
} else {
old.setPriority(evObject.getPriority());
}
}
if (evObject.getReminder() != null) {
old.setReminders(Arrays.asList(evObject.getReminder()));
}
String status = evObject.getStatus();
if (status != null && !status.isEmpty()) {
if (Arrays.binarySearch(TASK_STATUS, status) < 0) {
return buildBadResponse(new ErrorResource("status must be one of " + StringUtils.join(TASK_STATUS, ","), "status"));
} else {
old.setStatus(status);
// Actually task status is saved in eventState field
old.setEventState(status);
}
}
String name = evObject.getName();
if (name != null) {
name = name.trim();
if (name.isEmpty()) {
return buildBadResponse(new ErrorResource("name must not be empty", "name"));
} else {
old.setSummary(evObject.getName());
}
}
old.setToDateTime(fromTo[1].getTime());
if (evObject.getDelegation() != null) {
old.setTaskDelegator(StringUtils.join(evObject.getDelegation(), ","));
}
return null;
}
private void setEventCategory(CalendarEvent old, String catId) {
if (catId != null) {
try {
EventCategory cat = calendarServiceInstance().getEventCategory(currentUserId(), catId);
if (cat != null) {
old.setEventCategoryId(cat.getId());
old.setEventCategoryName(cat.getName());
}
} catch (Exception e) {
log.debug(e.getMessage(), e);
}
}
}
private Response buildCalendar(Calendar cal, CalendarResource calR) {
if (calR.getColor() != null) {
cal.setCalendarColor(calR.getColor());
}
if (calR.getOwner() != null) {
cal.setCalendarOwner(calR.getOwner());
}
if (calR.getDescription() != null) {
cal.setDescription(calR.getDescription());
}
Set<String> viewPermissions = new HashSet<String>();
if (calR.getEditPermission() != null && !calR.getEditPermission().isEmpty()) {
cal.setEditPermission(calR.getEditPermission().split(Utils.SEMICOLON));
for (String permission : cal.getEditPermission()) {
viewPermissions.add(permission);
}
}
if (calR.getViewPermission() != null && !calR.getViewPermission().isEmpty()) {
for (String permission : calR.getViewPermission().split(Utils.SEMICOLON)) {
viewPermissions.add(permission);
}
}
if (viewPermissions.size() > 0) {
cal.setViewPermission(viewPermissions.toArray(new String[viewPermissions.size()]));
}
if (calR.getGroups() != null) {
cal.setGroups(calR.getGroups());
}
String name = calR.getName();
if (name != null) {
name = name.trim();
if (name.isEmpty() || containSpecialChar(name)) {
return buildBadResponse(new ErrorResource("calendar name is empty or contains special characters", "name"));
} else {
cal.setName(calR.getName());
}
}
if (calR.getPrivateURL() != null) {
cal.setPrivateUrl(calR.getPrivateURL());
}
if (calR.getPublicURL() != null) {
cal.setPublicUrl(calR.getPublicURL());
}
if (calR.getTimeZone() != null) {
cal.setTimeZone(calR.getTimeZone());
}
return null;
}
private boolean containSpecialChar(String value) {
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (Character.isLetter(c) || Character.isDigit(c) || c == '_' || c == '-' || Character.isSpaceChar(c)) {
continue;
}
return true;
}
return false;
}
private ResponseBuilder buildJsonP(Object resource, String jsonp) throws Exception {
ResponseBuilder response = null;
if (jsonp != null) {
String json = null;
if (resource instanceof Map) json = new JSONObject((Map<?, ?>)resource).toString();
else {
JsonGeneratorImpl generatorImpl = new JsonGeneratorImpl();
json = generatorImpl.createJsonObject(resource).toString();
}
StringBuilder sb = new StringBuilder(jsonp);
sb.append("(").append(json).append(");");
response = Response.ok(sb.toString(), new MediaType("text", "javascript")).cacheControl(nc);
} else {
response = Response.ok(resource, MediaType.APPLICATION_JSON).cacheControl(nc);
}
return response;
}
private Object buildTaskResource(CalendarEvent event,
UriInfo uriInfo,
String expand,
String fields) throws Exception {
CalendarService service = calendarServiceInstance();
String basePath = getBasePath(uriInfo);
TaskResource evtResource = new TaskResource(event, basePath);
List<Expand> expands = Expand.parse(expand);
for (Expand exp : expands) {
if ("calendar".equals(exp.getField())) {
Calendar cal = service.getCalendarById(event.getCalendarId());
setCalType(cal);
evtResource.setCal(new CalendarResource(cal, basePath));
} else if ("categories".equals(exp.getField())) {
String categoryId = event.getEventCategoryId();
if (categoryId != null) {
EventCategory evCat = service.getEventCategory(currentUserId(), categoryId);
if (evCat != null) {
CategoryResource[] catRs = new CategoryResource[] {new CategoryResource(evCat, basePath)};
evtResource.setCats(Utils.<CategoryResource>subArray(catRs, exp.getOffset(), exp.getLimit()));
}
}
} else if ("attachments".equals(exp.getField())) {
if (event.getAttachment() != null) {
List<AttachmentResource> attRs = new LinkedList<AttachmentResource>();
for (Attachment att : event.getAttachment()) {
attRs.add(new AttachmentResource(att, basePath));
}
attRs = Utils.subList(attRs, exp.getOffset(), exp.getLimit());
evtResource.setAtts(attRs.toArray(new AttachmentResource[attRs.size()]));
}
}
}
return extractObject(evtResource, fields);
}
private Object buildEventResource(CalendarEvent ev, UriInfo uriInfo, String expand, String fields) throws Exception {
CalendarService service = calendarServiceInstance();
String basePath = getBasePath(uriInfo);
EventResource evtResource = new EventResource(ev, basePath);
List<Expand> expands = Expand.parse(expand);
for (Expand exp : expands) {
if ("calendar".equals(exp.getField())) {
Calendar cal = service.getCalendarById(ev.getCalendarId());
setCalType(cal);
evtResource.setCal(new CalendarResource(cal, basePath));
} else if ("categories".equals(exp.getField())) {
String categoryId = ev.getEventCategoryId();
if (categoryId != null) {
EventCategory evCat = service.getEventCategory(currentUserId(), categoryId);
if (evCat != null) {
CategoryResource[] catRs = new CategoryResource[] {new CategoryResource(evCat, basePath)};
evtResource.setCats(Utils.<CategoryResource>subArray(catRs, exp.getOffset(), exp.getLimit()));
}
}
} else if ("originalEvent".equals(exp.getField())) {
String orgId = ev.getOriginalReference();
if (orgId != null) {
CalendarEvent orgEv = service.getEventById(orgId);
if (orgEv != null) {
evtResource.setOEvent(new EventResource(orgEv, basePath));
}
}
} else if ("attachments".equals(exp.getField())) {
if (ev.getAttachment() != null) {
List<AttachmentResource> attRs = new LinkedList<AttachmentResource>();
for (Attachment att : ev.getAttachment()) {
attRs.add(new AttachmentResource(att, basePath));
}
attRs = Utils.subList(attRs, exp.getOffset(), exp.getLimit());
evtResource.setAtts(attRs.toArray(new AttachmentResource[attRs.size()]));
}
}
}
return extractObject(evtResource, fields);
}
private Object buildFeedResource(FeedData feed, List<String> calIds, UriInfo uriInfo,
String expand, String fields) throws Exception {
CalendarService service = calendarServiceInstance();
String basePath = getBasePath(uriInfo);
FeedResource feedResource = new FeedResource(feed, calIds.toArray(new String[calIds.size()]), basePath);
List<Expand> expands = Expand.parse(expand);
for (Expand exp : expands) {
if ("calendars".equals(exp.getField())) {
List<Serializable> calendars = new ArrayList<Serializable>();
for(String calId : Utils.subList(calIds, exp.getOffset(), exp.getLimit())) {
Calendar cal = service.getCalendarById(calId);
setCalType(cal);
calendars.add(new CalendarResource(cal, getBasePath(uriInfo)));
}
feedResource.setCals(Utils.subList(calendars, exp.getOffset(), exp.getLimit()));
}
}
return extractObject(feedResource, fields);
}
private Object buildInvitationResource(Invitation invitation,
UriInfo uriInfo,
String expand,
String fields) throws Exception {
CalendarService service = calendarServiceInstance();
String basePath = getBasePath(uriInfo);
InvitationResource ivtResource = new InvitationResource(invitation, basePath);
List<Expand> expands = Expand.parse(expand);
for (Expand exp : expands) {
if ("event".equals(exp.getField())) {
CalendarEvent event = service.getEventById(invitation.getEventId());
ivtResource.setEvt(new EventResource(event, basePath));
}
}
return extractObject(ivtResource, fields);
}
private String digest(byte[] data) throws Exception {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] hashCode = md5.digest(data);
//Can't compile if return byte[] due to the bug from eXo rest framework
return String.valueOf(hashCode);
}
private void setCalType(Calendar cal) {
try {
cal.setCalType(calendarServiceInstance().getTypeOfCalendar(currentUserId(), cal.getId()));
} catch (Exception e) {
log.error(e);
}
}
private void saveEvent(int calType, CalendarEvent event, boolean bln) throws Exception {
saveEvent(event.getCalendarId(), event.getCalendarId(), calType, calType, event, null, bln);
}
private void saveEvent(String fromCal, String toCal, int fromType, int toType, CalendarEvent event, RecurringUpdateType recurringUpdateType, boolean bln) throws Exception {
CalendarService calService = calendarServiceInstance();
if (recurringUpdateType == null) {
//create new event or update non-repeat event
if (bln) {
switch (toType) {
case Calendar.TYPE_PRIVATE:
calService.saveUserEvent(currentUserId(), event.getCalendarId(), event, bln);
break;
case Calendar.TYPE_PUBLIC:
calService.savePublicEvent(event.getCalendarId(), event, bln);
break;
case Calendar.TYPE_SHARED:
calService.saveEventToSharedCalendar(currentUserId(), event.getCalendarId(), event,bln);
break;
default:
break;
}
} else {
List<CalendarEvent> listEvent = Arrays.asList(event);
if (org.exoplatform.calendar.service.Utils.isExceptionOccurrence(event)) {
calService.updateOccurrenceEvent(fromCal, toCal, String.valueOf(fromType), String.valueOf(toType), listEvent, currentUserId());
} else {
calService.moveEvent(fromCal, toCal, String.valueOf(fromType), String.valueOf(toType), listEvent, currentUserId()) ;
}
}
} else {
//update repeat event
CalendarEvent original = calService.getRepetitiveEvent(event);
recurringUpdateType = recurringUpdateType == null ? RecurringUpdateType.ONE : recurringUpdateType;
switch (recurringUpdateType) {
case ALL:
calService.saveAllSeriesEvents(event, currentUserId());
break;
case FOLLOWING:
calService.saveFollowingSeriesEvents(original, event, currentUserId());
break;
case ONE:
calService.saveOneOccurrenceEvent(original, event, currentUserId());
break;
}
}
}
public static class Expand {
private String field;
private int offset;
private int limit;
public Expand(String field, int offset, int limit) {
this.field = field;
this.offset = offset;
this.limit = limit;
}
public static List<Expand> parse(String expand) {
List<Expand> expands = new LinkedList<Expand>();
if (expand != null) {
String[] frags = expand.split(",");
List<String> tmp = new LinkedList<String>();
for (int i = 0; i < frags.length; i++) {
String str = frags[i].trim();
if (!str.contains("(") && str.contains("")) {
tmp.add(str);
} else if (str.contains("(") && i + 1 < frags.length) {
tmp.add(str + "," + frags[++i]);
}
}
for (String exp : tmp) {
String fieldName = null;
int offset = 0;
int limit = -1;
if (exp != null) {
exp = exp.trim();
int i =exp.indexOf('(');
if (i > 0) {
fieldName = exp.substring(0, i).trim();
try {
offset = Integer.parseInt(exp.substring(exp.indexOf("offset:") + "offset:".length(), exp.indexOf(",")).trim());
limit = Integer.parseInt(exp.substring(exp.indexOf("limit:") + "limit:".length(), exp.indexOf(")")).trim());
} catch (Exception ex) {
log.debug(ex);
}
} else {
fieldName = exp;
}
}
expands.add(new Expand(fieldName, offset, limit));
}
}
return expands;
}
public String getField() {
return field;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
}
}