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
015package org.apache.hivemind.lib.impl;
016
017import java.lang.reflect.Modifier;
018
019import org.apache.hivemind.ApplicationRuntimeException;
020import org.apache.hivemind.ServiceImplementationFactory;
021import org.apache.hivemind.ServiceImplementationFactoryParameters;
022import org.apache.hivemind.service.BodyBuilder;
023import org.apache.hivemind.service.ClassFab;
024import org.apache.hivemind.service.ClassFabUtils;
025import org.apache.hivemind.service.ClassFactory;
026import org.apache.hivemind.service.MethodIterator;
027import org.apache.hivemind.service.MethodSignature;
028import org.apache.hivemind.util.ConstructorUtils;
029import org.apache.hivemind.util.PropertyAdaptor;
030import org.apache.hivemind.util.PropertyUtils;
031
032/**
033 * Factory that dynamically exposes a property of another service. A proxy is constructed that
034 * accesses the target service and obtains a property from that. The service interface of the
035 * constructed service must match the type of the exposed property.
036 * 
037 * @author Howard Lewis Ship
038 */
039public class ServicePropertyFactory implements ServiceImplementationFactory
040{
041    private ClassFactory _classFactory;
042
043    public Object createCoreServiceImplementation(
044            ServiceImplementationFactoryParameters factoryParameters)
045    {
046        ServicePropertyFactoryParameter p = (ServicePropertyFactoryParameter) factoryParameters
047                .getParameters().get(0);
048
049        Object targetService = p.getService();
050        String propertyName = p.getPropertyName();
051
052        PropertyAdaptor pa = PropertyUtils.getPropertyAdaptor(targetService, propertyName);
053
054        String readMethodName = pa.getReadMethodName();
055
056        if (readMethodName == null)
057            throw new ApplicationRuntimeException(ImplMessages.servicePropertyNotReadable(
058                    propertyName,
059                    targetService), null, p.getLocation(), null);
060        Class serviceInterface = factoryParameters.getServiceInterface();
061
062        if (!(serviceInterface.isAssignableFrom(pa.getPropertyType())))
063            throw new ApplicationRuntimeException(ImplMessages.servicePropertyWrongType(
064                    propertyName,
065                    targetService,
066                    pa.getPropertyType(),
067                    serviceInterface), p.getLocation(), null);
068
069        // Now we're good to go.
070
071        String name = ClassFabUtils.generateClassName(serviceInterface);
072
073        ClassFab cf = _classFactory.newClass(name, Object.class);
074
075        addInfrastructure(cf, targetService, serviceInterface, propertyName, readMethodName);
076
077        addMethods(
078                cf,
079                factoryParameters.getServiceId(),
080                serviceInterface,
081                propertyName,
082                targetService);
083
084        Class proxyClass = cf.createClass();
085
086        try
087        {
088            return ConstructorUtils.invokeConstructor(proxyClass, new Object[]
089            { targetService });
090        }
091        catch (Throwable ex)
092        {
093            throw new ApplicationRuntimeException(ex.getMessage(), p.getLocation(), ex);
094        }
095    }
096
097    private void addInfrastructure(ClassFab cf, Object targetService, Class serviceInterface,
098            String propertyName, String readPropertyMethodName)
099    {
100        cf.addInterface(serviceInterface);
101
102        Class targetServiceClass = ClassFabUtils.getInstanceClass(targetService, serviceInterface);
103
104        cf.addField("_targetService", targetServiceClass);
105
106        cf.addConstructor(new Class[]
107        { targetServiceClass }, null, "{ super(); _targetService = $1; }");
108
109        BodyBuilder b = new BodyBuilder();
110
111        b.begin();
112        b.addln(
113                "{0} property = _targetService.{1}();",
114                serviceInterface.getName(),
115                readPropertyMethodName);
116
117        b.addln("if (property == null)");
118        b.add("  throw new java.lang.NullPointerException(");
119        b.addQuoted(ImplMessages.servicePropertyWasNull(propertyName, targetService));
120        b.addln(");");
121
122        b.addln("return property;");
123
124        b.end();
125
126        MethodSignature sig = new MethodSignature(serviceInterface, "_targetServiceProperty", null,
127                null);
128        cf.addMethod(Modifier.FINAL | Modifier.PRIVATE, sig, b.toString());
129    }
130
131    private void addMethods(ClassFab cf, String serviceId, Class serviceInterface,
132            String propertyName, Object targetService)
133    {
134        MethodIterator mi = new MethodIterator(serviceInterface);
135
136        while (mi.hasNext())
137        {
138            MethodSignature sig = mi.next();
139
140            String body = "return ($r) _targetServiceProperty()." + sig.getName() + "($$);";
141
142            cf.addMethod(Modifier.PUBLIC, sig, body);
143        }
144
145        if (!mi.getToString())
146            ClassFabUtils.addToStringMethod(cf, ImplMessages.servicePropertyToString(
147                    serviceId,
148                    serviceInterface,
149                    propertyName,
150                    targetService));
151    }
152
153    public void setClassFactory(ClassFactory factory)
154    {
155        _classFactory = factory;
156    }
157}