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