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 }