Skip to content


Web 1.0 mit Wicket – Seitenparameter und Links

Mit Wicket ist es sehr einfach, Webanwendungen zu schreiben. Wenn man dem Wicket-Pfad folgt und keine besonderen Wünsche hat. Im folgenden besteht der Wunsch darin, den Zustand einer Seite in Seitenparametern abzulegen. Wenn durch einen Link, also eine Aktion, die der Nutzer wählen kann, nur ein Parameter verändert wird, sollen natürlich alle anderen Parameter unverändert weitergereicht werden.

Zuerst erstellen wir uns ein paar Hilfsklassen, welche die Handhabung wesentlich vereinfachen werden. Die Idee ist dabei folgende: die Parameter, die mit dem Request übergeben werden, werden auf Attribute einer JavaBean gemappt und mit Wicket-Bordmitteln in den entsprechenden Datentyp konvertiert. Aus der JavaBean kann man dann die gewünschten Werte auslesen. Die Attribute der JavaBean können dann neu gesetzt werden. Ein weiteres Mal wird mit Wicket-Bordmitteln aus den Attributen der JavaBean eine Liste von Parametern gewonnen, die dann (fast) direkt in einem Link benutzt werden können.

Zuerst erstellen wir uns eine Annotation, mit der wir die Attribute markieren, die von und in PageParameter umgewandelt werden sollen.

 Java |  copy code |? 
01
import java.lang.annotation.ElementType;
02
import java.lang.annotation.Retention;
03
import java.lang.annotation.RetentionPolicy;
04
import java.lang.annotation.Target;
05
 
06
@Retention(RetentionPolicy.RUNTIME)
07
@Target(ElementType.METHOD)
08
public @interface PublicProperty
09
{	
10
}

Als nächstes erstellen wir uns ein Interface, was von der JavaBean implementiert werden muss. Die Aufgabe des Interface liegt darin, sicherzustellen, dass von der JavaBean eine Kopie angefertigt werden kann, damit die Änderungen an der Kopie und nicht am Original vorgenommen werden.

 Java |  copy code |? 
1
import org.apache.wicket.IClusterable;
2
 
3
public interface PageStateBeanInterface<T extends PageStateBeanInterface<?>> extends IClusterable
4
{
5
	public T getClone();
6
}
7

Jetzt kommt der aufwendigste Teil. Wir schreiben uns eine Hilfsklasse, welche die Transformation von und in PageParameter durchführt.

 Java |  copy code |? 
001
import java.lang.reflect.Method;
002
import java.util.ArrayList;
003
import java.util.HashMap;
004
import java.util.List;
005
import java.util.Locale;
006
import java.util.Map;
007
 
008
import org.apache.wicket.Application;
009
import org.apache.wicket.IConverterLocator;
010
import org.apache.wicket.PageParameters;
011
import org.apache.wicket.Session;
012
import org.apache.wicket.model.PropertyModel;
013
import org.apache.wicket.util.convert.IConverter;
014
 
