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}