001: /*
002: * $Id: AutoComponentResolver.java,v 1.4 2005/01/18 08:04:29 jonathanlocke
003: * Exp $ $Revision: 460110 $ $Date: 2006-04-02 09:32:18 +0200 (Sun, 02 Apr 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket.markup.resolver;
019:
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.InvocationTargetException;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025:
026: import wicket.Component;
027: import wicket.MarkupContainer;
028: import wicket.WicketRuntimeException;
029: import wicket.markup.ComponentTag;
030: import wicket.markup.MarkupException;
031: import wicket.markup.MarkupStream;
032: import wicket.markup.WicketTag;
033: import wicket.markup.parser.filter.WicketTagIdentifier;
034: import wicket.util.lang.Classes;
035:
036: /**
037: * <wicket:component class="myApp.MyTable" key=value> tags may be used to add
038: * Wicket components (e.g. a specialized PageableListView) and pass parameters (e.g. the number
039: * of rows per list view page). The object is automatically instantiated, initialized
040: * and added to the page's component hierarchy.
041: * <p>
042: * Note: The component must have a constructor with a single String parameter:
043: * the component name.
044: * <p>
045: * Note: The component must provide a setter for each key/value attribute provided.
046: *
047: * @author Juergen Donnerstag
048: */
049: public final class AutoComponentResolver implements IComponentResolver {
050: private static final long serialVersionUID = 1L;
051:
052: static {
053: // register "wicket:fragement"
054: WicketTagIdentifier.registerWellKnownTagName("component");
055: }
056:
057: /**
058: * Temporary storage for containers currently being rendered. Thus child
059: * components can be re-parented. Remember: <wicket:component> are an
060: * exception to the rule. Though the markup of the children are nested
061: * inside <wicket:component>, their respective Java components are not.
062: * They must be added to the parent container of <wicket:component>.
063: */
064: private final Map nestedComponents = new HashMap();
065:
066: /**
067: * @see wicket.markup.resolver.IComponentResolver#resolve(MarkupContainer, MarkupStream,
068: * ComponentTag)
069: * @param container
070: * The container parsing its markup
071: * @param markupStream
072: * The current markupStream
073: * @param tag
074: * The current component tag while parsing the markup
075: * @return true, if componentId was handle by the resolver. False,
076: * otherwise
077: */
078: public final boolean resolve(final MarkupContainer container,
079: final MarkupStream markupStream, final ComponentTag tag) {
080: // It must be <wicket:...>
081: if (tag instanceof WicketTag) {
082: // It must be <wicket:component...>
083: final WicketTag wicketTag = (WicketTag) tag;
084: if (wicketTag.isComponentTag()) {
085: // Create and initialize the component
086: final Component component = createComponent(container,
087: wicketTag);
088: if (component != null) {
089: // 1. push the current component onto the stack
090: nestedComponents.put(component, null);
091:
092: try {
093: // 2. Add it to the hierarchy and render it
094: container.autoAdd(component);
095: } finally {
096: // 3. remove it from the stack
097: nestedComponents.remove(component);
098: }
099:
100: return true;
101: }
102: }
103: }
104:
105: // Re-parent children of <wicket:component>.
106: if ((tag.getId() != null)
107: && nestedComponents.containsKey(container)) {
108: MarkupContainer parent = container.getParent();
109:
110: // Take care of nested <wicket:component>
111: while ((parent != null)
112: && nestedComponents.containsKey(parent)) {
113: parent = parent.getParent();
114: }
115:
116: if (parent != null) {
117: final Component component = parent.get(tag.getId());
118: if (component != null) {
119: component.render(markupStream);
120: return true;
121: }
122: }
123: }
124:
125: // We were not able to handle the componentId
126: return false;
127: }
128:
129: /**
130: * Based on the tag, create and initalize the component.
131: *
132: * @param container The current container. The new compent will be added to that container.
133: * @param tag The tag containing the information about component
134: * @return The new component
135: * @throws WicketRuntimeException in case the component could not be created
136: */
137: // Wicket is current not using any bean util jar, which is why ...
138: private final Component createComponent(
139: final MarkupContainer container, final WicketTag tag) {
140: // If no component name is given, create a page-unique one yourself.
141: String componentId = tag.getNameAttribute();
142: if (componentId == null) {
143: componentId = "anonymous-"
144: + container.getPage().getAutoIndex();
145: }
146:
147: // Get the component class name
148: final String classname = tag.getAttributes().getString("class");
149: if ((classname == null) || (classname.trim().length() == 0)) {
150: throw new MarkupException(
151: "Tag <wicket:component> must have attribute 'class'");
152: }
153:
154: // Load the class. In case a Groovy Class Resolver has been provided,
155: // the name might be a Groovy file.
156: // Note: Spring based components are not supported this way. May be we
157: // should provide a ComponentFactory like we provide a PageFactory.
158: final Class componentClass = container.getSession()
159: .getClassResolver().resolveClass(classname);
160:
161: // construct the component. It must have a constructor with a single
162: // String (componentId) parameter.
163: final Component component;
164: try {
165: final Constructor constructor = componentClass
166: .getConstructor(new Class[] { String.class });
167: component = (Component) constructor
168: .newInstance(new Object[] { componentId });
169: } catch (NoSuchMethodException e) {
170: throw new MarkupException(
171: "Unable to create Component from wicket tag: Cause: "
172: + e.getMessage());
173: } catch (InvocationTargetException e) {
174: throw new MarkupException(
175: "Unable to create Component from wicket tag: Cause: "
176: + e.getMessage());
177: } catch (IllegalAccessException e) {
178: throw new MarkupException(
179: "Unable to create Component from wicket tag: Cause: "
180: + e.getMessage());
181: } catch (InstantiationException e) {
182: throw new MarkupException(
183: "Unable to create Component from wicket tag: Cause: "
184: + e.getMessage());
185: } catch (ClassCastException e) {
186: throw new MarkupException(
187: "Unable to create Component from wicket tag: Cause: "
188: + e.getMessage());
189: } catch (SecurityException e) {
190: throw new MarkupException(
191: "Unable to create Component from wicket tag: Cause: "
192: + e.getMessage());
193: }
194:
195: // Get all remaining attributes and invoke the component's setters
196: Iterator iter = tag.getAttributes().entrySet().iterator();
197: while (iter.hasNext()) {
198: final Map.Entry entry = (Map.Entry) iter.next();
199: final String key = (String) entry.getKey();
200: final String value = (String) entry.getValue();
201:
202: // Ignore attributes 'name' and 'class'
203: if ("name".equalsIgnoreCase(key)
204: || ("class".equalsIgnoreCase(key))) {
205: continue;
206: }
207:
208: Classes.invokeSetter(component, key, value, container
209: .getLocale());
210: }
211:
212: return component;
213: }
214: }
|