blob: a641c0b1f5bc918f05a6499b786ecf0040dd0528 [file] [log] [blame]
Stuart McCullochd00f9712009-07-13 10:06:47 +00001package aQute.bnd.make;
2
3import java.io.*;
4import java.util.*;
5import java.util.regex.*;
6
7import aQute.bnd.service.*;
8import aQute.lib.filter.*;
9import aQute.lib.osgi.*;
10import aQute.libg.version.*;
11
12/**
13 * This class is an analyzer plugin. It looks at the properties and tries to
14 * find out if the Service-Component header contains the bnd shortut syntax. If
15 * not, the header is copied to the output, if it does, an XML file is created
16 * and added to the JAR and the header is modified appropriately.
17 */
18public class ServiceComponent implements AnalyzerPlugin {
19 public final static String NAMESPACE_STEM = "http://www.osgi.org/xmlns/scr";
20 public final static String JIDENTIFIER = "<<identifier>>";
21 public final static String COMPONENT_FACTORY = "factory:";
22 public final static String COMPONENT_SERVICEFACTORY = "servicefactory:";
23 public final static String COMPONENT_IMMEDIATE = "immediate:";
24 public final static String COMPONENT_ENABLED = "enabled:";
25 public final static String COMPONENT_DYNAMIC = "dynamic:";
26 public final static String COMPONENT_MULTIPLE = "multiple:";
27 public final static String COMPONENT_PROVIDE = "provide:";
28 public final static String COMPONENT_OPTIONAL = "optional:";
29 public final static String COMPONENT_PROPERTIES = "properties:";
30 public final static String COMPONENT_IMPLEMENTATION = "implementation:";
31
32 // v1.1.0
33 public final static String COMPONENT_VERSION = "version:";
34 public final static String COMPONENT_CONFIGURATION_POLICY = "configuration-policy:";
35 public final static String COMPONENT_MODIFIED = "modified:";
36 public final static String COMPONENT_ACTIVATE = "activate:";
37 public final static String COMPONENT_DEACTIVATE = "deactivate:";
38
39 public final static String[] componentDirectives = new String[] {
40 COMPONENT_FACTORY, COMPONENT_IMMEDIATE, COMPONENT_ENABLED,
41 COMPONENT_DYNAMIC, COMPONENT_MULTIPLE, COMPONENT_PROVIDE,
42 COMPONENT_OPTIONAL, COMPONENT_PROPERTIES, COMPONENT_IMPLEMENTATION,
43 COMPONENT_SERVICEFACTORY, COMPONENT_VERSION,
44 COMPONENT_CONFIGURATION_POLICY, COMPONENT_MODIFIED,
45 COMPONENT_ACTIVATE, COMPONENT_DEACTIVATE };
46
47 public final static Set<String> SET_COMPONENT_DIRECTIVES = new HashSet<String>(
48 Arrays
49 .asList(componentDirectives));
50
51 public final static Set<String> SET_COMPONENT_DIRECTIVES_1_1 = //
52 new HashSet<String>(
53 Arrays
54 .asList(
55 COMPONENT_VERSION,
56 COMPONENT_CONFIGURATION_POLICY,
57 COMPONENT_MODIFIED,
58 COMPONENT_ACTIVATE,
59 COMPONENT_DEACTIVATE));
60
61 public boolean analyzeJar(Analyzer analyzer) throws Exception {
62
63 ComponentMaker m = new ComponentMaker(analyzer);
64
65 Map<String, Map<String, String>> l = m.doServiceComponent();
66
67 if (!l.isEmpty())
68 analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor
69 .printClauses(l, ""));
70
71 analyzer.getInfo(m, "Service Component");
72 m.close();
73 return false;
74 }
75
76 private static class ComponentMaker extends Processor {
77 Analyzer analyzer;
78
79 ComponentMaker(Analyzer analyzer) {
80 super(analyzer);
81 this.analyzer = analyzer;
82 }
83
84 Map<String, Map<String, String>> doServiceComponent() throws Exception {
85 String header = getProperty(SERVICE_COMPONENT);
86 return doServiceComponent(header);
87 }
88
89 /**
90 * Check if a service component header is actually referring to a class.
91 * If so, replace the reference with an XML file reference. This makes
92 * it easier to create and use components.
93 *
94 * @throws UnsupportedEncodingException
95 *
96 */
97 public Map<String, Map<String, String>> doServiceComponent(
98 String serviceComponent) throws IOException {
99 Map<String, Map<String, String>> list = newMap();
100 Map<String, Map<String, String>> sc = parseHeader(serviceComponent);
101 Map<String, String> empty = Collections.emptyMap();
102
103 for (Iterator<Map.Entry<String, Map<String, String>>> i = sc
104 .entrySet().iterator(); i.hasNext();) {
105 Map.Entry<String, Map<String, String>> entry = i.next();
106 String name = entry.getKey();
107 Map<String, String> info = entry.getValue();
108 if (name == null) {
109 error("No name in Service-Component header: " + info);
110 continue;
111 }
112 if (name.indexOf("*") >= 0 || analyzer.getJar().exists(name)) {
113 // Normal service component, we do not process them
114 list.put(name, info);
115 } else {
116 String impl = name;
117
118 if (info.containsKey(COMPONENT_IMPLEMENTATION))
119 impl = info.get(COMPONENT_IMPLEMENTATION);
120
121 if (!analyzer.checkClass(impl)) {
122 error("Not found Service-Component header: " + name);
123 } else {
124 // We have a definition, so make an XML resources
125 Resource resource = createComponentResource(name, info);
126 analyzer.getJar().putResource(
127 "OSGI-INF/" + name + ".xml", resource);
128 list.put("OSGI-INF/" + name + ".xml", empty);
129 }
130 }
131 }
132 return list;
133 }
134
135 /**
136 * Create the resource for a DS component.
137 *
138 * @param list
139 * @param name
140 * @param info
141 * @throws UnsupportedEncodingException
142 */
143 Resource createComponentResource(String name, Map<String, String> info)
144 throws IOException {
145 String namespace = getNamespace(info);
146 ByteArrayOutputStream out = new ByteArrayOutputStream();
147 PrintWriter pw = new PrintWriter(new OutputStreamWriter(out,
148 "UTF-8"));
149 pw.println("<?xml version='1.0' encoding='utf-8'?>");
150 pw.print("<component name='" + name + "'");
151 if (namespace != null) {
152 pw.print(" xmlns='" + namespace + "'");
153 }
154
155 doAttribute(pw, info.get(COMPONENT_FACTORY), "factory");
156 doAttribute(pw, info.get(COMPONENT_IMMEDIATE), "immediate",
157 "false", "true");
158 doAttribute(pw, info.get(COMPONENT_ENABLED), "enabled", "true",
159 "false");
160 doAttribute(pw, info.get(COMPONENT_CONFIGURATION_POLICY),
161 "configuration-policy", "optional", "require", "ignore");
162 doAttribute(pw, info.get(COMPONENT_ACTIVATE), "activate",
163 JIDENTIFIER);
164 doAttribute(pw, info.get(COMPONENT_DEACTIVATE), "deactivate",
165 JIDENTIFIER);
166 doAttribute(pw, info.get(COMPONENT_MODIFIED), "modified",
167 JIDENTIFIER);
168
169 pw.println(">");
170
171 // Allow override of the implementation when people
172 // want to choose their own name
173 String impl = (String) info.get(COMPONENT_IMPLEMENTATION);
174 pw.println(" <implementation class='"
175 + (impl == null ? name : impl) + "'/>");
176
177 String provides = info.get(COMPONENT_PROVIDE);
178 boolean servicefactory = Boolean.getBoolean(info
179 .get(COMPONENT_SERVICEFACTORY)
180 + "");
181 provides(pw, provides, servicefactory);
182 properties(pw, info);
183 reference(info, pw);
184 pw.println("</component>");
185 pw.close();
186 byte[] data = out.toByteArray();
187 out.close();
188 return new EmbeddedResource(data, 0);
189 }
190
191 private void doAttribute(PrintWriter pw, String value, String name,
192 String... matches) {
193 if (value != null) {
194 if (matches.length != 0) {
195 if (matches.length == 1 && matches[0].equals(JIDENTIFIER)) {
196 if (!Verifier.isIdentifier(value))
197 error(
198 "Component attribute %s has value %s but is not a Java identifier",
199 name, value);
200 } else {
201
202 if (!Verifier.isMember(value, matches))
203 error(
204 "Component attribute %s has value %s but is not a member of %s",
205 name, value, Arrays.toString(matches));
206 }
207 }
208 pw.print(" ");
209 pw.print(name);
210 pw.print("='");
211 pw.print(value);
212 pw.print("'");
213 }
214 }
215
216 /**
217 * Check if we need to use the v1.1 namespace (or later).
218 *
219 * @param info
220 * @return
221 */
222 private String getNamespace(Map<String, String> info) {
223 String version = info.get(COMPONENT_VERSION);
224 if (version != null) {
225 try {
226 Version v = new Version(version);
227 return NAMESPACE_STEM + "/v" + v;
228 } catch (Exception e) {
229 error("version: specified on component header but not a valid version: "
230 + version);
231 return null;
232 }
233 }
234 for (String key : info.keySet()) {
235 if (SET_COMPONENT_DIRECTIVES_1_1.contains(key)) {
236 return NAMESPACE_STEM + "/v1.1.0";
237 }
238 }
239 return null;
240 }
241
242 /**
243 * Print the Service-Component properties element
244 *
245 * @param pw
246 * @param info
247 */
248 void properties(PrintWriter pw, Map<String, String> info) {
249 Collection<String> properties = split(info
250 .get(COMPONENT_PROPERTIES));
251 for (Iterator<String> p = properties.iterator(); p.hasNext();) {
252 String clause = p.next();
253 int n = clause.indexOf('=');
254 if (n <= 0) {
255 error("Not a valid property in service component: "
256 + clause);
257 } else {
258 String type = null;
259 String name = clause.substring(0, n);
260 if (name.indexOf('@') >= 0) {
261 String parts[] = name.split("@");
262 name = parts[1];
263 type = parts[0];
264 }
265 String value = clause.substring(n + 1).trim();
266 // TODO verify validity of name and value.
267 pw.print("<property name='");
268 pw.print(name);
269 pw.print("'");
270
271 if (type != null) {
272 if (VALID_PROPERTY_TYPES.matcher(type).matches()) {
273 pw.print(" type='");
274 pw.print(type);
275 pw.print("'");
276 } else {
277 warning("Invalid property type '" + type
278 + "' for property " + name);
279 }
280 }
281
282 String parts[] = value.split("\\s*(\\||\\n)\\s*");
283 if (parts.length > 1) {
284 pw.println(">");
285 for (String part : parts) {
286 pw.println(part);
287 }
288 pw.println("</property>");
289 } else {
290 pw.print(" value='");
291 pw.print(parts[0]);
292 pw.print("'/>");
293 }
294 }
295 }
296 }
297
298 /**
299 * @param pw
300 * @param provides
301 */
302 void provides(PrintWriter pw, String provides, boolean servicefactory) {
303 if (provides != null) {
304 if (!servicefactory)
305 pw.println(" <service>");
306 else
307 pw.println(" <service servicefactory='true'>");
308
309 StringTokenizer st = new StringTokenizer(provides, ",");
310 while (st.hasMoreTokens()) {
311 String interfaceName = st.nextToken();
312 pw.println(" <provide interface='" + interfaceName
313 + "'/>");
314 if (!analyzer.checkClass(interfaceName))
315 error("Component definition provides a class that is neither imported nor contained: "
316 + interfaceName);
317 }
318 pw.println(" </service>");
319 }
320 }
321
322 public final static Pattern REFERENCE = Pattern.compile("([^(]+)(\\(.+\\))?");
323
324 /**
325 * @param info
326 * @param pw
327 */
328
329 void reference(Map<String, String> info, PrintWriter pw) {
330 Collection<String> dynamic = new ArrayList<String>(split(info.get(COMPONENT_DYNAMIC)));
331 Collection<String> optional = new ArrayList<String>(split(info.get(COMPONENT_OPTIONAL)));
332 Collection<String> multiple = new ArrayList<String>(split(info.get(COMPONENT_MULTIPLE)));
333
334 for (Iterator<Map.Entry<String, String>> r = info.entrySet()
335 .iterator(); r.hasNext();) {
336 Map.Entry<String, String> ref = r.next();
337 String referenceName = (String) ref.getKey();
338 String target = null;
339 String interfaceName = (String) ref.getValue();
340 if (interfaceName == null || interfaceName.length() == 0) {
341 error("Invalid Interface Name for references in Service Component: "
342 + referenceName + "=" + interfaceName);
343 }
344 char c = interfaceName.charAt(interfaceName.length() - 1);
345 if ("?+*~".indexOf(c) >= 0) {
346 if (c == '?' || c == '*' || c == '~')
347 optional.add(referenceName);
348 if (c == '+' || c == '*')
349 multiple.add(referenceName);
350 if (c == '+' || c == '*' || c == '?')
351 dynamic.add(referenceName);
352 interfaceName = interfaceName.substring(0, interfaceName
353 .length() - 1);
354 }
355
356 if (referenceName.endsWith(":")) {
357 if (!SET_COMPONENT_DIRECTIVES.contains(referenceName))
358 error("Unrecognized directive in Service-Component header: "
359 + referenceName);
360 continue;
361 }
362
363 Matcher m = REFERENCE.matcher(interfaceName);
364 if (m.matches()) {
365 interfaceName = m.group(1);
366 target = m.group(2);
367 }
368
369 if (!analyzer.checkClass(interfaceName))
370 error("Component definition refers to a class that is neither imported nor contained: "
371 + interfaceName);
372
373 pw.print(" <reference name='" + referenceName
374 + "' interface='" + interfaceName + "'");
375
376 String cardinality = optional.contains(referenceName) ? "0"
377 : "1";
378 cardinality += "..";
379 cardinality += multiple.contains(referenceName) ? "n" : "1";
380 if (!cardinality.equals("1..1"))
381 pw.print(" cardinality='" + cardinality + "'");
382
383 if (Character.isLowerCase(referenceName.charAt(0))) {
384 String z = referenceName.substring(0, 1).toUpperCase()
385 + referenceName.substring(1);
386 pw.print(" bind='set" + z + "'");
387 pw.print(" unbind='unset" + z + "'");
388 // TODO Verify that the methods exist
389 }
390
391 if (dynamic.contains(referenceName)) {
392 pw.print(" policy='dynamic'");
393 }
394
395 if (target != null) {
396 Filter filter = new Filter(target);
397 if (filter.verify() == null)
398 pw.print(" target='" + filter.toString() + "'");
399 else
400 error("Target for " + referenceName
401 + " is not a correct filter: " + target + " "
402 + filter.verify());
403 }
404 pw.println("/>");
405 }
406 }
407 }
408}