Brian O'Connor
Committed by Gerrit Code Review

Adding src/main/webapp resources to WARs

This fixes onos-gui, so now onos-rest and onos-gui work.
We also exclude duplicate resources in the output jar.

Change-Id: I5fef1376a9f7e88cb7248a606e8f568f641ab45b
1 import random 1 import random
2 2
3 DEBUG_ARG='JAVA_TOOL_OPTIONS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y"' 3 DEBUG_ARG='JAVA_TOOL_OPTIONS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y"'
4 +FORCE_INSTALL=True
5 +NONE='NONE'
4 6
5 def osgi_jar( 7 def osgi_jar(
6 name, 8 name,
...@@ -12,7 +14,10 @@ def osgi_jar( ...@@ -12,7 +14,10 @@ def osgi_jar(
12 license = 'NONE', 14 license = 'NONE',
13 description = '', 15 description = '',
14 debug = False, 16 debug = False,
15 - web_context = 'NONE', 17 + import_packages = '*',
18 + export_packages = '*',
19 + include_resources = NONE,
20 + web_context = NONE,
16 **kwargs 21 **kwargs
17 ): 22 ):
18 23
...@@ -37,6 +42,9 @@ def osgi_jar( ...@@ -37,6 +42,9 @@ def osgi_jar(
37 group_id, #group id 42 group_id, #group id
38 version, #version 43 version, #version
39 license, #license url 44 license, #license url
45 + "'%s'" % import_packages, #packages to import
46 + "'%s'" % export_packages, #packages to export
47 + include_resources, #custom includes to classpath
40 web_context, #web context (REST API only) 48 web_context, #web context (REST API only)
41 description, #description 49 description, #description
42 ) 50 )
...@@ -55,6 +63,7 @@ def osgi_jar( ...@@ -55,6 +63,7 @@ def osgi_jar(
55 name = osgi_jar_name, 63 name = osgi_jar_name,
56 bash = bash, 64 bash = bash,
57 out = name + '.jar', 65 out = name + '.jar',
66 + srcs = glob(['src/main/webapp/**']),
58 visibility = [], #intentially, not visible 67 visibility = [], #intentially, not visible
59 ) 68 )
60 69
...@@ -89,9 +98,11 @@ def osgi_jar( ...@@ -89,9 +98,11 @@ def osgi_jar(
89 '-DartifactId=%s' % name, 98 '-DartifactId=%s' % name,
90 '-Dversion=%s' % version, 99 '-Dversion=%s' % version,
91 '-Dpackaging=jar' )) 100 '-Dpackaging=jar' ))
92 - # TODO This rule must be run every time, adding random number as rule input. 101 + cmd = mvn_cmd + ' > $OUT'
93 - # We should make this configurable, perhaps with a flag. 102 + if FORCE_INSTALL:
94 - cmd = 'FOO=%s ' % random.random() + mvn_cmd + ' > $OUT' 103 + # Add a random number to the command to force this rule to run.
104 + # TODO We should make this configurable from CLI, perhaps with a flag.
105 + cmd = 'FOO=%s ' % random.random() + cmd
95 genrule( 106 genrule(
96 name = name + '-install', 107 name = name + '-install',
97 bash = cmd, 108 bash = cmd,
......
...@@ -16,16 +16,28 @@ ...@@ -16,16 +16,28 @@
16 16
17 package org.onlab.osgiwrap; 17 package org.onlab.osgiwrap;
18 18
19 +import aQute.bnd.header.Attrs;
20 +import aQute.bnd.header.Parameters;
19 import aQute.bnd.osgi.Analyzer; 21 import aQute.bnd.osgi.Analyzer;
20 import aQute.bnd.osgi.Builder; 22 import aQute.bnd.osgi.Builder;
23 +import aQute.bnd.osgi.FileResource;
21 import aQute.bnd.osgi.Jar; 24 import aQute.bnd.osgi.Jar;
25 +import aQute.bnd.osgi.Resource;
22 import com.google.common.base.Joiner; 26 import com.google.common.base.Joiner;
23 import com.google.common.base.MoreObjects; 27 import com.google.common.base.MoreObjects;
24 import com.google.common.collect.Lists; 28 import com.google.common.collect.Lists;
25 import com.google.common.collect.Maps; 29 import com.google.common.collect.Maps;
30 +import com.google.common.collect.Sets;
26 import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin; 31 import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
27 32
28 import java.io.File; 33 import java.io.File;
34 +import java.io.IOException;
35 +import java.nio.file.FileVisitResult;
36 +import java.nio.file.FileVisitor;
37 +import java.nio.file.Path;
38 +import java.nio.file.Paths;
39 +import java.nio.file.SimpleFileVisitor;
40 +import java.nio.file.attribute.BasicFileAttributes;
29 import java.util.Arrays; 41 import java.util.Arrays;
30 import java.util.HashSet; 42 import java.util.HashSet;
31 import java.util.List; 43 import java.util.List;
...@@ -34,10 +46,13 @@ import java.util.Objects; ...@@ -34,10 +46,13 @@ import java.util.Objects;
34 import java.util.Set; 46 import java.util.Set;
35 import java.util.jar.Manifest; 47 import java.util.jar.Manifest;
36 48
49 +import static java.nio.file.Files.walkFileTree;
50 +
37 /** 51 /**
38 * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs. 52 * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
39 */ 53 */
40 public class OSGiWrapper { 54 public class OSGiWrapper {
55 + private static final String NONE = "NONE";
41 56
42 private String inputJar; 57 private String inputJar;
43 private String outputJar; 58 private String outputJar;
...@@ -48,14 +63,18 @@ public class OSGiWrapper { ...@@ -48,14 +63,18 @@ public class OSGiWrapper {
48 private String bundleSymbolicName; 63 private String bundleSymbolicName;
49 private String bundleVersion; 64 private String bundleVersion;
50 65
66 + private String importPackages;
67 + private String exportPackages;
68 + private String includeResources;
69 + private Set<String> includedResources = Sets.newHashSet();
70 +
51 private String bundleDescription; 71 private String bundleDescription;
52 private String bundleLicense; 72 private String bundleLicense;
53 73
54 private String webContext; 74 private String webContext;
55 75
56 public static void main(String[] args) { 76 public static void main(String[] args) {
57 - 77 + if (args.length < 10) {
58 - if (args.length < 7) {
59 System.err.println("Not enough args"); 78 System.err.println("Not enough args");
60 System.exit(1); 79 System.exit(1);
61 } 80 }
...@@ -67,16 +86,21 @@ public class OSGiWrapper { ...@@ -67,16 +86,21 @@ public class OSGiWrapper {
67 String group = args[4]; 86 String group = args[4];
68 String version = args[5]; 87 String version = args[5];
69 String license = args[6]; 88 String license = args[6];
70 - String webContext = args[7]; 89 + String importPackages = args[7];
71 - String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 8, args.length)); 90 + String exportPackages = args[8];
91 + String includeResources = args[9];
92 + String webContext = args[10];
93 + String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 11, args.length));
72 94
73 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp, 95 OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
74 name, group, 96 name, group,
75 version, license, 97 version, license,
98 + importPackages, exportPackages,
99 + includeResources,
76 webContext, desc); 100 webContext, desc);
77 wrapper.log(wrapper + "\n"); 101 wrapper.log(wrapper + "\n");
78 if (!wrapper.execute()) { 102 if (!wrapper.execute()) {
79 - System.err.println("ERROR"); 103 + System.err.printf("Error generating %s\n", name);
80 System.exit(2); 104 System.exit(2);
81 } 105 }
82 } 106 }
...@@ -89,6 +113,9 @@ public class OSGiWrapper { ...@@ -89,6 +113,9 @@ public class OSGiWrapper {
89 String groupId, 113 String groupId,
90 String bundleVersion, 114 String bundleVersion,
91 String bundleLicense, 115 String bundleLicense,
116 + String importPackages,
117 + String exportPackages,
118 + String includeResources,
92 String webContext, 119 String webContext,
93 String bundleDescription) { 120 String bundleDescription) {
94 this.inputJar = inputJar; 121 this.inputJar = inputJar;
...@@ -106,6 +133,12 @@ public class OSGiWrapper { ...@@ -106,6 +133,12 @@ public class OSGiWrapper {
106 this.bundleLicense = bundleLicense; 133 this.bundleLicense = bundleLicense;
107 this.bundleDescription = bundleDescription; 134 this.bundleDescription = bundleDescription;
108 135
136 + this.importPackages = importPackages;
137 + this.exportPackages = exportPackages;
138 + if (!Objects.equals(includeResources, NONE)) {
139 + this.includeResources = includeResources;
140 + }
141 +
109 this.webContext = webContext; 142 this.webContext = webContext;
110 } 143 }
111 144
...@@ -122,17 +155,20 @@ public class OSGiWrapper { ...@@ -122,17 +155,20 @@ public class OSGiWrapper {
122 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}"); 155 //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
123 156
124 // There are no good defaults so make sure you set the Import-Package 157 // There are no good defaults so make sure you set the Import-Package
125 - analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*"); 158 + analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackages);
126 159
127 // TODO include version in export, but not in import 160 // TODO include version in export, but not in import
128 - analyzer.setProperty(Analyzer.EXPORT_PACKAGE, "*"); 161 + analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackages);
129 162
130 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck 163 // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
131 - //analyzer.setProperty(analyzer.INCLUDE_RESOURCE, ...) 164 + if (includeResources != null) {
165 + analyzer.setProperty(Analyzer.INCLUDE_RESOURCE, includeResources);
166 + }
167 +
132 if (isWab()) { 168 if (isWab()) {
133 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/"); 169 analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
134 analyzer.setProperty("Web-ContextPath", webContext); 170 analyzer.setProperty("Web-ContextPath", webContext);
135 - analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet"); 171 + analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet,org.jvnet.mimepull\n");
136 } 172 }
137 } 173 }
138 174
...@@ -165,6 +201,10 @@ public class OSGiWrapper { ...@@ -165,6 +201,10 @@ public class OSGiWrapper {
165 scrDescriptorBndPlugin.setReporter(analyzer); 201 scrDescriptorBndPlugin.setReporter(analyzer);
166 scrDescriptorBndPlugin.analyzeJar(analyzer); 202 scrDescriptorBndPlugin.analyzeJar(analyzer);
167 203
204 + if (includeResources != null) {
205 + doIncludeResources(analyzer);
206 + }
207 +
168 // Repack the JAR as a WAR 208 // Repack the JAR as a WAR
169 doWabStaging(analyzer); 209 doWabStaging(analyzer);
170 210
...@@ -176,10 +216,14 @@ public class OSGiWrapper { ...@@ -176,10 +216,14 @@ public class OSGiWrapper {
176 216
177 if (analyzer.isOk()) { 217 if (analyzer.isOk()) {
178 analyzer.getJar().setManifest(manifest); 218 analyzer.getJar().setManifest(manifest);
179 - analyzer.save(new File(outputJar), true); 219 + if (analyzer.save(new File(outputJar), true)) {
180 - log("Saved!\n"); 220 + log("Saved!\n");
221 + } else {
222 + warn("Failed to create jar \n");
223 + return false;
224 + }
181 } else { 225 } else {
182 - warn("%s\n", analyzer.getErrors()); 226 + warn("Analyzer Errors:\n%s\n", analyzer.getErrors());
183 return false; 227 return false;
184 } 228 }
185 229
...@@ -193,7 +237,7 @@ public class OSGiWrapper { ...@@ -193,7 +237,7 @@ public class OSGiWrapper {
193 } 237 }
194 238
195 private boolean isWab() { 239 private boolean isWab() {
196 - return !Objects.equals(webContext, "NONE"); 240 + return !Objects.equals(webContext, NONE);
197 } 241 }
198 242
199 private void doWabStaging(Analyzer analyzer) throws Exception { 243 private void doWabStaging(Analyzer analyzer) throws Exception {
...@@ -215,6 +259,88 @@ public class OSGiWrapper { ...@@ -215,6 +259,88 @@ public class OSGiWrapper {
215 dot.rename(path, "WEB-INF/classes/" + path); 259 dot.rename(path, "WEB-INF/classes/" + path);
216 } 260 }
217 } 261 }
262 +
263 + Path wabRoot = Paths.get(wab);
264 + includeFiles(dot, null, wabRoot.toString());
265 + }
266 +
267 + /**
268 + * Parse the Bundle-Includes header. Files in the bundles Include header are
269 + * included in the jar. The source can be a directory or a file.
270 + *
271 + * @throws Exception
272 + */
273 + private void doIncludeResources(Analyzer analyzer) throws Exception {
274 + String includes = analyzer.getProperty(Analyzer.INCLUDE_RESOURCE);
275 + if (includes == null) {
276 + return;
277 + }
278 + Parameters clauses = analyzer.parseHeader(includes);
279 + Jar jar = analyzer.getJar();
280 +
281 + for (Map.Entry<String, Attrs> entry : clauses.entrySet()) {
282 + String name = entry.getKey();
283 + Map<String, String> extra = entry.getValue();
284 + // TODO consider doing something with extras
285 +
286 + String[] parts = name.split("\\s*=\\s*");
287 + String source = parts[0];
288 + String destination = parts[0];
289 + if (parts.length == 2) {
290 + source = parts[1];
291 + }
292 +
293 + includeFiles(jar, destination, source);
294 + }
295 + }
296 +
297 + private void includeFiles(Jar jar, String destinationRoot, String sourceRoot)
298 + throws IOException {
299 + Path sourceRootPath = Paths.get(sourceRoot);
300 + // iterate through sources
301 + // put each source on the jar
302 + FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
303 + @Override
304 + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
305 + Path relativePath = sourceRootPath.relativize(file);
306 + String destination = destinationRoot != null ?
307 + destinationRoot + "/" + relativePath.toString() : //TODO
308 + relativePath.toString();
309 +
310 + addFileToJar(jar, destination, file.toAbsolutePath().toString());
311 + return FileVisitResult.CONTINUE;
312 + }
313 + };
314 + File dir = new File(sourceRoot);
315 + if (dir.isFile()) {
316 + addFileToJar(jar, destinationRoot, dir.getAbsolutePath());
317 + } else if (dir.isDirectory()) {
318 + walkFileTree(sourceRootPath, visitor);
319 + } else {
320 + warn("Skipping resource in bundle %s: %s (File Not Found)\n",
321 + bundleSymbolicName, sourceRoot);
322 + }
323 + }
324 +
325 + private boolean addFileToJar(Jar jar, String destination, String sourceAbsPath) {
326 + if (includedResources.contains(sourceAbsPath)) {
327 + log("Skipping already included resource: %s\n", sourceAbsPath);
328 + return false;
329 + }
330 + File file = new File(sourceAbsPath);
331 + if (!file.isFile()) {
332 + throw new RuntimeException(
333 + String.format("Skipping non-existent file: %s\n", sourceAbsPath));
334 + }
335 + Resource resource = new FileResource(file);
336 + if (jar.getResource(destination) != null) {
337 + warn("Skipping duplicate resource: %s\n", destination);
338 + return false;
339 + }
340 + jar.putResource(destination, resource);
341 + includedResources.add(sourceAbsPath);
342 + log("Adding resource: %s\n", destination);
343 + return true;
218 } 344 }
219 345
220 private void log(String format, Object... objects) { 346 private void log(String format, Object... objects) {
......
...@@ -22,6 +22,17 @@ TEST_DEPS = [ ...@@ -22,6 +22,17 @@ TEST_DEPS = [
22 '//lib:TEST', 22 '//lib:TEST',
23 ] 23 ]
24 24
25 +RESOURCES = [
26 + 'WEB-INF/classes/index.html=src/main/webapp/index.html',
27 + 'WEB-INF/classes/login.html=src/main/webapp/login.html',
28 + 'WEB-INF/classes/error.html=src/main/webapp/error.html',
29 + 'WEB-INF/classes/not-ready.html=src/main/webapp/not-ready.html',
30 + 'WEB-INF/classes/onos.js=src/main/webapp/onos.js',
31 + 'WEB-INF/classes/nav.html=src/main/webapp/nav.html',
32 + 'WEB-INF/classes/app/view=src/main/webapp/app/view',
33 + 'WEB-INF/classes/raw=src/main/webapp/raw',
34 +]
35 +
25 osgi_jar( 36 osgi_jar(
26 name = CURRENT_NAME, 37 name = CURRENT_NAME,
27 srcs = glob([SRC + '/*.java']), 38 srcs = glob([SRC + '/*.java']),
...@@ -29,6 +40,9 @@ osgi_jar( ...@@ -29,6 +40,9 @@ osgi_jar(
29 resources_root = RESOURCES_ROOT, 40 resources_root = RESOURCES_ROOT,
30 deps = COMPILE_DEPS, 41 deps = COMPILE_DEPS,
31 visibility = ['PUBLIC'], 42 visibility = ['PUBLIC'],
43 + include_resources = ','.join(RESOURCES),
44 + web_context = '/onos/ui',
45 + debug = False,
32 ) 46 )
33 47
34 java_test( 48 java_test(
......