package no.java.incogito.web.resources;
import fj.F;
import fj.F2;
import static fj.Function.curry;
import fj.P1;
import fj.control.parallel.Callables;
import fj.data.Java;
import fj.data.List;
import fj.data.Option;
import static fj.data.Option.fromString;
import static fj.data.Option.join;
import static fj.data.Option.some;
import no.java.incogito.Functions;
import static no.java.incogito.Functions.compose;
import no.java.incogito.IO;
import no.java.incogito.PatternMatcher;
import no.java.incogito.application.IncogitoApplication;
import no.java.incogito.application.OperationResult;
import no.java.incogito.application.OperationResult.NotFoundOperationResult;
import no.java.incogito.application.OperationResult.OkOperationResult;
import static no.java.incogito.application.OperationResult.fromOption;
import no.java.incogito.domain.Event;
import no.java.incogito.domain.IncogitoUri;
import no.java.incogito.domain.IncogitoUri.IncogitoEventsUri.IncogitoEventUri;
import no.java.incogito.domain.IncogitoUri.IncogitoRestEventsUri.IncogitoRestEventUri;
import no.java.incogito.domain.Label;
import no.java.incogito.domain.Level;
import no.java.incogito.domain.Level.LevelId;
import no.java.incogito.domain.Room;
import no.java.incogito.domain.Session;
import no.java.incogito.domain.SessionId;
import no.java.incogito.domain.User;
import no.java.incogito.domain.UserSessionAssociation.InterestLevel;
import static no.java.incogito.dto.EventListXml.eventListXml;
import no.java.incogito.dto.EventXml;
import no.java.incogito.dto.IncogitoXml;
import no.java.incogito.dto.SessionListXml;
import no.java.incogito.dto.SessionXml;
import no.java.incogito.web.WebFunctions;
import static no.java.incogito.web.WebFunctions.generateCalendarCss;
import static no.java.incogito.web.resources.XmlFunctions.eventToXml;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import javax.ws.rs.core.SecurityContext;
import java.io.File;
/**
* REST-ful wrapper around IncogitoApplication.
* <p/>
* TODO: Add checks on every method that uses SecurityContext to check for nulls.
*
* @author <a href="mailto:trygvis@java.no">Trygve Laugstøl</a>
* @version $Id$
*/
@Component
@Path("/rest")
@Produces({"application/xml", "application/json"})
@SuppressWarnings({"UnusedDeclaration"})
public class IncogitoResource {
private final IncogitoApplication incogito;
private final CacheControl cacheForOneHourCacheControl;
@Autowired
public IncogitoResource(IncogitoApplication incogito) {
this.incogito = incogito;
cacheForOneHourCacheControl = new CacheControl();
cacheForOneHourCacheControl.setMaxAge(3600);
}
@GET
public Response getIncogito(@Context SecurityContext securityContext) {
return Response.ok(new IncogitoXml(incogito.getConfiguration().baseurl, getUserName.f(securityContext))).build();
}
@Path("/events")
@GET
public Response getEvents() {
F<List<Event>, List<EventXml>> eventListToXml = XmlFunctions.eventListToXml.f(new IncogitoUri(incogito.getConfiguration().baseurl));
return toJsr311(incogito.getEvents().
ok().map(compose(eventListXml, compose(Java.<EventXml>List_ArrayList(), eventListToXml))));
}
@Path("/events/{eventName}/calendar.css")
@GET
@Produces("text/css")
public Response getEventCalendarCss(@PathParam("eventName") final String eventName) {
final F<List<Room>, List<String>> generateCss = WebFunctions.generateCalendarCss.f(incogito.getConfiguration().cssConfiguration);
return toJsr311(incogito.getEventByName(eventName).ok().map(new F<Event, String>() {
public String f(Event event) {
// final F<List<Room>, List<String>> generateCss = generateCalendarCss.
// f(incogito.getConfiguration().cssConfiguration).
// f(incogito.getConfiguration().findEventConfigurationByName(eventName).some());
return generateCss.f(event.presentationRooms).foldRight(Functions.String_join.f("\n"), "");
}
}), cacheForOneHourCacheControl);
}
@Path("/events/{eventName}/session.css")
@GET
@Produces("text/css")
public Response getEventSessionCss(@PathParam("eventName") final String eventName) {
final F<Event, List<String>> generateSessionCss = WebFunctions.generateSessionCss.f(incogito.getConfiguration());
return toJsr311(incogito.getEventByName(eventName).ok().map(new F<Event, String>() {
public String f(Event event) {
return generateSessionCss.f(event).foldRight(Functions.String_join.f("\n"), "");
}
}), cacheForOneHourCacheControl);
}
@Path("/events/{eventName}/icons/levels/{level}.png")
@GET
@Produces("image/png")
public Response getLevelIcon(@PathParam("eventName") final String eventName,
@PathParam("level") final String level) {
OperationResult<Event> eventResult = incogito.getEventByName(eventName);
if (!eventResult.isOk()) {
return toJsr311(eventResult);
}
Option<LevelId> levelOption = some(level).bind(Level.LevelId.valueOf);
if (!levelOption.isSome()) {
return toJsr311(OperationResult.<Object>notFound("Level '" + level + "' not known."));
}
Option<File> fileOption = eventResult.value().levels.get(levelOption.some()).map(Level.iconFile_);
if (!fileOption.isSome()) {
return toJsr311(OperationResult.<Object>notFound("No icon for level '" + level + "'."));
}
// TODO: How about some caching here?
Option<byte[]> bytes = join(fileOption.
map(IO.<byte[]>runFileInputStream_().f(IO.ByteArrays.streamToByteArray)).
map(compose(P1.<Option<byte[]>>__1(), Callables.<byte[]>option())));
return toJsr311(fromOption(bytes, "Unable to read level file."), cacheForOneHourCacheControl);
}
@Path("/events/{eventName}/icons/labels/{label}.png")
@GET
@Produces("image/png")
public Response getLabelIcon(@PathParam("eventName") final String eventName,
@PathParam("label") final String label) {
OperationResult<Event> eventResult = incogito.getEventByName(eventName);
if (!eventResult.isOk()) {
return toJsr311(eventResult);
}
Option<File> fileOption = eventResult.value().labelMap.get(label).map(Label.iconFile_);
if (!fileOption.isSome()) {
return toJsr311(OperationResult.<Object>notFound("No icon for label '" + label + "'."));
}
// TODO: How about some caching here?
Option<byte[]> bytes = join(fileOption.
map(IO.<byte[]>runFileInputStream_().f(IO.ByteArrays.streamToByteArray)).
map(compose(P1.<Option<byte[]>>__1(), Callables.<byte[]>option())));
return toJsr311(fromOption(bytes, "Unable to read label file."), cacheForOneHourCacheControl);
}
@Path("/events/{eventName}")
@GET
public Response getEvent(@PathParam("eventName") final String eventName) {
return toJsr311(incogito.getEventByName(eventName).ok().map(eventToXml.f(new IncogitoUri(incogito.getConfiguration().baseurl))));
}
@Path("/events/{eventName}/sessions")
@GET
public Response getSessionsForEvent(@PathParam("eventName") final String eventName) {
IncogitoUri incogitoUri = new IncogitoUri(incogito.getConfiguration().baseurl);
F<List<Session>, List<SessionXml>> sessionToXmlList = List.<Session, SessionXml>map_().
f(XmlFunctions.sessionToXml.f(incogitoUri.restEvents().eventUri(eventName)).f(incogitoUri.events().eventUri(eventName)));
return toJsr311(incogito.getSessions(eventName).
ok().map(compose(SessionListXml.sessionListXml, sessionToXmlList)));
}
@Path("/events/{eventName}/sessions/{sessionId}")
@GET
public Response getSessionForEvent(@PathParam("eventName") final String eventName,
@PathParam("sessionId") final String sessionId) {
IncogitoUri incogitoUri = new IncogitoUri(incogito.getConfiguration().baseurl);
IncogitoRestEventUri restEventUri = incogitoUri.restEvents().eventUri(eventName);
IncogitoEventUri eventUri = incogitoUri.events().eventUri(eventName);
// TODO: Consider replacing this with the configured host name and base url
F<Session, SessionXml> sessionToXml = XmlFunctions.sessionToXml.f(restEventUri).f(eventUri);
return toJsr311(incogito.getSession(eventName, new SessionId(sessionId)).
ok().map(sessionToXml));
}
@Path("/events/{eventName}/sessions/{sessionId}/speaker-photos/{index}")
@GET
public Response getPersonPhoto(@PathParam("eventName") final String eventName,
@PathParam("sessionId") final String sessionId,
@PathParam("index") final int index) {
return toJsr311(incogito.getSpeakerPhotoForSession(sessionId, index), cacheForOneHourCacheControl);
}
@Path("/events/{eventName}/sessions/{sessionId}/session-interest")
@POST
public Response setSessionInterest(@Context final SecurityContext securityContext,
@PathParam("eventName") final String eventName,
@PathParam("sessionId") final String sessionId,
String payload) {
Option<String> userName = getUserName.f(securityContext);
if (userName.isNone()) {
return Response.status(Status.UNAUTHORIZED).build();
}
OperationResult<User> result = incogito.setInterestLevel(userName.some(),
eventName,
new SessionId(sessionId),
InterestLevel.valueOf(payload));
return this.<User>defaultResponsePatternMatcher().
add(OkOperationResult.class, this.<OperationResult<User>>created()).
match(result);
}
@Path("/events/{eventName}/my-schedule")
@GET
public Response getMySchedule(@Context final SecurityContext securityContext,
@PathParam("eventName") final String eventName) {
String name = securityContext.getUserPrincipal().getName();
if (name == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
return getScheduleForUser(securityContext, eventName, name);
}
@Path("/events/{eventName}/schedules/{userName}")
@GET
public Response getScheduleForUser(@Context final SecurityContext securityContext,
@PathParam("eventName") final String eventName,
@PathParam("userName") final String userName) {
IncogitoUri incogitoUri = new IncogitoUri(incogito.getConfiguration().baseurl);
IncogitoRestEventUri restEventUri = incogitoUri.restEvents().eventUri(eventName);
IncogitoEventUri eventUri = incogitoUri.events().eventUri(eventName);
return toJsr311(incogito.getSchedule(eventName, userName).ok().
map(XmlFunctions.scheduleToXml.f(restEventUri).f(eventUri)));
}
// -----------------------------------------------------------------------
//
// -----------------------------------------------------------------------
private <T> F<T, ResponseBuilder> ok() {
return new F<T, ResponseBuilder>() {
public ResponseBuilder f(T operationResult) {
Object value = ((OperationResult) operationResult).value();
ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE);
return Response.ok(operationResult);
}
};
}
private F<CacheControl, F<ResponseBuilder, ResponseBuilder>> cacheControl = curry(new F2<CacheControl, ResponseBuilder, ResponseBuilder>() {
public ResponseBuilder f(CacheControl cacheControl, ResponseBuilder responseBuilder) {
return responseBuilder.cacheControl(cacheControl);
}
});
private F<ResponseBuilder, Response> build = new F<ResponseBuilder, Response>() {
public Response f(ResponseBuilder responseBuilder) {
return responseBuilder.build();
}
};
private <T> F<T, Response> created() {
return new F<T, Response>() {
public Response f(T operationResult) {
Object value = ((OperationResult) operationResult).value();
ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE);
return Response.status(Status.CREATED).build();
}
};
}
private <T> F<T, Response> notFound() {
return new F<T, Response>() {
public Response f(T operationResult) {
NotFoundOperationResult o = (NotFoundOperationResult) operationResult;
return Response.status(NOT_FOUND).
header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN).
entity(o.message + "\n").
build();
}
};
}
private <T> PatternMatcher<OperationResult<T>, Response> defaultResponsePatternMatcher() {
return PatternMatcher.<OperationResult<T>, Response>match().
add(NotFoundOperationResult.class, this.<OperationResult<T>>notFound());
}
private <T> Response toJsr311(OperationResult<T> result) {
return this.<T>defaultResponsePatternMatcher().
add(OkOperationResult.class, compose(build, this.<OperationResult<T>>ok())).
match(result);
}
private <T> Response toJsr311(OperationResult<T> result, CacheControl cacheControl) {
return this.<T>defaultResponsePatternMatcher().
add(OkOperationResult.class, compose(build, this.cacheControl.f(cacheControl), this.<OperationResult<T>>ok())).
match(result);
}
private F<SecurityContext, Option<String>> getUserName = new F<SecurityContext, Option<String>>() {
public Option<String> f(SecurityContext securityContext) {
return securityContext.getUserPrincipal() == null ? Option.<String>none() : fromString(securityContext.getUserPrincipal().getName());
}
};
}