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.DifferentClassVersionsException; 020import org.apache.tapestry5.commons.util.UnknownValueException; 021import org.apache.tapestry5.internal.services.ComponentClassCache; 022import org.apache.tapestry5.ioc.internal.util.InternalUtils; 023import org.apache.tapestry5.model.MutableComponentModel; 024import org.apache.tapestry5.plastic.*; 025import org.apache.tapestry5.runtime.Component; 026import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 027import org.apache.tapestry5.services.transform.TransformationSupport; 028import org.slf4j.Logger; 029 030/** 031 * Recognizes the {@link org.apache.tapestry5.annotations.InjectComponent} annotation, and converts the field into a 032 * read-only field containing the component. The id of the component may be explicitly stated or will be determined 033 * from the field name. 034 */ 035public class InjectComponentWorker implements ComponentClassTransformWorker2 036{ 037 private final class InjectedComponentFieldValueConduit extends ReadOnlyComponentFieldConduit 038 { 039 private final ComponentResources resources; 040 041 private final String fieldName, componentId, type; 042 043 private Component embedded; 044 045 private boolean optional; 046 047 private InjectedComponentFieldValueConduit(ComponentResources resources, String fieldName, String type, 048 String componentId, boolean optional) 049 { 050 super(resources, fieldName); 051 052 this.resources = resources; 053 this.fieldName = fieldName; 054 this.componentId = componentId; 055 this.type = type; 056 this.optional = optional; 057 058 resources.getPageLifecycleCallbackHub().addPageAttachedCallback(new Runnable() 059 { 060 public void run() 061 { 062 load(); 063 } 064 }); 065 } 066 067 private void load() 068 { 069 try 070 { 071 embedded = resources.getEmbeddedComponent(componentId); 072 } catch (UnknownValueException ex) 073 { 074 if (this.optional) { 075 return; 076 } 077 078 throw new RuntimeException(String.format("Unable to inject component into field %s of class %s: %s", 079 fieldName, getComponentClassName(), ex.getMessage()), ex); 080 } 081 082 @SuppressWarnings("rawtypes") 083 Class fieldType = classCache.forName(type); 084 085 if (!fieldType.isInstance(embedded)) 086 { 087 final String className = fieldType.getName(); 088 final String message = String 089 .format( 090 "Unable to inject component '%s' into field %s of %s. Class %s is not assignable to a field of type %s.", 091 componentId, fieldName, getComponentClassName(), 092 embedded.getClass().getName(), className); 093 if (embedded.getClass().getName().equals(className)) 094 { 095 logger.warn(message); 096 throw new DifferentClassVersionsException(message, className, 097 fieldType.getClassLoader(), embedded.getClass().getClassLoader()); 098 } 099 else 100 { 101 throw new RuntimeException(message); 102 } 103 } 104 105 } 106 107 private String getComponentClassName() 108 { 109 return resources.getComponentModel().getComponentClassName(); 110 } 111 112 public Object get(Object instance, InstanceContext context) 113 { 114 return embedded; 115 } 116 } 117 118 private final ComponentClassCache classCache; 119 120 private final Logger logger; 121 122 public InjectComponentWorker(ComponentClassCache classCache, final Logger logger) 123 { 124 this.classCache = classCache; 125 this.logger = logger; 126 } 127 128 public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) 129 { 130 for (PlasticField field : plasticClass.getFieldsWithAnnotation(InjectComponent.class)) 131 { 132 InjectComponent annotation = field.getAnnotation(InjectComponent.class); 133 134 field.claim(annotation); 135 136 final String type = field.getTypeName(); 137 138 final String componentId = getComponentId(field, annotation); 139 140 final String fieldName = field.getName(); 141 142 final boolean optional = annotation.optional(); 143 144 ComputedValue<FieldConduit<Object>> provider = new ComputedValue<FieldConduit<Object>>() 145 { 146 public FieldConduit<Object> get(InstanceContext context) 147 { 148 ComponentResources resources = context.get(ComponentResources.class); 149 150 return new InjectedComponentFieldValueConduit(resources, fieldName, type, componentId, optional); 151 } 152 }; 153 154 field.setComputedConduit(provider); 155 } 156 157 } 158 159 private String getComponentId(PlasticField field, InjectComponent annotation) 160 { 161 String id = annotation.value(); 162 163 if (InternalUtils.isNonBlank(id)) 164 return id; 165 166 return InternalUtils.stripMemberName(field.getName()); 167 } 168}