015
public class BeanPagePropertyUtil
016
{
017
	public static <B> PageParameters getBeanPageParameters(B bean)
018
	{
019
		return new PageParameters(getParameter(bean));
020
	}
021
 
022
	public static <B> PageParameters getBeanPageParameters(B bean,B defaults)
023
	{
024
		Map<String, Object> beanParameter = getParameter(bean);
025
		Map<String, Object> defaultParameter = getParameter(defaults);
026
		for (String s : defaultParameter.keySet())
027
		{
028
			Object defaultValue = defaultParameter.get(s);
029
			if (defaultValue!=null)
030
			{
031
				Object curValue = beanParameter.get(s);
032
				if (defaultValue.equals(curValue))
033
				{
034
					beanParameter.remove(s);
035
				}
036
			}
037
		}
038
		return new PageParameters(beanParameter);
039
	}
040
 
041
	protected static <B> List<String> getPublicProperties(B bean)
042
	{
043
		List<String> ret=new ArrayList<String>();
044
 
045
		Method[] methods = bean.getClass().getMethods();
046
		for (Method m : methods)
047
		{
048
			PublicProperty annotation = m.getAnnotation(PublicProperty.class);
049
			if (annotation!=null)
050
			{
051
				String name = m.getName();
052
				if (name.startsWith("get")) ret.add(name.substring(3));
053
				else
054
				{
055
					if (name.startsWith("is")) ret.add(name.substring(2));
056
				}
057
			}
058
		}
059
 
060
		return ret;
061
	}
062
 
063
	public static <B> Map<String,Object> getParameter(B bean)
064
	{
065
		Map<String,Object> ret=new HashMap<String, Object>();
066
 
067
		Locale locale = Session.get().getLocale();
068
		IConverterLocator converterLocator = Application.get().getConverterLocator();
069
 
070
		for (String s : getPublicProperties(bean))
071
		{
072
			PropertyModel<?> propertyModel = new PropertyModel(bean,s);
073
			IConverter converter = converterLocator.getConverter(propertyModel.getObjectClass());
074
			Object value = propertyModel.getObject();
075
			if (value!=null)
076
			{
077
				ret.put(s, converter.convertToString(value, locale));
078
			}
079
		}
080
		return ret;
081
	}
082
 
083
	public static <B> void setParameter(B bean,PageParameters pageParameters)
084
	{
085
		Locale locale = Session.get().getLocale();
086
		IConverterLocator converterLocator = Application.get().getConverterLocator();
087
 
088
		for (String s : getPublicProperties(bean))
089
		{
090
			PropertyModel<Object> propertyModel = new PropertyModel<Object>(bean,s);
091
			IConverter converter = converterLocator.getConverter(propertyModel.getObjectClass());
092
			String svalue = pageParameters.getString(s);
093
			if (svalue!=null)
094
			{
095
				propertyModel.setObject(converter.convertToObject(svalue, locale));
096
			}
097
			else
098
			{
099
				propertyModel.setObject(null);
100
			}
101
		}
102
	}
103
 
104
	public static <B> void setParameter(B bean,Map<String,?> parameter)
105
	{
106
		for (String s : getPublicProperties(bean))
107
		{
108
			if (parameter.containsKey(s))
109
			{
110
				PropertyModel<Object> propertyModel = new PropertyModel<Object>(bean,s);
111
				Object value=parameter.get(s);
112
				propertyModel.setObject(value);
113
			}
114
		}
115
	}
116
}
117

Es ist wichtig, darauf hinzuweisen, dass es vorgesehen ist, eine mit Standardwerten initialisierte JavaBean als Abgleich zu benutzen. So kann sichergestellt werden, dass Seitenparameter dann aus der Url entfernt werden, wenn der Wert dem Standardwert entspricht. Das reduziert die Gefahr für Double-Content-Probleme wesentlich.

Jetzt haben wir alles zusammen, um Seitenparameter in JavaBean-Attribute zu überführen und zurück wandeln zu können. Als nächstes benötigen wir noch ein paar Komponenten, die den Prozess der Parameterlistenerstellung und der Konvertierung für uns übernehmen. Dazu fügen wir der Seite eine Komponente hinzu, die folgendes Interface implementiert:

 Java |  copy code |? 
1
public interface PageStateInterface<B extends PageStateBeanInterface<B>>
2
{
3
	public B getState();
4
	public B getDefaults();
5
}
6

Die Komponente konvertiert die Seitenparameter in die Attribute und stellt über die Schnittstelle die beiden Zustände zur Verfügung. Damit andere Komponenten auf diese Werte zugreifen können, erstellen wir gleichzeitig eine Funktion, welche die Komponente im Komponentenbaum sucht.

 Java |  copy code |? 
01
import org.apache.wicket.Component;
02
import org.apache.wicket.Page;
03
import org.apache.wicket.PageParameters;
04
import org.apache.wicket.markup.html.panel.Panel;
05
 
06
public class PageContext<B extends PageStateBeanInterface<B>> extends Panel implements PageStateInterface<B>
07
{
08
	private static final Logger _logger=LoggerFactory.getLogger(PageContext.class);
09
 
10
	B _defaults;
11
 
12
	B _state;
13
 
14
	public PageContext(String id, PageParameters pageParameters, B defaults)
15
	{
16
		super(id);
17
 
18
		_defaults=defaults;
19
		_state=_defaults.getClone();
20
		BeanPagePropertyUtil.setParameter(_state, pageParameters);
21
	}
22
 
23
	public B getDefaults()
24
	{
25
		return _defaults;
26
	}
27
 
28
	public B getState()
29
	{
30
		return _state.getClone();
31
	}
32
 
33
	public static <B extends PageStateBeanInterface<B>> PageStateInterface<B> getPageState(Page page, Class<? extends B> type)
34
	{
35
		NodeVisitor visitor=new NodeVisitor(type);
36
		page.visitChildren(PageContext.class, visitor);
37
		return visitor.getPageState();
38
	}
39
 
40
	static class NodeVisitor<B extends PageStateBeanInterface<B>> implements IVisitor<Component>
41
	{
42
		Class<B> _type;
43
 
44
		PageStateInterface<B> _pageState;
45
 
46
		public NodeVisitor(Class<B> type)
47
		{
48
			_type=type;
49
		}
50
 
51
		public PageStateInterface<B> getPageState()
52
		{
53
			return _pageState;
54
		}
55
 
56
		public Object component(Component component)
57
		{
58
			if (component instanceof PageContext)
59
			{
60
				PageContext rawContext=(PageContext) component;
61
				if (_type.isAssignableFrom(rawContext.getDefaults().getClass()))
62
				{
63
					_pageState=rawContext;
64
					return IVisitor.STOP_TRAVERSAL;
65
				}
66
			}
67
			return IVisitor.CONTINUE_TRAVERSAL;
68
		}
69
	}
70
}
71

