/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.aries.blueprint.annotation.impl; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.apache.aries.blueprint.annotation.Arg; import org.apache.aries.blueprint.annotation.Bean; import org.apache.aries.blueprint.annotation.Bind; import org.apache.aries.blueprint.annotation.Blueprint; import org.apache.aries.blueprint.annotation.Destroy; import org.apache.aries.blueprint.annotation.Init; import org.apache.aries.blueprint.annotation.Inject; import org.apache.aries.blueprint.annotation.Reference; import org.apache.aries.blueprint.annotation.ReferenceList; import org.apache.aries.blueprint.annotation.ReferenceListener; import org.apache.aries.blueprint.annotation.Register; import org.apache.aries.blueprint.annotation.RegistrationListener; import org.apache.aries.blueprint.annotation.Service; import org.apache.aries.blueprint.annotation.ServiceProperty; import org.apache.aries.blueprint.annotation.Unbind; import org.apache.aries.blueprint.annotation.Unregister; import org.apache.aries.blueprint.annotation.service.BlueprintAnnotationScanner; import org.apache.aries.blueprint.jaxb.Targument; import org.apache.aries.blueprint.jaxb.Tbean; import org.apache.aries.blueprint.jaxb.Tblueprint; import org.apache.aries.blueprint.jaxb.Tdescription; import org.apache.aries.blueprint.jaxb.Tinterfaces; import org.apache.aries.blueprint.jaxb.Tproperty; import org.apache.aries.blueprint.jaxb.Treference; import org.apache.aries.blueprint.jaxb.TreferenceList; import org.apache.aries.blueprint.jaxb.TreferenceListener; import org.apache.aries.blueprint.jaxb.TregistrationListener; import org.apache.aries.blueprint.jaxb.Tservice; import org.apache.aries.blueprint.jaxb.TservicePropertyEntry; import org.apache.aries.blueprint.jaxb.TtypeConverters; import org.apache.aries.blueprint.jaxb.Tvalue; import org.apache.xbean.finder.BundleAnnotationFinder; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.blueprint.container.Converter; import org.osgi.service.packageadmin.PackageAdmin; public class BlueprintAnnotationScannerImpl implements BlueprintAnnotationScanner { private final BundleContext context; public BlueprintAnnotationScannerImpl(BundleContext bc) { this.context = bc; } private BundleContext getBlueprintExtenderContext() { Bundle[] bundles = this.context.getBundles(); for (Bundle b : bundles) { if (b.getSymbolicName().equals("org.apache.aries.blueprint.core")) { return b.getBundleContext(); } } return null; } private BundleAnnotationFinder createBundleAnnotationFinder(Bundle bundle) { ServiceReference sr = this.context.getServiceReference(PackageAdmin.class.getName()); PackageAdmin pa = (PackageAdmin) this.context.getService(sr); BundleAnnotationFinder baf = null; try { baf = new BundleAnnotationFinder(pa, bundle); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } this.context.ungetService(sr); return baf; } public URL createBlueprintModel(Bundle bundle) { Tblueprint tblueprint = generateBlueprintModel(bundle); if (tblueprint != null) { // create the generated blueprint xml file in bundle storage // area BundleContext ctx = getBlueprintExtenderContext(); if (ctx == null) { // blueprint extender doesn't exist, let' still generate the // bundle, using the bundle's bundle context ctx = bundle.getBundleContext(); } File dir = ctx.getDataFile(bundle.getSymbolicName() + "/" + bundle.getVersion() + "/"); if (!dir.exists()) { dir.mkdirs(); } String blueprintPath = cachePath(bundle, "annotation-generated-blueprint.xml"); File file = ctx.getDataFile(blueprintPath); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { marshallOBRModel(tblueprint, file); } catch (JAXBException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { return file.toURL(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return null; } private void marshallOBRModel(Tblueprint tblueprint, File blueprintFile) throws JAXBException { JAXBContext context = JAXBContext.newInstance(Tblueprint.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal(tblueprint, blueprintFile); } private Tblueprint generateBlueprintModel(Bundle bundle) { BundleAnnotationFinder baf = createBundleAnnotationFinder(bundle); // we don't trust baf when it comes to returning classes just once (ARIES-654) Set<Class> blueprintClasses = new LinkedHashSet<Class>(baf.findAnnotatedClasses(Blueprint.class)); Set<Class> beanClasses = new HashSet<Class>(baf.findAnnotatedClasses(Bean.class)); Set<Class> refListenerClasses = new HashSet<Class>(baf.findAnnotatedClasses(ReferenceListener.class)); Set<Class> regListenerClasses = new HashSet<Class>(baf.findAnnotatedClasses(RegistrationListener.class)); Map<String, TreferenceListener> reflMap = new HashMap<String, TreferenceListener>(); Map<String, TregistrationListener> reglMap = new HashMap<String, TregistrationListener>(); Tblueprint tblueprint = new Tblueprint(); if (!blueprintClasses.isEmpty()) { // use the first annotated blueprint annotation Blueprint blueprint = (Blueprint)blueprintClasses.iterator().next().getAnnotation(Blueprint.class); tblueprint.setDefaultActivation(blueprint.defaultActivation()); tblueprint.setDefaultAvailability(blueprint.defaultAvailability()); tblueprint.setDefaultTimeout(convertToBigInteger(blueprint.defaultTimeout())); } List<Object> components = tblueprint.getServiceOrReferenceListOrBean(); // try to process classes that have @ReferenceListener or @RegistrationLister first // as we want the refl and regl maps populated before processing @Bean annotation. for (Class refListener : refListenerClasses) { Bean bean = (Bean) refListener.getAnnotation(Bean.class); // register the treference with its id TreferenceListener tref = generateTrefListener(refListener); if (bean.id().length() > 0) { reflMap.put(bean.id(), tref); } else { throw new BlueprintAnnotationException("Unable to find the id for the @ReferenceListener annotated class " + refListener.getName()); } } for (Class regListener : regListenerClasses) { Bean bean = (Bean) regListener.getAnnotation(Bean.class); // register the tregistrationListener with its id TregistrationListener tref = generateTregListener(regListener); if (bean.id().length() > 0) { reglMap.put(bean.id(), tref); } else { throw new BlueprintAnnotationException("Unable to find the id for the @RegistrationListener annotated class " + regListener.getName()); } } for (Class clazz : beanClasses) { // @Bean annotation detected Bean bean = (Bean)clazz.getAnnotation(Bean.class); Tbean tbean = new Tbean(); // process depends-on property String[] dependsOn = bean.dependsOn(); if (!containsValid(dependsOn)) { tbean.setDependsOn(null); } else { List<String> dons = Arrays.asList(dependsOn); tbean.setDependsOn(dons); } // process id property String id = bean.id(); if (id.length() > 0) { tbean.setId(id); } else { // should we auto generate an id, based on the class name? tbean.setId(clazz.getSimpleName()); } // process the clazz property tbean.setClazz(clazz.getName()); // process activation String activation = bean.activation(); if (activation.length() > 0) { if (activation.equalsIgnoreCase("eager") || activation.equalsIgnoreCase("lazy")) { tbean.setActivation(bean.activation()); } else { throw new BlueprintAnnotationException("Invalid bean activation value " + activation + " for " + clazz.getName()); } } // process description if (bean.description().length() > 0) { Tdescription desp = new Tdescription(); desp.getContent().add(bean.description()); tbean.setDescription(desp); } // process scope String scope = bean.scope(); if (scope.length() > 0) { if (scope.equalsIgnoreCase("singleton") || scope.equalsIgnoreCase("prototype")) { tbean.setScope(scope); } else { throw new BlueprintAnnotationException("Invalid bean scope value " + scope + " for " + clazz.getName()); } } // process factory ref String factoryRef = bean.factoryRef(); if (factoryRef.length() > 0) { tbean.setFactoryRef(factoryRef); } // process factory method String factoryMethod = bean.factoryMethod(); if (factoryMethod.length() > 0) { tbean.setFactoryMethod(factoryMethod); } List<Object> props = tbean.getArgumentOrPropertyOrAny(); // process args Arg[] args = bean.args(); if (args.length > 0) { for (int i = 0; i < args.length; i++) { Targument targ = createTargument(args[i]); if (targ != null) { props.add(targ); } } } Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { if (fields[i].isAnnotationPresent(Inject.class)) { if (fields[i].isAnnotationPresent(Reference.class)) { // the field is also annotated with @Reference Reference ref = fields[i].getAnnotation(Reference.class); Treference tref = generateTref(ref, reflMap); components.add(tref); } else if (fields[i].isAnnotationPresent(ReferenceList.class)) { // the field is also annotated with @ReferenceList ReferenceList ref = fields[i].getAnnotation(ReferenceList.class); TreferenceList tref = generateTrefList(ref, reflMap); components.add(tref); } else { Tproperty tp = createTproperty(fields[i].getName(), fields[i].getAnnotation(Inject.class)); props.add(tp); } } } // check if the bean also declares init, destroy or inject annotation on methods Method[] methods = clazz.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(Init.class)) { tbean.setInitMethod(methods[i].getName()); } else if (methods[i].isAnnotationPresent(Destroy.class)) { tbean.setDestroyMethod(methods[i].getName()); } else if (methods[i].isAnnotationPresent(Inject.class)) { String propertyName = convertFromMethodName(methods[i].getName()); Tproperty tp = createTproperty(propertyName, methods[i].getAnnotation(Inject.class)); props.add(tp); } else if (methods[i].isAnnotationPresent(Arg.class)) { Targument targ = createTargument(methods[i].getAnnotation(Arg.class)); props.add(targ); } } // check if the bean also declares service if (clazz.getAnnotation(Service.class) != null) { Tservice tservice = generateTservice(clazz, id, reglMap); components.add(tservice); } // check if the clazz implement Converter, if so, it is Converter boolean isConverter = isConverter(clazz); if (isConverter) { TtypeConverters converters = tblueprint.getTypeConverters(); List<Object> objects = converters.getBeanOrReferenceOrRef(); objects.add(tbean); } else { components.add(tbean); } } return tblueprint; } private TreferenceListener generateTrefListener(Class refListener) { ReferenceListener rl = (ReferenceListener) refListener.getAnnotation(ReferenceListener.class); String ref = rl.ref(); String bind = null; String unbind = null; // also check bind/unbind method Method[] methods = refListener.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(Bind.class)) { if (bind == null) { bind = methods[i].getName(); } else if (!bind.equals(methods[i].getName())) { throw new BlueprintAnnotationException("@Bind annottaed method for reference listener " + refListener.getName() + " are not consistent"); } continue; } if (methods[i].isAnnotationPresent(Unbind.class)) { if (unbind == null) { unbind = methods[i].getName(); } else if (!unbind.equals(methods[i].getName())) { throw new BlueprintAnnotationException("@Unbind annotated method for reference listener " + refListener.getName() + " are not consistent"); } continue; } } TreferenceListener trl = new TreferenceListener(); if (bind != null) { trl.setBindMethod(bind); } if (unbind != null) { trl.setUnbindMethod(unbind); } if (ref != null) { trl.setRefAttribute(ref); } return trl; } private TregistrationListener generateTregListener(Class regListener) { RegistrationListener rl = (RegistrationListener) regListener.getAnnotation(RegistrationListener.class); String register = null; String unregister = null; // also check bind/unbind method Method[] methods = regListener.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isAnnotationPresent(Register.class)) { if (register == null) { register = methods[i].getName(); } else if (!register.equals(methods[i].getName())) { throw new BlueprintAnnotationException("@Register annottaed method for registration listener " + regListener.getName() + " are not consistent"); } continue; } if (methods[i].isAnnotationPresent(Unregister.class)) { if (unregister == null) { unregister = methods[i].getName(); } else if (!unregister.equals(methods[i].getName())) { throw new BlueprintAnnotationException("@Unregister annotated method for registration listener " + regListener.getName() + " are not consistent"); } continue; } } TregistrationListener trl = new TregistrationListener(); if (register != null) { trl.setRegistrationMethod(register); } if (unregister != null) { trl.setUnregistrationMethod(unregister); } return trl; } private Targument createTargument(Arg arg) { String value = arg.value(); String ref = arg.ref(); Targument targ = null; if (value.length() > 0) { targ = new Targument(); targ.setValueAttribute(value); } if (ref.length() > 0) { if (targ == null) { targ = new Targument(); } targ.setRefAttribute(ref); } // TODO process description, index of Arg annotation return targ; } private String convertFromMethodName(String name) { if (name.length() > 3) { name = name.substring(3); } else { throw new BlueprintAnnotationException("The annotated method name " + name + " is invalid"); } String firstChar = name.substring(0, 1).toLowerCase(); if (name.length() == 1) { return firstChar; } else { return firstChar + name.substring(1); } } /** * @param nm method or field name * @param inj inject annotation * @return */ private Tproperty createTproperty(String nm, Inject inj) { String value = inj.value(); String ref = inj.ref(); String name = inj.name(); String desp = inj.description(); Tproperty tp = new Tproperty(); if (value.length() > 0) { Tvalue tvalue = new Tvalue(); tvalue.setContent(value); tp.setValue(tvalue); } if (ref.length() > 0) { tp.setRefAttribute(ref); } if (name.length() > 0) { tp.setName(name); } else { tp.setName(nm); } if (desp.length() > 0) { Tdescription tdesp = new Tdescription(); tdesp.getContent().add(desp); tp.setDescription(tdesp); } return tp; } private boolean isConverter(Class clazz) { Class[] classes = clazz.getInterfaces(); for (int i = 0; i < classes.length; i++) { if (classes[i].getName().equals(Converter.class.getName())) { return true; } } return false; } private BigInteger convertToBigInteger(int timeout) { return BigInteger.valueOf(timeout * 1000); } private boolean containsValid(String[] dependsOn) { for (int i = 0; i < dependsOn.length; i++) { if (dependsOn[i].length() != 0) { return true; } } return false; } // copy from blueprint extender private String cachePath(Bundle bundle, String filePath) { return bundle.getSymbolicName() + "/" + bundle.getVersion() + "/" + filePath; } private Treference generateTref(Reference ref, Map<String, TreferenceListener> reflMap) { String id = ref.id(); String availability = ref.availability(); String compName = ref.componentName(); String desp = ref.description(); String filter = ref.filter(); Class<?> serviceInterface = ref.serviceInterface(); ReferenceListener[] refListeners = ref.referenceListeners(); int timeout = ref.timeout(); Treference tref = new Treference(); // can not think of configuring depends on for reference tref.setDependsOn(null); if (id.length() > 0) { tref.setId(id); } if (availability.length() > 0) { tref.setAvailability(availability); } if (compName.length() > 0) { tref.setComponentName(compName); } if (desp.length() > 0) { Tdescription value = new Tdescription(); value.getContent().add(desp); tref.setDescription(value); } if (filter.length() > 0) { tref.setFilter(filter); } if (serviceInterface != Object.class) { tref.setInterface(serviceInterface.getName()); } if (timeout > 0) { tref.setTimeout(convertToBigInteger(timeout)); } for (ReferenceListener rl : refListeners) { String rf = rl.ref(); TreferenceListener trl = reflMap.get(rf); if (trl != null) { trl.setRefAttribute(rf); tref.getReferenceListener().add(trl); } else { throw new BlueprintAnnotationException("Unable to find the ReferenceListener ref " + rf); } } return tref; } private TreferenceList generateTrefList(ReferenceList ref, Map<String, TreferenceListener> reflMap) { String id = ref.id(); String availability = ref.availability(); String compName = ref.componentName(); String desp = ref.description(); String filter = ref.filter(); Class<?> serviceInterface = ref.serviceInterface(); ReferenceListener[] refListeners = ref.referenceListeners(); TreferenceList tref = new TreferenceList(); // can not think of configuring depends on for referencelist tref.setDependsOn(null); if (id.length() > 0) { tref.setId(id); } if (availability.length() > 0) { tref.setAvailability(availability); } if (compName.length() > 0) { tref.setComponentName(compName); } if (desp.length() > 0) { Tdescription value = new Tdescription(); value.getContent().add(desp); tref.setDescription(value); } if (filter.length() > 0) { tref.setFilter(filter); } if (serviceInterface != Object.class) { tref.setInterface(serviceInterface.getName()); } for (ReferenceListener rl : refListeners) { String rf = rl.ref(); TreferenceListener trl = reflMap.get(rf); if (trl != null) { trl.setRefAttribute(rf); tref.getReferenceListener().add(trl); } else { throw new BlueprintAnnotationException("Unable to find the ReferenceListener ref " + rf); } } return tref; } private Tservice generateTservice(Class clazz, String id, Map<String, TregistrationListener> reglMap) { Service service = (Service) clazz.getAnnotation(Service.class); Class<?>[] interfaces = service.interfaces(); int ranking = service.ranking(); String autoExport = service.autoExport(); ServiceProperty[] serviceProperties = service.serviceProperties(); RegistrationListener[] regListeners = service.registerationListeners(); Tservice tservice = new Tservice(); // can not think of configuring depends on for service tservice.setDependsOn(null); // use the bean id as the ref value since we are exposing service for the bean tservice.setRefAttribute(id); if (autoExport.length() > 0) { tservice.setAutoExport(autoExport); } if (ranking > 0) { tservice.setRanking(ranking); } for (Class<?> interf : interfaces) { Tinterfaces tInterfaces = new Tinterfaces(); if (interf != null) { tInterfaces.getValue().add(interf.getName()); } tservice.setInterfaces(tInterfaces); } // process service property. only key value as string are supported for now for (ServiceProperty sp : serviceProperties) { if (sp != null) { String key = sp.key(); String value = sp.value(); if (key.length() > 0 && value.length() > 0) { TservicePropertyEntry tsp = new TservicePropertyEntry(); tsp.setKey(key); tsp.setValueAttribute(value); tservice.getServiceProperties().getEntry().add(tsp); } } } for (RegistrationListener regListener : regListeners) { String ref = regListener.ref(); if (ref.length() > 0) { TregistrationListener tregListener = reglMap.get(ref); tregListener.setRefAttribute(ref); tservice.getRegistrationListener().add(tregListener); } else { throw new BlueprintAnnotationException("No ref id for service registration listener " + " for " + clazz.getName()); } } return tservice; } }