All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.bind.BindComposer Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/* BindComposer.java

	Purpose:
		
	Description:
		
	History:
		Jun 22, 2011 10:09:50 AM, Created by henrichen

Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.bind;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.bind.annotation.AfterCompose;
import org.zkoss.bind.impl.AbstractAnnotatedMethodInvoker;
import org.zkoss.bind.impl.AnnotationUtil;
import org.zkoss.bind.impl.BindEvaluatorXUtil;
import org.zkoss.bind.impl.MiscUtil;
import org.zkoss.bind.impl.ValidationMessagesImpl;
import org.zkoss.bind.sys.BindEvaluatorX;
import org.zkoss.bind.sys.ValidationMessages;
import org.zkoss.bind.sys.debugger.BindingAnnotationInfoChecker;
import org.zkoss.bind.sys.debugger.DebuggerFactory;
import org.zkoss.bind.tracker.impl.BindUiLifeCycle;
import org.zkoss.lang.Strings;
import org.zkoss.util.CacheMap;
import org.zkoss.util.IllegalSyntaxException;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.metainfo.Annotation;
import org.zkoss.zk.ui.metainfo.ComponentInfo;
import org.zkoss.zk.ui.select.Selectors;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.util.Composer;
import org.zkoss.zk.ui.util.ComposerExt;
import org.zkoss.zk.ui.util.ConventionWires;

/**
 * Base composer to apply ZK Bind.
 * @author henrichen
 * @since 6.0.0
 */
@SuppressWarnings("rawtypes")
public class BindComposer implements Composer, ComposerExt, Serializable {
	
	private static final long serialVersionUID = 1463169907348730644L;
	
	private static final Logger _log = LoggerFactory.getLogger(BindComposer.class);
	
	private static final String VM_ID = "$VM_ID$";
	private static final String BINDER_ID = "$BINDER_ID$";
	
	private Object _viewModel;
	private AnnotateBinder _binder;
	
	private final Map _converters;
	private final Map _validators;
	private final BindEvaluatorX evalx;
	
	private static final String ID_ANNO = "id";
	private static final String INIT_ANNO = "init";
	
	private static final String VALUE_ANNO_ATTR = "value";
	
	private static final String COMPOSER_NAME_ATTR = "composerName";
	private static final String VIEW_MODEL_ATTR = "viewModel";
	private static final String BINDER_ATTR = "binder";
	private static final String VALIDATION_MESSAGES_ATTR = "validationMessages";
	
	private static final String QUEUE_NAME_ANNO_ATTR = "queueName";
	private static final String QUEUE_SCOPE_ANNO_ATTR = "queueScope";
	
	private final static Map, List> _afterComposeMethodCache = 
		new CacheMap, List>(600,CacheMap.DEFAULT_LIFETIME);
	
	public BindComposer() {
		setViewModel(this);
		_converters = new HashMap(8);
		_validators = new HashMap(8);
		evalx = BindEvaluatorXUtil.createEvaluator(null);
	}
	
	public Binder getBinder() {
		return _binder;
	}
	
	//can assign a separate view model, default to this
	public void setViewModel(Object viewModel) {
		_viewModel = viewModel;
		if (this._binder != null) {
			this._binder.setViewModel(_viewModel);
		}
	}
	
	public Object getViewModel() {
		return _viewModel;
	}
	
	public Converter getConverter(String name) {
		Converter conv = _converters.get(name);
		return conv;
	}
	
	public Validator getValidator(String name) {
		Validator validator = _validators.get(name);
		return validator;
	}
	
	public void addConverter(String name, Converter converter) {
		_converters.put(name, converter);
	}
	
	public void addValidator(String name, Validator validator) {
		_validators.put(name, validator);
	}
	
	//--ComposerExt//
	public ComponentInfo doBeforeCompose(Page page, Component parent,
			ComponentInfo compInfo) throws Exception {
		return compInfo;
	}



