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 }