    001    // Copyright 2008, 2010, 2011, 2012 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    import org.apache.tapestry5.ComponentResources;
    018    import org.apache.tapestry5.annotations.InjectComponent;
    019    import org.apache.tapestry5.internal.services.ComponentClassCache;
    020    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    021    import org.apache.tapestry5.ioc.util.UnknownValueException;
    022    import org.apache.tapestry5.model.MutableComponentModel;
    023    import org.apache.tapestry5.plastic.*;
    024    import org.apache.tapestry5.runtime.Component;
    025    import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    026    import org.apache.tapestry5.services.transform.TransformationSupport;
    027
    028    /**
    029     * Recognizes the {@link org.apache.tapestry5.annotations.InjectComponent} annotation, and converts the field into a
    030     * read-only field containing the component. The id of the component may be explicitly stated or will be determined
    031     * from the field name.
    032     */
    033    public class InjectComponentWorker implements ComponentClassTransformWorker2
    034    {
    035        private final class InjectedComponentFieldValueConduit extends ReadOnlyComponentFieldConduit
    036        {
    037            private final ComponentResources resources;
    038
    039            private final String fieldName, componentId, type;
    040
    041            private Component embedded;
    042
    043            private InjectedComponentFieldValueConduit(ComponentResources resources, String fieldName, String type,
    044                                                       String componentId)
    045            {
    046                super(resources, fieldName);
    047
    048                this.resources = resources;
    049                this.fieldName = fieldName;
    050                this.componentId = componentId;
    051                this.type = type;
    052
    053                resources.getPageLifecycleCallbackHub().addPageAttachedCallback(new Runnable()
    054                {
    055                    public void run()
    056                    {
    057                        load();
    058                    }
    059                });
    060            }
    061
    062            private void load()
    063            {
    064                try
    065                {
    066                    embedded = resources.getEmbeddedComponent(componentId);
    067                } catch (UnknownValueException ex)
    068                {
    069                    throw new RuntimeException(String.format("Unable to inject component into field %s of class %s: %s",
    070                            fieldName, getComponentClassName(), ex.getMessage()), ex);
    071                }
    072
    073                Class fieldType = classCache.forName(type);
    074
    075                if (!fieldType.isInstance(embedded))
    076                    throw new RuntimeException(
    077                            String
    078                                    .format(
    079                                            "Unable to inject component '%s' into field %s of %s. Class %s is not assignable to a field of type %s.",
    080                                            componentId, fieldName, getComponentClassName(),
    081                                            embedded.getClass().getName(), fieldType.getName()));
    082            }
    083
    084            private String getComponentClassName()
    085            {
    086                return resources.getComponentModel().getComponentClassName();
    087            }
    088
    089            public Object get(Object instance, InstanceContext context)
    090            {
    091                return embedded;
    092            }
    093        }
    094
    095        private final ComponentClassCache classCache;
    096
    097        public InjectComponentWorker(ComponentClassCache classCache)
    098        {
    099            this.classCache = classCache;
    100        }
    101
    102        public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
    103        {
    104            for (PlasticField field : plasticClass.getFieldsWithAnnotation(InjectComponent.class))
    105            {
    106                InjectComponent annotation = field.getAnnotation(InjectComponent.class);
    107
    108                field.claim(annotation);
    109
    110                final String type = field.getTypeName();
    111
    112                final String componentId = getComponentId(field, annotation);
    113
    114                final String fieldName = field.getName();
    115
    116                ComputedValue<FieldConduit<Object>> provider = new ComputedValue<FieldConduit<Object>>()
    117                {
    118                    public FieldConduit<Object> get(InstanceContext context)
    119                    {
    120                        ComponentResources resources = context.get(ComponentResources.class);
    121
    122                        return new InjectedComponentFieldValueConduit(resources, fieldName, type, componentId);
    123                    }
    124                };
    125
    126                field.setComputedConduit(provider);
    127            }
    128
    129        }
    130
    131        private String getComponentId(PlasticField field, InjectComponent annotation)
    132        {
    133            String id = annotation.value();
    134
    135            if (InternalUtils.isNonBlank(id))
    136                return id;
    137
    138            return InternalUtils.stripMemberName(field.getName());
    139        }
    140    }