/*
* Copyright 2007-2009 the original author or authors.
*
* 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.paoding.rose.web.impl.thread;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.paoding.rose.web.Dispatcher;
import net.paoding.rose.web.Invocation;
import net.paoding.rose.web.InvocationUtils;
import net.paoding.rose.web.RequestPath;
import net.paoding.rose.web.annotation.ReqMethod;
import net.paoding.rose.web.impl.mapping.EngineGroup;
import net.paoding.rose.web.impl.mapping.MappingNode;
import net.paoding.rose.web.impl.mapping.MatchResult;
import net.paoding.rose.web.impl.module.Module;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
*
* @author 王志亮 [qieqie.wang@gmail.com]
*
*/
public class Rose implements EngineChain {
protected static final Log logger = LogFactory.getLog(Rose.class);
private final List<Module> modules;
private final MappingNode mappingTree;
private final RequestPath path;
private final HttpServletRequest originalHttpRequest;
private final HttpServletResponse originalHttpResponse;
private boolean started;
private List<LinkedEngine> engines = new ArrayList<LinkedEngine>(6);
private List<MatchResult> matchResults;
private InvocationBean inv;
private int curIndexOfChain;
private final LinkedList<AfterCompletion> afterCompletions = new LinkedList<AfterCompletion>();
public Rose(List<Module> modules, MappingNode mappingTree, HttpServletRequest httpRequest,
HttpServletResponse httpResponse, RequestPath requestPath) {
this.mappingTree = mappingTree;
this.modules = modules;
this.originalHttpRequest = httpRequest;
this.originalHttpResponse = httpResponse;
this.path = requestPath;
}
public MappingNode getMappingTree() {
return mappingTree;
}
public InvocationBean getInvocation() {
return inv;
}
public List<Module> getModules() {
return modules;
}
public List<LinkedEngine> getEngines() {
return engines;
}
/**
* 启动rose逻辑,对请求进行匹配判断,如果匹配未能成功返回false; <br>
* 对匹配成功的启动相关的架构处理逻辑直至整个请求的完成
*
* @return
* @throws Throwable
*/
public boolean start() throws Throwable {
if (this.started) {
throw new IllegalStateException("don't start again");
}
this.started = true;
return innerStart();
}
public List<MatchResult> getMatchResults() {
return matchResults;
}
/**
* @throws IndexOutOfBoundsException
*/
@Override
public Object doNext() throws Throwable {
return engines.get(--curIndexOfChain).execute(this);
}
private boolean innerStart() throws Throwable {
final boolean debugEnabled = logger.isDebugEnabled();
final List<MatchResult> matchResults = mappingTree.match(this.path);
if (matchResults == null) {
// not rose uri
if (debugEnabled) {
logger.debug("not rose uri: '" + this.path.getUri() + "'");
}
return false;
}
final MatchResult lastMatched = matchResults.get(matchResults.size() - 1);
final EngineGroup leafEngineGroup = lastMatched.getMappingNode().getLeafEngines();
if (leafEngineGroup.size() == 0) {
// not rose uri
if (debugEnabled) {
logger.debug("not rose uri, not exits leaf engines for it: '" + this.path.getUri()
+ "'");
}
return false;
}
final LinkedEngine leafEngine = select(leafEngineGroup.getEngines(path.getMethod()));
if (leafEngine == null) {
// 405 Method Not Allowed
/*
* The method specified in the Request-Line is not allowed for the
* resource identified by the Request-URI. The response MUST include an
* Allow header containing a list of valid methods for the requested
* resource.
*/
StringBuilder allow = new StringBuilder();
final String gap = ", ";
for (ReqMethod method : leafEngineGroup.getAllowedMethods()) {
allow.append(method.toString()).append(gap);
}
if (allow.length() > 0) {
allow.setLength(allow.length() - gap.length());
}
originalHttpResponse.addHeader("Allow", allow.toString());
originalHttpResponse.sendError(405, this.path.getUri());
// true: don't forward to next filter or servlet
return true;
}
if (debugEnabled) {
ActionEngine actionEngine = (ActionEngine) leafEngine.getTarget();
logger.debug("mapped '" + path.getUri() + "' to "
+ actionEngine.getControllerClass().getName() + "#"
+ actionEngine.getMethod().getName());
}
// bind engines
LinkedEngine tempEngine = leafEngine;
MappingNode moduleNode = null;
MappingNode controllerNode = null;
while (tempEngine != null) {
engines.add(tempEngine);
Class<? extends Engine> target = tempEngine.getTarget().getClass();
if (target == ModuleEngine.class) {
moduleNode = tempEngine.getNode();
} else if (target == ControllerEngine.class) {
controllerNode = tempEngine.getNode();
}
tempEngine = tempEngine.getParent();
}
// set module/controller/action path to RequsetPath object
StringBuilder sb = new StringBuilder(path.getUri().length());
MatchResult moduleMatchResult = null;
MatchResult controllerMatchResult = null;
for (int i = 0; i < matchResults.size(); i++) {
MatchResult matchResult = matchResults.get(i);
sb.append(matchResult.getValue());
if (matchResult.getMappingNode() == moduleNode) {
moduleMatchResult = matchResult;
path.setModulePath(sb.toString()); // modulePath
Assert.notNull(engines.get(2));
sb.setLength(0);
}
if (matchResult.getMappingNode() == controllerNode) {
controllerMatchResult = matchResult;
path.setControllerPath(sb.toString()); // controllerPath
Assert.notNull(engines.get(1));
sb.setLength(0);
}
}
path.setActionPath(sb.toString()); // actionPath
Assert.notNull(moduleMatchResult);
Assert.notNull(controllerMatchResult);
this.matchResults = matchResults;
this.curIndexOfChain = engines.size();
Map<String, String> uriParameters = null;
for (int i = matchResults.size() - 1; i >= 0; i--) {
MatchResult matchResult = matchResults.get(i);
String name = matchResult.getParameterName();
if (name != null) {
if (uriParameters == null) {
uriParameters = new HashMap<String, String>(matchResults.size() << 1);
}
uriParameters.put(name, matchResult.getValue());
}
}
HttpServletRequest httpRequest = originalHttpRequest;
if (uriParameters != null && uriParameters.size() > 0) {
httpRequest = new ParameteredUriRequest(originalHttpRequest, uriParameters);
}
// originalThreadRequest可能为null,特别是在portal框架下
HttpServletRequest originalThreadRequest = InvocationUtils.getCurrentThreadRequest();
//
Invocation preInvocation = null;
if (path.getDispatcher() != Dispatcher.REQUEST) {
preInvocation = InvocationUtils.getInvocation(originalHttpRequest);
}
// invocation 对象 代表一次Rose调用
InvocationBean inv = new InvocationBean(httpRequest, originalHttpResponse, path);
inv.setRose(this);
inv.setPreInvocation(preInvocation);
//
InvocationUtils.bindRequestToCurrentThread(httpRequest);
InvocationUtils.bindInvocationToRequest(inv, httpRequest);
// invoke the engine chain
this.inv = inv;
Throwable error = null;
try {
Object instuction = ((EngineChain) this).doNext();
if (":continue".equals(instuction)) {
return false;
}
} catch (Throwable local) {
error = local;
throw local;
} finally {
for (AfterCompletion task : afterCompletions) {
try {
task.afterCompletion(inv, error);
} catch (Throwable e) {
logger.error("", e);
}
}
if (originalThreadRequest != null) {
InvocationUtils.bindRequestToCurrentThread(originalThreadRequest);
} else {
InvocationUtils.unindRequestFromCurrentThread();
}
// 更新绑定的invocation,只对于那些forward后request.setAttibute影响了前者的有效。(include的不用处理了,已经做了snapshot了)
if (preInvocation != null) {
InvocationUtils.bindInvocationToRequest(preInvocation, httpRequest);
}
}
return true;
}
private LinkedEngine select(LinkedEngine[] engines) {
LinkedEngine selectedEngine = null;
int score = 0;
for (LinkedEngine engine : engines) {
int candidate = engine.isAccepted(this.originalHttpRequest);
if (logger.isDebugEnabled()) {
logger.debug("Score of " + engine.getClass().getName() + ":" + candidate);
}
if (candidate > score) {
selectedEngine = engine;
score = candidate;
}
}
if (logger.isDebugEnabled()) {
if (selectedEngine == null) {
logger.debug("No engine selected.");
} else {
String msg;
if (selectedEngine.getTarget() instanceof ActionEngine) {
ActionEngine actionEngine = (ActionEngine) selectedEngine.getTarget();
msg = actionEngine.getController().getClass().getName() + "#"
+ actionEngine.getMethod().getName();
} else {
msg = selectedEngine.toString();
}
logger.debug("Engine selected:" + msg);
}
}
return selectedEngine;
}
@Override
public void addAfterCompletion(AfterCompletion task) {
afterCompletions.addFirst(task);
}
}