	public void doBeforeComposeChildren(Component comp) throws Exception {
		//init viewmodel first
		_viewModel = initViewModel(evalx, comp);
		_binder = initBinder(evalx, comp);
		ValidationMessages _vmsgs = initValidationMessages(evalx, comp, _binder);
		
		//wire before call init
		Selectors.wireVariables(comp, _viewModel, Selectors.newVariableResolvers(_viewModel.getClass(), null));
		if(_vmsgs!=null){
			_binder.setValidationMessages(_vmsgs);
		}
		
		try{
			BinderKeeper keeper = BinderKeeper.getInstance(comp);
			keeper.book(_binder, comp);
		
			_binder.init(comp, _viewModel, getViewModelInitArgs(evalx,comp));
		}catch(Exception x){
			throw MiscUtil.mergeExceptionInfo(x, comp);
		}
		
		//to apply composer-name
		ConventionWires.wireController(comp, this);
	}
	
	//--Composer--//
	public void doAfterCompose(T comp) throws Exception {
		_binder.initAnnotatedBindings();
		
		// trigger ViewModel's @AfterCompose method.
		new AbstractAnnotatedMethodInvoker(AfterCompose.class, _afterComposeMethodCache){
			protected boolean shouldLookupSuperclass(AfterCompose annotation) {
				return annotation.superclass();
			}}.invokeMethod(_binder, getViewModelInitArgs(evalx,comp));
			
		// call loadComponent
		BinderKeeper keeper = BinderKeeper.getInstance(comp);
		if(keeper.isRootBinder(_binder)){
			keeper.loadComponentForAllBinders();
		}
	}
	
	private Map getViewModelInitArgs(BindEvaluatorX evalx,Component comp) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Collection anncol = compCtrl.getAnnotations(VIEW_MODEL_ATTR, INIT_ANNO);
		if(anncol.size()==0) return null;
		final Annotation ann = anncol.iterator().next();
		
		final Map attrs = ann.getAttributes(); //(tag, tagExpr)
		Map args = null;

