001    // Copyright 2008, 2010, 2011 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    
015    package org.apache.tapestry5.internal.transform;
016    
017    import org.apache.tapestry5.ComponentResources;
018    import org.apache.tapestry5.annotations.InjectComponent;
019    import org.apache.tapestry5.internal.services.ComponentClassCache;
020    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
021    import org.apache.tapestry5.ioc.util.UnknownValueException;
022    import org.apache.tapestry5.model.MutableComponentModel;
023    import org.apache.tapestry5.plastic.*;
024    import org.apache.tapestry5.runtime.Component;
025    import org.apache.tapestry5.runtime.PageLifecycleAdapter;
026    import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
027    import org.apache.tapestry5.services.transform.TransformationSupport;
028    
029    /**
030     * Recognizes the {@link org.apache.tapestry5.annotations.InjectComponent} annotation, and converts the field into a
031     * read-only field containing the component. The id of the component may be explicitly stated or will be determined
032     * from the field name.
033     */
034    public class InjectComponentWorker implements ComponentClassTransformWorker2
035    {
036        private final class InjectedComponentFieldValueConduit extends ReadOnlyComponentFieldConduit
037        {
038            private final ComponentResources resources;
039            private final String fieldName, componentId, type;
040    
041            private Component embedded;
042    
043            private InjectedComponentFieldValueConduit(final 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.addPageLifecycleListener(new PageLifecycleAdapter()
054                {
055                    public void containingPageDidLoad()
056                    {
057                        load();
058    
059                        resources.removePageLifecycleListener(this);
060                    }
061                });
062            }
063    
064            private void load()
065            {
066                try
067                {
068                    embedded = resources.getEmbeddedComponent(componentId);
069                } catch (UnknownValueException ex)
070                {
071                    throw new RuntimeException(String.format("Unable to inject component into field %s of class %s: %s",
072                            fieldName, getComponentClassName(), ex.getMessage()), ex);
073                }
074    
075                Class fieldType = classCache.forName(type);
076    
077                if (!fieldType.isInstance(embedded))
078                    throw new RuntimeException(
079                            String
080                                    .format(
081                                            "Unable to inject component '%s' into field %s of %s. Class %s is not assignable to a field of type %s.",
082                                            componentId, fieldName, getComponentClassName(),
083                                            embedded.getClass().getName(), fieldType.getName()));
084            }
085    
086            private String getComponentClassName()
087            {
088                return resources.getComponentModel().getComponentClassName();
089            }
090    
091            public Object get(Object instance, InstanceContext context)
092            {
093                return embedded;
094            }
095        }
096    
097        private final ComponentClassCache classCache;
098    
099        public InjectComponentWorker(ComponentClassCache classCache)
100        {
101            this.classCache = classCache;
102        }
103    
104        public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
105        {
106            for (PlasticField field : plasticClass.getFieldsWithAnnotation(InjectComponent.class))
107            {
108                InjectComponent annotation = field.getAnnotation(InjectComponent.class);
109    
110                field.claim(annotation);
111    
112                final String type = field.getTypeName();
113    
114                final String componentId = getComponentId(field, annotation);
115    
116                final String fieldName = field.getName();
117    
118                ComputedValue<FieldConduit<Object>> provider = new ComputedValue<FieldConduit<Object>>()
119                {
120                    public FieldConduit<Object> get(InstanceContext context)
121                    {
122                        ComponentResources resources = context.get(ComponentResources.class);
123    
124                        return new InjectedComponentFieldValueConduit(resources, fieldName, type, componentId);
125                    }
126                };
127    
128                field.setComputedConduit(provider);
129            }
130    
131        }
132    
133        private String getComponentId(PlasticField field, InjectComponent annotation)
134        {
135            String id = annotation.value();
136    
137            if (InternalUtils.isNonBlank(id))
138                return id;
139    
140            return InternalUtils.stripMemberName(field.getName());
141        }
142    }