Des weiteren erstellen wir eine Linkklasse, die sich um das setzen der richtigen Seitenparameter kümmert.

 Java |  copy code |? 
01
import java.util.Map;
02
 
03
import org.apache.wicket.Page;
04
import org.apache.wicket.PageParameters;
05
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
06
import org.apache.wicket.util.collections.MiniMap;
07
 
08
public class PageStateLink<P extends Page, B extends PageStateBeanInterface<B>> extends BookmarkablePageLink<P>
09
{
10
	Map<String, ?> _linkParameter;
11
 
12
	Class<B> _beanType;
13
 
14
	public PageStateLink(String id, Class<P> pageClass, Class<B> beanType, Map<String, ?> linkParameter)
15
	{
16
		super(id, pageClass);
17
		_beanType=beanType;
18
		_linkParameter=linkParameter;
19
		if (_linkParameter == null) _linkParameter=new HashMap<String,?>();
20
	}
21
 
22
	public PageStateLink(String id, Class<P> pageClass, Class<B> beanType)
23
	{
24
		this(id, pageClass, beanType, null);
25
	}
26
 
27
	@Override
28
	protected void onBeforeRender()
29
	{
30
		PageStateInterface<B> pageState=PageContext.getPageState(getPage(), _beanType);
31
		if (pageState != null)
32
		{
33
			B bean=pageState.getState();
34
			B defaults=pageState.getDefaults();
35
			BeanPagePropertyUtil.setParameter(bean, _linkParameter);
36
			onAfterSetParameter(bean);
37
			PageParameters beanPageParameters=BeanPagePropertyUtil.getBeanPageParameters(bean, defaults);
38
			applyPageParameter(beanPageParameters);
39
		}
40
		super.onBeforeRender();
41
	}
42
 
43
	protected void onAfterSetParameter(B bean)
44
	{
45
 
46
	}
47
 
48
	private void applyPageParameter(PageParameters pageParameters)
49
	{
50
		this.parameters=pageParametersToMiniMap(pageParameters);
51
	}
52
 
53
	private MiniMap<String, Object> pageParametersToMiniMap(PageParameters parameters)
54
	{
55
		if (parameters != null)
56
		{
57
			MiniMap<String, Object> map=new MiniMap<String, Object>(parameters, parameters.keySet().size());
58
			return map;
59
		}
60
		else
61
		{
62
			return null;
63
		}
64
 
65
	}
66
}
67

Die Methode applyPageParameter wurde nur aus Geschwindigkeitsgründen erstellt, da sonst jeder Aufruf von setParameter dazu führt, dass die interne Map kopiert wird.

Um auf die JavaBean über ein Modell zuzugreifen, schreiben wir uns noch eine Modellklasse, bevor wir uns dann ansehen, wie man die Klassen dann benutzt.

 Java |  copy code |? 
01
import org.apache.wicket.Component;
02
import org.apache.wicket.model.LoadableDetachableModel;
03
 
04
public class PageStateModel<B extends PageStateBeanInterface<B>> extends LoadableDetachableModel<B>
05
{
06
	Component _component;
07
	Class<? extends B> _type;
08
 
09
	public PageStateModel(Component component, Class<? extends B> type)
10
  {
11
		_component=component;
12
		_type=type;
13
  }
14
 
15
	@Override
16
	protected B load()
17
	{
18
	  PageStateInterface<B> pageState=PageContext.getPageState(_component.getPage(), _type);
19
	  if (pageState!=null)
20
	  {
21
	  	return pageState.getState();
22
	  }
23
		return null;
24
	}
25
}
26

