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