001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.ioc.internal.services; 014 015import org.apache.tapestry5.commons.services.PlasticProxyFactory; 016import org.apache.tapestry5.commons.util.CollectionFactory; 017import org.apache.tapestry5.plastic.*; 018import org.slf4j.Logger; 019 020import java.util.Iterator; 021import java.util.List; 022 023/** 024 * Used by the {@link org.apache.tapestry5.ioc.internal.services.PipelineBuilderImpl} to create bridge classes and to 025 * create instances of bridge classes. A bridge class implements the <em>service</em> interface. Within the chain, 026 * bridge 1 is passed to filter 1. Invoking methods on bridge 1 will invoke methods on filter 2. 027 */ 028public class BridgeBuilder<S, F> 029{ 030 private static final MethodDescription GET_PIPELINE_FILTER = new MethodDescription("java.lang.Object", 031 "getPipelineFilter"); 032 033 private static final MethodDescription GET_PIPELINE_NEXT = new MethodDescription("java.lang.Object", 034 "getPipelineNext"); 035 036 private final Logger logger; 037 038 private final Class<S> serviceInterface; 039 040 private final Class<F> filterInterface; 041 042 private final String header; 043 044 private final FilterMethodAnalyzer filterMethodAnalyzer; 045 046 private final PlasticProxyFactory proxyFactory; 047 048 private ClassInstantiator<S> instantiator; 049 050 public BridgeBuilder(Logger logger, Class<S> serviceInterface, Class<F> filterInterface, PlasticProxyFactory proxyFactory) 051 { 052 this.logger = logger; 053 this.serviceInterface = serviceInterface; 054 this.filterInterface = filterInterface; 055 056 this.proxyFactory = proxyFactory; 057 058 header = PipelineDescriber.header(serviceInterface, filterInterface); 059 060 filterMethodAnalyzer = new FilterMethodAnalyzer(serviceInterface); 061 } 062 063 /** 064 * Instantiates a bridge object. 065 * 066 * @param nextBridge 067 * the next Bridge object in the pipeline, or the terminator service 068 * @param filter 069 * the filter object for this step of the pipeline 070 */ 071 public S instantiateBridge(S nextBridge, F filter) 072 { 073 if (instantiator == null) 074 createInstantiator(); 075 076 return instantiator.with(filterInterface, filter).with(serviceInterface, nextBridge).newInstance(); 077 } 078 079 private void createInstantiator() 080 { 081 instantiator = proxyFactory.createProxy(serviceInterface, new PlasticClassTransformer() 082 { 083 @Override 084 public void transform(PlasticClass plasticClass) 085 { 086 PlasticField filterField = plasticClass.introduceField(filterInterface, "filter") 087 .injectFromInstanceContext(); 088 PlasticField nextField = plasticClass.introduceField(serviceInterface, "next") 089 .injectFromInstanceContext(); 090 091 processMethods(plasticClass, filterField, nextField); 092 093 introducePipelineStep(plasticClass, filterField, nextField); 094 } 095 }); 096 } 097 098 /** 099 * Makes the bridge a {@link PipelineStep}, exposing the filter it wraps and what it delegates to, and adds a 100 * {@code toString()} that describes the pipeline from this step onwards. 101 */ 102 private void introducePipelineStep(PlasticClass plasticClass, final PlasticField filterField, 103 final PlasticField nextField) 104 { 105 // Rule out that a service interface declares one of the methods we want to introduce. 106 // Fall back to a plain description. 107 108 if (plasticClass.isMethodImplemented(GET_PIPELINE_FILTER) || plasticClass.isMethodImplemented(GET_PIPELINE_NEXT)) 109 { 110 plasticClass.addToString(header); 111 112 return; 113 } 114 115 plasticClass.introduceInterface(PipelineStep.class); 116 117 plasticClass.introduceMethod(GET_PIPELINE_FILTER, new InstructionBuilderCallback() 118 { 119 @Override 120 public void doBuild(InstructionBuilder builder) 121 { 122 builder.loadThis().getField(filterField).returnResult(); 123 } 124 }); 125 126 plasticClass.introduceMethod(GET_PIPELINE_NEXT, new InstructionBuilderCallback() 127 { 128 @Override 129 public void doBuild(InstructionBuilder builder) 130 { 131 builder.loadThis().getField(nextField).returnResult(); 132 } 133 }); 134 135 addToString(plasticClass); 136 } 137 138 /** 139 * Adds a {@code toString()} that delegates to 140 * {@link PipelineDescriber#describe(String, PipelineStep)}, describing the whole chain from this bridge onwards. 141 * 142 * Nothing is added when {@code toString()} is part of the service interface itself; in that case the method is 143 * already bridged to the filter, and that takes precedence. The pipeline can still be walked as a 144 * {@link PipelineStep}. 145 */ 146 private void addToString(PlasticClass plasticClass) 147 { 148 if (plasticClass.isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) 149 return; 150 151 plasticClass.introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() 152 { 153 @Override 154 public void doBuild(InstructionBuilder builder) 155 { 156 builder.loadConstant(header).loadThis(); 157 158 builder.invokeStatic(PipelineDescriber.class, String.class, "describe", String.class, 159 PipelineStep.class); 160 161 builder.returnResult(); 162 } 163 }); 164 } 165 166 private void processMethods(PlasticClass plasticClass, PlasticField filterField, PlasticField nextField) 167 { 168 List<MethodSignature> serviceMethods = CollectionFactory.newList(); 169 List<MethodSignature> filterMethods = CollectionFactory.newList(); 170 171 MethodIterator mi = new MethodIterator(serviceInterface); 172 173 while (mi.hasNext()) 174 { 175 serviceMethods.add(mi.next()); 176 } 177 178 mi = new MethodIterator(filterInterface); 179 180 while (mi.hasNext()) 181 { 182 filterMethods.add(mi.next()); 183 } 184 185 while (!serviceMethods.isEmpty()) 186 { 187 MethodSignature ms = serviceMethods.remove(0); 188 189 addBridgeMethod(plasticClass, filterField, nextField, ms, filterMethods); 190 } 191 192 reportExtraFilterMethods(filterMethods); 193 } 194 195 private void reportExtraFilterMethods(List filterMethods) 196 { 197 Iterator i = filterMethods.iterator(); 198 199 while (i.hasNext()) 200 { 201 MethodSignature ms = (MethodSignature) i.next(); 202 203 logger.error("Method {} of filter interface {} does not have a matching method in {}.", ms, filterInterface.getName(), serviceInterface.getName()); 204 } 205 } 206 207 /** 208 * Finds a matching method in filterMethods for the given service method. A matching method has the same signature 209 * as the service interface method, but with an additional parameter matching the service interface itself. 210 * 211 * The matching method signature from the list of filterMethods is removed and code generation strategies for making 212 * the two methods call each other are added. 213 */ 214 private void addBridgeMethod(PlasticClass plasticClass, PlasticField filterField, PlasticField nextField, 215 final MethodSignature ms, List filterMethods) 216 { 217 PlasticMethod method = plasticClass.introduceMethod(ms.getMethod()); 218 219 Iterator i = filterMethods.iterator(); 220 221 while (i.hasNext()) 222 { 223 MethodSignature fms = (MethodSignature) i.next(); 224 225 int position = filterMethodAnalyzer.findServiceInterfacePosition(ms, fms); 226 227 if (position >= 0) 228 { 229 bridgeServiceMethodToFilterMethod(method, filterField, nextField, position, ms, fms); 230 i.remove(); 231 return; 232 } 233 } 234 235 method.changeImplementation(new InstructionBuilderCallback() 236 { 237 @Override 238 public void doBuild(InstructionBuilder builder) 239 { 240 String message = String.format("Method %s has no match in filter interface %s.", ms, filterInterface.getName()); 241 242 logger.error(message); 243 244 builder.throwException(RuntimeException.class, message); 245 } 246 }); 247 } 248 249 private void bridgeServiceMethodToFilterMethod(PlasticMethod method, final PlasticField filterField, 250 final PlasticField nextField, final int position, MethodSignature ms, final MethodSignature fms) 251 { 252 method.changeImplementation(new InstructionBuilderCallback() 253 { 254 @Override 255 public void doBuild(InstructionBuilder builder) 256 { 257 builder.loadThis().getField(filterField); 258 259 int argumentIndex = 0; 260 261 for (int i = 0; i < fms.getParameterTypes().length; i++) 262 { 263 if (i == position) 264 { 265 builder.loadThis().getField(nextField); 266 } else 267 { 268 builder.loadArgument(argumentIndex++); 269 } 270 } 271 272 builder.invoke(fms.getMethod()).returnResult(); 273 } 274 }); 275 } 276 277}