    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 boolean optional;
    044
    045        private InjectedComponentFieldValueConduit(ComponentResources resources, String fieldName, String type,
    046                                                   String componentId, boolean optional)
    047        {
    048            super(resources, fieldName);
    049
    050            this.resources = resources;
    051            this.fieldName = fieldName;
    052            this.componentId = componentId;
    053            this.type = type;
    054            this.optional = optional;
    055
    056            resources.getPageLifecycleCallbackHub().addPageAttachedCallback(new Runnable()
    057            {
    058                public void run()
    059                {
    060                    load();
    061                }
    062            });
    063        }
    064
    065        private void load()
    066        {
    067            try
    068            {
    069                embedded = resources.getEmbeddedComponent(componentId);
    070            } catch (UnknownValueException ex)
    071            {
    072                if (this.optional) {
    073                    return;
    074                }
    075
    076                throw new RuntimeException(String.format("Unable to inject component into field %s of class %s: %s",
    077                        fieldName, getComponentClassName(), ex.getMessage()), ex);
    078            }
    079
    080            Class fieldType = classCache.forName(type);
    081
    082            if (!fieldType.isInstance(embedded))
    083                throw new RuntimeException(
    084                        String
    085                                .format(
    086                                        "Unable to inject component '%s' into field %s of %s. Class %s is not assignable to a field of type %s.",
    087                                        componentId, fieldName, getComponentClassName(),
    088                                        embedded.getClass().getName(), fieldType.getName()));
    089        }
    090
    091        private String getComponentClassName()
    092        {
    093            return resources.getComponentModel().getComponentClassName();
    094        }
    095
    096        public Object get(Object instance, InstanceContext context)
    097        {
    098            return embedded;
    099        }
    100    }
    101
    102    private final ComponentClassCache classCache;
    103
    104    public InjectComponentWorker(ComponentClassCache classCache)
    105    {
    106        this.classCache = classCache;
    107    }
    108
    109    public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
    110    {
    111        for (PlasticField field : plasticClass.getFieldsWithAnnotation(InjectComponent.class))
    112        {
    113            InjectComponent annotation = field.getAnnotation(InjectComponent.class);
    114
    115            field.claim(annotation);
    116
    117            final String type = field.getTypeName();
    118
    119            final String componentId = getComponentId(field, annotation);
    120
    121            final String fieldName = field.getName();
    122
    123            final boolean optional = annotation.optional();
    124
    125            ComputedValue<FieldConduit<Object>> provider = new ComputedValue<FieldConduit<Object>>()
    126            {
    127                public FieldConduit<Object> get(InstanceContext context)
    128                {
    129                    ComponentResources resources = context.get(ComponentResources.class);
    130
    131                    return new InjectedComponentFieldValueConduit(resources, fieldName, type, componentId, optional);
    132                }
    133            };
    134
    135            field.setComputedConduit(provider);
    136        }
    137
    138    }
    139
    140    private String getComponentId(PlasticField field, InjectComponent annotation)
    141    {
    142        String id = annotation.value();
    143
    144        if (InternalUtils.isNonBlank(id))
    145            return id;
    146
    147        return InternalUtils.stripMemberName(field.getName());
    148    }
    149}