Package aQute.bnd.make.component

Source Code of aQute.bnd.make.component.ServiceComponent$ComponentMaker

package aQute.bnd.make.component;

import java.io.*;
import java.util.*;
import java.util.regex.*;

import aQute.bnd.annotation.component.*;
import aQute.bnd.make.metatype.*;
import aQute.bnd.service.*;
import aQute.lib.osgi.*;
import aQute.lib.osgi.Clazz.*;
import aQute.libg.version.*;

/**
* This class is an analyzer plugin. It looks at the properties and tries to
* find out if the Service-Component header contains the bnd shortut syntax. If
* not, the header is copied to the output, if it does, an XML file is created
* and added to the JAR and the header is modified appropriately.
*/
public class ServiceComponent implements AnalyzerPlugin {

  public boolean analyzeJar(Analyzer analyzer) throws Exception {

    ComponentMaker m = new ComponentMaker(analyzer);

    Map<String, Map<String, String>> l = m.doServiceComponent();

    analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l));

    analyzer.getInfo(m, "Service-Component: ");
    m.close();

    return false;
  }

  private static class ComponentMaker extends Processor {
    Analyzer  analyzer;

    ComponentMaker(Analyzer analyzer) {
      super(analyzer);
      this.analyzer = analyzer;
    }

    /**
     * Iterate over the Service Component entries. There are two cases:
     * <ol>
     * <li>An XML file reference</li>
     * <li>A FQN/wildcard with a set of attributes</li>
     * </ol>
     *
     * An XML reference is immediately expanded, an FQN/wildcard is more
     * complicated and is delegated to
     * {@link #componentEntry(Map, String, Map)}.
     *
     * @throws Exception
     */
    Map<String, Map<String, String>> doServiceComponent() throws Exception {
      Map<String, Map<String, String>> serviceComponents = newMap();
      String header = getProperty(SERVICE_COMPONENT);
      Map<String, Map<String, String>> sc = parseHeader(header);

      for (Map.Entry<String, Map<String, String>> entry : sc.entrySet()) {
        String name = entry.getKey();
        Map<String, String> info = entry.getValue();

        try {
          if (name.indexOf('/') >= 0 || name.endsWith(".xml")) {
            // Normal service component, we do not process it
            serviceComponents.put(name, EMPTY);
          } else {
            componentEntry(serviceComponents, name, info);
          }
        } catch (Exception e) {
          e.printStackTrace();
          error("Invalid Service-Component header: %s %s, throws %s", name, info, e);
        }
      }
      return serviceComponents;
    }

    /**
     * Parse an entry in the Service-Component header. This header supports
     * the following types:
     * <ol>
     * <li>An FQN + attributes describing a component</li>
     * <li>A wildcard expression for finding annotated components.</li>
     * </ol>
     * The problem is the distinction between an FQN and a wildcard because
     * an FQN can also be used as a wildcard.
     *
     * If the info specifies {@link Constants#NOANNOTATIONS} then wildcards
     * are an error and the component must be fully described by the info.
     * Otherwise the FQN/wildcard is expanded into a list of classes with
     * annotations. If this list is empty, the FQN case is interpreted as a
     * complete component definition. For the wildcard case, it is checked
     * if any matching classes for the wildcard have been compiled for a
     * class file format that does not support annotations, this can be a
     * problem with JSR14 who silently ignores annotations. An error is
     * reported in such a case.
     *
     * @param serviceComponents
     * @param name
     * @param info
     * @throws Exception
     * @throws IOException
     */
    private void componentEntry(Map<String, Map<String, String>> serviceComponents,
        String name, Map<String, String> info) throws Exception, IOException {

      boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS));
      boolean fqn = Verifier.isFQN(name);

      if (annotations) {

        // Annotations possible!

        Collection<Clazz> annotatedComponents = analyzer.getClasses("",
            QUERY.ANNOTATION.toString(), Component.class.getName(), //
            QUERY.NAMED.toString(), name //
            );

        if (fqn) {
          if (annotatedComponents.isEmpty()) {

            // No annotations, fully specified in header

            createComponentResource(serviceComponents, name, info);
          } else {

            // We had a FQN so expect only one

            for (Clazz c : annotatedComponents) {
              annotated(serviceComponents, c, info);
            }
          }
        } else {

          // We did not have an FQN, so expect the use of wildcards

          if (annotatedComponents.isEmpty())
            checkAnnotationsFeasible(name);
          else
            for (Clazz c : annotatedComponents) {
              annotated(serviceComponents, c, info);
            }
        }
      } else {
        // No annotations
        if (fqn)
          createComponentResource(serviceComponents, name, info);
        else
          error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name);

      }
    }

    /**
     * Check if annotations are actually feasible looking at the class
     * format. If the class format does not provide annotations then it is
     * no use specifying annotated components.
     *
     * @param name
     * @return
     * @throws Exception
     */
    private Collection<Clazz> checkAnnotationsFeasible(String name) throws Exception {
      Collection<Clazz> not = analyzer.getClasses("", QUERY.NAMED.toString(), name //
          );
     
      if (not.isEmpty())
        if ( "*".equals(name))
          return not;
        else
          error("Specified %s but could not find any class matching this pattern", name);

      for (Clazz c : not) {
        if (c.getFormat().hasAnnotations())
          return not;
      }
     
      warning("Wildcards are used (%s) requiring annotations to decide what is a component. Wildcard maps to classes that are compiled with java.target < 1.5. Annotations were introduced in Java 1.5",
          name);
     
      return not;
    }

    void annotated(Map<String, Map<String, String>> components, Clazz c,
        Map<String, String> info) throws Exception {
      // Get the component definition
      // from the annotations
      Map<String, String> map = ComponentAnnotationReader.getDefinition(c, this);

      // Pick the name, the annotation can override
      // the name.
      String localname = map.get(COMPONENT_NAME);
      if (localname == null)
        localname = c.getFQN();

      // Override the component info without manifest
      // entries. We merge the properties though.

      String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES),
          map.remove(COMPONENT_PROPERTIES));
      if (merged != null && merged.length() > 0)
        map.put(COMPONENT_PROPERTIES, merged);
      map.putAll(info);
      createComponentResource(components, localname, map);
    }

    private void createComponentResource(Map<String, Map<String, String>> components,
        String name, Map<String, String> info) throws IOException {

      // We can override the name in the parameters
      if (info.containsKey(COMPONENT_NAME))
        name = info.get(COMPONENT_NAME);

      // Assume the impl==name, but allow override
      String impl = name;
      if (info.containsKey(COMPONENT_IMPLEMENTATION))
        impl = info.get(COMPONENT_IMPLEMENTATION);

      // Check if such a class exists
      analyzer.referTo(impl);

      boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false)
          || designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true);

      // If we had a designate, we want a default configuration policy of
      // require.
      if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null)
        info.put(COMPONENT_CONFIGURATION_POLICY, "require");

      // We have a definition, so make an XML resources
      Resource resource = createComponentResource(name, impl, info);
      analyzer.getJar().putResource("OSGI-INF/" + name + ".xml", resource);

      components.put("OSGI-INF/" + name + ".xml", EMPTY);

    }

    /**
     * Create a Metatype and Designate record out of the given
     * configurations.
     *
     * @param name
     * @param config
     */
    private boolean designate(String name, String config, boolean factory) {
      if (config == null)
        return false;

      for (String c : Processor.split(config)) {
        Clazz clazz = analyzer.getClassspace().get(Clazz.fqnToPath(c));
        if (clazz != null) {
          analyzer.referTo(c);
          MetaTypeReader r = new MetaTypeReader(clazz, analyzer);
          r.setDesignate(name, factory);
          String rname = "OSGI-INF/metatype/" + name + ".xml";

          analyzer.getJar().putResource(rname, r);
        } else {
          analyzer.error(
              "Cannot find designated configuration class %s for component %s", c,
              name);
        }
      }
      return true;
    }

    /**
     * Create the resource for a DS component.
     *
     * @param list
     * @param name
     * @param info
     * @throws UnsupportedEncodingException
     */
    Resource createComponentResource(String name, String impl, Map<String, String> info)
        throws IOException {
      String namespace = getNamespace(info);
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, Constants.DEFAULT_CHARSET));
      pw.println("<?xml version='1.0' encoding='utf-8'?>");
      if (namespace != null)
        pw.print("<scr:component xmlns:scr='" + namespace + "'");
      else
        pw.print("<component");

      doAttribute(pw, name, "name");
      doAttribute(pw, info.get(COMPONENT_FACTORY), "factory");
      doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate", "false", "true");
      doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true", "false");
      doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY), "configuration-policy",
          "optional", "require", "ignore");
      doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate", JIDENTIFIER);
      doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate", JIDENTIFIER);
      doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified", JIDENTIFIER);

      pw.println(">");

      // Allow override of the implementation when people
      // want to choose their own name
      pw.println("  <implementation class='" + (impl == null ? name : impl) + "'/>");

      String provides = info.get(COMPONENT_PROVIDE);
      boolean servicefactory = Processor.isTrue(info.get(COMPONENT_SERVICEFACTORY));

      if (servicefactory && Processor.isTrue(info.get(COMPONENT_IMMEDIATE))) {
        // TODO can become error() if it is up to me
        warning("For a Service Component, the immediate option and the servicefactory option are mutually exclusive for %(%s)",
            name, impl);
      }
      provide(pw, provides, servicefactory, impl);
      properties(pw, info);
      reference(info, pw);

      if (namespace != null)
        pw.println("</scr:component>");
      else
        pw.println("</component>");

      pw.close();
      byte[] data = out.toByteArray();
      out.close();
      return new EmbeddedResource(data, 0);
    }

    private void doAttribute(PrintWriter pw, String value, String name, String... matches) {
      if (value != null) {
        if (matches.length != 0) {
          if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) {
            if (!Verifier.isIdentifier(value))
              error("Component attribute %s has value %s but is not a Java identifier",
                  name, value);
          } else {

            if (!Verifier.isMember(value, matches))
              error("Component attribute %s has value %s but is not a member of %s",
                  name, value, Arrays.toString(matches));
          }
        }
        pw.print(" ");
        pw.print(name);
        pw.print("='");
        pw.print(value);
        pw.print("'");
      }
    }

    /**
     * Check if we need to use the v1.1 namespace (or later).
     *
     * @param info
     * @return
     */
    private String getNamespace(Map<String, String> info) {
      String version = info.get(COMPONENT_VERSION);
      if (version != null) {
        try {
          Version v = new Version(version);
          return NAMESPACE_STEM + "/v" + v;
        } catch (Exception e) {
          error("version: specified on component header but not a valid version: "
              + version);
          return null;
        }
      }
      for (String key : info.keySet()) {
        if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) {
          return NAMESPACE_STEM + "/v1.1.0";
        }
      }
      return null;
    }

    /**
     * Print the Service-Component properties element
     *
     * @param pw
     * @param info
     */
    void properties(PrintWriter pw, Map<String, String> info) {
      Collection<String> properties = split(info.get(COMPONENT_PROPERTIES));
      for (Iterator<String> p = properties.iterator(); p.hasNext();) {
        String clause = p.next();
        int n = clause.indexOf('=');
        if (n <= 0) {
          error("Not a valid property in service component: " + clause);
        } else {
          String type = null;
          String name = clause.substring(0, n);
          if (name.indexOf('@') >= 0) {
            String parts[] = name.split("@");
            name = parts[1];
            type = parts[0];
          } else if (name.indexOf(':') >= 0) {
            String parts[] = name.split(":");
            name = parts[0];
            type = parts[1];
          }
          String value = clause.substring(n + 1).trim();
          // TODO verify validity of name and value.
          pw.print("  <property name='");
          pw.print(name);
          pw.print("'");

          if (type != null) {
            if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
              pw.print(" type='");
              pw.print(type);
              pw.print("'");
            } else {
              warning("Invalid property type '" + type + "' for property " + name);
            }
          }

          String parts[] = value.split("\\s*(\\||\\n)\\s*");
          if (parts.length > 1) {
            pw.println(">");
            for (String part : parts) {
              pw.println(part);
            }
            pw.println("</property>");
          } else {
            pw.print(" value='");
            pw.print(parts[0]);
            pw.println("'/>");
          }
        }
      }
    }

    /**
     * @param pw
     * @param provides
     */
    void provide(PrintWriter pw, String provides, boolean servicefactory, String impl) {
      if (provides != null) {
        if (!servicefactory)
          pw.println("  <service>");
        else
          pw.println("  <service servicefactory='true'>");

        StringTokenizer st = new StringTokenizer(provides, ",");
        while (st.hasMoreTokens()) {
          String interfaceName = st.nextToken();
          pw.println("    <provide interface='" + interfaceName + "'/>");
          analyzer.referTo(interfaceName);

          // TODO verifies the impl. class extends or implements the
          // interface
        }
        pw.println("  </service>");
      } else if (servicefactory)
        warning("The servicefactory:=true directive is set but no service is provided, ignoring it");
    }

    public final static Pattern  REFERENCE  = Pattern.compile("([^(]+)(\\(.+\\))?");

    /**
     * @param info
     * @param pw
     */

    void reference(Map<String, String> info, PrintWriter pw) {
      Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC)));
      Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL)));
      Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE)));

      Collection<String> descriptors = split(info.get(COMPONENT_DESCRIPTORS));

      for (Map.Entry<String, String> entry : info.entrySet()) {

        // Skip directives
        String referenceName = entry.getKey();
        if (referenceName.endsWith(":")) {
          if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
            error("Unrecognized directive in Service-Component header: "
                + referenceName);
          continue;
        }

        // Parse the bind/unbind methods from the name
        // if set. They are separated by '/'
        String bind = null;
        String unbind = null;

        boolean unbindCalculated = false;

        if (referenceName.indexOf('/') >= 0) {
          String parts[] = referenceName.split("/");
          referenceName = parts[0];
          bind = parts[1];
          if (parts.length > 2) {
            unbind = parts[2];
          } else {
            unbindCalculated = true;
            if (bind.startsWith("add"))
              unbind = bind.replaceAll("add(.+)", "remove$1");
            else
              unbind = "un" + bind;
          }
        } else if (Character.isLowerCase(referenceName.charAt(0))) {
          unbindCalculated = true;
          bind = "set" + Character.toUpperCase(referenceName.charAt(0))
              + referenceName.substring(1);
          unbind = "un" + bind;
        }

        String interfaceName = entry.getValue();
        if (interfaceName == null || interfaceName.length() == 0) {
          error("Invalid Interface Name for references in Service Component: "
              + referenceName + "=" + interfaceName);
          continue;
        }

        // If we have descriptors, we have analyzed the component.
        // So why not check the methods
        if (descriptors.size() > 0) {
          // Verify that the bind method exists
          if (!descriptors.contains(bind))
            error("The bind method %s for %s not defined", bind, referenceName);

          // Check if the unbind method exists
          if (!descriptors.contains(unbind)) {
            if (unbindCalculated)
              // remove it
              unbind = null;
            else
              error("The unbind method %s for %s not defined", unbind, referenceName);
          }
        }
        // Check tje cardinality by looking at the last
        // character of the value
        char c = interfaceName.charAt(interfaceName.length() - 1);
        if ("?+*~".indexOf(c) >= 0) {
          if (c == '?' || c == '*' || c == '~')
            optional.add(referenceName);
          if (c == '+' || c == '*')
            multiple.add(referenceName);
          if (c == '+' || c == '*' || c == '?')
            dynamic.add(referenceName);
          interfaceName = interfaceName.substring(0, interfaceName.length() - 1);
        }

        // Parse the target from the interface name
        // The target is a filter.
        String target = null;
        Matcher m = REFERENCE.matcher(interfaceName);
        if (m.matches()) {
          interfaceName = m.group(1);
          target = m.group(2);
        }

        analyzer.referTo(interfaceName);

        pw.printf("  <reference name='%s'", referenceName);
        pw.printf(" interface='%s'", interfaceName);

        String cardinality = optional.contains(referenceName) ? "0" : "1";
        cardinality += "..";
        cardinality += multiple.contains(referenceName) ? "n" : "1";
        if (!cardinality.equals("1..1"))
          pw.print(" cardinality='" + cardinality + "'");

        if (bind != null) {
          pw.printf(" bind='%s'", bind);
          if (unbind != null) {
            pw.printf(" unbind='%s'", unbind);
          }
        }

        if (dynamic.contains(referenceName)) {
          pw.print(" policy='dynamic'");
        }

        if (target != null) {
          // Filter filter = new Filter(target);
          // if (filter.verify() == null)
          // pw.print(" target='" + filter.toString() + "'");
          // else
          // error("Target for " + referenceName
          // + " is not a correct filter: " + target + " "
          // + filter.verify());
          pw.print(" target='" + escape(target) + "'");
        }
        pw.println("/>");
      }
    }
  }

  /**
   * Escape a string, do entity conversion.
   */
  static String escape(String s) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      switch (c) {
      case '<':
        sb.append("&lt;");
        break;
      case '>':
        sb.append("&gt;");
        break;
      case '&':
        sb.append("&amp;");
        break;
      case '\'':
        sb.append("&quot;");
        break;
      default:
        sb.append(c);
        break;
      }
    }
    return sb.toString();
  }

}
TOP

Related Classes of aQute.bnd.make.component.ServiceComponent$ComponentMaker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.