Stuart McCulloch | 26e7a5a | 2011-10-17 10:31:43 +0000 | [diff] [blame^] | 1 | package aQute.bnd.make.component; |
| 2 | |
| 3 | import java.io.*; |
| 4 | import java.util.*; |
| 5 | import java.util.regex.*; |
| 6 | |
| 7 | import aQute.bnd.annotation.component.*; |
| 8 | import aQute.bnd.make.metatype.*; |
| 9 | import aQute.bnd.service.*; |
| 10 | import aQute.lib.osgi.*; |
| 11 | import aQute.lib.osgi.Clazz.*; |
| 12 | import aQute.libg.version.*; |
| 13 | |
| 14 | /** |
| 15 | * This class is an analyzer plugin. It looks at the properties and tries to |
| 16 | * find out if the Service-Component header contains the bnd shortut syntax. If |
| 17 | * not, the header is copied to the output, if it does, an XML file is created |
| 18 | * and added to the JAR and the header is modified appropriately. |
| 19 | */ |
| 20 | public class ServiceComponent implements AnalyzerPlugin { |
| 21 | public final static String NAMESPACE_STEM = "http://www.osgi.org/xmlns/scr"; |
| 22 | public final static String JIDENTIFIER = "<<identifier>>"; |
| 23 | public final static String COMPONENT_NAME = "name:"; |
| 24 | public final static String COMPONENT_FACTORY = "factory:"; |
| 25 | public final static String COMPONENT_SERVICEFACTORY = "servicefactory:"; |
| 26 | public final static String COMPONENT_IMMEDIATE = "immediate:"; |
| 27 | public final static String COMPONENT_ENABLED = "enabled:"; |
| 28 | public final static String COMPONENT_DYNAMIC = "dynamic:"; |
| 29 | public final static String COMPONENT_MULTIPLE = "multiple:"; |
| 30 | public final static String COMPONENT_PROVIDE = "provide:"; |
| 31 | public final static String COMPONENT_OPTIONAL = "optional:"; |
| 32 | public final static String COMPONENT_PROPERTIES = "properties:"; |
| 33 | public final static String COMPONENT_IMPLEMENTATION = "implementation:"; |
| 34 | public final static String COMPONENT_DESIGNATE = "designate:"; |
| 35 | public final static String COMPONENT_DESIGNATEFACTORY = "designateFactory:"; |
| 36 | public final static String COMPONENT_DESCRIPTORS = ".descriptors:"; |
| 37 | |
| 38 | // v1.1.0 |
| 39 | public final static String COMPONENT_VERSION = "version:"; |
| 40 | public final static String COMPONENT_CONFIGURATION_POLICY = "configuration-policy:"; |
| 41 | public final static String COMPONENT_MODIFIED = "modified:"; |
| 42 | public final static String COMPONENT_ACTIVATE = "activate:"; |
| 43 | public final static String COMPONENT_DEACTIVATE = "deactivate:"; |
| 44 | |
| 45 | final static Map<String, String> EMPTY = Collections.emptyMap(); |
| 46 | |
| 47 | public final static String[] componentDirectives = new String[] { |
| 48 | COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED, COMPONENT_DYNAMIC, |
| 49 | COMPONENT_MULTIPLE, COMPONENT_PROVIDE, COMPONENT_OPTIONAL, COMPONENT_PROPERTIES, |
| 50 | COMPONENT_IMPLEMENTATION, COMPONENT_SERVICEFACTORY, COMPONENT_VERSION, |
| 51 | COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED, COMPONENT_ACTIVATE, |
| 52 | COMPONENT_DEACTIVATE, COMPONENT_NAME, COMPONENT_DESCRIPTORS, COMPONENT_DESIGNATE, |
| 53 | COMPONENT_DESIGNATEFACTORY }; |
| 54 | |
| 55 | public final static Set<String> SET_COMPONENT_DIRECTIVES = new HashSet<String>( |
| 56 | Arrays.asList(componentDirectives)); |
| 57 | |
| 58 | public final static Set<String> SET_COMPONENT_DIRECTIVES_1_1 = // |
| 59 | new HashSet<String>( |
| 60 | Arrays.asList( |
| 61 | COMPONENT_VERSION, |
| 62 | COMPONENT_CONFIGURATION_POLICY, |
| 63 | COMPONENT_MODIFIED, |
| 64 | COMPONENT_ACTIVATE, |
| 65 | COMPONENT_DEACTIVATE)); |
| 66 | |
| 67 | public boolean analyzeJar(Analyzer analyzer) throws Exception { |
| 68 | |
| 69 | ComponentMaker m = new ComponentMaker(analyzer); |
| 70 | |
| 71 | Map<String, Map<String, String>> l = m.doServiceComponent(); |
| 72 | |
| 73 | analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l)); |
| 74 | |
| 75 | analyzer.getInfo(m, "Service-Component: "); |
| 76 | m.close(); |
| 77 | |
| 78 | return false; |
| 79 | } |
| 80 | |
| 81 | private static class ComponentMaker extends Processor { |
| 82 | Analyzer analyzer; |
| 83 | |
| 84 | ComponentMaker(Analyzer analyzer) { |
| 85 | super(analyzer); |
| 86 | this.analyzer = analyzer; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Iterate over the Service Component entries. There are two cases: |
| 91 | * <ol> |
| 92 | * <li>An XML file reference</li> |
| 93 | * <li>A FQN/wildcard with a set of attributes</li> |
| 94 | * </ol> |
| 95 | * |
| 96 | * An XML reference is immediately expanded, an FQN/wildcard is more |
| 97 | * complicated and is delegated to |
| 98 | * {@link #componentEntry(Map, String, Map)}. |
| 99 | * |
| 100 | * @throws Exception |
| 101 | */ |
| 102 | Map<String, Map<String, String>> doServiceComponent() throws Exception { |
| 103 | Map<String, Map<String, String>> serviceComponents = newMap(); |
| 104 | String header = getProperty(SERVICE_COMPONENT); |
| 105 | Map<String, Map<String, String>> sc = parseHeader(header); |
| 106 | |
| 107 | for (Map.Entry<String, Map<String, String>> entry : sc.entrySet()) { |
| 108 | String name = entry.getKey(); |
| 109 | Map<String, String> info = entry.getValue(); |
| 110 | |
| 111 | try { |
| 112 | if (name.indexOf('/') >= 0 || name.endsWith(".xml")) { |
| 113 | // Normal service component, we do not process it |
| 114 | serviceComponents.put(name, EMPTY); |
| 115 | } else { |
| 116 | componentEntry(serviceComponents, name, info); |
| 117 | } |
| 118 | } catch (Exception e) { |
| 119 | e.printStackTrace(); |
| 120 | error("Invalid Service-Component header: %s %s, throws %s", name, info, e); |
| 121 | } |
| 122 | } |
| 123 | return serviceComponents; |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Parse an entry in the Service-Component header. This header supports |
| 128 | * the following types: |
| 129 | * <ol> |
| 130 | * <li>An FQN + attributes describing a component</li> |
| 131 | * <li>A wildcard expression for finding annotated components.</li> |
| 132 | * </ol> |
| 133 | * The problem is the distinction between an FQN and a wildcard because |
| 134 | * an FQN can also be used as a wildcard. |
| 135 | * |
| 136 | * If the info specifies {@link Constants#NOANNOTATIONS} then wildcards |
| 137 | * are an error and the component must be fully described by the info. |
| 138 | * Otherwise the FQN/wildcard is expanded into a list of classes with |
| 139 | * annotations. If this list is empty, the FQN case is interpreted as a |
| 140 | * complete component definition. For the wildcard case, it is checked |
| 141 | * if any matching classes for the wildcard have been compiled for a |
| 142 | * class file format that does not support annotations, this can be a |
| 143 | * problem with JSR14 who silently ignores annotations. An error is |
| 144 | * reported in such a case. |
| 145 | * |
| 146 | * @param serviceComponents |
| 147 | * @param name |
| 148 | * @param info |
| 149 | * @throws Exception |
| 150 | * @throws IOException |
| 151 | */ |
| 152 | private void componentEntry(Map<String, Map<String, String>> serviceComponents, |
| 153 | String name, Map<String, String> info) throws Exception, IOException { |
| 154 | |
| 155 | boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS)); |
| 156 | boolean fqn = Verifier.isFQN(name); |
| 157 | |
| 158 | if (annotations) { |
| 159 | |
| 160 | // Annotations possible! |
| 161 | |
| 162 | Collection<Clazz> annotatedComponents = analyzer.getClasses("", |
| 163 | QUERY.ANNOTATION.toString(), Component.class.getName(), // |
| 164 | QUERY.NAMED.toString(), name // |
| 165 | ); |
| 166 | |
| 167 | if (fqn) { |
| 168 | if (annotatedComponents.isEmpty()) { |
| 169 | |
| 170 | // No annotations, fully specified in header |
| 171 | |
| 172 | createComponentResource(serviceComponents, name, info); |
| 173 | } else { |
| 174 | |
| 175 | // We had a FQN so expect only one |
| 176 | |
| 177 | for (Clazz c : annotatedComponents) { |
| 178 | annotated(serviceComponents, c, info); |
| 179 | } |
| 180 | } |
| 181 | } else { |
| 182 | |
| 183 | // We did not have an FQN, so expect the use of wildcards |
| 184 | |
| 185 | if (annotatedComponents.isEmpty()) |
| 186 | checkAnnotationsFeasible(name); |
| 187 | else |
| 188 | for (Clazz c : annotatedComponents) { |
| 189 | annotated(serviceComponents, c, info); |
| 190 | } |
| 191 | } |
| 192 | } else { |
| 193 | // No annotations |
| 194 | if (fqn) |
| 195 | createComponentResource(serviceComponents, name, info); |
| 196 | else |
| 197 | error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name); |
| 198 | |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Check if annotations are actually feasible looking at the class |
| 204 | * format. If the class format does not provide annotations then it is |
| 205 | * no use specifying annotated components. |
| 206 | * |
| 207 | * @param name |
| 208 | * @return |
| 209 | * @throws Exception |
| 210 | */ |
| 211 | private Collection<Clazz> checkAnnotationsFeasible(String name) throws Exception { |
| 212 | Collection<Clazz> not = analyzer.getClasses("", QUERY.NAMED.toString(), name // |
| 213 | ); |
| 214 | |
| 215 | if (not.isEmpty()) |
| 216 | if ( "*".equals(name)) |
| 217 | return not; |
| 218 | else |
| 219 | error("Specified %s but could not find any class matching this pattern", name); |
| 220 | |
| 221 | for (Clazz c : not) { |
| 222 | if (c.getFormat().hasAnnotations()) |
| 223 | return not; |
| 224 | } |
| 225 | |
| 226 | 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", |
| 227 | name); |
| 228 | |
| 229 | return not; |
| 230 | } |
| 231 | |
| 232 | void annotated(Map<String, Map<String, String>> components, Clazz c, |
| 233 | Map<String, String> info) throws Exception { |
| 234 | // Get the component definition |
| 235 | // from the annotations |
| 236 | Map<String, String> map = ComponentAnnotationReader.getDefinition(c, this); |
| 237 | |
| 238 | // Pick the name, the annotation can override |
| 239 | // the name. |
| 240 | String localname = map.get(COMPONENT_NAME); |
| 241 | if (localname == null) |
| 242 | localname = c.getFQN(); |
| 243 | |
| 244 | // Override the component info without manifest |
| 245 | // entries. We merge the properties though. |
| 246 | |
| 247 | String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES), |
| 248 | map.remove(COMPONENT_PROPERTIES)); |
| 249 | if (merged != null && merged.length() > 0) |
| 250 | map.put(COMPONENT_PROPERTIES, merged); |
| 251 | map.putAll(info); |
| 252 | createComponentResource(components, localname, map); |
| 253 | } |
| 254 | |
| 255 | private void createComponentResource(Map<String, Map<String, String>> components, |
| 256 | String name, Map<String, String> info) throws IOException { |
| 257 | |
| 258 | // We can override the name in the parameters |
| 259 | if (info.containsKey(COMPONENT_NAME)) |
| 260 | name = info.get(COMPONENT_NAME); |
| 261 | |
| 262 | // Assume the impl==name, but allow override |
| 263 | String impl = name; |
| 264 | if (info.containsKey(COMPONENT_IMPLEMENTATION)) |
| 265 | impl = info.get(COMPONENT_IMPLEMENTATION); |
| 266 | |
| 267 | // Check if such a class exists |
| 268 | analyzer.referTo(impl); |
| 269 | |
| 270 | boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false) |
| 271 | || designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true); |
| 272 | |
| 273 | // If we had a designate, we want a default configuration policy of |
| 274 | // require. |
| 275 | if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null) |
| 276 | info.put(COMPONENT_CONFIGURATION_POLICY, "require"); |
| 277 | |
| 278 | // We have a definition, so make an XML resources |
| 279 | Resource resource = createComponentResource(name, impl, info); |
| 280 | analyzer.getJar().putResource("OSGI-INF/" + name + ".xml", resource); |
| 281 | |
| 282 | components.put("OSGI-INF/" + name + ".xml", EMPTY); |
| 283 | |
| 284 | } |
| 285 | |
| 286 | /** |
| 287 | * Create a Metatype and Designate record out of the given |
| 288 | * configurations. |
| 289 | * |
| 290 | * @param name |
| 291 | * @param config |
| 292 | */ |
| 293 | private boolean designate(String name, String config, boolean factory) { |
| 294 | if (config == null) |
| 295 | return false; |
| 296 | |
| 297 | for (String c : Processor.split(config)) { |
| 298 | Clazz clazz = analyzer.getClassspace().get(Clazz.fqnToPath(c)); |
| 299 | if (clazz != null) { |
| 300 | analyzer.referTo(c); |
| 301 | MetaTypeReader r = new MetaTypeReader(clazz, analyzer); |
| 302 | r.setDesignate(name, factory); |
| 303 | String rname = "OSGI-INF/metatype/" + name + ".xml"; |
| 304 | |
| 305 | analyzer.getJar().putResource(rname, r); |
| 306 | } else { |
| 307 | analyzer.error( |
| 308 | "Cannot find designated configuration class %s for component %s", c, |
| 309 | name); |
| 310 | } |
| 311 | } |
| 312 | return true; |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * Create the resource for a DS component. |
| 317 | * |
| 318 | * @param list |
| 319 | * @param name |
| 320 | * @param info |
| 321 | * @throws UnsupportedEncodingException |
| 322 | */ |
| 323 | Resource createComponentResource(String name, String impl, Map<String, String> info) |
| 324 | throws IOException { |
| 325 | String namespace = getNamespace(info); |
| 326 | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| 327 | PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, Constants.DEFAULT_CHARSET)); |
| 328 | pw.println("<?xml version='1.0' encoding='utf-8'?>"); |
| 329 | if (namespace != null) |
| 330 | pw.print("<scr:component xmlns:scr='" + namespace + "'"); |
| 331 | else |
| 332 | pw.print("<component"); |
| 333 | |
| 334 | doAttribute(pw, name, "name"); |
| 335 | doAttribute(pw, info.get(COMPONENT_FACTORY), "factory"); |
| 336 | doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate", "false", "true"); |
| 337 | doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true", "false"); |
| 338 | doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY), "configuration-policy", |
| 339 | "optional", "require", "ignore"); |
| 340 | doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate", JIDENTIFIER); |
| 341 | doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate", JIDENTIFIER); |
| 342 | doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified", JIDENTIFIER); |
| 343 | |
| 344 | pw.println(">"); |
| 345 | |
| 346 | // Allow override of the implementation when people |
| 347 | // want to choose their own name |
| 348 | pw.println(" <implementation class='" + (impl == null ? name : impl) + "'/>"); |
| 349 | |
| 350 | String provides = info.get(COMPONENT_PROVIDE); |
| 351 | boolean servicefactory = Processor.isTrue(info.get(COMPONENT_SERVICEFACTORY)); |
| 352 | |
| 353 | if (servicefactory && Processor.isTrue(info.get(COMPONENT_IMMEDIATE))) { |
| 354 | // TODO can become error() if it is up to me |
| 355 | warning("For a Service Component, the immediate option and the servicefactory option are mutually exclusive for %(%s)", |
| 356 | name, impl); |
| 357 | } |
| 358 | provide(pw, provides, servicefactory, impl); |
| 359 | properties(pw, info); |
| 360 | reference(info, pw); |
| 361 | |
| 362 | if (namespace != null) |
| 363 | pw.println("</scr:component>"); |
| 364 | else |
| 365 | pw.println("</component>"); |
| 366 | |
| 367 | pw.close(); |
| 368 | byte[] data = out.toByteArray(); |
| 369 | out.close(); |
| 370 | return new EmbeddedResource(data, 0); |
| 371 | } |
| 372 | |
| 373 | private void doAttribute(PrintWriter pw, String value, String name, String... matches) { |
| 374 | if (value != null) { |
| 375 | if (matches.length != 0) { |
| 376 | if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) { |
| 377 | if (!Verifier.isIdentifier(value)) |
| 378 | error("Component attribute %s has value %s but is not a Java identifier", |
| 379 | name, value); |
| 380 | } else { |
| 381 | |
| 382 | if (!Verifier.isMember(value, matches)) |
| 383 | error("Component attribute %s has value %s but is not a member of %s", |
| 384 | name, value, Arrays.toString(matches)); |
| 385 | } |
| 386 | } |
| 387 | pw.print(" "); |
| 388 | pw.print(name); |
| 389 | pw.print("='"); |
| 390 | pw.print(value); |
| 391 | pw.print("'"); |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | /** |
| 396 | * Check if we need to use the v1.1 namespace (or later). |
| 397 | * |
| 398 | * @param info |
| 399 | * @return |
| 400 | */ |
| 401 | private String getNamespace(Map<String, String> info) { |
| 402 | String version = info.get(COMPONENT_VERSION); |
| 403 | if (version != null) { |
| 404 | try { |
| 405 | Version v = new Version(version); |
| 406 | return NAMESPACE_STEM + "/v" + v; |
| 407 | } catch (Exception e) { |
| 408 | error("version: specified on component header but not a valid version: " |
| 409 | + version); |
| 410 | return null; |
| 411 | } |
| 412 | } |
| 413 | for (String key : info.keySet()) { |
| 414 | if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) { |
| 415 | return NAMESPACE_STEM + "/v1.1.0"; |
| 416 | } |
| 417 | } |
| 418 | return null; |
| 419 | } |
| 420 | |
| 421 | /** |
| 422 | * Print the Service-Component properties element |
| 423 | * |
| 424 | * @param pw |
| 425 | * @param info |
| 426 | */ |
| 427 | void properties(PrintWriter pw, Map<String, String> info) { |
| 428 | Collection<String> properties = split(info.get(COMPONENT_PROPERTIES)); |
| 429 | for (Iterator<String> p = properties.iterator(); p.hasNext();) { |
| 430 | String clause = p.next(); |
| 431 | int n = clause.indexOf('='); |
| 432 | if (n <= 0) { |
| 433 | error("Not a valid property in service component: " + clause); |
| 434 | } else { |
| 435 | String type = null; |
| 436 | String name = clause.substring(0, n); |
| 437 | if (name.indexOf('@') >= 0) { |
| 438 | String parts[] = name.split("@"); |
| 439 | name = parts[1]; |
| 440 | type = parts[0]; |
| 441 | } else if (name.indexOf(':') >= 0) { |
| 442 | String parts[] = name.split(":"); |
| 443 | name = parts[0]; |
| 444 | type = parts[1]; |
| 445 | } |
| 446 | String value = clause.substring(n + 1).trim(); |
| 447 | // TODO verify validity of name and value. |
| 448 | pw.print(" <property name='"); |
| 449 | pw.print(name); |
| 450 | pw.print("'"); |
| 451 | |
| 452 | if (type != null) { |
| 453 | if (VALID_PROPERTY_TYPES.matcher(type).matches()) { |
| 454 | pw.print(" type='"); |
| 455 | pw.print(type); |
| 456 | pw.print("'"); |
| 457 | } else { |
| 458 | warning("Invalid property type '" + type + "' for property " + name); |
| 459 | } |
| 460 | } |
| 461 | |
| 462 | String parts[] = value.split("\\s*(\\||\\n)\\s*"); |
| 463 | if (parts.length > 1) { |
| 464 | pw.println(">"); |
| 465 | for (String part : parts) { |
| 466 | pw.println(part); |
| 467 | } |
| 468 | pw.println("</property>"); |
| 469 | } else { |
| 470 | pw.print(" value='"); |
| 471 | pw.print(parts[0]); |
| 472 | pw.println("'/>"); |
| 473 | } |
| 474 | } |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | /** |
| 479 | * @param pw |
| 480 | * @param provides |
| 481 | */ |
| 482 | void provide(PrintWriter pw, String provides, boolean servicefactory, String impl) { |
| 483 | if (provides != null) { |
| 484 | if (!servicefactory) |
| 485 | pw.println(" <service>"); |
| 486 | else |
| 487 | pw.println(" <service servicefactory='true'>"); |
| 488 | |
| 489 | StringTokenizer st = new StringTokenizer(provides, ","); |
| 490 | while (st.hasMoreTokens()) { |
| 491 | String interfaceName = st.nextToken(); |
| 492 | pw.println(" <provide interface='" + interfaceName + "'/>"); |
| 493 | analyzer.referTo(interfaceName); |
| 494 | |
| 495 | // TODO verifies the impl. class extends or implements the |
| 496 | // interface |
| 497 | } |
| 498 | pw.println(" </service>"); |
| 499 | } else if (servicefactory) |
| 500 | warning("The servicefactory:=true directive is set but no service is provided, ignoring it"); |
| 501 | } |
| 502 | |
| 503 | public final static Pattern REFERENCE = Pattern.compile("([^(]+)(\\(.+\\))?"); |
| 504 | |
| 505 | /** |
| 506 | * @param info |
| 507 | * @param pw |
| 508 | */ |
| 509 | |
| 510 | void reference(Map<String, String> info, PrintWriter pw) { |
| 511 | Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC))); |
| 512 | Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL))); |
| 513 | Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE))); |
| 514 | |
| 515 | Collection<String> descriptors = split(info.get(COMPONENT_DESCRIPTORS)); |
| 516 | |
| 517 | for (Map.Entry<String, String> entry : info.entrySet()) { |
| 518 | |
| 519 | // Skip directives |
| 520 | String referenceName = entry.getKey(); |
| 521 | if (referenceName.endsWith(":")) { |
| 522 | if (!SET_COMPONENT_DIRECTIVES.contains(referenceName)) |
| 523 | error("Unrecognized directive in Service-Component header: " |
| 524 | + referenceName); |
| 525 | continue; |
| 526 | } |
| 527 | |
| 528 | // Parse the bind/unbind methods from the name |
| 529 | // if set. They are separated by '/' |
| 530 | String bind = null; |
| 531 | String unbind = null; |
| 532 | |
| 533 | boolean unbindCalculated = false; |
| 534 | |
| 535 | if (referenceName.indexOf('/') >= 0) { |
| 536 | String parts[] = referenceName.split("/"); |
| 537 | referenceName = parts[0]; |
| 538 | bind = parts[1]; |
| 539 | if (parts.length > 2) { |
| 540 | unbind = parts[2]; |
| 541 | } else { |
| 542 | unbindCalculated = true; |
| 543 | if (bind.startsWith("add")) |
| 544 | unbind = bind.replaceAll("add(.+)", "remove$1"); |
| 545 | else |
| 546 | unbind = "un" + bind; |
| 547 | } |
| 548 | } else if (Character.isLowerCase(referenceName.charAt(0))) { |
| 549 | unbindCalculated = true; |
| 550 | bind = "set" + Character.toUpperCase(referenceName.charAt(0)) |
| 551 | + referenceName.substring(1); |
| 552 | unbind = "un" + bind; |
| 553 | } |
| 554 | |
| 555 | String interfaceName = entry.getValue(); |
| 556 | if (interfaceName == null || interfaceName.length() == 0) { |
| 557 | error("Invalid Interface Name for references in Service Component: " |
| 558 | + referenceName + "=" + interfaceName); |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | // If we have descriptors, we have analyzed the component. |
| 563 | // So why not check the methods |
| 564 | if (descriptors.size() > 0) { |
| 565 | // Verify that the bind method exists |
| 566 | if (!descriptors.contains(bind)) |
| 567 | error("The bind method %s for %s not defined", bind, referenceName); |
| 568 | |
| 569 | // Check if the unbind method exists |
| 570 | if (!descriptors.contains(unbind)) { |
| 571 | if (unbindCalculated) |
| 572 | // remove it |
| 573 | unbind = null; |
| 574 | else |
| 575 | error("The unbind method %s for %s not defined", unbind, referenceName); |
| 576 | } |
| 577 | } |
| 578 | // Check tje cardinality by looking at the last |
| 579 | // character of the value |
| 580 | char c = interfaceName.charAt(interfaceName.length() - 1); |
| 581 | if ("?+*~".indexOf(c) >= 0) { |
| 582 | if (c == '?' || c == '*' || c == '~') |
| 583 | optional.add(referenceName); |
| 584 | if (c == '+' || c == '*') |
| 585 | multiple.add(referenceName); |
| 586 | if (c == '+' || c == '*' || c == '?') |
| 587 | dynamic.add(referenceName); |
| 588 | interfaceName = interfaceName.substring(0, interfaceName.length() - 1); |
| 589 | } |
| 590 | |
| 591 | // Parse the target from the interface name |
| 592 | // The target is a filter. |
| 593 | String target = null; |
| 594 | Matcher m = REFERENCE.matcher(interfaceName); |
| 595 | if (m.matches()) { |
| 596 | interfaceName = m.group(1); |
| 597 | target = m.group(2); |
| 598 | } |
| 599 | |
| 600 | analyzer.referTo(interfaceName); |
| 601 | |
| 602 | pw.printf(" <reference name='%s'", referenceName); |
| 603 | pw.printf(" interface='%s'", interfaceName); |
| 604 | |
| 605 | String cardinality = optional.contains(referenceName) ? "0" : "1"; |
| 606 | cardinality += ".."; |
| 607 | cardinality += multiple.contains(referenceName) ? "n" : "1"; |
| 608 | if (!cardinality.equals("1..1")) |
| 609 | pw.print(" cardinality='" + cardinality + "'"); |
| 610 | |
| 611 | if (bind != null) { |
| 612 | pw.printf(" bind='%s'", bind); |
| 613 | if (unbind != null) { |
| 614 | pw.printf(" unbind='%s'", unbind); |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | if (dynamic.contains(referenceName)) { |
| 619 | pw.print(" policy='dynamic'"); |
| 620 | } |
| 621 | |
| 622 | if (target != null) { |
| 623 | // Filter filter = new Filter(target); |
| 624 | // if (filter.verify() == null) |
| 625 | // pw.print(" target='" + filter.toString() + "'"); |
| 626 | // else |
| 627 | // error("Target for " + referenceName |
| 628 | // + " is not a correct filter: " + target + " " |
| 629 | // + filter.verify()); |
| 630 | pw.print(" target='" + escape(target) + "'"); |
| 631 | } |
| 632 | pw.println("/>"); |
| 633 | } |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | /** |
| 638 | * Escape a string, do entity conversion. |
| 639 | */ |
| 640 | static String escape(String s) { |
| 641 | StringBuffer sb = new StringBuffer(); |
| 642 | for (int i = 0; i < s.length(); i++) { |
| 643 | char c = s.charAt(i); |
| 644 | switch (c) { |
| 645 | case '<': |
| 646 | sb.append("<"); |
| 647 | break; |
| 648 | case '>': |
| 649 | sb.append(">"); |
| 650 | break; |
| 651 | case '&': |
| 652 | sb.append("&"); |
| 653 | break; |
| 654 | case '\'': |
| 655 | sb.append("""); |
| 656 | break; |
| 657 | default: |
| 658 | sb.append(c); |
| 659 | break; |
| 660 | } |
| 661 | } |
| 662 | return sb.toString(); |
| 663 | } |
| 664 | |
| 665 | } |