001 // Copyright 2008, 2009 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.ioc.internal.services;
016
017 import org.apache.tapestry5.ioc.MethodAdvice;
018 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019 import org.apache.tapestry5.ioc.internal.util.Defense;
020 import org.apache.tapestry5.ioc.internal.util.OneShotLock;
021 import org.apache.tapestry5.ioc.services.*;
022
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.Method;
025 import java.lang.reflect.Modifier;
026 import java.util.Arrays;
027 import java.util.Map;
028 import java.util.Set;
029
030 public class AspectInterceptorBuilderImpl<T> implements AspectInterceptorBuilder<T>
031 {
032 private final ClassFactory classFactory;
033
034 private final Class<T> serviceInterface;
035
036 private final ClassFab interceptorFab;
037
038 private final ConstantInjectorImpl injector;
039
040 private final String delegateFieldName;
041
042 private final String description;
043
044 private final OneShotLock lock = new OneShotLock();
045
046 private final Set<Method> remainingMethods = CollectionFactory.newSet();
047
048 private final Map<Method, AdvisedMethodInvocationBuilder> methodToBuilder = CollectionFactory.newMap();
049
050 /**
051 * Set to true if we ever see toString() as a method of the interface; either advised or pass thru. If false at the
052 * end, we add our own implementation.
053 */
054 private boolean sawToString;
055
056 public AspectInterceptorBuilderImpl(ClassFactory classFactory, Class<T> serviceInterface, T delegate,
057 String description)
058 {
059 this.classFactory = classFactory;
060 this.serviceInterface = serviceInterface;
061 this.description = description;
062
063 interceptorFab = this.classFactory.newClass(serviceInterface);
064
065 injector = new ConstantInjectorImpl(interceptorFab);
066
067 delegateFieldName = injector.inject(serviceInterface, delegate);
068
069 remainingMethods.addAll(Arrays.asList(serviceInterface.getMethods()));
070 }
071
072 public void adviseMethod(Method method, MethodAdvice advice)
073 {
074 Defense.notNull(method, "method");
075 Defense.notNull(advice, "advice");
076
077 lock.check();
078
079 AdvisedMethodInvocationBuilder builder = methodToBuilder.get(method);
080
081 if (builder == null)
082 {
083 if (!remainingMethods.contains(method))
084 throw new IllegalArgumentException(
085 String.format("Method %s is not defined for interface %s.", method, serviceInterface));
086
087 // One less method to pass thru to the delegate
088
089 remainingMethods.remove(method);
090
091 sawToString |= ClassFabUtils.isToString(method);
092
093 builder = new AdvisedMethodInvocationBuilder(classFactory, serviceInterface, method);
094
095 methodToBuilder.put(method, builder);
096 }
097
098 builder.addAdvice(advice);
099 }
100
101 public void adviseAllMethods(MethodAdvice advice)
102 {
103 for (Method m : serviceInterface.getMethods())
104 adviseMethod(m, advice);
105 }
106
107 public Class getInterface()
108 {
109 return serviceInterface;
110 }
111
112 public T build()
113 {
114 lock.lock();
115
116 // Finish up each method that has been advised
117
118 for (AdvisedMethodInvocationBuilder builder : methodToBuilder.values())
119 {
120 builder.commit(interceptorFab, delegateFieldName, injector);
121 }
122
123 // Hit all the methods that haven't been referenced so far.
124
125 addPassthruMethods();
126
127 // And if we haven't seen a toString(), we can add it now.
128
129 if (!sawToString)
130 interceptorFab.addToString(description);
131
132 injector.implementConstructor();
133
134 Class interceptorClass = interceptorFab.createClass();
135
136 Object[] parameters = injector.getParameters();
137
138 try
139 {
140 Constructor constructor = interceptorClass.getConstructors()[0];
141
142 Object raw = constructor.newInstance(parameters);
143
144 return serviceInterface.cast(raw);
145 }
146 catch (Exception ex)
147 {
148 throw new RuntimeException(ex);
149 }
150 }
151
152 private void addPassthruMethods()
153 {
154 for (Method m : remainingMethods)
155 {
156 sawToString |= ClassFabUtils.isToString(m);
157
158 MethodSignature sig = new MethodSignature(m);
159
160 String body = String.format("return ($r) %s.%s($$);", delegateFieldName, m.getName());
161
162 interceptorFab.addMethod(Modifier.PUBLIC, sig, body);
163 }
164 }
165 }