Ok. Das war ganz schön aufwendig, aber dafür ist die Verwendung um so einfacher. Wir erstellen eine JavaBean und eine Seite, auf der wir dann die Komponenten einbinden.

 Java |  copy code |? 
01
public class ConfigBean implements PageStateBeanInterface<ConfigBean>
02
{
03
	Integer _start;
04
	Integer _stop;
05
	String _name;
06
 
07
	@PublicProperty
08
	public Integer getStart()
09
	{
10
		return _start;
11
	}
12
 
13
	public void setStart(Integer start)
14
	{
15
		_start=start;
16
	}
17
 
18
	@PublicProperty
19
	public Integer getStop()
20
	{
21
		return _stop;
22
	}
23
 
24
	public void setStop(Integer stop)
25
	{
26
		_stop=stop;
27
	}
28
 
29
	@PublicProperty
30
	public String getName()
31
	{
32
		return _name;
33
	}
34
 
35
	public void setName(String name)
36
	{
37
		_name=name;
38
	}
39
 
40
	public ConfigBean getClone()
41
	{
42
		ConfigBean ret=new ConfigBean();
43
		ret._name=_name;
44
		ret._start=_start;
45
		ret._stop=_stop;
46
		return ret;
47
	}
48
}

 Java |  copy code |? 
01
import org.apache.wicket.PageParameters;
02
import org.apache.wicket.markup.html.WebPage;
03
import org.apache.wicket.markup.html.basic.Label;
04
import org.apache.wicket.markup.html.panel.Panel;
05
import org.apache.wicket.model.IModel;
06
import org.apache.wicket.model.PropertyModel;
07
 
08
public class TestPage extends WebPage
09
{
10
	IModel<ConfigBean> _config=new PageStateModel<ConfigBean>(this, ConfigBean.class);
11
 
12
	public StatelessTestPage(PageParameters pageParameters)
13
	{
14
		add(new PageContext<ConfigBean>("context", pageParameters, new ConfigBean()));
15
 
16
		add(new Label("start", new PropertyModel<Integer>(_config, "start")));
17
		add(new Label("stop", new PropertyModel<Integer>(_config, "stop")));
18
		add(new SubPanel("sub"));
19
	}
20
 
21
	public static class SubPanel extends Panel
22
	{
23
		IModel<ConfigBean> _config=new PageStateModel<ConfigBean>(this, ConfigBean.class);
24
 
25
		public SubPanel(String id)
26
		{
27
			super(id);
28
 
29
			add(new Label("name", new PropertyModel<Integer>(_config, "name")));
30
 
31
			PageStateLink<StatelessTestPage, ConfigBean> link=new PageStateLink<StatelessTestPage, ConfigBean>("link", StatelessTestPage.class, ConfigBean.class, new HashMap<String,Object>("Name","Klaus"));
32
			add(link);
33
			PageStateLink<StatelessTestPage, ConfigBean> link2=new PageStateLink<StatelessTestPage, ConfigBean>("link2", StatelessTestPage.class, ConfigBean.class, new HashMap<String,Object>("Start", 1));
34
			add(link2);
35
			PageStateLink<StatelessTestPage, ConfigBean> link3=new PageStateLink<StatelessTestPage, ConfigBean>("link3", StatelessTestPage.class, ConfigBean.class, new HashMap<String,Object>("Start", null));
36
			add(link3);
37
			PageStateLink<StatelessTestPage, ConfigBean> link4=new PageStateLink<StatelessTestPage, ConfigBean>("link4", StatelessTestPage.class, ConfigBean.class, new HashMap<String,Object>("Name", "Bert", "Stop", null));
38
			add(link4);
39
		}
40
	}
41
}
42

Wie man sieht, muss ich bei den Modellen eigentlich nichts besonderes machen. Die Links übergibt man eine Map mit neuen Parametern, mit denen der aktuelle Zustand, der in der JavaBean gespeichert wurde, für diesen Link, diese Nutzeraktion überschrieben wird. Dabei spielt es keine Rolle, in welcher Komponente so ein Link benutzt wird, da sich die Linkklasse und die Modellklasse selbsttätig um die Informationen bemühen.

Der hier vorgeschlagene Ansatz ist sicher a) verbesserungswürdig und b) ausbaufähig. Er soll als Anregung dienen, wie man dieses und möglicherweise ähnliche Probleme lösen kann und dabei besonders von der Komponentenarchitektur von Wicket profitieren kann.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Andere Beiträge

Posted in Allgemein, Wicket.

Tagged with , , , , .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.