/*
* 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.instruction;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import net.paoding.rose.util.SpringUtils;
import net.paoding.rose.web.Invocation;
import net.paoding.rose.web.impl.thread.InvocationBean;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
/**
* @author 王志亮 [qieqie.wang@gmail.com]
*/
public class InstructionExecutorImpl implements InstructionExecutor {
private Log logger = LogFactory.getLog(getClass());
@Override
public Object render(Invocation inv, Object instruction) throws IOException, ServletException,
Exception {
instruction = translatesToInstructionObject((InvocationBean) inv, instruction);
if (instruction != null && !Thread.currentThread().isInterrupted()) {
((Instruction) instruction).render(inv);
}
return instruction;
}
/**
* @param inv
* @param instruction
* @return
* @throws StackOverflowError
*/
private Instruction translatesToInstructionObject(InvocationBean inv, Object instruction)
throws StackOverflowError {
int count = 0;
while (!(instruction instanceof Instruction)) {
if (count++ > 50) {
throw new StackOverflowError("Unable to parse the instruction to an"
+ " Instruction object less than " + count + " times. Is the instruction"
+ " that returned by your controller" + " action is right?");
}
if (Thread.interrupted() || instruction == null) {
return null;
} else {
if (instruction.getClass() != String.class
&& !ClassUtils.isPrimitiveOrWrapper(instruction.getClass())
&& instruction.getClass().getComponentType() == null
&& instruction.getClass().getAnnotation(Component.class) != null) {
SpringUtils.autowire(instruction, inv.getApplicationContext());
}
instruction = parseInstruction(inv, instruction);
}
}
return (Instruction) instruction;
}
protected Object parseInstruction(Invocation inv, Object ins) {
if (logger.isDebugEnabled()) {
logger.debug("parset instruction:" + ins.getClass().getName() + ": '" + ins + "'");
}
if (ClassUtils.isPrimitiveOrWrapper(ins.getClass())) {
return Text.text(ins);
} else if (ins instanceof CharSequence) {
String str = ins.toString();
if (str.length() == 0 || str.equals("@")) {
return null;
}
if (str.charAt(0) == '@') {
return Text.text(str.substring(1)); // 具体content-type等信息由@HttpFeatures决定
}
if (str.charAt(0) == '/') {
return new ViewInstruction(str);
}
if (str.startsWith("r:") || str.startsWith("redirect:")) {
StringInstruction si = new StringInstruction(false, str
.substring(str.indexOf(':') + 1));
if (si.innerInstruction.startsWith("http://")
|| si.innerInstruction.startsWith("https://")) {
return Redirect.location(si.innerInstruction);
}
return si;
}
if (str.startsWith("pr:") || str.startsWith("perm-redirect:")) {
StringInstruction si = new StringInstruction(false, str
.substring(str.indexOf(':') + 1));
si.permanentlyWhenRedirect = true;
if (si.innerInstruction.startsWith("http://")
|| si.innerInstruction.startsWith("https://")) {
return si.permanentlyIfNecessary(Redirect.location(si.innerInstruction));
}
return si;
}
if (str.startsWith("f:") || str.startsWith("forward:")) {
return new StringInstruction(true, str.substring(str.indexOf(':') + 1));
}
if (str.startsWith("e:") || str.startsWith("error:")) {
int begin = str.indexOf(':') + 1;
int codeEnd = str.indexOf(';');
if (codeEnd == -1) {
String text = str.substring(begin);
if (text.length() > 0 && NumberUtils.isNumber(text)) {
return HttpError.code(Integer.parseInt(text));
}
return HttpError.code(500, text);
} else {
return HttpError.code(Integer.parseInt(str.substring(begin, codeEnd)), str
.substring(codeEnd + 1).trim());
}
}
if (str.startsWith("s:") || str.startsWith("status:")) {
int begin = str.indexOf(':') + 1;
int codeEnd = str.indexOf(';', begin);
if (codeEnd == -1) {
inv.getResponse().setStatus(Integer.parseInt(str.substring(begin)));
return null;
} else {
// setStatus(int, String)是不推荐的,所以';'后的我们不认为是msg
// 这和sendError不一样
inv.getResponse().setStatus(Integer.parseInt(str.substring(begin, codeEnd)));
for (int i = codeEnd; i < str.length(); i++) {
if (str.charAt(i) != ' ') {
str = str.substring(i + 1);
break;
}
}
}
}
if (str.equals(":continue")) {
return null;
}
return new StringInstruction(null, str);
} else if (ins.getClass() == StringInstruction.class) {
StringInstruction fr = (StringInstruction) ins;
String str = fr.innerInstruction;
int queryIndex = str.indexOf('?');
for (int i = (queryIndex == -1) ? str.length() - 1 : queryIndex - 1; i >= 0; i--) {
if (str.charAt(i) != ':') {
continue;
}
if (i > 0 && str.charAt(i - 1) == '\\') { // 转义符号
str = str.substring(0, i - 1) + str.substring(i);
i--;
continue;
}
int cmdEnd = i;
int cmdBeforeBegin = i - 1;
while (cmdBeforeBegin >= 0 && str.charAt(cmdBeforeBegin) != ':') {
cmdBeforeBegin--;
}
String prefix = str.subSequence(cmdBeforeBegin + 1, cmdEnd).toString();
String body = str.subSequence(i + 1, str.length()).toString();
if ("a".equals(prefix) || "action".equals(prefix)) {
if (fr.isReirect()) {
return fr.permanentlyIfNecessary(Redirect.action(body));
} else {
return Forward.action(body);
}
}
if ("c".equals(prefix) || "controller".equals(prefix)) {
if (fr.isReirect()) {
return fr.permanentlyIfNecessary(Redirect.controller(body));
} else {
return Forward.controller(body);
}
}
if ("m".equals(prefix) || "module".equals(prefix)) {
if (fr.isReirect()) {
return fr.permanentlyIfNecessary(Redirect.module(body));
} else {
return Forward.module(body);
}
}
logger.warn("skip the prefix '" + prefix + ":' of " + str);
if (fr.isReirect()) {
return fr.permanentlyIfNecessary(Redirect.location(str));
} else if (fr.isForward()) {
return Forward.path(str);
} else {
return new ViewInstruction(str);
}
}
if (fr.isReirect()) {
return fr.permanentlyIfNecessary(Redirect.location(str));
} else if (fr.isForward()) {
return Forward.path(str);
}
return new ViewInstruction(str);
} else if (ins instanceof InputStream) {
return new InputStreamInstruction((InputStream) ins);
} else if (ins instanceof byte[]) {
return new InputStreamInstruction(new ByteArrayInputStream((byte[]) ins));
} else {
return Text.text(ins.toString());
}
}
private class StringInstruction {
Boolean forward;
String innerInstruction;
boolean permanentlyWhenRedirect = false;
public StringInstruction(Boolean forward, String innerInstruction) {
super();
this.forward = forward;
this.innerInstruction = innerInstruction;
}
public RedirectInstruction permanentlyIfNecessary(RedirectInstruction redirectInstruction) {
if (permanentlyWhenRedirect) {
redirectInstruction.permanently();
}
return redirectInstruction;
}
public boolean isReirect() {
return forward != null && !forward.booleanValue();
}
public boolean isForward() {
return forward != null && forward.booleanValue();
}
@Override
public String toString() {
return forward == null ? "" : (forward ? "f:" : "r:") + innerInstruction;
}
}
}