package com.ether; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONNull; import net.sf.json.JSONObject; import net.sf.json.util.JSONUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Required; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; import com.ether.annotation.ControllerMethod; import com.ether.annotation.Mandatory; import com.ether.annotation.ParamName; import com.ether.annotation.UseJSON; public class ControllerEther extends AbstractController { @Override @Nullable protected ModelAndView handleRequestInternal(@NotNull final HttpServletRequest request, @NotNull final HttpServletResponse response) throws WebException, EtherException { try { if (!_initialized) initialize(); request.setCharacterEncoding(UTF8Charset.getEncoding()); final String methodName = _methodNameResolver.getHandlerMethodName(request); final MethodDescription methodDescription = _methods.get(methodName); if (null == methodDescription) { WebHelper.displayError( response, methodName ); return null; } return invokeMethod(methodDescription, request, response); } catch (UnsupportedEncodingException e) { throw new WebException(WebException.WebCode.ERROR_UNSUPPORTED_UTF8_ENCODING, e); } catch (NoSuchRequestHandlingMethodException e) { throw new WebException(WebException.WebCode.ERROR_NO_REQUEST_HANDLING_METHOD, e); } } @Nullable private ModelAndView invokeMethod( @NotNull final MethodDescription methodDescription, @NotNull final HttpServletRequest request, @NotNull final HttpServletResponse response) throws WebException { try { // Parse parameters final Object[] params = buildParams(methodDescription, request); // Invoke method (go to controller) final Object result = methodDescription.getMethod().invoke(this, params); if (!methodDescription.getView().isEmpty()) { response.setStatus(HttpServletResponse.SC_OK); if (result instanceof Map) return new ModelAndView(methodDescription.getView(), (Map) result); else return new ModelAndView(methodDescription.getView(), "result", result); } else if (methodDescription.isJsonResult()) { response.setStatus(HttpServletResponse.SC_OK); final String jsonResult = convertToJson(result); WebHelper.writeJsonToResponse(response, jsonResult); return null; } else { response.setStatus(HttpServletResponse.SC_NO_CONTENT); return null; } } catch (Throwable e) { throw new WebException(e); } } @NotNull private String convertToJson( @Nullable final Object object ) { if( null == object) return JSONNull.getInstance().toString(); if( object instanceof JSON ) return object.toString(); if( object instanceof Collection ) return _jsonHelper.toJSON( (Collection) object ).toString(); if( object.getClass().isArray() ) return _jsonHelper.toJSON( object ).toString(); if( object instanceof Number || object instanceof Boolean || object instanceof String ) return JSONUtils.valueToString( object ); return _jsonHelper.toJSON( object ).toString(); } @NotNull private Object[] buildParams(@NotNull final MethodDescription methodDescription, @NotNull final HttpServletRequest request) throws InvalidParameterException { final List params = new ArrayList(); for (final ParamDescription paramDescription : methodDescription.getParams()) { if (null == paramDescription) { params.add(null); continue; } final boolean canBeNull = !paramDescription.isPrimitive() && !paramDescription.isMandatory(); final String paramValue = WebHelper.getRequestParameter(request, paramDescription.getName(), null, canBeNull); final boolean useJSON = paramDescription.isUsingJSON(); params.add(convertParameter(paramDescription.getType(), paramValue, useJSON)); } return params.toArray(new Object[params.size()]); } @Nullable private Object convertParameter( @NotNull final Class paramType, @Nullable final String paramValue, final boolean useJSON ) { if( null == paramValue ) return null; if( useJSON ) return _jsonHelper.fromJSON( _jsonHelper.toJSON( paramValue ), paramType ); if( JSONObject.class.equals( paramType ) ) return _jsonHelper.toJSON( paramValue ); if( JSONArray.class.equals( paramType ) ) return _jsonHelper.toJSON( paramValue ); if( String.class .equals( paramType ) ) return paramValue; if( Boolean.TYPE.equals( paramType ) || Boolean.class.equals( paramType ) ) return Boolean.valueOf( paramValue ); if( Integer.TYPE.equals( paramType ) || Integer.class.equals( paramType ) ) return Integer.valueOf( paramValue ); if( Long.TYPE.equals( paramType ) || Long.class.equals( paramType ) ) return Long.valueOf( paramValue ); if( Float.TYPE.equals( paramType ) || Float.class.equals( paramType ) ) return Float.valueOf( paramValue ); if( Double.TYPE.equals( paramType ) || Double.class.equals( paramType ) ) return Double.valueOf( paramValue ); if( Date.class.isAssignableFrom( paramType ) ) return new Date( Long.valueOf( paramValue ) ); if( paramType.isEnum() ) return Enum.valueOf( paramType, paramValue ); if( Map.class.isAssignableFrom( paramType ) ) return JSONObject.fromObject( paramValue ); throw new UnsupportedOperationException( "Unsupported: cast parameter to " + paramType.getName() ); } private void initialize() throws WebException { for( final Method method : getClass().getMethods() ) { final ControllerMethod annotation = method.getAnnotation( ControllerMethod.class ); if( null != annotation ) { final MethodDescription methodDescription = new MethodDescription( method, annotation ); fillMethodDescription( method, methodDescription ); _methods.put( method.getName(), methodDescription ); } } _initialized = true; } private void fillMethodDescription( @NotNull final Method method, @NotNull final MethodDescription methodDescription ) throws WebException { final Class[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); if(paramTypes.length != paramAnnotations.length) throw new WebException(WebException.WebCode.ERROR_NUMBER_OF_PARAM_TYPES_NOT_EQUAL_TO_PARAM_ANNOTATIONS, paramTypes.length+" "+paramAnnotations.length); for( int i = 0; i < paramTypes.length; ++i ) { ParamName paramNameAnnotation = null; Mandatory mandatoryAnnotation = null; UseJSON useJSONAnnotation = null; for( final Annotation paramAnnotation : paramAnnotations[i] ) { if( paramAnnotation instanceof ParamName ) paramNameAnnotation = (ParamName) paramAnnotation; if( paramAnnotation instanceof Mandatory ) mandatoryAnnotation = (Mandatory) paramAnnotation; if( paramAnnotation instanceof UseJSON ) useJSONAnnotation = (UseJSON) paramAnnotation; } if( null == paramNameAnnotation ) methodDescription.addNullParam(); else { final boolean mandatory = ( null != mandatoryAnnotation ); final boolean useJSON = ( null != useJSONAnnotation ); methodDescription.addParam( paramNameAnnotation.value(), paramTypes[i], mandatory, useJSON ); } } } @Required public void setMethodNameResolver( final MethodNameResolver methodNameResolver ) { _methodNameResolver = methodNameResolver; } protected JSONHelper getJsonHelper() { return _jsonHelper; } @Required public void setJsonHelper( final JSONHelper jsonHelper ) { _jsonHelper = jsonHelper; } protected Map getMethods() { return _methods; } private static final Log LOGGER = LogFactory.getLog( ControllerEther.class ); private JSONHelper _jsonHelper; private MethodNameResolver _methodNameResolver; private Map _methods = new HashMap(); private boolean _initialized; /*=false*/ }