From 5b5daa245f983dd2820df9012fd039ee21816e78 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 27 Jun 2012 14:48:58 +0200
Subject: [PATCH] OMA-78: implement REST API for calendar

---
 .../collaboration/CollaborationManager.java   |   5 +
 .../CollaborationManagerImpl.java             |  43 +-
 .../collaboration/CollaborationTools.java     |  32 +-
 .../commons/calendar/restapi/CalendarVO.java  |  75 +++
 .../calendar/restapi/CalendarWebService.java  | 365 +++++++++++++++
 .../commons/calendar/restapi/EventVO.java     |  97 ++++
 .../course/nodes/cal/CourseCalendars.java     |  27 +-
 .../modules/fo/restapi/ForumWebService.java   |   1 -
 .../olat/restapi/_spring/restApiContext.xml   |   1 +
 .../java/org/olat/restapi/CalendarTest.java   | 438 ++++++++++++++++++
 10 files changed, 1050 insertions(+), 34 deletions(-)
 create mode 100644 src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java
 create mode 100644 src/main/java/org/olat/commons/calendar/restapi/CalendarWebService.java
 create mode 100644 src/main/java/org/olat/commons/calendar/restapi/EventVO.java
 create mode 100644 src/test/java/org/olat/restapi/CalendarTest.java

diff --git a/src/main/java/org/olat/collaboration/CollaborationManager.java b/src/main/java/org/olat/collaboration/CollaborationManager.java
index f21cd9a0bde..3a41b69d59c 100644
--- a/src/main/java/org/olat/collaboration/CollaborationManager.java
+++ b/src/main/java/org/olat/collaboration/CollaborationManager.java
@@ -19,7 +19,10 @@
  */
 package org.olat.collaboration;
 
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+import org.olat.core.gui.UserRequest;
 import org.olat.core.id.OLATResourceable;
+import org.olat.group.BusinessGroup;
 
 /**
  * 
@@ -30,5 +33,7 @@ public interface CollaborationManager {
 	public String getFolderRelPath(OLATResourceable ores);
 	
 	public Long lookupFolderAccess(OLATResourceable ores);
+	
+	public KalendarRenderWrapper getCalendar(BusinessGroup group, UserRequest ureq, boolean isAdmin);
 
 }
diff --git a/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java b/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java
index c77ff99769d..28ca29d6bf6 100644
--- a/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java
+++ b/src/main/java/org/olat/collaboration/CollaborationManagerImpl.java
@@ -24,10 +24,17 @@ import static org.olat.collaboration.CollaborationTools.PROP_CAT_BG_COLLABTOOLS;
 
 import java.util.List;
 
+import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.commons.calendar.CalendarManager;
+import org.olat.commons.calendar.CalendarManagerFactory;
+import org.olat.commons.calendar.model.KalendarConfig;
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
 import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.commons.persistence.DBQuery;
+import org.olat.core.gui.UserRequest;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.manager.BasicManager;
+import org.olat.group.BusinessGroup;
 import org.olat.properties.Property;
 
 /**
@@ -40,7 +47,7 @@ public class CollaborationManagerImpl extends BasicManager implements Collaborat
 	public String getFolderRelPath(OLATResourceable ores) {
 		return "/cts/folders/" + ores.getResourceableTypeName() + "/" + ores.getResourceableId();
 	}
-	
+
 	//fxdiff VCRP-8: collaboration tools folder access control
 	public Long lookupFolderAccess(OLATResourceable ores) {
 		StringBuilder query = new StringBuilder();
@@ -62,4 +69,38 @@ public class CollaborationManagerImpl extends BasicManager implements Collaborat
 			return props.get(0);
 		}
 	}
+
+	@Override
+	public KalendarRenderWrapper getCalendar(BusinessGroup businessGroup, UserRequest ureq, boolean isAdmin) {
+	
+		// do not use a global translator since in the fututre a collaborationtools
+		// may be shared among users
+
+		// get the calendar
+		CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager();
+		KalendarRenderWrapper calRenderWrapper = calManager.getGroupCalendar(businessGroup);
+		boolean isOwner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(ureq.getIdentity(), businessGroup.getOwnerGroup());
+		if (!(isAdmin || isOwner)) {
+			// check if participants have read/write access
+			int iCalAccess = CollaborationTools.CALENDAR_ACCESS_OWNERS;
+			Long lCalAccess = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup).lookupCalendarAccess();
+			if (lCalAccess != null) iCalAccess = lCalAccess.intValue();
+			if (iCalAccess == CollaborationTools.CALENDAR_ACCESS_ALL) {
+				calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
+			} else {
+				calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY);
+			}
+		} else {
+			calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
+		}
+		KalendarConfig config = calManager.findKalendarConfigForIdentity(calRenderWrapper.getKalendar(), ureq);
+		if (config != null) {
+			calRenderWrapper.getKalendarConfig().setCss(config.getCss());
+			calRenderWrapper.getKalendarConfig().setVis(config.isVis());
+		}
+		calRenderWrapper.getKalendarConfig().setResId(businessGroup.getKey());
+		return calRenderWrapper;
+	}
+	
+	
 }
diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java
index 40c0ff699db..0c38bddfd98 100644
--- a/src/main/java/org/olat/collaboration/CollaborationTools.java
+++ b/src/main/java/org/olat/collaboration/CollaborationTools.java
@@ -39,7 +39,6 @@ import org.olat.basesecurity.BaseSecurityManager;
 import org.olat.basesecurity.Constants;
 import org.olat.commons.calendar.CalendarManager;
 import org.olat.commons.calendar.CalendarManagerFactory;
-import org.olat.commons.calendar.model.KalendarConfig;
 import org.olat.commons.calendar.ui.CalendarController;
 import org.olat.commons.calendar.ui.WeeklyCalendarController;
 import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
@@ -416,32 +415,9 @@ public class CollaborationTools implements Serializable {
 	 * @return Configured WeeklyCalendarController
 	 */
 	public CalendarController createCalendarController(UserRequest ureq, WindowControl wControl, BusinessGroup businessGroup, boolean isAdmin) {
-		// do not use a global translator since in the fututre a collaborationtools
-		// may be shared among users
-		List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>();
-		// get the calendar
-		CalendarManager calManager = CalendarManagerFactory.getInstance().getCalendarManager();
-		KalendarRenderWrapper calRenderWrapper = calManager.getGroupCalendar(businessGroup);
-		boolean isOwner = BaseSecurityManager.getInstance().isIdentityInSecurityGroup(ureq.getIdentity(), businessGroup.getOwnerGroup());
-		if (!(isAdmin || isOwner)) {
-			// check if participants have read/write access
-			int iCalAccess = CollaborationTools.CALENDAR_ACCESS_OWNERS;
-			Long lCalAccess = CollaborationToolsFactory.getInstance().getOrCreateCollaborationTools(businessGroup).lookupCalendarAccess();
-			if (lCalAccess != null) iCalAccess = lCalAccess.intValue();
-			if (iCalAccess == CollaborationTools.CALENDAR_ACCESS_ALL) {
-				calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
-			} else {
-				calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_ONLY);
-			}
-		} else {
-			calRenderWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
-		}
-		KalendarConfig config = calManager.findKalendarConfigForIdentity(calRenderWrapper.getKalendar(), ureq);
-		if (config != null) {
-			calRenderWrapper.getKalendarConfig().setCss(config.getCss());
-			calRenderWrapper.getKalendarConfig().setVis(config.isVis());
-		}
-		calRenderWrapper.getKalendarConfig().setResId(businessGroup.getKey());
+		CollaborationManager collaborationManager = CoreSpringFactory.getImpl(CollaborationManager.class);
+		KalendarRenderWrapper calRenderWrapper = collaborationManager.getCalendar(businessGroup, ureq, isAdmin);
+	
 		if (businessGroup.getType().equals(BusinessGroup.TYPE_LEARNINGROUP)) {
 			// add linking
 			List<OLATResource> resources = BGContextManagerImpl.getInstance().findOLATResourcesForBGContext(businessGroup.getGroupContext());
@@ -457,6 +433,8 @@ public class CollaborationTools implements Serializable {
 				}
 			}
 		}
