001 // Copyright 2004, 2005 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.hivemind.impl;
016
017 import java.io.Serializable;
018 import java.lang.reflect.Modifier;
019
020 import org.apache.hivemind.internal.Module;
021 import org.apache.hivemind.internal.ser.ServiceSerializationHelper;
022 import org.apache.hivemind.service.BodyBuilder;
023 import org.apache.hivemind.service.ClassFab;
024 import org.apache.hivemind.service.ClassFabUtils;
025 import org.apache.hivemind.service.ClassFactory;
026 import org.apache.hivemind.service.MethodIterator;
027 import org.apache.hivemind.service.MethodSignature;
028
029 /**
030 * Class used to assist extension points in creating proxies.
031 *
032 * @author Howard Lewis Ship
033 */
034 public final class ProxyBuilder
035 {
036 private Class _serviceInterface;
037
038 private ClassFab _classFab;
039
040 private String _type;
041
042 /**
043 * Constructs a new builder. The type will be incorporated into value returned by the
044 * <code>toString()</code> method.
045 * The generated proxy has an constructor that expects the extension point id as single parameter.
046 *
047 * @param type
048 * used as part of the <code>toString()</code> method's return value
049 * @param module
050 * the module that constructs the proxy
051 * @param serviceInterface
052 * the interface of the proxied class.
053 * @param declaredInterface
054 * the interface of the proxied class or the class itself if the proxied class is actually a POJO.
055 * @param outerProxy
056 * if false, then the proxy can extend the configuration points service interface always.
057 * If true and the declared interface is actually a bean class (not
058 * an interface), then the proxy will be a subclass of that bean.
059 */
060 public ProxyBuilder(String type, Module module, Class serviceInterface, Class declaredInterface, boolean outerProxy)
061 {
062 _type = type;
063 _serviceInterface = serviceInterface;
064
065 ClassFactory factory = (ClassFactory) module.getService(
066 "hivemind.ClassFactory",
067 ClassFactory.class);
068
069 boolean extendBeanClass = outerProxy && !declaredInterface.isInterface();
070 Class baseClass = extendBeanClass ? declaredInterface : Object.class;
071
072 _classFab = factory.newClass(ClassFabUtils.generateClassName(_serviceInterface), baseClass);
073
074 _classFab.addField("_extensionPointId", String.class);
075
076 _classFab.addConstructor(new Class[] { String.class }, null, "{ _extensionPointId = $1; }");
077
078 if (!extendBeanClass)
079 _classFab.addInterface(_serviceInterface);
080
081 // Not exactly certain this will work with non-interface beans that already
082 // are serializable!
083
084 if (outerProxy)
085 addSerializable();
086 }
087
088 /** @since 1.1 */
089 private void addSerializable()
090 {
091 _classFab.addInterface(Serializable.class);
092
093 BodyBuilder bb = new BodyBuilder();
094
095 bb.add(
096 "return {0}.getServiceSerializationSupport().getServiceTokenForService(_extensionPointId);",
097 ServiceSerializationHelper.class.getName());
098
099 MethodSignature sig = new MethodSignature(Object.class, "writeReplace", null, null);
100
101 _classFab.addMethod(Modifier.PRIVATE, sig, bb.toString());
102 }
103
104 public ClassFab getClassFab()
105 {
106 return _classFab;
107 }
108
109 /**
110 * @see #addServiceMethods(String, boolean)
111 */
112 public void addServiceMethods(String indirection)
113 {
114 addServiceMethods(indirection, true);
115 }
116
117 /**
118 * Creates the service methods for the class.
119 *
120 * @param indirection
121 * the name of a variable, or a method invocation snippet, used to redirect the
122 * invocation on the proxy to the actual service implementation.
123 * @param addToString if true, a implementation of the toString method is generated that
124 * returns some info about the proxy
125 */
126 public void addServiceMethods(String indirection, boolean addToString)
127 {
128 BodyBuilder builder = new BodyBuilder();
129
130 MethodIterator mi = new MethodIterator(_serviceInterface);
131 while (mi.hasNext())
132 {
133 MethodSignature m = mi.next();
134 if( !_classFab.containsMethod( m ) )
135 {
136 builder.clear();
137 builder.begin();
138 builder.add("return ($r) ");
139 builder.add(indirection);
140 builder.add(".");
141 builder.add(m.getName());
142 builder.addln("($$);");
143 builder.end();
144 _classFab.addMethod(Modifier.PUBLIC, m, builder.toString());
145 }
146 }
147
148 if (!mi.getToString() && addToString)
149 ClassFabUtils.addToStringMethod(_classFab, "<" + _type + " for \" + _extensionPointId + \""
150 + "(" + _serviceInterface.getName() + ")>");
151 }
152 }