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}