Java tutorial
/* * 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 com.laxser.blitz.web.impl.thread; import static org.springframework.validation.BindingResult.MODEL_KEY_PREFIX; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.SpringVersion; import org.springframework.util.Assert; import org.springframework.validation.Errors; import com.laxser.blitz.BlitzVersion; import com.laxser.blitz.util.BlitzStringUtil; import com.laxser.blitz.web.ControllerInterceptor; import com.laxser.blitz.web.InterceptorDelegate; import com.laxser.blitz.web.Invocation; import com.laxser.blitz.web.InvocationChain; import com.laxser.blitz.web.ParamValidator; import com.laxser.blitz.web.RequestPath; import com.laxser.blitz.web.annotation.HttpFeatures; import com.laxser.blitz.web.annotation.IfParamExists; import com.laxser.blitz.web.annotation.Intercepted; import com.laxser.blitz.web.annotation.Return; import com.laxser.blitz.web.impl.module.Module; import com.laxser.blitz.web.impl.validation.ParameterBindingResult; import com.laxser.blitz.web.paramresolver.MethodParameterResolver; import com.laxser.blitz.web.paramresolver.ParamMetaData; import com.laxser.blitz.web.paramresolver.ParamResolver; import com.laxser.blitz.web.paramresolver.ParameterNameDiscovererImpl; import com.laxser.blitz.web.paramresolver.ResolverFactoryImpl; /** *@author laxser Date 2012-3-23 ?4:42:43 @contact [duqifan@gmail.com] @ActionEngine.java */ public final class ActionEngine implements Engine { private final static Log logger = LogFactory.getLog(ActionEngine.class); private final Module module; private final Class<?> controllerClass; private final Object controller; private final Method method; private final HttpFeatures httpFeatures; private final InterceptorDelegate[] interceptors; private final ParamValidator[] validators; private final ParamExistenceChecker[] paramExistenceChecker; private final MethodParameterResolver methodParameterResolver; private transient String toStringCache; public ActionEngine(Module module, Class<?> controllerClass, Object controller, Method method) { this.module = module; this.controllerClass = controllerClass; this.controller = controller; this.method = method; this.interceptors = compileInterceptors(); this.methodParameterResolver = compileParamResolvers(); this.validators = compileValidators(); this.paramExistenceChecker = compileParamExistenceChecker(); HttpFeatures httpFeatures = method.getAnnotation(HttpFeatures.class); if (httpFeatures == null) { httpFeatures = this.controllerClass.getAnnotation(HttpFeatures.class); } this.httpFeatures = httpFeatures; } public InterceptorDelegate[] getRegisteredInterceptors() { return interceptors; } public Class<?> getControllerClass() { return controllerClass; } public Object getController() { return controller; } public Method getMethod() { return method; } public String[] getParameterNames() { return methodParameterResolver.getParameterNames(); } private MethodParameterResolver compileParamResolvers() { ParameterNameDiscovererImpl parameterNameDiscoverer = new ParameterNameDiscovererImpl(); ResolverFactoryImpl resolverFactory = new ResolverFactoryImpl(); for (ParamResolver resolver : module.getCustomerResolvers()) { resolverFactory.addCustomerResolver(resolver); } return new MethodParameterResolver(this.controllerClass, method, parameterNameDiscoverer, resolverFactory); } private ParamValidator[] compileValidators() { @SuppressWarnings("rawtypes") Class[] parameterTypes = method.getParameterTypes(); List<ParamValidator> validators = module.getValidators(); ParamValidator[] registeredValidators = new ParamValidator[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { for (ParamValidator validator : validators) { if (validator.supports(methodParameterResolver.getParamMetaDatas()[i])) { registeredValidators[i] = validator; break; } } } // return registeredValidators; } private InterceptorDelegate[] compileInterceptors() { List<InterceptorDelegate> interceptors = module.getInterceptors(); List<InterceptorDelegate> registeredInterceptors = new ArrayList<InterceptorDelegate>(interceptors.size()); for (InterceptorDelegate interceptor : interceptors) { ControllerInterceptor most = InterceptorDelegate.getMostInnerInterceptor(interceptor); if (!most.getClass().getName().startsWith("net.paoding.blitz.web")) { // ?@Intercepted (@Intercepted??????) Intercepted intercepted = method.getAnnotation(Intercepted.class); if (intercepted == null) { // @Inheritedannotationclass.getAnnotation???? intercepted = this.controllerClass.getAnnotation(Intercepted.class); } // @Interceptedallowdeny if (intercepted != null) { // 3.1 deny? if (BlitzStringUtil.matches(intercepted.deny(), interceptor.getName())) { if (logger.isDebugEnabled()) { logger.debug("action '" + controllerClass.getName() + "#" + method.getName() + "': remove interceptor by @Intercepted.deny: " + most.getClass().getName()); } continue; } // 3.2 allow? if (!BlitzStringUtil.matches(intercepted.allow(), interceptor.getName())) { if (logger.isDebugEnabled()) { logger.debug("action '" + controllerClass.getName() + "#" + method.getName() + "': remove interceptor by @Intercepted.allow: " + most.getClass().getName()); } continue; } } } // ???? if (interceptor.isForAction(controllerClass, method)) { registeredInterceptors.add(interceptor); } else { if (logger.isDebugEnabled()) { logger.debug("action '" + controllerClass.getName() + "#" + method.getName() + "': remove interceptor by interceptor.isForAction: " + most.getClass().getName()); } } } // if (logger.isDebugEnabled()) { logger.debug("interceptors of " + controllerClass.getName() + "#" + method.getName() + "=(" + registeredInterceptors.size() + "/" + interceptors.size() + ")" + registeredInterceptors); } return registeredInterceptors.toArray(new InterceptorDelegate[registeredInterceptors.size()]); } /** * ?{@link IfParamExists} * * @author Li Weibo (weibo.leo@gmail.com) */ private static interface ParamExistenceChecker { public int check(Map<String, String[]> params); } /** * ???{@link ParamExistenceChecker} * * @return */ private ParamExistenceChecker[] compileParamExistenceChecker() { IfParamExists ifParamExists = method.getAnnotation(IfParamExists.class); //IfParamExistsIfParamExists("")? if (ifParamExists == null || ifParamExists.value().trim().length() == 0) { return new ParamExistenceChecker[] {}; } List<ParamExistenceChecker> checkers = new ArrayList<ParamExistenceChecker>(); //? String value = ifParamExists.value(); //???: type&subtype=value&anothername=value2 String[] terms = StringUtils.split(value, "&"); Assert.isTrue(terms.length >= 1); //? //'&'??term? for (final String term : terms) { final int index = term.indexOf('='); //'=' if (index == -1) { //=????term??? checkers.add(new ParamExistenceChecker() { final String paramName = term.trim(); @Override public int check(Map<String, String[]> params) { String[] paramValues = params.get(paramName); if (logger.isDebugEnabled()) { logger.debug(this.toString() + " is checking param:" + paramName + "=" + Arrays.toString(paramValues)); } //????ok if (paramValues != null && paramValues.length > 0) { return 10; } else { return -1; } } }); } else { //term'=' final String paramName = term.substring(0, index).trim(); //??? final String expected = term.substring(index + 1).trim(); //? if (expected.startsWith(":")) { //expected? Pattern tmpPattern = null; try { tmpPattern = Pattern.compile(expected.substring(1)); } catch (PatternSyntaxException e) { logger.error("@IfParamExists pattern error, " + controllerClass.getName() + "#" + method.getName(), e); } final Pattern pattern = tmpPattern; //?final checkers.add(new ParamExistenceChecker() { @Override public int check(Map<String, String[]> params) { String[] paramValues = params.get(paramName); if (logger.isDebugEnabled()) { logger.debug(this.toString() + " is checking param:" + paramName + "=" + Arrays.toString(paramValues) + ", pattern=" + pattern.pattern()); } if (paramValues == null) { //??? return -1; } for (String paramValue : paramValues) { if (pattern != null && pattern.matcher(paramValue).matches()) { return 12; } } return -1; } }); } else { //expected?"" checkers.add(new ParamExistenceChecker() { @Override public int check(Map<String, String[]> params) { String[] paramValues = params.get(paramName); if (logger.isDebugEnabled()) { logger.debug(this.toString() + " is checking param:" + paramName + "=" + Arrays.toString(paramValues) + ", expected=" + expected); } if (paramValues == null) { //??? return -1; } for (String paramValue : paramValues) { if (expected.equals(paramValue)) { return 13;// 13?12 } } return -1; } }); } } } return checkers.toArray(new ParamExistenceChecker[] {}); } @Override public int isAccepted(HttpServletRequest request) { if (paramExistenceChecker.length == 0) { //??1 return 1; } int total = 0; Map<String, String[]> params = resolveQueryString(request.getQueryString()); for (ParamExistenceChecker checker : paramExistenceChecker) { int c = checker.check(params); if (c == -1) { //-1?? if (logger.isDebugEnabled()) { logger.debug("Accepted check not passed by " + checker.toString()); } return -1; } //FIXME ?????? //????? total += c; } return total; } private Map<String, String[]> resolveQueryString(String queryString) { Map<String, String[]> params; if (queryString == null || queryString.length() == 0) { params = Collections.emptyMap(); } else { params = new HashMap<String, String[]>(); String[] kvs = queryString.split("&"); for (String kv : kvs) { String[] pair = kv.split("="); if (pair.length == 2) { mapPut(params, pair[0], pair[1]); } else if (pair.length == 1) { mapPut(params, pair[0], ""); } else { logger.error("Illegal queryString:" + queryString); } } } return params; } private void mapPut(Map<String, String[]> map, String key, String value) { String[] values = map.get(key); if (values == null) { values = new String[] { value }; } else { values = Arrays.copyOf(values, values.length + 1); values[values.length - 1] = value; } map.put(key, values); } @Override public Object execute(Blitz blitz) throws Throwable { try { return innerExecute(blitz); } catch (Throwable local) { throw createException(blitz, local); } } protected Object innerExecute(Blitz blitz) throws Throwable { Invocation inv = blitz.getInvocation(); // creates parameter binding result (not bean, just simple type, like int, Integer, int[] ... ParameterBindingResult paramBindingResult = new ParameterBindingResult(inv); logger.info("inv ?"); logger.info(MODEL_KEY_PREFIX); String paramBindingResultName = MODEL_KEY_PREFIX + paramBindingResult.getObjectName(); inv.addModel(paramBindingResultName, paramBindingResult); // resolves method parameters, adds the method parameters to model Object[] methodParameters = methodParameterResolver.resolve(inv, paramBindingResult); ((InvocationBean) inv).setMethodParameters(methodParameters); String[] parameterNames = methodParameterResolver.getParameterNames(); Object instruction = null; ParamMetaData[] metaDatas = methodParameterResolver.getParamMetaDatas(); // validators for (int i = 0; i < this.validators.length; i++) { if (validators[i] != null && !(methodParameters[i] instanceof Errors)) { Errors errors = inv.getBindingResult(parameterNames[i]); instruction = validators[i].validate(// metaDatas[i], inv, methodParameters[i], errors); if (logger.isDebugEnabled()) { logger.debug("do validate [" + validators[i].getClass().getName() + "] and return '" + instruction + "'"); } // instruction?null?boolean==>??? if (instruction != null) { if (instruction instanceof Boolean) { continue; } if (instruction instanceof String && ((String) instruction).length() == 0) { continue; } return instruction; } } } // for (int i = 0; i < parameterNames.length; i++) { if (parameterNames[i] != null && methodParameters[i] != null && inv.getModel().get(parameterNames[i]) != methodParameters[i]) { inv.addModel(parameterNames[i], methodParameters[i]); } } // intetceptors & controller return new InvocationChainImpl(blitz).doNext(); } private class InvocationChainImpl implements InvocationChain { private final boolean debugEnabled = logger.isDebugEnabled(); private int index = -1; private final Blitz blitz; private Object instruction; public InvocationChainImpl(Blitz blitz) { this.blitz = blitz; } @Override public Object doNext() throws Exception { if (++index < interceptors.length) { // ++index -10 InterceptorDelegate interceptor = interceptors[index]; // blitz.addAfterCompletion(interceptor); Object instruction = interceptor.roundInvocation(blitz.getInvocation(), this); // if (debugEnabled) { logger.debug( "interceptor[" + interceptor.getName() + "] do round and return '" + instruction + "'"); } // null???instruction // ?!! if (instruction != null) { this.instruction = instruction; } return this.instruction; } else if (index == interceptors.length) { // applies http features before the resolvers if (httpFeatures != null) { applyHttpFeatures(blitz.getInvocation()); } this.instruction = method.invoke(controller, blitz.getInvocation().getMethodParameters()); // @Return if (this.instruction == null) { Return returnAnnotation = method.getAnnotation(Return.class); if (returnAnnotation != null) { this.instruction = returnAnnotation.value(); } } return this.instruction; } throw new IndexOutOfBoundsException("don't call twice 'chain.doNext()' in one intercpetor; index=" + index + "; interceptors.length=" + interceptors.length); } } private Exception createException(Blitz blitz, Throwable exception) { final RequestPath requestPath = blitz.getInvocation().getRequestPath(); StringBuilder sb = new StringBuilder(1024); sb.append("[Blitz-").append(BlitzVersion.getVersion()).append("@Spring-") .append(SpringVersion.getVersion()); sb.append("]Error happended: ").append(requestPath.getMethod()); sb.append(" ").append(requestPath.getUri()); sb.append("->"); sb.append(this).append(" params="); sb.append(Arrays.toString(blitz.getInvocation().getMethodParameters())); InvocationTargetException servletException = new InvocationTargetException(exception, sb.toString()); return servletException; } private void applyHttpFeatures(final Invocation inv) throws UnsupportedEncodingException { HttpServletResponse response = inv.getResponse(); if (StringUtils.isNotBlank(httpFeatures.charset())) { response.setCharacterEncoding(httpFeatures.charset()); if (logger.isDebugEnabled()) { logger.debug("set response.characterEncoding by HttpFeatures:" + httpFeatures.charset()); } } if (StringUtils.isNotBlank(httpFeatures.contentType())) { String contentType = httpFeatures.contentType(); if (contentType.equals("json")) { contentType = "application/json"; } else if (contentType.equals("xml")) { contentType = "text/xml"; } else if (contentType.equals("html")) { contentType = "text/html"; } else if (contentType.equals("plain") || contentType.equals("text")) { contentType = "text/plain"; } response.setContentType(contentType); if (logger.isDebugEnabled()) { logger.debug("set response.contentType by HttpFeatures:" + response.getContentType()); } } } @Override public String toString() { if (toStringCache == null) { Class<?>[] parameterTypes = method.getParameterTypes(); String appPackageName = this.controllerClass.getPackage().getName(); if (appPackageName.indexOf('.') != -1) { appPackageName = appPackageName.substring(0, appPackageName.lastIndexOf('.')); } String methodParamNames = ""; for (int i = 0; i < parameterTypes.length; i++) { if (methodParamNames.length() == 0) { methodParamNames = showSimpleName(parameterTypes[i], appPackageName); } else { methodParamNames = methodParamNames + ", " + showSimpleName(parameterTypes[i], appPackageName); } } toStringCache = ""// + showSimpleName(method.getReturnType(), appPackageName) + " " + method.getName() // + "(" + methodParamNames + ")"; } return toStringCache; } private String showSimpleName(Class<?> parameterType, String appPackageName) { if (parameterType.getName().startsWith("com.laxser") || parameterType.getName().startsWith("java.lang") || parameterType.getName().startsWith("java.util") || parameterType.getName().startsWith(appPackageName)) { return parameterType.getSimpleName(); } return parameterType.getName(); } public void destroy() { } }