package com.google.sitebricks.routing;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import net.jcip.annotations.Immutable;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.sitebricks.Respond;
import com.google.sitebricks.StringBuilderRespond;
import com.google.sitebricks.binding.FlashCache;
import com.google.sitebricks.binding.RequestBinder;
import com.google.sitebricks.client.transport.Json;
import com.google.sitebricks.conversion.ValidationConverter;
import com.google.sitebricks.headless.HeadlessRenderer;
import com.google.sitebricks.headless.Reply;
import com.google.sitebricks.headless.Request;
import com.google.sitebricks.rendering.resource.ResourcesService;
import com.google.sitebricks.routing.PageBook.Page;
/**
* @author Dhanji R. Prasanna (dhanji@gmail.com)
*/
@Immutable
@Singleton
class WidgetRoutingDispatcher implements RoutingDispatcher {
private final PageBook book;
private final RequestBinder binder;
private final ResourcesService resourcesService;
private final Provider<FlashCache> flashCacheProvider;
private final HeadlessRenderer headlessRenderer;
@Inject
Provider<HttpServletRequest> httpServletRequestProvider;
@Inject
private ValidationConverter validationConvertor;
@Inject
public WidgetRoutingDispatcher(PageBook book, RequestBinder<String> binder,
ResourcesService resourcesService,
Provider<FlashCache> flashCacheProvider,
HeadlessRenderer headlessRenderer) {
this.headlessRenderer = headlessRenderer;
this.book = book;
this.binder = binder;
this.resourcesService = resourcesService;
this.flashCacheProvider = flashCacheProvider;
}
public Object dispatch(Request request, Events event) throws IOException {
String uri = request.path();
//first try dispatching as a static resource service
Respond respond = resourcesService.serve(uri);
if (null != respond)
return respond;
// Otherwise try to dispatch as a widget/page
// Check if there is a page chain link sitting here
// for this page.
// NOTE(dhanji): we must use remove, to atomically
// remove the page and process it in one go. It is
// also worth coordinating this with conversation request
// queueing.
// TODO(dhanji): Change flashcache to use temporary cookies instead.
PageBook.Page page = flashCacheProvider.get().remove(uri);
// If there is no link, obtain page via Guice as normal.
if (null == page)
page = book.get(uri);
//could not dispatch as there was no match
if (null == page)
return null;
final Object instance = page.instantiate();
if (page.isHeadless()) {
return bindAndReply(request, page, instance);
} else {
//fire events and render responder
return bindAndRespond(request, page, instance);
}
}
private Object bindAndReply(Request request, Page page, Object instance) throws IOException {
// bind request (sets request params, etc).
binder.bind(request, instance);
Object response = null;
try {
// call the appropriate handler.
response = fireEvent(request, page, instance);
}
catch (ValidationException ve) {
ConstraintViolationException cve = (ConstraintViolationException) ve.getCause();
Set<? extends ConstraintViolation<?>> scv = (Set<? extends ConstraintViolation<?>>) cve.getConstraintViolations();
List<String> errors = validationConvertor.to(scv);
response = Reply.with(errors).as(Json.class).badRequest();
}
return response;
}
private Object bindAndRespond(Request request, PageBook.Page page, Object instance)
throws IOException {
//bind request
binder.bind(request, instance);
// fire get/post events
Object redirect = null;
List<String> errors = null;
try {
redirect = fireEvent(request, page, instance);
}
catch (ValidationException ve) {
ve.getCause().printStackTrace();
ConstraintViolationException cve = (ConstraintViolationException) ve.getCause();
Set<? extends ConstraintViolation<?>> scv = (Set<? extends ConstraintViolation<?>>) cve.getConstraintViolations();
errors = validationConvertor.to(scv);
}
//render to respond
Respond respond = new StringBuilderRespond(instance);
respond.setErrors(errors);
if (null != redirect) {
if (redirect instanceof String)
respond.redirect((String) redirect);
else if (redirect instanceof Class) {
PageBook.Page targetPage = book.forClass((Class<?>) redirect);
// should never be null coz it is validated on compile.
respond.redirect(contextualize(request, targetPage.getUri()));
} else if (redirect instanceof Reply<?>) {
// To allow non-headless pages to use Reply<?> for more advanced HTTP responses
return redirect;
} else {
// Handle page-chaining driven redirection.
PageBook.Page targetPage = book.forInstance(redirect);
// should never be null coz it will be validated at compile time.
flashCacheProvider.get().put(targetPage.getUri(), targetPage);
// Send to the canonical address of the page. This is also
// verified at compile, not be a variablized matcher.
respond.redirect(contextualize(request, targetPage.getUri()));
}
} else {
page.widget().render(instance, respond);
}
return respond;
}
// We're sure the request parameter map is a Map<String, String[]>
@SuppressWarnings("unchecked")
private Object fireEvent(Request request, PageBook.Page page, Object instance)
throws IOException {
final String method = request.method();
final String pathInfo = request.path();
return page.doMethod(method.toLowerCase(), instance, pathInfo, request);
}
private static String contextualize(Request request, String targetUri) {
return request.context() + targetUri;
}
}