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