/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* 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 com.alibaba.citrus.turbine.support;
import static com.alibaba.citrus.service.requestcontext.util.RequestContextUtil.*;
import static com.alibaba.citrus.service.uribroker.uri.URIBroker.URIType.*;
import static com.alibaba.citrus.util.ArrayUtil.*;
import static com.alibaba.citrus.util.Assert.ExceptionType.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.citrus.service.pull.PullService;
import com.alibaba.citrus.service.requestcontext.RequestContext;
import com.alibaba.citrus.service.requestcontext.lazycommit.LazyCommitRequestContext;
import com.alibaba.citrus.service.requestcontext.parser.CookieParser;
import com.alibaba.citrus.service.requestcontext.parser.ParameterParser;
import com.alibaba.citrus.service.requestcontext.parser.ParserRequestContext;
import com.alibaba.citrus.service.requestcontext.util.RequestContextUtil;
import com.alibaba.citrus.service.uribroker.URIBrokerService;
import com.alibaba.citrus.service.uribroker.uri.URIBroker;
import com.alibaba.citrus.turbine.Context;
import com.alibaba.citrus.turbine.TurbineRunDataInternal;
import com.alibaba.citrus.turbine.uribroker.uri.TurbineURIBroker;
import com.alibaba.citrus.util.internal.ActionEventUtil;
import com.alibaba.citrus.webx.WebxComponent;
import com.alibaba.citrus.webx.WebxException;
import com.alibaba.citrus.webx.util.WebxUtil;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
/** 实现<code>TurbineRunData</code>接口。 */
public class TurbineRunDataImpl implements TurbineRunDataInternal {
private final RequestContext topRequestContext;
private final LazyCommitRequestContext lazyCommitRequestContext;
private final ParserRequestContext parserRequestContext;
private final Map<String, PullService> pullServices;
private final Map<String, Context> contexts;
private final Parameters forwardParameters = new ForwardParametersImpl();
private final ModuleTraces moduleTraces = new ModuleTraces();
private TargetTuple targetTuple = new TargetTuple();
private TargetTuple redirectTargetTuple;
private WebxComponent currentComponent;
private boolean layoutEnabled;
private String layoutTemplateOverride;
private URIBroker redirectURI;
public TurbineRunDataImpl(HttpServletRequest request) {
this(request, null);
}
/** 创建一个turbine rundata,使用指定的context。 这种形式用于创建error pipeline的rundata对象。 */
public TurbineRunDataImpl(HttpServletRequest request, Context context) {
this.topRequestContext = assertNotNull(RequestContextUtil.getRequestContext(request),
"no request context defined in request attributes");
this.lazyCommitRequestContext = findRequestContext(topRequestContext, LazyCommitRequestContext.class);
this.parserRequestContext = findRequestContext(topRequestContext, ParserRequestContext.class);
this.pullServices = createHashMap();
this.contexts = createHashMap();
if (context != null) {
// 将context中的内容复制到新的context中,但是不要包含pull tools。
Context newContext = getContext();
Set<String> keys;
if (context instanceof PullableMappedContext) {
keys = ((PullableMappedContext) context).keySetWithoutPulling();
} else {
keys = context.keySet();
}
for (String key : keys) {
newContext.put(key, context.get(key));
}
}
}
private String normalizeComponentName(String componentName) {
componentName = trimToNull(componentName);
if (componentName != null) {
WebxComponent currentComponent = getCurrentComponent();
if (componentName.equals(currentComponent.getName())) {
componentName = null;
}
}
return componentName;
}
private WebxComponent getCurrentComponent() {
if (currentComponent == null) {
currentComponent = WebxUtil.getCurrentComponent(getRequest());
}
return currentComponent;
}
private LazyCommitRequestContext getLazyCommitRequestContext() {
return assertNotNull(lazyCommitRequestContext, "no lazyCommitRequestContext defined in request-contexts");
}
private ParserRequestContext getParserRequestContext() {
return assertNotNull(parserRequestContext, "no parserRequestContext defined in request-contexts");
}
private PullService getPullService(String componentName) {
componentName = normalizeComponentName(componentName);
if (!pullServices.containsKey(componentName)) {
WebxComponent component;
if (componentName == null) {
component = getCurrentComponent();
} else {
component = assertNotNull(getCurrentComponent().getWebxComponents().getComponent(componentName),
"could not find webx component: %s", componentName);
}
ApplicationContext context = component.getApplicationContext();
PullService pullService;
try {
pullService = (PullService) context.getBean("pullService", PullService.class);
} catch (NoSuchBeanDefinitionException e) {
pullService = null;
}
pullServices.put(componentName, pullService);
}
return pullServices.get(componentName); // maybe null
}
public RequestContext getRequestContext() {
return topRequestContext;
}
public HttpServletRequest getRequest() {
return topRequestContext.getRequest();
}
public HttpServletResponse getResponse() {
return topRequestContext.getResponse();
}
public ParameterParser getParameters() {
return getParserRequestContext().getParameters();
}
public CookieParser getCookies() {
return getParserRequestContext().getCookies();
}
public String getTarget() {
return targetTuple.getTarget();
}
public void setTarget(String target) {
targetTuple.setTarget(target);
}
public String getAction() {
return targetTuple.getAction();
}
public void setAction(String action) {
targetTuple.setAction(action);
}
public String getActionEvent() {
return targetTuple.getActionEvent();
}
public void setActionEvent(String actionEvent) {
targetTuple.setActionEvent(actionEvent);
}
public String getRedirectTarget() {
return redirectTargetTuple == null ? null : redirectTargetTuple.getTarget();
}
public void setRedirectTarget(String redirectTarget) {
setRedirectTargetAndAction(redirectTarget, null, null);
}
private void setRedirectTargetAndAction(String redirectTarget, String redirectAction, String redirectActionEvent) {
redirectTarget = trimToNull(redirectTarget);
// 如果target不相同,才需要重定向。
if (!isEquals(getTarget(), redirectTarget)) {
redirectTargetTuple = new TargetTuple()
.setTarget(redirectTarget).setAction(redirectAction).setActionEvent(redirectActionEvent);
}
}
public boolean doRedirectTarget() {
if (redirectTargetTuple != null) {
targetTuple = redirectTargetTuple;
redirectTargetTuple = null;
// 设置request,以便 ActionEventAdapter 不借助rundata也可取得actionEvent值。
ActionEventUtil.setEventName(getRequest(), getActionEvent());
return true;
} else {
return false;
}
}
public String getRedirectLocation() {
commitRedirectLocation();
return getLazyCommitRequestContext().getRedirectLocation();
}
public void setRedirectLocation(String redirectLocation) {
try {
getResponse().sendRedirect(redirectLocation);
} catch (IOException e) {
throw new WebxException("Could not redirect to URI: " + redirectLocation, e);
}
}
/** 设置用于重定向的uri broker。该uri会在下一次检查isRedirected()时被设置到response中。 */
private void setRedirectLocation(URIBroker uri) {
this.redirectURI = uri;
}
private void commitRedirectLocation() {
if (redirectURI != null) {
String uri = redirectURI.setURIType(full).render();
redirectURI = null; // reset
setRedirectLocation(uri);
}
}
public boolean isRedirected() {
commitRedirectLocation(); // 确保redirect uri broker被提交
return redirectTargetTuple != null || getLazyCommitRequestContext().isRedirected();
}
public Context getContext() {
return getContext(null);
}
public Context getContext(String componentName) {
componentName = normalizeComponentName(componentName);
Context context = contexts.get(componentName);
if (context == null) {
PullService pullService = getPullService(componentName);
if (pullService != null) {
context = new PullableMappedContext(pullService.getContext());
} else {
context = new MappedContext();
}
contexts.put(componentName, context);
}
return context;
}
public Context getCurrentContext() {
if (moduleTraces.isEmpty()) {
return null;
} else {
return moduleTraces.getLast().getContext();
}
}
public void pushContext(Context context) {
pushContext(context, null);
}
public void pushContext(Context context, String template) {
moduleTraces.addLast(new ModuleTrace(context, template));
}
public Context popContext() throws IllegalStateException {
assertTrue(!moduleTraces.isEmpty(), ILLEGAL_STATE, "can't popContext without pushContext");
return moduleTraces.removeLast().getContext();
}
public String getControlTemplate() {
if (moduleTraces.isEmpty()) {
return null;
} else {
return moduleTraces.getLast().getTemplate();
}
}
public void setControlTemplate(String template) {
assertTrue(!moduleTraces.isEmpty(), ILLEGAL_STATE, "can't setControlTemplate without pushContext");
moduleTraces.getLast().setTemplate(template);
}
public boolean isLayoutEnabled() {
return layoutEnabled;
}
public void setLayoutEnabled(boolean enabled) {
this.layoutEnabled = enabled;
}
/**
* 明确指定layout模板,覆盖默认的layout规则。 注意如果指定了layout,则<code>layoutEnabled</code>
* 将被设置成<code>true</code>。
*/
public void setLayout(String layoutTemplate) {
layoutTemplateOverride = trimToNull(layoutTemplate);
if (layoutTemplateOverride != null) {
setLayoutEnabled(true);
}
}
/** 取得明确指定的layout模板。 */
public String getLayoutTemplateOverride() {
return layoutTemplateOverride;
}
public Parameters forwardTo(String target) {
setRedirectTarget(target);
return forwardParameters;
}
public Parameters forwardTo(String target, String action, String actionEvent) {
setRedirectTargetAndAction(target, action, actionEvent);
return forwardParameters;
}
public RedirectParameters redirectTo(String uriName) {
uriName = assertNotNull(trimToNull(uriName), "no uriName");
URIBrokerService uris = (URIBrokerService) getCurrentComponent().getApplicationContext().getBean(
"uriBrokerService", URIBrokerService.class);
URIBroker uri = assertNotNull(uris.getURIBroker(uriName), "could not find uri broker named \"%s\"", uriName);
setRedirectLocation(uri);
return new RedirectParametersImpl(uriName, uri);
}
/** 进行外部重定向,指定一个完整的URL location。 */
public void redirectToLocation(String location) {
setRedirectLocation(location);
}
private class ForwardParametersImpl implements Parameters {
public Parameters withParameter(String name, String... values) {
if (!isEmptyArray(values)) {
getParameters().setStrings(name, values);
}
return this;
}
public void end() {
}
@Override
public String toString() {
return "forwardTo(" + getRedirectTarget() + ") " + getParameters();
}
}
private class RedirectParametersImpl implements RedirectParameters {
private final String uriName;
private final URIBroker uri;
public RedirectParametersImpl(String uriName, URIBroker uri) {
this.uriName = uriName;
this.uri = uri;
}
public RedirectParameters withTarget(String target) {
assertTrue(uri instanceof TurbineURIBroker, "URI is not a turbine-uri: %s", uriName);
((TurbineURIBroker) uri).setTarget(target);
return this;
}
public Parameters withParameter(String name, String... values) {
uri.setQueryData(name, values);
return this;
}
public URIBroker uri() {
return uri;
}
public void end() {
commitRedirectLocation();
}
@Override
public String toString() {
return "redirectTo(" + uri + ")";
}
}
/** 代表module的调用栈。 */
private class ModuleTraces extends LinkedList<ModuleTrace> {
private static final long serialVersionUID = 8167955929944105578L;
}
/** 代表一个module调用的信息。 */
private class ModuleTrace {
private final Context context;
private String template;
public ModuleTrace(Context context, String template) {
this.context = assertNotNull(context, "context");
this.template = template;
}
public Context getContext() {
return context;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
}
private static class TargetTuple {
private String target;
private String action;
private String actionEvent;
public String getTarget() {
return target;
}
public TargetTuple setTarget(String target) {
this.target = trimToNull(target);
return this;
}
public String getAction() {
return action;
}
public TargetTuple setAction(String action) {
this.action = trimToNull(action);
return this;
}
public String getActionEvent() {
return actionEvent;
}
public TargetTuple setActionEvent(String actionEvent) {
this.actionEvent = trimToNull(actionEvent);
return this;
}
@Override
public String toString() {
return String.format("target=%s, action=%s, actionEvent=%s", target, action, actionEvent);
}
}
}