    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
    015package org.apache.tapestry5.internal.transform;
    016
    017import org.apache.tapestry5.ComponentResources;
    018import org.apache.tapestry5.annotations.InjectComponent;
    019import org.apache.tapestry5.commons.util.UnknownValueException;
    020import org.apache.tapestry5.internal.services.ComponentClassCache;
    021import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    022import org.apache.tapestry5.model.MutableComponentModel;
    023import org.apache.tapestry5.plastic.*;
    024import org.apache.tapestry5.runtime.Component;
    025import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    026import 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 */
    033public 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}