package com.khs.sherpa.servlet.request;
/*
* Copyright 2012 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.
*/
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.security.DenyAll;
import javax.annotation.security.RolesAllowed;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.cglib.proxy.Enhancer;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import com.google.common.base.Predicates;
import com.khs.sherpa.annotation.Action;
import com.khs.sherpa.annotation.Endpoint;
import com.khs.sherpa.annotation.Param;
import com.khs.sherpa.context.ApplicationContext;
import com.khs.sherpa.context.ApplicationContextAware;
import com.khs.sherpa.events.RequestEvent;
import com.khs.sherpa.exception.NoSuchManagedBeanExcpetion;
import com.khs.sherpa.exception.SherpaActionNotFoundException;
import com.khs.sherpa.exception.SherpaPermissionExcpetion;
import com.khs.sherpa.exception.SherpaRuntimeException;
import com.khs.sherpa.json.service.Authentication;
import com.khs.sherpa.json.service.JsonProvider;
import com.khs.sherpa.json.service.SessionStatus;
import com.khs.sherpa.json.service.SessionToken;
import com.khs.sherpa.json.service.SessionTokenService;
import com.khs.sherpa.processor.DefaultRequestProcessor;
import com.khs.sherpa.processor.RequestProcessor;
import com.khs.sherpa.processor.RestfulRequestProcessor;
import com.khs.sherpa.servlet.RequestMapper;
import com.khs.sherpa.util.Constants;
import com.khs.sherpa.util.JsonUtil;
import com.khs.sherpa.util.MethodUtil;
import com.khs.sherpa.util.SherpaPredicates;
import com.khs.sherpa.util.Util;
public class DefaultSherpaRequest implements SherpaRequest {
private static Logger logger = Logger.getLogger(DefaultSherpaRequest.class.getSimpleName());
private Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private ApplicationContext applicationContext;
private HttpServletRequest request;
private HttpServletResponse response;
private RequestProcessor requestProcessor;
public Object getAttribute(String name) {
return attributes.get(name);
}
public void setAttribute(String name, Object object) {
attributes.put(name, object);
}
public void doService(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
Collection<RequestEvent> events = applicationContext.getManagedBeans(RequestEvent.class);
for(RequestEvent event: events) {
event.before(applicationContext, request, response);
}
ServletOutputStream output = null;
JsonProvider jsonProvider = null;
try {
output = response.getOutputStream();
jsonProvider = applicationContext.getManagedBean(JsonProvider.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
String callback = null;
if ((Boolean)applicationContext.getAttribute(ApplicationContext.SETTINGS_JSONP)) {
callback = request.getParameter("callback");
}
// set the correct Content type
response.setContentType("application/json");
if(callback != null) {
response.setContentType("text/javascript");
}
try {
JsonUtil.map(this.proccess(), jsonProvider, output, callback);
} catch (RuntimeException e) {
e.printStackTrace();
JsonUtil.error(e.getMessage(), jsonProvider, output, callback);
throw e;
}
for(RequestEvent event: events) {
event.after(applicationContext, request, response);
}
}
@SuppressWarnings("unchecked")
protected Object proccess() throws SherpaRuntimeException {
if(!isRestful()) {
requestProcessor = new DefaultRequestProcessor();
} else {
requestProcessor = new RestfulRequestProcessor(applicationContext);
}
String endpoint = requestProcessor.getEndpoint(request);
String action = requestProcessor.getAction(request);
String httpMethod = request.getMethod();
if(StringUtils.isEmpty(endpoint)) {
if(action.equals(Constants.AUTHENTICATE_ACTION)) {
return this.processAuthenication();
} else if(action.equals(Constants.VALID)) {
return this.processValid();
}
}
Object target = null;
Set<Method> methods = null;
try {
String userid = request.getHeader("userid");
if(userid == null) {
userid = request.getParameter("userid");
}
String token = request.getHeader("token");
if(token == null) {
token = request.getParameter("token");
}
this.hasPermission(applicationContext.getType(endpoint), userid, token);
target = applicationContext.getManagedBean(endpoint);
if(ApplicationContextAware.class.isAssignableFrom(target.getClass())) {
((ApplicationContextAware)target).setApplicationContext(applicationContext);
}
methods = Reflections.getAllMethods(applicationContext.getType(endpoint),
Predicates.and(
Predicates.not(SherpaPredicates.withAssignableFrom(Object.class)),
ReflectionUtils.withModifier(Modifier.PUBLIC),
Predicates.not(ReflectionUtils.withModifier(Modifier.ABSTRACT)),
Predicates.not(SherpaPredicates.withGeneric()),
Predicates.and(SherpaPredicates.withAssignableFrom(Enhancer.isEnhanced(target.getClass())? target.getClass().getSuperclass(): target.getClass())),
Predicates.or(
ReflectionUtils.withName(action),
Predicates.and(
ReflectionUtils.withAnnotation(Action.class),
SherpaPredicates.withActionAnnotationValueEqualTo(action)
)
))
);
if(methods.size() == 0) {
throw new SherpaActionNotFoundException(action);
}
} catch (NoSuchManagedBeanExcpetion e) {
throw new SherpaRuntimeException(e);
}
return this.processEndpoint(target, methods.toArray(new Method[] {}), httpMethod);
}
protected Object processEndpoint(Object target, Method[] methods, String httpMethod) {
Method method = MethodUtil.validateHttpMethods(methods, httpMethod);
String userid = request.getHeader("userid");
if(userid == null) {
userid = request.getParameter("userid");
}
String token = request.getHeader("token");
if(token == null) {
token = request.getParameter("token");
}
this.hasPermission(method, userid, token);
Action annotation = method.getAnnotation(Action.class);
if (annotation == null) {
throw new SherpaRuntimeException("Error executing"+target+" @Action annotation required for not endpoint methods" );
}
if(annotation.contentType() != null) {
response.setContentType(method.getAnnotation(Action.class).contentType().type);
}
return this.invokeMethod(target, method);
}
protected void hasPermission(Class<?> target, String userid, String token) {
SessionTokenService service = null;
try {
service = applicationContext.getManagedBean(SessionTokenService.class);
} catch (NoSuchManagedBeanExcpetion e) {
throw new SherpaRuntimeException(e);
}
// make sure Endpoint Authentication is turned on
if((Boolean)applicationContext.getAttribute(ApplicationContext.SETTINGS_ENDPOINT_AUTH) == false) {
return;
}
Endpoint endpoint = null;
if(Enhancer.isEnhanced(target)) {
endpoint = target.getSuperclass().getAnnotation(Endpoint.class);
} else {
endpoint = target.getAnnotation(Endpoint.class);
}
// make sure its authenicated
if(endpoint.authenticated() && !service.isActive(userid, token).equals(SessionStatus.AUTHENTICATED)) {
throw new SherpaPermissionExcpetion("User status [" + service.isActive(userid, token) + "]", service.isActive(userid, token).toString());
}
}
protected void hasPermission(Method method, String userid, String token) {
SessionTokenService service = null;
try {
service = applicationContext.getManagedBean(SessionTokenService.class);
} catch (NoSuchManagedBeanExcpetion e) {
throw new SherpaRuntimeException(e);
}
if(method.isAnnotationPresent(DenyAll.class)) {
throw new SherpaPermissionExcpetion("method ["+method.getName()+"] in class ["+method.getDeclaringClass().getCanonicalName()+"] has `@DenyAll` annotation", "DENY_ALL");
}
if(method.isAnnotationPresent(RolesAllowed.class)) {
boolean fail = true;
for(String role: method.getAnnotation(RolesAllowed.class).value()) {
if(service.hasRole(userid, token, role)) {
fail = false;
}
}
if(fail) {
throw new SherpaPermissionExcpetion("method ["+method.getName()+"] in class ["+method.getDeclaringClass().getCanonicalName()+"] has `@RolesAllowed` annotation", "DENY_ROLE" );
}
}
}
protected Object processValid() throws SherpaRuntimeException {
String userid = request.getParameter("userid");
String token = request.getParameter("token");
SessionTokenService service = null;
Map<String, Object> resp = new HashMap<String, Object>();
try {
service = applicationContext.getManagedBean(SessionTokenService.class);
} catch (NoSuchManagedBeanExcpetion e) {
throw new SherpaRuntimeException(e);
}
resp.put("userid", userid);
resp.put("token", token);
resp.put("status", service.isActive(userid, token));
return resp;
}
protected Object processAuthenication() throws SherpaRuntimeException {
String userid = request.getParameter("userid");
String password = request.getParameter("password");
try {
Authentication authentication = new Authentication(applicationContext);
SessionToken token = authentication.authenticate(userid, password, request, response);
boolean hasAdminRole = applicationContext.getManagedBean(SessionTokenService.class)
.hasRole(token.getUserid(), token.getToken(), (String) applicationContext.getAttribute(ApplicationContext.SETTINGS_ADMIN_USER));
// load the sherpa admin user
if(hasAdminRole) {
String[] roles = token.getRoles();
token.setRoles(Util.append(roles, "SHERPA_ADMIN"));
}
return token;
} catch (NoSuchManagedBeanExcpetion e) {
throw new SherpaRuntimeException(e);
}
}
protected Object invokeMethod(Object target, Method method){
try {
Object obj = method.invoke(target, this.getParams(method));
return obj;
} catch (Exception e) {
if(e.getCause() != null && e.getCause().getClass().isAssignableFrom(SherpaRuntimeException.class)) {
logger.throwing(target.getClass().getName(), method.getName(), e.getCause());
throw new SherpaRuntimeException(e.getCause().getMessage());
}
logger.throwing(target.getClass().getName(), method.getName(), e);
throw new SherpaRuntimeException("unable to execute method ["+method.getName()+"] in class ["+target.getClass().getCanonicalName()+"]", e);
} finally {
}
}
private Object[] getParams(Method method) {
RequestMapper map = new RequestMapper();
map.setApplicationContext(applicationContext);
map.setRequest(request);
map.setResponse(response);
map.setRequestProcessor(requestProcessor);
Class<?>[] types = method.getParameterTypes();
Object[] params = null;
// get parameters
if(types.length > 0) {
params = new Object[types.length];
}
Annotation[][] parameters = method.getParameterAnnotations();
for(int i=0; i<parameters.length; i++) {
Class<?> type = types[i];
Annotation annotation = null;
if(parameters[i].length > 0 ) {
for(Annotation an: parameters[i]) {
if(an.annotationType().isAssignableFrom(Param.class)) {
annotation = an;
break;
}
}
}
params[i] = map.map(method.getClass().getName(),method.getName(),type, annotation);
}
return params;
}
private boolean isRestful() {
String path = request.getContextPath();
if(path.equals("/")) {
path = request.getServletPath();
} else {
path += request.getServletPath();
}
return !path.equals(request.getRequestURI());
}
@Action(disabled = true)
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}