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