+		
+		List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>();
 		calendars.add(calRenderWrapper);
 		
 		WeeklyCalendarController calendarController = new WeeklyCalendarController(
diff --git a/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java b/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java
new file mode 100644
index 00000000000..ab258957059
--- /dev/null
+++ b/src/main/java/org/olat/commons/calendar/restapi/CalendarVO.java
@@ -0,0 +1,75 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.commons.calendar.restapi;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "catalogVO")
+public class CalendarVO {
+	
+	private String id;
+	private String type;
+	private boolean subscribed;
+	
+	public CalendarVO() {
+		//
+	}
+	
+	public CalendarVO(KalendarRenderWrapper wrapper) {
+		id = wrapper.getKalendar().getType() + "_" + wrapper.getKalendar().getCalendarID();
+		subscribed = wrapper.isSubscribed();
+		type = wrapper.getKalendar().getType();
+	}
+	
+	public String getId() {
+		return id;
+	}
+	
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public boolean isSubscribed() {
+		return subscribed;
+	}
+
+	public void setSubscribed(boolean subscribed) {
+		this.subscribed = subscribed;
+	}
+	
+
+	
+	
+
+}
diff --git a/src/main/java/org/olat/commons/calendar/restapi/CalendarWebService.java b/src/main/java/org/olat/commons/calendar/restapi/CalendarWebService.java
new file mode 100644
index 00000000000..09a5a520ea1
--- /dev/null
+++ b/src/main/java/org/olat/commons/calendar/restapi/CalendarWebService.java
@@ -0,0 +1,365 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.commons.calendar.restapi;
+
+import static org.olat.restapi.security.RestSecurityHelper.getUserRequest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.basesecurity.IdentityShort;
+import org.olat.collaboration.CollaborationManager;
+import org.olat.collaboration.CollaborationTools;
+import org.olat.commons.calendar.CalendarManager;
+import org.olat.commons.calendar.CalendarManagerFactory;
+import org.olat.commons.calendar.model.KalendarConfig;
+import org.olat.commons.calendar.model.KalendarEvent;
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.id.Identity;
+import org.olat.core.id.IdentityEnvironment;
+import org.olat.core.id.Roles;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.nodes.INode;
+import org.olat.core.util.tree.Visitor;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.config.CourseConfig;
+import org.olat.course.nodes.CalCourseNode;
+import org.olat.course.nodes.cal.CourseCalendars;
+import org.olat.course.run.userview.CourseTreeVisitor;
+import org.olat.group.BusinessGroup;
+import org.olat.group.BusinessGroupManager;
+import org.olat.group.BusinessGroupManagerImpl;
+import org.olat.group.SearchBusinessGroupParams;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.repository.SearchRepositoryEntryParameters;
+import org.olat.resource.accesscontrol.AccessResult;
+import org.olat.resource.accesscontrol.manager.ACFrontendManager;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+@Path("users/{identityKey}/calendars")
+public class CalendarWebService {
+	
+	private static final OLog log = Tracing.createLoggerFor(CalendarWebService.class);
+	
+	
+	@GET
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getCalendars(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) {
+		UserRequest ureq = getUserRequest(httpRequest);
+		if(!ureq.getUserSession().isAuthenticated()) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		} else if (ureq.getIdentity() == null || !ureq.getIdentity().getKey().equals(identityKey)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		CollectCalendars visitor = new CollectCalendars();
+		getCalendars(visitor, ureq);
+		
+		List<KalendarRenderWrapper> wrappers = visitor.getWrappers();
+		CalendarVO[] voes = new CalendarVO[wrappers.size()];
+		int count = 0;
+		for(KalendarRenderWrapper wrapper:wrappers) {
+			voes[count++] = new CalendarVO(wrapper);
+		}
+		return Response.ok(voes).build();
+	}
+	
+	@GET
+	@Path("{calendarId}/events")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getEventsByCalendar(@PathParam("calendarId") String calendarId,
+			@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) {
+		UserRequest ureq = getUserRequest(httpRequest);
+		if(!ureq.getUserSession().isAuthenticated()) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		} else if (ureq.getIdentity() == null || !ureq.getIdentity().getKey().equals(identityKey)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		KalendarRenderWrapper calendar = getCalendar(ureq, calendarId);
+		if(calendar == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		} else if (!hasReadAccess(calendar)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		List<EventVO> events = new ArrayList<EventVO>();
+		Collection<KalendarEvent> kalEvents = calendar.getKalendar().getEvents();
+		for(KalendarEvent kalEvent:kalEvents) {
+			EventVO eventVo = new EventVO(kalEvent);
+			events.add(eventVo);
+		}
+
+		EventVO[] voes = new EventVO[events.size()];
+		voes = events.toArray(voes);
+		return Response.ok(voes).build();
+	}
+	
+	@PUT
+	@Path("{calendarId}/events")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response putEventByCalendar(@PathParam("calendarId") String calendarId,
+			@PathParam("identityKey") Long identityKey, EventVO event, @Context HttpServletRequest httpRequest) {
+		return addEventByCalendar(calendarId, identityKey, event, httpRequest);
+	}
+	
+	@POST
+	@Path("{calendarId}/events")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	@Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response postEventByCalendar(@PathParam("calendarId") String calendarId,
+			@PathParam("identityKey") Long identityKey, EventVO event, @Context HttpServletRequest httpRequest) {
+		return addEventByCalendar(calendarId, identityKey, event, httpRequest);
+	}
+	
+	private Response addEventByCalendar(String calendarId, Long identityKey, EventVO event, HttpServletRequest httpRequest) {
+		UserRequest ureq = getUserRequest(httpRequest);
+		if(!ureq.getUserSession().isAuthenticated()) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		} else if (ureq.getIdentity() == null || !ureq.getIdentity().getKey().equals(identityKey)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		KalendarRenderWrapper calendar = getCalendar(ureq, calendarId);
+		if(calendar == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		} else if(!hasWriteAccess(calendar)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+
+		CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+		if(event.getId() == null) {
+			String id = UUID.randomUUID().toString();
+			KalendarEvent kalEvent = new KalendarEvent(id, event.getSubject(), event.getBegin(), event.getEnd());
+			calendarManager.addEventTo(calendar.getKalendar(), kalEvent);
+		} else {
+			KalendarEvent kalEvent = calendar.getKalendar().getEvent(event.getId());
+			if(kalEvent == null) {
+				kalEvent = new KalendarEvent(event.getId(), event.getSubject(), event.getBegin(), event.getEnd());
+				calendarManager.addEventTo(calendar.getKalendar(), kalEvent);
+			} else {
+				kalEvent.setBegin(event.getBegin());
+				kalEvent.setEnd(event.getEnd());
+				kalEvent.setSubject(event.getSubject());
+				kalEvent.setDescription(event.getDescription());
+			}
+		}
+
+		return Response.ok().build();
+	}
+	
+	
+	@GET
+	@Path("events")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getEvents(@PathParam("identityKey") Long identityKey, @Context HttpServletRequest httpRequest) {
+		UserRequest ureq = getUserRequest(httpRequest);
+		if(!ureq.getUserSession().isAuthenticated()) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		} else if (ureq.getIdentity() == null || !ureq.getIdentity().getKey().equals(identityKey)) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		CollectCalendars visitor = new CollectCalendars();
+		getCalendars(visitor, ureq);
+		List<KalendarRenderWrapper> wrappers = visitor.getWrappers();
+		List<EventVO> events = new ArrayList<EventVO>();
+		for(KalendarRenderWrapper wrapper:wrappers) {
+			Collection<KalendarEvent> kalEvents = wrapper.getKalendar().getEvents();
+			for(KalendarEvent kalEvent:kalEvents) {
+				EventVO eventVo = new EventVO(kalEvent);
+				events.add(eventVo);
+			}
+		}
+
+		EventVO[] voes = new EventVO[events.size()];
+		voes = events.toArray(voes);
+		return Response.ok(voes).build();
+	}
+	
+	private boolean hasReadAccess(KalendarRenderWrapper wrapper) {
+		if(wrapper.getAccess() == KalendarRenderWrapper.ACCESS_READ_ONLY) {
+			return true;
+		}
+		if(wrapper.getAccess() == KalendarRenderWrapper.ACCESS_READ_WRITE) {
+			return true;
+		}
+		return false;
+	}
+	
+	private boolean hasWriteAccess(KalendarRenderWrapper wrapper) {
+		if(wrapper.getAccess() == KalendarRenderWrapper.ACCESS_READ_WRITE) {
+			return true;
+		}
+		return false;
+	}
+	
+	private KalendarRenderWrapper getCalendar(UserRequest ureq, String calendarId) {
+		int typeIndex = calendarId.indexOf('_');
+		if(typeIndex <= 0 || (typeIndex + 1 >= calendarId.length())) {
+			return null;
+		} 
+		String type = calendarId.substring(0, typeIndex);
+		String id = calendarId.substring(typeIndex + 1);
+		
+		KalendarRenderWrapper wrapper = null;
+		if("group".equals(type)) {
+			Long groupId = Long.parseLong(id);
+			BusinessGroup group = BusinessGroupManagerImpl.getInstance().loadBusinessGroup(groupId, false);
+			if(BusinessGroupManagerImpl.getInstance().isIdentityInBusinessGroup(ureq.getIdentity(), group)) {
+				CollaborationManager collaborationManager = CoreSpringFactory.getImpl(CollaborationManager.class);
+				wrapper = collaborationManager.getCalendar(group, ureq, false);
+			}
+		} else if("course".equals(type)) {
+			Long courseId = Long.parseLong(id);
+			ICourse course = CourseFactory.loadCourse(courseId);
+			wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, course, null);
+		} else if("user".equals(type)) {
+			List<String> identityName = Collections.singletonList(id);
+			List<IdentityShort> shorts = BaseSecurityManager.getInstance().findShortIdentitiesByName(identityName);
+			if(shorts.size() == 1 && shorts.get(0).getKey().equals(ureq.getIdentity().getKey())) {
+				wrapper = getPersonalCalendar(ureq);
+			}
+		}
+		return wrapper;
+	}
+	
+	private KalendarRenderWrapper getPersonalCalendar(UserRequest ureq) {
+	// get the personal calendar
+			CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+			KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(ureq.getIdentity());
+			calendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
+			KalendarConfig personalKalendarConfig = calendarManager.findKalendarConfigForIdentity(
+					calendarWrapper.getKalendar(), ureq);
+			if (personalKalendarConfig != null) {
+				calendarWrapper.getKalendarConfig().setCss(personalKalendarConfig.getCss());
+				calendarWrapper.getKalendarConfig().setVis(personalKalendarConfig.isVis());
+			}
+			return calendarWrapper;
+	}
+	
+	private void getCalendars(CalendarVisitor calVisitor, UserRequest ureq) {	
+		Roles roles = ureq.getUserSession().getRoles();
+		Identity retrievedUser = ureq.getIdentity();
+
+		KalendarRenderWrapper personalWrapper = getPersonalCalendar(ureq);
+		calVisitor.visit(personalWrapper);
+		
+		RepositoryManager rm = RepositoryManager.getInstance();
+		ACFrontendManager acManager = (ACFrontendManager)CoreSpringFactory.getBean("acFrontendManager");
+		SearchRepositoryEntryParameters repoParams = new SearchRepositoryEntryParameters(retrievedUser, roles, "CourseModule");
+		repoParams.setOnlyExplicitMember(true);
+		List<RepositoryEntry> entries = rm.genericANDQueryWithRolesRestriction(repoParams, 0, -1, true);
+		for(RepositoryEntry entry:entries) {
+			AccessResult result = acManager.isAccessible(entry, retrievedUser, false);
+			if(result.isAccessible()) {
+				try {
+					final ICourse course = CourseFactory.loadCourse(entry.getOlatResource());
+					CourseConfig config = course.getCourseEnvironment().getCourseConfig();
+					if(config.isCalendarEnabled()) {
+						KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, entry.getOlatResource(), null);
+						calVisitor.visit(wrapper);
+					} else {
+						IdentityEnvironment ienv = new IdentityEnvironment(retrievedUser, roles);
+						CalCourseNodeVisitor visitor = new CalCourseNodeVisitor();
+						new CourseTreeVisitor(course, ienv).visit(visitor);
+						if(visitor.isFound()) {
+							KalendarRenderWrapper wrapper = CourseCalendars.getCourseCalendarWrapper(ureq, entry.getOlatResource(), null);
+							calVisitor.visit(wrapper);
+						}
+					}
+				} catch (Exception e) {
+					log.error("", e);
+				}
+			}
+		}
+		
+		CollaborationManager collaborationManager = CoreSpringFactory.getImpl(CollaborationManager.class);
+		
+		//start found forums in groups
+		BusinessGroupManager bgm = BusinessGroupManagerImpl.getInstance();
+		SearchBusinessGroupParams params = new SearchBusinessGroupParams();
+		params.addTypes(BusinessGroup.TYPE_BUDDYGROUP, BusinessGroup.TYPE_LEARNINGROUP, BusinessGroup.TYPE_RIGHTGROUP);
+		params.addTools(CollaborationTools.TOOL_CALENDAR);
+		List<BusinessGroup> groups = bgm.findBusinessGroups(params, retrievedUser, true, true, null, 0, -1);
+		for(BusinessGroup group:groups) {
+			KalendarRenderWrapper wrapper = collaborationManager.getCalendar(group, ureq, false);
+			calVisitor.visit(wrapper);
+		}
+	}
+	
+	private static interface CalendarVisitor {
+		public void visit(KalendarRenderWrapper wrapper);
+	}
+	
+	private static class CollectCalendars implements CalendarVisitor {
+		private final List<KalendarRenderWrapper> wrappers = new ArrayList<KalendarRenderWrapper>();
+
+		public List<KalendarRenderWrapper> getWrappers() {
+			return wrappers;
+		}
+
+		@Override
+		public void visit(KalendarRenderWrapper wrapper) {
+			wrappers.add(wrapper);
+		}
+	}
+	
+	private static class CalCourseNodeVisitor implements Visitor {
+		private boolean found = false;
+		
+		public boolean isFound() {
+			return found;
+		}
+		
+		@Override
+		public void visit(INode node) {
+			if(node instanceof CalCourseNode) {
+				found = true;
+			}
+		}
+	}
+}
diff --git a/src/main/java/org/olat/commons/calendar/restapi/EventVO.java b/src/main/java/org/olat/commons/calendar/restapi/EventVO.java
new file mode 100644
index 00000000000..9fff994bcb1
--- /dev/null
+++ b/src/main/java/org/olat/commons/calendar/restapi/EventVO.java
@@ -0,0 +1,97 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.commons.calendar.restapi;
+
+import java.util.Date;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.olat.commons.calendar.model.KalendarEvent;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "catalogVO")
+public class EventVO {
+	
+	private String id;
+	private String subject;
+	private String description;
+	
+	private Date begin;
+	private Date end;
+	
+	public EventVO() {
+		//
+	}
+	
+	public EventVO(KalendarEvent event) {
+		id = event.getID();
+		subject = event.getSubject();
+		description = event.getDescription();
+		
+		begin = event.getBegin();
+		end = event.getEnd();
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getSubject() {
+		return subject;
+	}
+
+	public void setSubject(String subject) {
+		this.subject = subject;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public Date getBegin() {
+		return begin;
+	}
+
+	public void setBegin(Date begin) {
+		this.begin = begin;
+	}
+
+	public Date getEnd() {
+		return end;
+	}
+
+	public void setEnd(Date end) {
+		this.end = end;
+	}
+	
+	
+	
+
+}
diff --git a/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java b/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java
index 31189e6e92d..c55a5086119 100644
--- a/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java
+++ b/src/main/java/org/olat/course/nodes/cal/CourseCalendars.java
@@ -79,9 +79,16 @@ public class CourseCalendars {
 		CourseCalendarSubscription calSubscription = new CourseCalendarSubscription(getKalendar(), ureq.getUserSession().getGuiPreferences());
 		return calSubscription;
 	}
-
-	public static CourseCalendars createCourseCalendarsWrapper(UserRequest ureq, WindowControl wControl, OLATResourceable ores, NodeEvaluation ne) {
-		List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>();
+	
+	/**
+	 * Return only the course calendar without any group calendar
+	 * @param ureq
+	 * @param wControl
+	 * @param ores
+	 * @param ne
+	 * @return
+	 */
+	public static KalendarRenderWrapper getCourseCalendarWrapper(UserRequest ureq, OLATResourceable ores, NodeEvaluation ne) {
 		CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
 		// add course calendar
 		ICourse course = CourseFactory.loadCourse(ores);
@@ -89,10 +96,11 @@ public class CourseCalendars {
 		CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
 		Identity identity = ureq.getIdentity();
 		boolean isPrivileged = cgm.isIdentityCourseAdministrator(identity)
-				|| ne.isCapabilityAccessible(CalCourseNode.EDIT_CONDITION_ID)
+				|| (ne != null && ne.isCapabilityAccessible(CalCourseNode.EDIT_CONDITION_ID))
 				|| RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(
 						RepositoryManager.getInstance().lookupRepositoryEntry(course, false), identity)
 				|| ureq.getUserSession().getRoles().isOLATAdmin();
+		
 		if (isPrivileged) {
 			courseKalendarWrapper.setAccess(KalendarRenderWrapper.ACCESS_READ_WRITE);
 		} else {
@@ -103,10 +111,19 @@ public class CourseCalendars {
 			courseKalendarWrapper.getKalendarConfig().setCss(config.getCss());
 			courseKalendarWrapper.getKalendarConfig().setVis(config.isVis());
 		}
+		return courseKalendarWrapper;
+	}
+
+	public static CourseCalendars createCourseCalendarsWrapper(UserRequest ureq, WindowControl wControl, OLATResourceable ores, NodeEvaluation ne) {
+		List<KalendarRenderWrapper> calendars = new ArrayList<KalendarRenderWrapper>();
+		KalendarRenderWrapper courseKalendarWrapper = getCourseCalendarWrapper(ureq, ores, ne);
 		// add link provider
+		ICourse course = CourseFactory.loadCourse(ores);
 		CourseLinkProviderController clpc = new CourseLinkProviderController(course, ureq, wControl);
 		courseKalendarWrapper.setLinkProvider(clpc);
-		calendars.add(courseKalendarWrapper);
+		
+		Identity identity = ureq.getIdentity();
+		CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
 
 		// add course group calendars
 		boolean isGroupManager = cgm.isIdentityCourseAdministrator(identity) || cgm.hasRight(identity, CourseRights.RIGHT_GROUPMANAGEMENT);
diff --git a/src/main/java/org/olat/modules/fo/restapi/ForumWebService.java b/src/main/java/org/olat/modules/fo/restapi/ForumWebService.java
index 795992b10b4..1f208b37319 100644
--- a/src/main/java/org/olat/modules/fo/restapi/ForumWebService.java
+++ b/src/main/java/org/olat/modules/fo/restapi/ForumWebService.java
@@ -28,7 +28,6 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.URI;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
diff --git a/src/main/java/org/olat/restapi/_spring/restApiContext.xml b/src/main/java/org/olat/restapi/_spring/restApiContext.xml
index cb0de72bf3f..bbb11326669 100644
--- a/src/main/java/org/olat/restapi/_spring/restApiContext.xml
+++ b/src/main/java/org/olat/restapi/_spring/restApiContext.xml
@@ -46,6 +46,7 @@
 				<value>org.olat.modules.fo.restapi.MyForumsWebService</value>
 				<value>org.olat.catalog.restapi.CatalogWebService</value>
 				<value>org.olat.notifications.restapi.NotificationsWebService</value>
+				<value>org.olat.commons.calendar.restapi.CalendarWebService</value>
 				<value>org.olat.restapi.log.LogWebService</value>
 			</list>
 		</property>
diff --git a/src/test/java/org/olat/restapi/CalendarTest.java b/src/test/java/org/olat/restapi/CalendarTest.java
new file mode 100644
index 00000000000..6e35401aaee
--- /dev/null
+++ b/src/test/java/org/olat/restapi/CalendarTest.java
@@ -0,0 +1,438 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.restapi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.util.EntityUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.commons.calendar.CalendarManager;
+import org.olat.commons.calendar.CalendarManagerFactory;
+import org.olat.commons.calendar.model.KalendarEvent;
+import org.olat.commons.calendar.restapi.CalendarVO;
+import org.olat.commons.calendar.restapi.EventVO;
+import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.DBFactory;
+import org.olat.core.id.Identity;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.config.CourseConfig;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.restapi.repository.course.CoursesWebService;
+import org.olat.restapi.support.vo.CourseConfigVO;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatJerseyTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class CalendarTest extends OlatJerseyTestCase {
+
+	private static ICourse course1, course2;
+	private static Identity id1, id2;
+	
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private DB dbInstance;
+	
+	@Before
+	public void startup() {
+		if(id1 == null) {
+			id1 = JunitTestHelper.createAndPersistIdentityAsUser("cal-1-" + UUID.randomUUID().toString());
+		}
+		if(id2 == null) {
+			id2 = JunitTestHelper.createAndPersistIdentityAsUser("cal-2-" + UUID.randomUUID().toString());
+		}
+		
+		if(course1 == null) {
+			//create a course with a calendar
+			CourseConfigVO config = new CourseConfigVO();
+			course1 = CoursesWebService.createEmptyCourse(id1, "Cal course", "Cal course", config);
+
+			dbInstance.commit();
+			ICourse course = CourseFactory.openCourseEditSession(course1.getResourceableId());
+			CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig();
+			courseConfig.setCalendarEnabled(true);
+			CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig);
+			CourseFactory.closeCourseEditSession(course.getResourceableId(),true);
+
+			dbInstance.commit();
+
+			CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+			KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course);
+
+			Calendar cal = Calendar.getInstance();
+			Date begin = cal.getTime();
+			cal.add(Calendar.HOUR_OF_DAY, 1);
+			Date end = cal.getTime();
+			KalendarEvent event = new KalendarEvent(UUID.randomUUID().toString(), "Unit test", begin, end);
+			calendarWrapper.getKalendar().addEvent(event);
+			
+			RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(course1, false);
+			securityManager.addIdentityToSecurityGroup(id1, entry.getParticipantGroup());
+			
+			dbInstance.commit();
+		}
+		
+		if(course2 == null) {
+			//create a course with a calendar
+			CourseConfigVO config = new CourseConfigVO();
+			course2 = CoursesWebService.createEmptyCourse(id2, "Cal course - 2", "Cal course - 2", config);
+
+			dbInstance.commit();
+			ICourse course = CourseFactory.openCourseEditSession(course2.getResourceableId());
+			CourseConfig courseConfig = course.getCourseEnvironment().getCourseConfig();
+			courseConfig.setCalendarEnabled(true);
+			CourseFactory.setCourseConfig(course.getResourceableId(), courseConfig);
+			CourseFactory.closeCourseEditSession(course.getResourceableId(),true);
+
+			dbInstance.commit();
+		}
+	}
+	
+	@After
+	public void tearDown() {
+		DBFactory.getInstance().commitAndCloseSession();
+	}
+
+	@Test
+	public void testGetCalendars() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id1.getName(), "A6B7C8"));
+		
+		URI uri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString()).path("calendars").build();
+		HttpGet method = conn.createGet(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertNotNull(vos);
+		assertEquals(2, vos.size());//course1 + personal
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testHijackCalendars() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id1.getName(), "A6B7C8"));
+		
+		URI uri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString()).path("calendars").build();
+		HttpGet method = conn.createGet(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(401, response.getStatusLine().getStatusCode());
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testGetEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id1.getName(), "A6B7C8"));
+		
+		URI uri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString()).path("calendars").path("events").build();
+		HttpGet method = conn.createGet(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<EventVO> vos = parseEventArray(response);
+		assertNotNull(vos);
+		assertEquals(1, vos.size());//Root-1
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testGetCalendarEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id1.getName(), "A6B7C8"));
+		
+		URI uri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString()).path("calendars").build();
+		HttpGet method = conn.createGet(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertNotNull(vos);
+		assertEquals(2, vos.size());//course1 + personal
+		CalendarVO calendar = getCourseCalendar(vos);
+
+		URI eventUri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpGet eventMethod = conn.createGet(eventUri, MediaType.APPLICATION_JSON, true);
+		HttpResponse eventResponse = conn.execute(eventMethod);
+		assertEquals(200, eventResponse.getStatusLine().getStatusCode());
+		List<EventVO> events = parseEventArray(eventResponse);
+		assertNotNull(events);
+		assertEquals(1, events.size());//Root-1
+		
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testOutputGetCalendarEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id1.getName(), "A6B7C8"));
+		
+		URI uri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString()).path("calendars").build();
+		HttpGet method = conn.createGet(uri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertNotNull(vos);
+		assertEquals(2, vos.size());//Root-1
+		CalendarVO calendar = getCourseCalendar(vos);
+
+		//get events and output as JSON
+		URI eventUri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpGet eventMethod = conn.createGet(eventUri, MediaType.APPLICATION_JSON, true);
+		HttpResponse eventResponse = conn.execute(eventMethod);
+		assertEquals(200, eventResponse.getStatusLine().getStatusCode());
+		String outputJson = EntityUtils.toString(eventResponse.getEntity());
+		System.out.println("*** JSON");
+		System.out.println(outputJson);
+
+		//get events and output as XML
+		URI eventXmlUri = UriBuilder.fromUri(getContextURI()).path("users").path(id1.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpGet eventXmlMethod = conn.createGet(eventXmlUri, MediaType.APPLICATION_XML, true);
+		HttpResponse eventXmlResponse = conn.execute(eventXmlMethod);
+		assertEquals(200, eventXmlResponse.getStatusLine().getStatusCode());
+		String outputXml = EntityUtils.toString(eventXmlResponse.getEntity());
+		System.out.println("*** XML");
+		System.out.println(outputXml);
+
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testPutCalendarEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id2.getName(), "A6B7C8"));
+		
+		URI calUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString()).path("calendars").build();
+		HttpGet calMethod = conn.createGet(calUri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(calMethod);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertNotNull(vos);
+		assertEquals(2, vos.size());
+		CalendarVO calendar = getCourseCalendar(vos);
+		
+		//create an event
+		EventVO event = new EventVO();
+		Calendar cal = Calendar.getInstance();
+		event.setBegin(cal.getTime());
+		cal.add(Calendar.HOUR_OF_DAY, 1);
+		event.setEnd(cal.getTime());
+		String subject = UUID.randomUUID().toString();
+		event.setSubject(subject);
+
+		URI eventUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpPut putEventMethod = conn.createPut(eventUri, MediaType.APPLICATION_JSON, true);
+		conn.addJsonEntity(putEventMethod, event);
+		HttpResponse putEventResponse = conn.execute(putEventMethod);
+		assertEquals(200, putEventResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(putEventResponse.getEntity());
+		
+		
+		//check if the event is saved
+		CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+		KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course2);
+		Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents();
+		
+		boolean found = false;
+		for(KalendarEvent savedEvent:savedEvents) {
+			if(subject.equals(savedEvent.getSubject())) {
+				found = true;
+			}
+		}
+		Assert.assertTrue(found);
+
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testPostCalendarEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id2.getName(), "A6B7C8"));
+		
+		URI calUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString()).path("calendars").build();
+		HttpGet calMethod = conn.createGet(calUri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(calMethod);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertTrue(vos != null && !vos.isEmpty());
+		CalendarVO calendar = getCourseCalendar(vos);
+		
+		//create an event
+		EventVO event = new EventVO();
+		Calendar cal = Calendar.getInstance();
+		event.setBegin(cal.getTime());
+		cal.add(Calendar.HOUR_OF_DAY, 1);
+		event.setEnd(cal.getTime());
+		String subject = UUID.randomUUID().toString();
+		event.setSubject(subject);
+
+		URI eventUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpPost postEventMethod = conn.createPost(eventUri, MediaType.APPLICATION_JSON, true);
+		conn.addJsonEntity(postEventMethod, event);
+		HttpResponse postEventResponse = conn.execute(postEventMethod);
+		assertEquals(200, postEventResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(postEventResponse.getEntity());
+		
+		
+		//check if the event is saved
+		CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+		KalendarRenderWrapper calendarWrapper = calendarManager.getCourseCalendar(course2);
+		Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents();
+		
+		boolean found = false;
+		for(KalendarEvent savedEvent:savedEvents) {
+			if(subject.equals(savedEvent.getSubject())) {
+				found = true;
+			}
+		}
+		Assert.assertTrue(found);
+
+		conn.shutdown();
+	}
+	
+	@Test
+	public void testPutPersonalCalendarEvents() throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login(id2.getName(), "A6B7C8"));
+		
+		URI calUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString()).path("calendars").build();
+		HttpGet calMethod = conn.createGet(calUri, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(calMethod);
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CalendarVO> vos = parseArray(response);
+		assertNotNull(vos);
+		assertEquals(2, vos.size());
+		CalendarVO calendar = getUserCalendar(vos);
+		
+		//create an event
+		EventVO event = new EventVO();
+		Calendar cal = Calendar.getInstance();
+		event.setBegin(cal.getTime());
+		cal.add(Calendar.HOUR_OF_DAY, 1);
+		event.setEnd(cal.getTime());
+		String subject = UUID.randomUUID().toString();
+		event.setSubject(subject);
+
+		URI eventUri = UriBuilder.fromUri(getContextURI()).path("users").path(id2.getKey().toString())
+				.path("calendars").path(calendar.getId()).path("events").build();
+		HttpPut putEventMethod = conn.createPut(eventUri, MediaType.APPLICATION_JSON, true);
+		conn.addJsonEntity(putEventMethod, event);
+		HttpResponse putEventResponse = conn.execute(putEventMethod);
+		assertEquals(200, putEventResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(putEventResponse.getEntity());
+		
+		
+		//check if the event is saved
+		CalendarManager calendarManager = CalendarManagerFactory.getInstance().getCalendarManager();
+		KalendarRenderWrapper calendarWrapper = calendarManager.getPersonalCalendar(id2);
+		Collection<KalendarEvent> savedEvents = calendarWrapper.getKalendar().getEvents();
+		
+		boolean found = false;
+		for(KalendarEvent savedEvent:savedEvents) {
+			if(subject.equals(savedEvent.getSubject())) {
+				found = true;
+			}
+		}
+		Assert.assertTrue(found);
+
+		conn.shutdown();
+	}
+	
+	protected CalendarVO getCourseCalendar(List<CalendarVO> vos) {
+		for(CalendarVO vo:vos) {
+			if(vo.getId().startsWith("course")) {
+				return vo;
+			}
+		}
+		return null;
+	}
+	
+	protected CalendarVO getUserCalendar(List<CalendarVO> vos) {
+		for(CalendarVO vo:vos) {
+			if(vo.getId().startsWith("user")) {
+				return vo;
+			}
+		}
+		return null;
+	}
+	
+	protected List<CalendarVO> parseArray(HttpResponse response) {
+		try {
+			InputStream body = response.getEntity().getContent();
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(body, new TypeReference<List<CalendarVO>>(){/* */});
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+	
+	protected List<EventVO> parseEventArray(HttpResponse response) {
+		try {
+			InputStream body = response.getEntity().getContent();
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(body, new TypeReference<List<EventVO>>(){/* */});
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+
+}
-- 
GitLab