001 // Copyright 2009, 2011 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry5.internal.pageload;
016
017 import org.apache.tapestry5.internal.TapestryInternalUtils;
018 import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
019 import org.apache.tapestry5.internal.services.Instantiator;
020 import org.apache.tapestry5.internal.structure.ComponentPageElement;
021 import org.apache.tapestry5.ioc.Location;
022 import org.apache.tapestry5.ioc.Orderable;
023 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
024 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
025 import org.apache.tapestry5.ioc.internal.util.TapestryException;
026 import org.apache.tapestry5.model.ComponentModel;
027 import org.apache.tapestry5.model.EmbeddedComponentModel;
028 import org.apache.tapestry5.services.ComponentClassResolver;
029 import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
030
031 import java.util.HashSet;
032 import java.util.Map;
033 import java.util.Set;
034
035 public class EmbeddedComponentAssemblerImpl implements EmbeddedComponentAssembler
036 {
037 private final ComponentInstantiatorSource instantiatorSource;
038
039 private final ComponentAssemblerSource assemblerSource;
040
041 private final ComponentResourceSelector selector;
042
043 private final ComponentModel componentModel;
044
045 private final Location location;
046
047 private final Map<String, Instantiator> mixinIdToInstantiator = CollectionFactory.newCaseInsensitiveMap();
048 private final Map<String, String[]> mixinsIdToOrderConstraints = CollectionFactory.newCaseInsensitiveMap();
049
050 /**
051 * Maps parameter names (both simple, and qualified with the mixin id) to the corresponding QualifiedParameterName.
052 */
053 private final Map<String, ParameterBinder> parameterNameToBinder = CollectionFactory.newCaseInsensitiveMap();
054
055 // The id of the mixin to receive informal parameters. If null, the component itself recieves them.
056 // If the component doesn't support them, they are quietly dropped.
057
058 private final String informalParametersMixinId;
059
060 private final String componentPsuedoMixinId;
061
062 private Map<String, Boolean> bound;
063
064 /**
065 * @param assemblerSource
066 * @param instantiatorSource used to access component models
067 * @param componentClassResolver used to convert mixin types to component models
068 * @param componentClassName class name of embedded component
069 * @param selector used to select template and other resources
070 * @param embeddedModel embedded model (may be null for components defined in the template)
071 * @param templateMixins list of mixins from the t:mixins element (possibly null)
072 * @param location location of components element in its container's template
073 */
074 public EmbeddedComponentAssemblerImpl(ComponentAssemblerSource assemblerSource,
075 ComponentInstantiatorSource instantiatorSource, ComponentClassResolver componentClassResolver,
076 String componentClassName, ComponentResourceSelector selector, EmbeddedComponentModel embeddedModel,
077 String templateMixins, Location location)
078 {
079 this.assemblerSource = assemblerSource;
080 this.instantiatorSource = instantiatorSource;
081 this.selector = selector;
082 this.location = location;
083
084 componentModel = getModel(componentClassName);
085
086 // Add the implementation mixins defined by the component model.
087
088 for (String className : componentModel.getMixinClassNames())
089 {
090 addMixin(className, componentModel.getOrderForMixin(className));
091 }
092
093 // If there's an embedded model (i.e., there was an @Component annotation)
094 // then it may define some mixins.
095
096 if (embeddedModel != null)
097 {
098 for (String className : embeddedModel.getMixinClassNames())
099 {
100 addMixin(className, embeddedModel.getConstraintsForMixin(className));
101 }
102 }
103
104 // And the template may include a t:mixins element to define yet more mixins.
105 // Template strings specified as:
106 for (String mixinDef : TapestryInternalUtils.splitAtCommas(templateMixins))
107 {
108 Orderable<String> order = TapestryInternalUtils.mixinTypeAndOrder(mixinDef);
109 String className = componentClassResolver.resolveMixinTypeToClassName(order.getId());
110
111 addMixin(className, order.getConstraints());
112 }
113
114 // Finally (new in 5.3, for TAP5-1680), the component itself can sometimes acts as a mixin;
115 // this allows for dealing with parameter name conflicts between the component and the mixin
116 // (especially where informal parameters are involved).
117
118 componentPsuedoMixinId = InternalUtils.lastTerm(componentClassName);
119
120 informalParametersMixinId = prescanMixins();
121
122 }
123
124 private String prescanMixins()
125 {
126 // Mixin id found to support informal parameters
127
128 String supportsInformals = null;
129
130 for (Map.Entry<String, Instantiator> entry : mixinIdToInstantiator.entrySet())
131 {
132 String mixinId = entry.getKey();
133 ComponentModel mixinModel = entry.getValue().getModel();
134
135 updateParameterNameToQualified(mixinId, mixinModel);
136
137 if (supportsInformals == null && mixinModel.getSupportsInformalParameters())
138 supportsInformals = mixinId;
139 }
140
141 // The component comes last and overwrites simple names from the others.
142
143 updateParameterNameToQualified(null, componentModel);
144
145 return supportsInformals;
146 }
147
148 private void updateParameterNameToQualified(String mixinId, ComponentModel model)
149 {
150 for (String parameterName : model.getParameterNames())
151 {
152 String defaultBindingPrefix = model.getParameterModel(parameterName).getDefaultBindingPrefix();
153
154 ParameterBinderImpl binder = new ParameterBinderImpl(mixinId, parameterName, defaultBindingPrefix);
155
156 parameterNameToBinder.put(parameterName, binder);
157
158 if (mixinId != null)
159 parameterNameToBinder.put(mixinId + "." + parameterName, binder);
160 }
161 }
162
163 private void addMixin(String className, String... order)
164 {
165 Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
166
167 String mixinId = InternalUtils.lastTerm(className);
168
169 if (mixinIdToInstantiator.containsKey(mixinId))
170 throw new TapestryException(PageloadMessages.uniqueMixinRequired(mixinId), location, null);
171
172 mixinIdToInstantiator.put(mixinId, mixinInstantiator);
173 mixinsIdToOrderConstraints.put(mixinId, order);
174 }
175
176 private ComponentModel getModel(String className)
177 {
178 return instantiatorSource.getInstantiator(className).getModel();
179 }
180
181 public ComponentAssembler getComponentAssembler()
182 {
183 return assemblerSource.getAssembler(componentModel.getComponentClassName(), selector);
184 }
185
186
187 public ParameterBinder createParameterBinder(String qualifiedParameterName)
188 {
189 int dotx = qualifiedParameterName.indexOf('.');
190
191 if (dotx < 0)
192 {
193 return createParameterBinderFromSimpleParameterName(qualifiedParameterName);
194 }
195
196 return createParameterBinderFromQualifiedParameterName(qualifiedParameterName, qualifiedParameterName.substring(0, dotx),
197 qualifiedParameterName.substring(dotx + 1));
198 }
199
200 private ParameterBinder createParameterBinderFromSimpleParameterName(String parameterName)
201 {
202
203 // Look for a *formal* parameter with the simple name on the component itself.
204
205 ParameterBinder binder = getComponentAssembler().getBinder(parameterName);
206
207 if (binder != null)
208 {
209 return binder;
210 }
211
212 // Next see if any mixin has a formal parameter with this simple name.
213
214 binder = parameterNameToBinder.get(parameterName);
215
216 if (binder != null)
217 {
218 return binder;
219 }
220
221
222 // So, is there an mixin that's claiming all informal parameters?
223
224 if (informalParametersMixinId != null)
225 {
226 return new ParameterBinderImpl(informalParametersMixinId, parameterName, null);
227 }
228
229 // Maybe the component claims informal parameters?
230 if (componentModel.getSupportsInformalParameters())
231 return new ParameterBinderImpl(null, parameterName, null);
232
233 // Otherwise, informal parameter are not supported by the component or any mixin.
234
235 return null;
236 }
237
238 private ParameterBinder createParameterBinderFromQualifiedParameterName(String qualifiedParameterName, String mixinId, String parameterName)
239 {
240
241 if (mixinId.equalsIgnoreCase(componentPsuedoMixinId))
242 {
243 return createParameterBinderForComponent(qualifiedParameterName, parameterName);
244 }
245
246 if (!mixinIdToInstantiator.containsKey(mixinId))
247 {
248 throw new TapestryException(
249 String.format("Mixin id for parameter '%s' not found. Attached mixins: %s.", qualifiedParameterName,
250 InternalUtils.joinSorted(mixinIdToInstantiator.keySet())), location,
251 null);
252 }
253
254 ParameterBinder binder = parameterNameToBinder.get(qualifiedParameterName);
255
256 if (binder != null)
257 {
258 return binder;
259 }
260
261 // Ok, so perhaps this is a qualified name for an informal parameter of the mixin.
262
263 Instantiator instantiator = mixinIdToInstantiator.get(mixinId);
264
265 assert instantiator != null;
266
267 return bindInformalParameter(qualifiedParameterName, mixinId, parameterName, instantiator.getModel());
268 }
269
270 private ParameterBinder bindInformalParameter(String qualifiedParameterName, String mixinId, String parameterName, ComponentModel model)
271 {
272 if (model.getSupportsInformalParameters())
273 {
274 return new ParameterBinderImpl(mixinId, parameterName, null);
275 }
276
277 // Pretty sure this was not caught as an error in 5.2.
278
279 throw new TapestryException(String.format("Binding parameter %s as an informal parameter does not make sense, as %s does not support informal parameters.",
280 qualifiedParameterName, model.getComponentClassName()), location, null);
281 }
282
283 private ParameterBinder createParameterBinderForComponent(String qualifiedParameterName, String parameterName)
284 {
285 ParameterBinder binder = getComponentAssembler().getBinder(parameterName);
286
287 if (binder != null)
288 {
289 return binder;
290 }
291
292 return bindInformalParameter(qualifiedParameterName, null, parameterName, componentModel);
293 }
294
295
296 public boolean isBound(String parameterName)
297 {
298 return InternalUtils.get(bound, parameterName) != null;
299 }
300
301 public void setBound(String parameterName)
302 {
303 if (bound == null)
304 bound = CollectionFactory.newCaseInsensitiveMap();
305
306 bound.put(parameterName, true);
307 }
308
309 public int addMixinsToElement(ComponentPageElement newElement)
310 {
311 for (Map.Entry<String, Instantiator> entry : mixinIdToInstantiator.entrySet())
312 {
313 String mixinId = entry.getKey();
314 Instantiator instantiator = entry.getValue();
315
316 newElement.addMixin(mixinId, instantiator, mixinsIdToOrderConstraints.get(mixinId));
317 }
318
319 return mixinIdToInstantiator.size();
320 }
321
322 public Location getLocation()
323 {
324 return location;
325 }
326
327 public Set<String> getFormalParameterNames()
328 {
329 return new HashSet<String>(componentModel.getParameterNames());
330 }
331 }