Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.zkoss.bind.impl.ParamCall Maven / Gradle / Ivy
package org.zkoss.bind.impl;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.Converter;
import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.BindingParams;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.CookieParam;
import org.zkoss.bind.annotation.Default;
import org.zkoss.bind.annotation.ExecutionArgParam;
import org.zkoss.bind.annotation.ExecutionParam;
import org.zkoss.bind.annotation.HeaderParam;
import org.zkoss.bind.annotation.QueryParam;
import org.zkoss.bind.annotation.Scope;
import org.zkoss.bind.annotation.ScopeParam;
import org.zkoss.bind.annotation.SelectorParam;
import org.zkoss.bind.init.ViewModelAnnotationResolvers;
import org.zkoss.bind.paranamer.AdaptiveParanamer;
import org.zkoss.bind.paranamer.CachingParanamer;
import org.zkoss.bind.paranamer.Paranamer;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.ReferenceBinding;
import org.zkoss.json.JSONAware;
import org.zkoss.lang.Classes;
import org.zkoss.lang.Strings;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.OperationException;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.metainfo.Parser;
import org.zkoss.zk.ui.select.Selectors;
/**
* To help invoke a method with {@link BindingParam} etc.. features.
* @author dennis
* @since 6.0.0
*/
public class ParamCall {
private static final Logger _log = LoggerFactory.getLogger(ParamCall.class);
private static final Paranamer _PARANAMER = new CachingParanamer(new AdaptiveParanamer());
protected Map, ParamResolver> _paramResolvers;
private List _types; //to map class type directly, regardless the annotation
private boolean _mappingType; //to enable the map class type without annotation, it is for compatible to rc2, only support BindeContext and Binder
private ContextObjects _contextObjects;
private static final String COOKIE_CACHE = "$PARAM_COOKIES$";
public static final String BINDING_PARAM_CALL_TYPE = "org.zkoss.bind.BindingParamCall.type";
private Component _root = null;
private Component _component = null;
private Execution _execution = null;
private Binder _binder = null;
private BindContext _bindContext = null;
public ParamCall() {
this(true);
}
public ParamCall(boolean mappingType) {
_paramResolvers = new HashMap, ParamResolver>();
_contextObjects = new ContextObjects();
_types = new ArrayList();
_mappingType = mappingType;
_paramResolvers.put(ContextParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = _contextObjects.get(((ContextParam) anno).value());
return val == null ? null : Classes.coerce(returnType, val);
}
});
}
public void setBindContext(BindContext ctx) {
_bindContext = ctx;
_types.add(new Type(ctx.getClass(), _bindContext));
}
public BindContext getBindContext() {
return _bindContext;
}
public void setBinder(Binder binder) {
_binder = binder;
_types.add(new Type(binder.getClass(), _binder));
_root = binder.getView();
}
public Binder getBinder() {
return _binder;
}
public void setBindingArgs(final Map bindingArgs) {
this._bindingArgs = bindingArgs;
_paramResolvers.put(BindingParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = bindingArgs.get(getAnnotatedParameterName(BindingParam.class, ((BindingParam) anno).value(), parameterName));
return resolveParameter0(val, returnType);
}
});
_paramResolvers.put(BindingParams.class, new ParamResolver() {
@Override
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
try {
Object bean = Classes.newInstance(returnType, null);
BeanInfo beanInfo = Introspector.getBeanInfo(returnType);
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
String propertyName = pd.getName();
Object propertyValue = bindingArgs.get(propertyName);
if (propertyValue != null || bindingArgs.containsKey(propertyName)) {
pd.getWriteMethod().invoke(
bean, Classes.coerce(pd.getPropertyType(), propertyValue)
);
}
}
return bean;
} catch (Exception e) {
_log.error("", e);
throw UiException.Aide.wrap(e);
}
}
});
}
protected String getAnnotatedParameterName(Class extends Annotation> annoClass,
String annoValue,
Supplier parameterName) {
if (!Strings.isEmpty(annoValue))
return annoValue;
String value = parameterName.get();
if (!Strings.isEmpty(value))
return value;
throw new UiException("The parameter name can't be inferred. Please specify @" + annoClass.getSimpleName() + "(value) instead.");
}
public void call(Object base, Method method) {
Method originalMethod = ViewModelAnnotationResolvers.getOriginalMethod(base, method);
Class>[] paramTypes = originalMethod.getParameterTypes();
java.lang.annotation.Annotation[][] parmAnnos = originalMethod.getParameterAnnotations();
Object[] params = new Object[paramTypes.length];
try {
for (int i = 0; i < paramTypes.length; i++) {
params[i] = resolveParameter(parmAnnos[i], paramTypes[i], originalMethod, i);
}
method.setAccessible(true); // Bug ZK-2428
method.invoke(base, params);
} catch (InvocationTargetException invokEx) {
//Ian YT Tsai (2012.06.20), while InvocationTargetException,
//using original exception is much meaningful.
Throwable c = invokEx.getCause();
if (c == null)
c = invokEx;
if (c instanceof OperationException) { //ZK-4255: Expectable exceptions
_log.debug("", c);
} else {
_log.error("", c);
}
throw UiException.Aide.wrap(c);
} catch (Exception e) {
_log.error("", e);
throw UiException.Aide.wrap(e);
}
}
protected Object resolveParameter(Annotation[] parmAnnos, Class> paramType, Method method, int index) {
Object val = null;
boolean hitResolver = false;
Default defAnno = null;
for (Annotation anno : parmAnnos) {
Class> annotype = anno.annotationType();
if (defAnno == null && annotype.equals(Default.class)) {
defAnno = (Default) anno;
continue;
}
ParamResolver resolver = _paramResolvers.get(annotype);
if (resolver == null)
continue;
hitResolver = true;
val = resolver.resolveParameter(anno, paramType, () -> {
String[] parameterNames = _PARANAMER.lookupParameterNames(method, false);
return index < parameterNames.length ? parameterNames[index] : "";
});
if (val != null) {
break;
}
//don't break until get a value
}
boolean isDefaultVal = false;
if (val == null && defAnno != null) {
isDefaultVal = true;
val = Classes.coerce(paramType, defAnno.value());
}
//to compatible to rc2, do we have to?
if (_mappingType && val == null && !hitResolver && _types != null) {
for (Type type : _types) {
if (type != null && paramType.isAssignableFrom(type.clz)) {
val = type.value;
isDefaultVal = false;
break;
}
}
}
if (val == null) {
val = resolvePositionalOrNoAnnoParameter(paramType, method, index);
} else if (isDefaultVal) { // from default
Object positionalVal = resolvePositionalOrNoAnnoParameter(paramType, method, index);
if (positionalVal != null)
val = positionalVal;
}
return val;
}
protected Map _bindingArgs;
private Object resolvePositionalOrNoAnnoParameter(Class> returnType, Method method, int index) {
Object val = null;
if (_bindingArgs != null) {
int argIndex = 0;
for (Map.Entry entry : _bindingArgs.entrySet()) {
// skip using positional if the param key is not auto-generated
if (argIndex == index) {
if (entry.getKey().startsWith(Parser.SIMPLIFIED_COMMAND_PARAM_PREFIX)) {
val = entry.getValue();
}
break;
}
argIndex++;
}
if (val == null) { // check param names
String[] parameterNames = _PARANAMER.lookupParameterNames(method, false);
if (index < parameterNames.length) {
val = _bindingArgs.get(parameterNames[index]);
}
}
return resolveParameter0(val, returnType);
}
return null;
}
protected Object resolveParameter0(Object val, Class> returnType) {
if (val != null && returnType.isAssignableFrom(val.getClass())) { //escape
return val;
} else if (Component.class.isAssignableFrom(returnType) && val instanceof String) {
return _root.getDesktop().getComponentByUuidIfAny((String) val);
} else if (val instanceof JSONAware) {
BindContext bindContext = getBindContext();
Binder binder = getBinder();
@SuppressWarnings("rawtypes")
Converter converter = binder.getConverter("jsonBindingParam");
if (converter != null) {
try {
bindContext.setAttribute(BINDING_PARAM_CALL_TYPE, returnType);
@SuppressWarnings("unchecked")
Object result = converter.coerceToBean(val, binder.getView(), bindContext);
return result;
} catch (Exception ex) {
return Classes.coerce(returnType, val);
} finally {
bindContext.setAttribute(BINDING_PARAM_CALL_TYPE, null);
}
} else
return Classes.coerce(returnType, val);
} else {
return val == null ? null : Classes.coerce(returnType, val);
}
}
//utility to hold implicit class and runtime value
private static class Type {
final Class> clz;
final Object value;
public Type(Class> clz, Object value) {
this.clz = clz;
this.value = value;
}
}
public interface ParamResolver {
/**
* @since 9.5.0
*/
default Object resolveParameter(T anno, Class> returnType, Supplier parameterName) {
return null;
}
}
public void setComponent(Component comp) {
_component = comp;
//scope param
_paramResolvers.put(ScopeParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
final String name = getAnnotatedParameterName(ScopeParam.class, ((ScopeParam) anno).value(), parameterName);
final Scope[] ss = ((ScopeParam) anno).scopes();
Object val = null;
for (Scope s : ss) {
switch (s) {
case AUTO:
if (ss.length == 1) {
if (_execution != null) val = _execution.getAttribute(name);
if (val == null) val = _component.getAttribute(name, true);
} else {
throw new UiException("don't use " + s + " with other scopes " + Arrays.toString(ss));
}
}
}
if (val == null) {
for (Scope scope : ss) {
final String scopeName = scope.getName();
Object scopeObj = Components.getImplicit(_component, scopeName);
if (scopeObj instanceof Map) {
val = ((Map, ?>) scopeObj).get(name);
if (val != null)
break;
} else if (scopeObj != null) {
_log.error("the scope of " + scopeName + " is not a Map, is " + scopeObj);
}
}
}
//zk-1469,
if (val instanceof ReferenceBinding) {
val = resolveReferenceBinding(name, (ReferenceBinding) val, returnType);
}
return val == null ? null : Classes.coerce(returnType, val);
}
});
//component
_paramResolvers.put(SelectorParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
final String selector = getAnnotatedParameterName(SelectorParam.class, ((SelectorParam) anno).value(), parameterName);
final List result = Selectors.find(_root, selector);
Object val;
if (!Collection.class.isAssignableFrom(returnType)) {
val = result.size() > 0 ? Classes.coerce(returnType, result.get(0)) : null;
} else {
val = Classes.coerce(returnType, result);
}
return val;
}
});
}
private Object resolveReferenceBinding(String name, ReferenceBinding rbinding, Class> returnType) {
BindEvaluatorX evalx = rbinding.getBinder().getEvaluatorX();
//resolve by name or by rbinding.propertyString directly?
Object val = BindEvaluatorXUtil.eval(evalx, rbinding.getComponent(), name, returnType, null);
//following is quick but not safe because of the null arg
// val = ((ReferenceBinding)val).getValue(null);
return val;
}
public void setExecution(Execution exec) {
_execution = exec;
//http param
_paramResolvers.put(QueryParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = _execution.getParameter(getAnnotatedParameterName(QueryParam.class, ((QueryParam) anno).value(), parameterName));
return val == null ? null : Classes.coerce(returnType, val);
}
});
_paramResolvers.put(HeaderParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = _execution.getHeader(getAnnotatedParameterName(HeaderParam.class, ((HeaderParam) anno).value(), parameterName));
return val == null ? null : Classes.coerce(returnType, val);
}
});
_paramResolvers.put(CookieParam.class, new ParamResolver() {
@SuppressWarnings("unchecked")
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Map m = (Map) _execution.getAttribute(COOKIE_CACHE);
if (m == null) {
final Object req = _execution.getNativeRequest();
m = new HashMap();
_execution.setAttribute(COOKIE_CACHE, m);
if (req instanceof HttpServletRequest) {
final Cookie[] cks = ((HttpServletRequest) req).getCookies();
if (cks != null) {
for (Cookie ck : cks) {
m.put(ck.getName().toLowerCase(java.util.Locale.ENGLISH), ck.getValue());
}
}
} else /* if(req instanceof PortletRequest)*/ {
//no cookie in protlet 1.0
}
}
Object val = m.get(getAnnotatedParameterName(CookieParam.class, ((CookieParam) anno).value(), parameterName).toLowerCase(java.util.Locale.ENGLISH));
return val == null ? null : Classes.coerce(returnType, val);
}
});
//execution
_paramResolvers.put(ExecutionParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = _execution.getAttribute(getAnnotatedParameterName(ExecutionParam.class, ((ExecutionParam) anno).value(), parameterName));
return val == null ? null : Classes.coerce(returnType, val);
}
});
_paramResolvers.put(ExecutionArgParam.class, new ParamResolver() {
public Object resolveParameter(Annotation anno, Class> returnType, Supplier parameterName) {
Object val = _execution.getArg().get(getAnnotatedParameterName(ExecutionArgParam.class, ((ExecutionArgParam) anno).value(), parameterName));
return val == null ? null : Classes.coerce(returnType, val);
}
});
}
class ContextObjects {
public Object get(ContextType type) {
switch (type) {
//bind contexts
case BIND_CONTEXT:
return _bindContext;
case BINDER:
return _binder;
case COMMAND_NAME:
return _bindContext == null ? null : _bindContext.getCommandName();
case TRIGGER_EVENT:
return _bindContext == null ? null : _bindContext.getTriggerEvent();
//zk execution contexts
case EXECUTION:
return _execution;
case COMPONENT:
return _component;
case SPACE_OWNER:
return _component == null ? null : _component.getSpaceOwner();
case VIEW:
return _binder == null ? null : _binder.getView();
case PAGE:
return _component == null ? null : _component.getPage();
case DESKTOP:
return _component == null ? null : _component.getDesktop();
case SESSION:
return _component == null ? null : Components.getImplicit(_component, "session");
case APPLICATION:
return _component == null ? null : Components.getImplicit(_component, "application");
}
return null;
}
}
}