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