001 // Copyright 2009, 2010, 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.transform; 016 017 018 import org.apache.tapestry5.ComponentResources; 019 import org.apache.tapestry5.annotations.BindParameter; 020 import org.apache.tapestry5.internal.InternalComponentResources; 021 import org.apache.tapestry5.internal.services.ComponentClassCache; 022 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 023 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 024 import org.apache.tapestry5.ioc.internal.util.TapestryException; 025 import org.apache.tapestry5.ioc.services.TypeCoercer; 026 import org.apache.tapestry5.ioc.util.AvailableValues; 027 import org.apache.tapestry5.ioc.util.UnknownValueException; 028 import org.apache.tapestry5.model.ComponentModel; 029 import org.apache.tapestry5.model.EmbeddedComponentModel; 030 import org.apache.tapestry5.model.MutableComponentModel; 031 import org.apache.tapestry5.plastic.*; 032 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 033 import org.apache.tapestry5.services.transform.TransformationSupport; 034 035 import java.util.List; 036 037 /** 038 * Responsible for identifying, via the {@link org.apache.tapestry5.annotations.BindParameter} annotation, mixin fields 039 * that should be bound to a core-component parameter value. 040 * 041 * @since 5.2.0 042 */ 043 public class BindParameterWorker implements ComponentClassTransformWorker2 044 { 045 private final class BoundParameterFieldValueConduit implements FieldConduit<Object> 046 { 047 private final String containerParameterName; 048 049 private final InternalComponentResources containerResources; 050 051 private final Class fieldType; 052 053 // Guarded by this 054 private ParameterConduit conduit; 055 056 private BoundParameterFieldValueConduit(String containerParameterName, 057 InternalComponentResources containerResources, Class fieldType) 058 { 059 this.containerParameterName = containerParameterName; 060 this.containerResources = containerResources; 061 this.fieldType = fieldType; 062 } 063 064 /** 065 * Defer obtaining the conduit object until needed, to deal with the complex 066 * lifecycle of 067 * parameters. Perhaps this can be addressed by converting constructors into 068 * methods invoked 069 * from the page loaded lifecycle method? 070 */ 071 private synchronized ParameterConduit getParameterConduit() 072 { 073 if (conduit == null) 074 { 075 // if the parameter is not a formal parameter then it must be a published parameter 076 if (containerResources.getComponentModel().isFormalParameter(containerParameterName)) 077 conduit = containerResources.getParameterConduit(containerParameterName); 078 else 079 conduit = getEmbeddedComponentResourcesForPublishedParameter(containerResources, containerParameterName) 080 .getParameterConduit(containerParameterName); 081 } 082 083 return conduit; 084 } 085 086 087 public Object get(Object instance, InstanceContext context) 088 { 089 // For the moment, this results in two passes through the TypeCoercer; we'll look 090 // to optimize that in the future. The first pass is deep inside ParameterConduit (coercing 091 // to the component parameter field type), the second is here (usually the same type so no 092 // real coercion necessary). 093 094 Object result = getParameterConduit().get(instance, context); 095 096 return typeCoercer.coerce(result, fieldType); 097 } 098 099 public void set(Object instance, InstanceContext context, Object newValue) 100 { 101 getParameterConduit().set(instance, context, newValue); 102 103 } 104 } 105 106 private final TypeCoercer typeCoercer; 107 108 private final ComponentClassCache componentClassCache; 109 110 public BindParameterWorker(TypeCoercer typeCoercer, ComponentClassCache componentClassCache) 111 { 112 this.typeCoercer = typeCoercer; 113 this.componentClassCache = componentClassCache; 114 } 115 116 public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) 117 { 118 for (PlasticField field : plasticClass.getFieldsWithAnnotation(BindParameter.class)) 119 { 120 convertFieldIntoContainerBoundParameter(field); 121 } 122 } 123 124 private void convertFieldIntoContainerBoundParameter(PlasticField field) 125 { 126 BindParameter annotation = field.getAnnotation(BindParameter.class); 127 128 field.claim(annotation); 129 130 final String[] possibleNames = annotation.value(); 131 132 final String fieldTypeName = field.getTypeName(); 133 134 final String fieldName = field.getName(); 135 136 ComputedValue<FieldConduit<Object>> computedConduit = new ComputedValue<FieldConduit<Object>>() 137 { 138 public FieldConduit<Object> get(InstanceContext context) 139 { 140 ComponentResources resources = context.get(ComponentResources.class); 141 142 try 143 { 144 return createConduit(resources, fieldTypeName, fieldName, possibleNames); 145 } catch (Exception ex) 146 { 147 throw new TapestryException(String.format( 148 "Failure binding parameter field '%s' of mixin %s (type %s): %s", fieldName, resources 149 .getCompleteId(), resources.getComponentModel().getComponentClassName(), 150 InternalUtils.toMessage(ex)), ex); 151 } 152 } 153 154 }; 155 156 field.setComputedConduit(computedConduit); 157 } 158 159 private FieldConduit<Object> createConduit(final ComponentResources resources, final String fieldTypeName, 160 final String fieldName, final String[] possibleNames) 161 { 162 if (!resources.isMixin()) 163 throw new TapestryException(TransformMessages.bindParameterOnlyOnMixin(fieldName, resources), null); 164 165 InternalComponentResources containerResources = (InternalComponentResources) resources.getContainerResources(); 166 167 // Evaluate this early so that we get a fast fail. 168 169 String containerParameterName = identifyParameterName(resources, InternalUtils.stripMemberName(fieldName), 170 possibleNames); 171 172 Class fieldType = componentClassCache.forName(fieldTypeName); 173 174 return new BoundParameterFieldValueConduit(containerParameterName, containerResources, fieldType); 175 } 176 177 private String identifyParameterName(ComponentResources resources, String firstGuess, String... otherGuesses) 178 { 179 ComponentModel model = resources.getContainerResources().getComponentModel(); 180 181 List<String> guesses = CollectionFactory.newList(); 182 guesses.add(firstGuess); 183 184 for (String name : otherGuesses) 185 { 186 guesses.add(name); 187 } 188 189 for (String name : guesses) 190 { 191 if (model.isFormalParameter(name)) 192 return name; 193 194 if(isPublishedParameter(model, name)) 195 return name; 196 } 197 198 String message = String.format("Containing component %s does not contain a formal parameter or a published parameter %s %s.", 199 200 model.getComponentClassName(), 201 202 guesses.size() == 1 ? "matching" : "matching any of", 203 204 InternalUtils.joinSorted(guesses)); 205 206 List<String> formalAndPublishedParameters = CollectionFactory.newList(model.getParameterNames()); 207 formalAndPublishedParameters.addAll(getPublishedParameters(model)); 208 209 throw new UnknownValueException(message, new AvailableValues("Formal and published parameters", formalAndPublishedParameters)); 210 } 211 212 /** 213 * Returns true if the parameter with the given parameterName is a published parameter 214 * of any of the embedded components for the component with the given model. 215 */ 216 private boolean isPublishedParameter(ComponentModel model, String parameterName) 217 { 218 for (String embeddedComponentId : model.getEmbeddedComponentIds()) 219 { 220 EmbeddedComponentModel embeddedComponentModel = model 221 .getEmbeddedComponentModel(embeddedComponentId); 222 if (embeddedComponentModel.getPublishedParameters().contains(parameterName)) return true; 223 } 224 225 return false; 226 } 227 228 private List<String> getPublishedParameters(ComponentModel model) 229 { 230 List<String> publishedParameters = CollectionFactory.newList(); 231 for (String embeddedComponentId : model.getEmbeddedComponentIds()) 232 { 233 EmbeddedComponentModel embeddedComponentModel = model.getEmbeddedComponentModel(embeddedComponentId); 234 publishedParameters.addAll(embeddedComponentModel.getPublishedParameters()); 235 } 236 return publishedParameters; 237 } 238 239 /** 240 * Returns the {@link InternalComponentResources} of an embeddedComponent that contains the published parameter 241 * publishedParameterName. This is basically a recursive search for published parameters. 242 */ 243 private InternalComponentResources getEmbeddedComponentResourcesForPublishedParameter(InternalComponentResources containerResources, 244 String publishedParameterName) 245 { 246 List<InternalComponentResources> embeddedComponentResourcesList = CollectionFactory.newList(); 247 248 embeddedComponentResourcesList.add(containerResources); 249 250 while(!embeddedComponentResourcesList.isEmpty()) 251 { 252 InternalComponentResources resources = embeddedComponentResourcesList.remove(0); 253 254 ComponentModel containerComponentModel = resources.getComponentModel(); 255 256 for(String embeddedComponentId : containerComponentModel.getEmbeddedComponentIds()) 257 { 258 EmbeddedComponentModel embeddedComponentModel = containerComponentModel 259 .getEmbeddedComponentModel(embeddedComponentId); 260 261 InternalComponentResources embeddedComponentResources = (InternalComponentResources) resources 262 .getEmbeddedComponent(embeddedComponentId).getComponentResources(); 263 /** 264 * If the parameter is not a formal parameter, then the parameter must be a published parameter 265 * of an embeddedComponent of the component we are currently examining. 266 */ 267 if(embeddedComponentModel.getPublishedParameters().contains(publishedParameterName) 268 && embeddedComponentResources.getComponentModel().isFormalParameter(publishedParameterName)) 269 { 270 return embeddedComponentResources; 271 } 272 273 embeddedComponentResourcesList.add(embeddedComponentResources); 274 } 275 } 276 277 return null; 278 } 279 } 280