/**
* Copyright 2010 Marko Lavikainen
*
* 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.contextfw.web.application.internal.component;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import net.contextfw.web.application.WebApplicationException;
import net.contextfw.web.application.component.Attribute;
import net.contextfw.web.application.component.Buildable;
import net.contextfw.web.application.component.Component;
import net.contextfw.web.application.component.CustomBuild;
import net.contextfw.web.application.component.Element;
import net.contextfw.web.application.component.ScriptContext;
import net.contextfw.web.application.component.ScriptElement;
import net.contextfw.web.application.internal.servlet.UriMapping;
import net.contextfw.web.application.lifecycle.AfterBuild;
import net.contextfw.web.application.lifecycle.BeforeBuild;
import net.contextfw.web.application.remote.PathParam;
import net.contextfw.web.application.remote.RequestParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public final class MetaComponent {
private static Map<Class<?>, Class<?>> primitives = new HashMap<Class<?>, Class<?>>();
static {
primitives.put(boolean.class, Boolean.class);
primitives.put(byte.class, Byte.class);
primitives.put(char.class, Character.class);
primitives.put(short.class, Short.class);
primitives.put(int.class, Integer.class);
primitives.put(long.class, Long.class);
primitives.put(float.class, Float.class);
primitives.put(double.class, Double.class);
}
Logger log = LoggerFactory.getLogger(MetaComponent.class);
private final Set<String> registeredNames = new HashSet<String>();
private final List<Method> beforeBuilds = new ArrayList<Method>();
private final List<Method> afterBuilds = new ArrayList<Method>();
public final List<Builder> builders = new ArrayList<Builder>();
public final List<Builder> updateBuilders = new ArrayList<Builder>();
public final List<Builder> partialBuilders = new ArrayList<Builder>();
private final List<Field> pathParamFields = new ArrayList<Field>();
private final List<Method> pathParamMethods = new ArrayList<Method>();
private final List<Field> requestParamFields = new ArrayList<Field>();
private final List<Method> requestParamMethods = new ArrayList<Method>();
private final List<Field> autoregisterFields = new ArrayList<Field>();
private final List<Field> fields = new ArrayList<Field>();
public final String buildName;
public final Buildable annotation;
private final Class<?> cl;
private final ComponentBuilder componentBuilder;
private final Gson gson;
private final ScriptContext scriptContext;
public MetaComponent(Class<?> rawCl,
ComponentBuilder componentBuilder,
Gson gson,
ScriptContext scriptContext) {
cl = getActualClass(rawCl);
this.componentBuilder = componentBuilder;
this.gson = gson;
this.scriptContext = scriptContext;
annotation = findBuildable();
if (annotation != null) {
buildName = getBuildableName();
iterateFields();
iterateMethods();
} else {
buildName = null;
}
}
private boolean processFieldBuilders(Field field) {
PropertyAccess<Object> propertyAccess =
new FieldPropertyAccess<Object>(field);
String name = null;
Builder builder = null;
if (field.getAnnotation(Element.class) != null) {
Element element = field.getAnnotation(Element.class);
name = "".equals(element.name()) ? field.getName()
: element.name();
builder = new ElementBuilder(componentBuilder, propertyAccess,
element.wrap() ? name : null, field.getName());
addToBuilders(element.onCreate(), element.onUpdate(), builder);
if (element.autoRegister()) {
autoregisterFields.add(field);
}
} else if (field.getAnnotation(Attribute.class) != null) {
Attribute attribute = field.getAnnotation(Attribute.class);
name = "".equals(attribute.name()) ? field.getName()
: attribute.name();
builder = new AttributeBuilder(propertyAccess, name,
field.getName());
addToBuilders(attribute.onCreate(), attribute.onUpdate(), builder);
} else if (field.getAnnotation(ScriptElement.class) != null) {
ScriptElement scriptElement = field
.getAnnotation(ScriptElement.class);
name = scriptElement.wrapper();
builder = new ScriptElementBuilder(scriptContext, gson,
propertyAccess, name, field.getName());
addToBuilders(scriptElement.onCreate(), scriptElement.onUpdate(), builder);
}
return builder != null;
}
private boolean processMethodBuilders(Method method) {
String name = null;
Builder builder = null;
if (method.getAnnotation(Element.class) != null) {
Element element = method.getAnnotation(Element.class);
name = "".equals(element.name()) ? method.getName()
: element.name();
builder = new ElementBuilder(componentBuilder, new MethodPropertyAccess(
method),
element.wrap() ? name : null, method.getName());
addToBuilders(element.onCreate(), element.onUpdate(), builder);
} else if (method.getAnnotation(Attribute.class) != null) {
Attribute attribute = method
.getAnnotation(Attribute.class);
name = "".equals(attribute.name()) ? method.getName()
: attribute.name();
builder = new AttributeBuilder(new MethodPropertyAccess(
method), name,
method.getName());
addToBuilders(attribute.onCreate(), attribute.onUpdate(), builder);
} else if (method.getAnnotation(CustomBuild.class) != null) {
CustomBuild customBuild = method
.getAnnotation(CustomBuild.class);
name = "".equals(customBuild.name()) ? method.getName()
: customBuild.name();
builder = new MethodCustomBuilder(method,
customBuild.wrap() ? name : null);
addToBuilders(customBuild.onCreate(), customBuild.onUpdate(), builder);
} else if (method.getAnnotation(ScriptElement.class) != null) {
ScriptElement scriptElement = method
.getAnnotation(ScriptElement.class);
name = scriptElement.wrapper();
builder = new ScriptElementBuilder(scriptContext, gson,
new MethodPropertyAccess(method), name, method.getName());
addToBuilders(scriptElement.onCreate(), scriptElement.onUpdate(), builder);
}
return builder != null;
}
private boolean canProcess(Field field) {
return !registeredNames.contains(field.getName());
}
private void setProcessed(Field field) {
registeredNames.add(field.getName());
}
private boolean canProcess(Method method) {
return !registeredNames.contains(method.getName());
}
private void setProcessed(Method method) {
registeredNames.add(method.getName());
}
private void iterateFields() {
Class<?> currentClass = cl;
while (currentClass != null) {
for (Field field : currentClass.getDeclaredFields()) {
if (processField(field)) {
setProcessed(field);
}
}
currentClass = currentClass.getSuperclass();
}
}
private boolean processField(Field field) {
field.setAccessible(true);
if (canProcess(field)) {
fields.add(field);
return processFieldBuilders(field) ||
processPathParam(field) ||
processRequestParam(field);
} else {
return false;
}
}
private void iterateMethods() {
Class<?> currentClass = cl;
while (currentClass != null) {
for (Method method : currentClass.getDeclaredMethods()) {
method.setAccessible(true);
if (canProcess(method) && processMethodBuilders(method)) {
setProcessed(method);
}
if (canProcess(method) && processBeforeBuilds(method)) {
setProcessed(method);
}
if (canProcess(method) && processAfterBuilds(method)) {
setProcessed(method);
}
if (canProcess(method) && processPathParam(method)) {
setProcessed(method);
}
if (canProcess(method) && processRequestParam(method)) {
setProcessed(method);
}
}
currentClass = currentClass.getSuperclass();
}
}
public static Class<?> getActualClass(Class<?> cl) {
Class<?> actual = cl;
while (actual.getSimpleName().contains("EnhancerByGuice")) {
actual = actual.getSuperclass();
}
return actual;
}
private Buildable findBuildable() {
Class<?> current = cl;
while (current != null) {
if (current.isAnnotationPresent(Buildable.class)) {
return current.getAnnotation(Buildable.class);
}
current = current.getSuperclass();
}
return null;
}
private String getBuildableName() {
if (annotation.wrap()) {
return ("".equals(annotation.name()) ? cl
.getSimpleName() : annotation.name());
} else {
return null;
}
}
private void addToBuilders(boolean onCreate,
boolean onUpdate,
Builder builder) {
if (onCreate) {
builders.add(builder);
}
if (onUpdate) {
updateBuilders.add(builder);
}
partialBuilders.add(builder);
}
public boolean processBeforeBuilds(Method method) {
if (method.getAnnotation(BeforeBuild.class) != null) {
beforeBuilds.add(method);
return true;
} else {
return false;
}
}
public boolean processAfterBuilds(Method method) {
if (method.getAnnotation(AfterBuild.class) != null) {
afterBuilds.add(method);
return true;
} else {
return false;
}
}
public boolean processPathParam(Field field) {
if (field.isAnnotationPresent(PathParam.class)) {
if (!primitives.containsKey(field.getType())) {
try {
field.getType().getConstructor(String.class);
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchMethodException e) {
throw new WebApplicationException(field,
"@PathParam-annotated field " +
"type does not contain constructor " +
"having String as parameter", e);
}
}
pathParamFields.add(field);
return true;
} else {
return false;
}
}
public boolean processRequestParam(Field field) {
if (field.isAnnotationPresent(RequestParam.class)) {
if (!primitives.containsKey(field.getType())) {
try {
field.getType().getConstructor(String.class);
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchMethodException e) {
throw new WebApplicationException(field,
"@RequestParam-annotated field " +
"type does not contain constructor " +
"having String as parameter", e);
}
}
requestParamFields.add(field);
return true;
} else {
return false;
}
}
public boolean processPathParam(Method method) {
if (method.isAnnotationPresent(PathParam.class)) {
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) {
throw new WebApplicationException(method,
"@PathParam annotated method does not take 1 parameter", null);
}
if (!primitives.containsKey(types[0])) {
try {
types[0].getConstructor(String.class);
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchMethodException e) {
throw new WebApplicationException(method,
"@PathParam-annotated method parameter " +
"type does not contain constructor " +
"having String as parameter", e);
}
}
pathParamMethods.add(method);
return true;
} else {
return false;
}
}
public boolean processRequestParam(Method method) {
if (method.isAnnotationPresent(RequestParam.class)) {
Class<?>[] types = method.getParameterTypes();
if (types.length != 1) {
throw new WebApplicationException(method,
"@RequestParam annotated method does not take 1 parameter", null);
}
if (!primitives.containsKey(types[0])) {
try {
types[0].getConstructor(String.class);
} catch (SecurityException e) {
throw new WebApplicationException(e);
} catch (NoSuchMethodException e) {
throw new WebApplicationException(method,
"@RequestParam-annotated method parameter " +
"type does not contain constructor " +
"having String as parameter", e);
}
}
requestParamMethods.add(method);
return true;
} else {
return false;
}
}
public void applyBeforeBuilds(Object obj) {
for (Method method : beforeBuilds) {
try {
method.invoke(obj);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e);
} catch (IllegalAccessException e) {
throw new WebApplicationException(e);
} catch (InvocationTargetException e) {
throw new WebApplicationException(e);
}
}
}
public void applyAfterBuilds(Object obj) {
for (Method method : afterBuilds) {
try {
method.invoke(obj);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e);
} catch (IllegalAccessException e) {
throw new WebApplicationException(e);
} catch (InvocationTargetException e) {
throw new WebApplicationException(e);
}
}
}
public void applyRequestParams(Object obj,
HttpServletRequest request) {
for (Field field : requestParamFields) {
RequestParam annotation = field.getAnnotation(RequestParam.class);
String name = "".equals(annotation.name()) ? field.getName() : annotation.name();
try {
field.set(obj, getValue(annotation,
field.getType(),
name,
request));
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (RuntimeException) e;
} else {
throw new WebApplicationException(e);
}
}
}
for (Method method : requestParamMethods) {
RequestParam annotation = method.getAnnotation(RequestParam.class);
String name = "".equals(annotation.name()) ? method.getName() : annotation.name();
try {
method.invoke(obj, getValue(annotation,
method.getParameterTypes()[0],
name,
request));
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (RuntimeException) e;
} else {
throw new WebApplicationException(e);
}
}
}
}
public void applyPathParams(Object obj,
UriMapping mapping,
String uri) {
for (Field field : pathParamFields) {
PathParam annotation = field.getAnnotation(PathParam.class);
String name = "".equals(annotation.name()) ? field.getName() : annotation.name();
try {
field.set(obj, getValue(annotation,
field.getType(),
name,
mapping,
uri));
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (RuntimeException) e;
} else {
throw new WebApplicationException(e);
}
}
}
for (Method method : pathParamMethods) {
PathParam annotation = method.getAnnotation(PathParam.class);
String name = "".equals(annotation.name()) ? method.getName() : annotation.name();
try {
method.invoke(obj, getValue(annotation,
method.getParameterTypes()[0],
name,
mapping,
uri));
} catch (Exception e) {
if (e instanceof WebApplicationException) {
throw (RuntimeException) e;
} else {
throw new WebApplicationException(e);
}
}
}
}
private Object getValue(RequestParam annotation,
Class<?> type,
String name,
HttpServletRequest request) {
String val = request.getParameter(name);
if (val == null) {
switch (annotation.onNull()) {
case SET_TO_NULL:
return null;
case RETHROW_CAUSE:
throw new WebApplicationException(cl, "Null value for request param: " + name, null);
case SEND_NOT_FOUND_ERROR:
case SEND_BAD_REQUEST_ERROR:
throw new MetaComponentException(annotation.onNull());
}
}
Object rv = null;
if (String.class == type) {
rv = val;
} else {
try {
if (primitives.containsKey(type)) {
rv = primitives.get(type).getConstructor(String.class).newInstance(val);
} else {
rv = type.getConstructor(String.class).newInstance(val);
}
} catch (Exception e) {
switch (annotation.onError()) {
case SET_TO_NULL:
return null;
case RETHROW_CAUSE:
throw new WebApplicationException(e);
case SEND_NOT_FOUND_ERROR:
case SEND_BAD_REQUEST_ERROR:
throw new MetaComponentException(annotation.onError());
}
}
}
return rv;
}
private Object getValue(PathParam annotation,
Class<?> type,
String name,
UriMapping mapping,
String uri) {
String val = mapping.findValue(uri, name);
if (val == null) {
switch (annotation.onNull()) {
case SET_TO_NULL:
return null;
case RETHROW_CAUSE:
throw new WebApplicationException(cl, "Null value for path param: " + name, null);
case SEND_NOT_FOUND_ERROR:
case SEND_BAD_REQUEST_ERROR:
throw new MetaComponentException(annotation.onNull());
}
}
Object rv = null;
if (String.class == type) {
rv = val;
} else {
try {
if (primitives.containsKey(type)) {
rv = primitives.get(type).getConstructor(String.class).newInstance(val);
} else {
rv = type.getConstructor(String.class).newInstance(val);
}
} catch (Exception e) {
switch (annotation.onError()) {
case SET_TO_NULL:
return null;
case RETHROW_CAUSE:
throw new WebApplicationException(e);
case SEND_NOT_FOUND_ERROR:
case SEND_BAD_REQUEST_ERROR:
throw new MetaComponentException(annotation.onError());
}
}
}
return rv;
}
public void registerChildren(Component parent) {
for (Field field : autoregisterFields) {
try {
Object child = field.get(parent);
if (child instanceof Component) {
parent.registerChild((Component)child);
}
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e);
} catch (IllegalAccessException e) {
throw new WebApplicationException(e);
}
}
}
}