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}