    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    }