		for (final Iterator> it = attrs.entrySet().iterator(); it.hasNext();) {
			final Entry entry = it.next();
			final String tag = entry.getKey();
			final String[] tagExpr = entry.getValue();
			if ("value".equals(tag)) {
				//ignore
			} else { //other unknown tag, keep as arguments
				if (args == null) {
					args = new HashMap();
				}
				args.put(tag, tagExpr);
			}
		}
		return args == null ? null : BindEvaluatorXUtil.parseArgs(_binder.getEvaluatorX(),args);
	}

	private Object initViewModel(BindEvaluatorX evalx, Component comp) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation idanno = compCtrl.getAnnotation(VIEW_MODEL_ATTR, ID_ANNO);
		final Annotation initanno = compCtrl.getAnnotation(VIEW_MODEL_ATTR, INIT_ANNO);
		String vmname = null;
		Object vm = null;
		
		BindingAnnotationInfoChecker checker = getBindingAnnotationInfoChecker();
		if(checker!=null){
			checker.checkViewModel(comp);
		}
		
		if(idanno==null && initanno==null){
			return _viewModel;
		}else if(idanno==null){
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage("you have to use @id to assign the name of view model",comp));
		}else if(initanno==null){
			throw new IllegalSyntaxException(MiscUtil.formatLocationMessage("you have to use @init to assign the view model",comp));
		}
		
		vmname = BindEvaluatorXUtil.eval(evalx,comp,AnnotationUtil.testString(idanno.getAttributeValues(VALUE_ANNO_ATTR),idanno),String.class);
		vm = BindEvaluatorXUtil.eval(evalx,comp,AnnotationUtil.testString(initanno.getAttributeValues(VALUE_ANNO_ATTR),initanno),Object.class);
		
		if(Strings.isEmpty(vmname)){
			throw new UiException(MiscUtil.formatLocationMessage("name of view model is empty",idanno));
		}
		
		try {
			if(vm instanceof String){
				Page page = comp.getPage();
				if(page==null){
					throw new UiException(MiscUtil.formatLocationMessage("can't find Page to resolve a view model class :'"+vm+"'",initanno));
				}else{
					vm = comp.getPage().resolveClass((String)vm);
				}
			}
			if(vm instanceof Class){
				vm = ((Class)vm).newInstance();
			}
		} catch (Exception e) {
			throw MiscUtil.mergeExceptionInfo(e, initanno);
		}
		if(vm == null){
			throw new UiException(MiscUtil.formatLocationMessage("view model of '"+vmname+"' is null",initanno));
		}else if(vm.getClass().isPrimitive()){
			throw new UiException(MiscUtil.formatLocationMessage("view model '"+vmname+"' is a primitive type, is "+vm,initanno));
		}
		comp.setAttribute(vmname, vm);
		comp.setAttribute(VM_ID, vmname);
		
		return vm;
	}
	
	private AnnotateBinder initBinder(BindEvaluatorX evalx, Component comp) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation idanno = compCtrl.getAnnotation(BINDER_ATTR, ID_ANNO);
		final Annotation initanno = compCtrl.getAnnotation(BINDER_ATTR, INIT_ANNO);
		Object binder = null;
		String bname = null;
		
		
		BindingAnnotationInfoChecker checker = getBindingAnnotationInfoChecker();
		if(checker!=null){
			checker.checkBinder(comp);
		}
		
		if(idanno!=null){
			bname = BindEvaluatorXUtil.eval(evalx, 
					comp,AnnotationUtil.testString(idanno.getAttributeValues(VALUE_ANNO_ATTR),idanno),String.class);
			if(Strings.isEmpty(bname)){
				throw new UiException(MiscUtil.formatLocationMessage("name of binder is empty",idanno));
			}
		}else{
			bname = "binder";
		}
		
		if(initanno!=null){
			binder = AnnotationUtil.testString(initanno.getAttributeValues(VALUE_ANNO_ATTR),initanno);
			String name = AnnotationUtil.testString(initanno.getAttributeValues(QUEUE_NAME_ANNO_ATTR),initanno);
			String scope = AnnotationUtil.testString(initanno.getAttributeValues(QUEUE_SCOPE_ANNO_ATTR),initanno);
			if(binder!=null){
				if(name!=null){
					_log.warn(MiscUtil.formatLocationMessage(QUEUE_NAME_ANNO_ATTR +" is not available if you use custom binder",initanno));
				}
				if(scope!=null){
					_log.warn(MiscUtil.formatLocationMessage(QUEUE_SCOPE_ANNO_ATTR +" is not available if you use custom binder",initanno));
				}
				
				binder = BindEvaluatorXUtil.eval(evalx,comp,(String)binder,Object.class);
				try {
					if(binder instanceof String){
						binder = comp.getPage().resolveClass((String)binder);
					}
					if(binder instanceof Class){
						binder = ((Class)binder).newInstance();
					}
				} catch (Exception e) {
					throw new UiException(e.getMessage(),e);
				}
				if(!(binder instanceof AnnotateBinder)){
					throw new UiException(MiscUtil.formatLocationMessage("evaluated binder is not a binder is "+binder,initanno));
				}
				
			}else {
				//no binder, create default binder with custom queue name and scope
				String expr;
				if(name!=null){
					name = BindEvaluatorXUtil.eval(evalx,comp,expr=name,String.class);
					if(Strings.isBlank(name)){
						throw new UiException(MiscUtil.formatLocationMessage("evaluated queue name is empty, expression is "+expr,initanno));
					}
				}
				if(scope!=null){
					scope = BindEvaluatorXUtil.eval(evalx,comp,expr=scope,String.class);
					if(Strings.isBlank(scope)){
						throw new UiException(MiscUtil.formatLocationMessage("evaluated queue scope is empty, expression is "+expr,initanno));
					}
				}
				binder = new AnnotateBinder(name,scope);
			}
		}else{
			binder = new AnnotateBinder();
		}
		
		//put to attribute, so binder could be referred by the name
		comp.setAttribute(bname, binder);
		comp.setAttribute(BINDER_ID, bname);
		
		return (AnnotateBinder)binder;
	}
	
	private ValidationMessages initValidationMessages(BindEvaluatorX evalx, Component comp,Binder binder) {
		final ComponentCtrl compCtrl = (ComponentCtrl) comp;
		final Annotation idanno = compCtrl.getAnnotation(VALIDATION_MESSAGES_ATTR, ID_ANNO);
		final Annotation initanno = compCtrl.getAnnotation(VALIDATION_MESSAGES_ATTR, INIT_ANNO);
		Object vmessages = null;
		String vname = null;
		
		BindingAnnotationInfoChecker checker = getBindingAnnotationInfoChecker();
		if(checker!=null){
			checker.checkValidationMessages(comp);
		}
		
		if(idanno!=null){
			vname = BindEvaluatorXUtil.eval(evalx, comp,
					AnnotationUtil.testString(idanno.getAttributeValues(VALUE_ANNO_ATTR), idanno), String.class);
			if(Strings.isEmpty(vname)){
				throw new UiException(MiscUtil.formatLocationMessage("name of ValidationMessages is empty",idanno));
			}
		}else{
			return null;//validation messages is default null
		}
		
		
		if(initanno!=null){
			vmessages = BindEvaluatorXUtil.eval(evalx, comp,
					AnnotationUtil.testString(initanno.getAttributeValues(VALUE_ANNO_ATTR), initanno), Object.class);
			try {
				if(vmessages instanceof String){
					vmessages = comp.getPage().resolveClass((String)vmessages);
				}
				if(vmessages instanceof Class){
					vmessages = ((Class)vmessages).newInstance();
				}
			} catch (Exception e) {
				throw new UiException(MiscUtil.formatLocationMessage(e.getMessage(),initanno),e);
			}
			if(!(vmessages instanceof ValidationMessages)){
				throw new UiException(MiscUtil.formatLocationMessage("evaluated validationMessages is not a ValidationMessages is "+vmessages,initanno));
			}
		}else{
			vmessages = new ValidationMessagesImpl();
		}
		
		//put to attribute, so binder could be referred by the name
		comp.setAttribute(vname, vmessages);
		
		return (ValidationMessages)vmessages;
	}

	

	public boolean doCatch(Throwable ex) throws Exception {
		return false;
	}

	public void doFinally() throws Exception {
		// ignore
	}
	
	
	//--notifyChange--//
	public void notifyChange(Object bean, String property) {
		getBinder().notifyChange(bean, property);
	}
	

	/**
	 * 
	 * 

A parsing scope context for storing Binders, and handle there loadComponent * invocation properly.

* *

if component trees with bindings are totally separated( none of * each contains another), then for each separated tree, there's only one keeper.

* * @author Ian Y.T Tsai(zanyking) */ private static class BinderKeeper{ private static final String KEY_BINDER_KEEPER = "$BinderKeeper$"; /** * get a Binder Keeper or create it by demand. * @param comp * @return */ static BinderKeeper getInstance(Component comp){ BinderKeeper keeper = (BinderKeeper) comp.getAttribute(KEY_BINDER_KEEPER, true); if(keeper == null){ comp.setAttribute(KEY_BINDER_KEEPER, keeper = new BinderKeeper(comp)); } return keeper; } private final LinkedList _queue; private Component _host; public BinderKeeper(final Component comp) { _host = comp; _queue = new LinkedList(); // ensure the keeper will always cleaned up Events.postEvent("onRootBinderHostDone", comp, null); comp.addEventListener("onRootBinderHostDone", new EventListener(){ public void onEvent(Event event) throws Exception { //suicide first... _host.removeEventListener("onRootBinderHostDone", this); BinderKeeper keeper = (BinderKeeper) _host.getAttribute(KEY_BINDER_KEEPER); if(keeper==null){ // suppose to be null... }else{ // The App is in trouble. // some error might happened during page processing // which cause loadComponent() never invoked. _host.removeAttribute(KEY_BINDER_KEEPER); } } }); } public void book(Binder binder, Component comp) { _queue.add(new Loader(binder, comp)); } public boolean isRootBinder(Binder binder){ return _queue.getFirst().binder == binder; } public void loadComponentForAllBinders(){ _host.removeAttribute(KEY_BINDER_KEEPER); for(Loader loader : _queue){ loader.load(); } } /** * for Binder to load Component. * @author Ian Y.T Tsai(zanyking) */ private static class Loader{ Binder binder; Component comp; public Loader(Binder _binder, Component comp) { super(); this.binder = _binder; this.comp = comp; } public void load(){ //ZK-1699, mark the comp and it's children are handling, to prevent load twice in include.src case BindUiLifeCycle.markLifeCycleHandling(comp); //load data binder.loadComponent(comp, true);//load all bindings } }//end of class... }//end of class... private BindingAnnotationInfoChecker getBindingAnnotationInfoChecker(){ DebuggerFactory factory = DebuggerFactory.getInstance(); return factory==null?null:factory.getAnnotationInfoChecker(); } }//end of class...




© 2015 - 2025 Weber Informatics LLC | Privacy Policy