/**
* Copyright 2010 Marko Lavikainen
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.contextfw.web.application.internal.service;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import net.contextfw.web.application.PageContext;
import net.contextfw.web.application.WebApplicationException;
import net.contextfw.web.application.PageHandle;
import net.contextfw.web.application.component.Component;
import net.contextfw.web.application.component.DOMBuilder;
import net.contextfw.web.application.internal.ComponentUpdateHandler;
import net.contextfw.web.application.internal.ComponentUpdateHandlerFactory;
import net.contextfw.web.application.internal.WebResponder;
import net.contextfw.web.application.internal.WebResponder.Mode;
import net.contextfw.web.application.internal.component.ComponentBuilder;
import net.contextfw.web.application.internal.component.InternalComponentRegister;
import net.contextfw.web.application.internal.component.WebApplicationComponent;
import net.contextfw.web.application.internal.initializer.InitializerContextImpl;
import net.contextfw.web.application.internal.servlet.UriMapping;
import net.contextfw.web.application.internal.util.AttributeHandler;
import net.contextfw.web.application.lifecycle.PageScoped;
import net.contextfw.web.application.lifecycle.ResourceView;
import net.contextfw.web.application.remote.ResourceBody;
import net.contextfw.web.application.remote.ResourceResponse;
import net.contextfw.web.application.scope.Provided;
import com.google.gson.Gson;
import com.google.inject.Inject;
import com.google.inject.Injector;
@PageScoped
public class WebApplicationImpl implements WebApplication {
@Inject
@Provided
private Gson gson;
@Inject
@Provided
private ComponentUpdateHandlerFactory euhf;
@Inject
@Provided
private ComponentBuilder builder;
private static volatile Map<String, ComponentUpdateHandler> updateHandlers = new HashMap<String, ComponentUpdateHandler>();
@Inject
@Provided
private Injector injector;
@Inject
private PageContext pageContext;
@Inject
private InternalComponentRegister componentRegister;
@Inject
private WebApplicationComponent rootComponent;
private List<Class<? extends Component>> chain;
private InitializerContextImpl context = null;
@Inject
@Provided
private WebResponder responder;
private Mode mode = Mode.INIT;
@Inject
@Provided
private AttributeHandler attributes;
@Inject
private PageHandle pageHandle;
//private final String contextPath;
private final WebApplicationConf conf;
@Inject
public WebApplicationImpl(WebApplicationConf conf) {
this.conf = conf;
}
@Override
public void initState(UriMapping mapping) {
context = new InitializerContextImpl(
builder,
mapping,
pageContext.getRequestURI()
.substring(pageContext.getRequest().getContextPath().length()),
injector,
pageContext.getRequest(),
chain);
getRootComponent().registerChild(context.initChild());
}
@Override
public boolean sendResponse() {
try {
if (mode == Mode.INIT) {
if (pageContext.getRedirectUrl() != null) {
pageContext.getResponse().sendRedirect(pageContext.getRedirectUrl());
return true;
} else if (pageContext.getErrorCode() != null) {
pageContext.getResponse().sendError(pageContext.getErrorCode(), pageContext.getErrorMsg());
return true;
} else if (pageContext.isReload()) {
StringBuilder sb = new StringBuilder(pageContext.getRequestURI());
if (pageContext.getQueryString() != null) {
sb.append("?").append(pageContext.getQueryString());
}
pageContext.getResponse().sendRedirect(sb.toString());
}
}
try {
if (mode == Mode.INIT && context.getLeaf() instanceof ResourceView) {
return sendResourceResponse();
} else {
sendNormalResponse();
return false;
}
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (WebApplicationException) e;
} else {
throw new WebApplicationException("Exception while trying to init state", e);
}
}
} catch (IOException e) {
throw new WebApplicationException(e);
}
}
private boolean sendResourceResponse() throws IOException {
boolean expire = true;
ResourceView leaf = (ResourceView) context.getLeaf();
try {
ResourceBody annotation = leaf.getClass().getMethod("getResponse")
.getAnnotation(ResourceBody.class);
if (annotation != null) {
expire = annotation.expire();
}
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchMethodException e) {
throw new WebApplicationException(e);
}
Object retVal = leaf.getResponse();
if (retVal == null) {
return expire;
}
if (retVal instanceof ResourceResponse) {
((ResourceResponse) retVal).serve(
pageContext.getRequest(),
pageContext.getResponse());
} else {
HttpServletResponse response = pageContext.getResponse();
setHeaders(response);
response.setContentType("application/json; charset=UTF-8");
gson.toJson(retVal, response.getWriter());
}
return expire;
}
private void sendNormalResponse() throws ServletException, IOException {
pageContext.getResponse().setContentType("text/html; charset=UTF-8");
DOMBuilder d;
if (mode == Mode.INIT) {
d = new DOMBuilder("WebApplication", attributes, builder, conf.getNamespaces());
} else {
d = new DOMBuilder("WebApplication.update",
attributes,
builder,
conf.getNamespaces());
}
d.attr("handle", pageHandle.toString());
d.attr("contextPath", pageContext.getRequest().getContextPath());
if (pageContext.getLocale() != null) {
d.attr("xml:lang", pageContext.getLocale().toString());
d.attr("lang", pageContext.getLocale().toString());
}
if (mode == Mode.INIT) {
getRootComponent().buildChild(d);
} else if (pageContext.getRedirectUrl() != null) {
d.descend("Redirect").attr("href", pageContext.getRedirectUrl());
} else if (pageContext.getErrorCode() != null) {
d.descend("Error").attr("code", pageContext.getErrorCode()).text(pageContext.getErrorMsg());
} else if (pageContext.isReload()) {
d.descend("Reload");
} else {
getRootComponent().buildChildUpdate(d, builder);
}
getRootComponent().clearCascadedUpdate();
if (conf.getXmlParamName() == null
|| pageContext.getRequest().getParameter(conf.getXmlParamName()) == null) {
responder.sendResponse(d.toDocument(), pageContext.getResponse(), mode);
} else {
responder.sendResponse(d.toDocument(), pageContext.getResponse(), Mode.XML);
}
}
@Override
public UpdateInvocation updateState(String componentId, String method) {
mode = Mode.UPDATE;
return updateElements(componentId, method);
}
@SuppressWarnings("unchecked")
protected UpdateInvocation updateElements(final String id, final String method) {
try {
Component element = componentRegister.findComponent(id);
String key = ComponentUpdateHandler.getKey(element.getClass(), method);
if (!updateHandlers.containsKey(key) || conf.isDevelopmentMode()) {
updateHandlers.put(key, euhf.createHandler(element.getClass(), method));
}
ComponentUpdateHandler handler = updateHandlers.get(key);
if (handler != null) {
if (handler.getDelayed() == null
|| !injector.getInstance(handler.getDelayed().value())
.isUpdateDelayed(element, pageContext.getRequest())) {
return new UpdateInvocation(
handler.isResource(),
handler.invoke(rootComponent, element, pageContext.getRequest())
);
} else {
return UpdateInvocation.DELAYED;
}
} else {
return UpdateInvocation.NONE;
}
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (WebApplicationException) e;
} else {
throw new WebApplicationException("Failed to update elements", e);
}
}
}
@Override
public void setInitializerChain(List<Class<? extends Component>> chain) {
this.chain = chain;
}
public void setHeaders(HttpServletResponse response) {
response.addHeader("Expires", "Sun, 19 Nov 1978 05:00:00 GMT");
response.addHeader("Last-Modified", new Date().toString());
response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// response.addHeader("Cache-Control","post-check=0, pre-check=0");
response.addHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
}
public WebApplicationComponent getRootComponent() {
return rootComponent;
}
}