Skip to content

Commit d2b6eb6

Browse files
author
krystian.panek
committed
Bundle assemblation feature added
1 parent 77bb26c commit d2b6eb6

File tree

8 files changed

+456
-6
lines changed

8 files changed

+456
-6
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<groupId>com.neva.felix</groupId>
2323
<artifactId>search-webconsole-plugin</artifactId>
2424
<packaging>bundle</packaging>
25-
<version>1.1.1-SNAPSHOT</version>
25+
<version>1.2.0-SNAPSHOT</version>
2626
<name>search-webconsole-plugin</name>
2727
<description>Search everywhere plugin for Apache Felix Web Console</description>
2828
<inceptionYear>2017</inceptionYear>

src/main/java/com/neva/felix/webconsole/plugins/search/SearchHttpTracker.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ private ImmutableSet<RestServlet> createRestServlets() {
5858
new ClassDecompileServlet(context),
5959
new ClassSearchServlet(context),
6060
new SourceGenerateServlet(context),
61-
new FileDownloadServlet(context)
61+
new FileDownloadServlet(context),
62+
new BundleAssembleServlet(context)
6263
);
6364
}
6465
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.neva.felix.webconsole.plugins.search.core;
2+
3+
import java.io.File;
4+
import org.apache.commons.lang3.StringUtils;
5+
import org.apache.commons.lang3.builder.EqualsBuilder;
6+
import org.apache.commons.lang3.builder.HashCodeBuilder;
7+
import org.osgi.framework.Bundle;
8+
9+
public class BundleJar {
10+
11+
private Bundle bundle;
12+
13+
private File jar;
14+
15+
public BundleJar(Bundle bundle, File jar) {
16+
this.bundle = bundle;
17+
this.jar = jar;
18+
}
19+
20+
public Bundle getBundle() {
21+
return bundle;
22+
}
23+
24+
public File getJar() {
25+
return jar;
26+
}
27+
28+
@Override
29+
public boolean equals(Object o) {
30+
if (this == o) return true;
31+
if (o == null || getClass() != o.getClass()) return false;
32+
33+
BundleJar that = (BundleJar) o;
34+
35+
return new EqualsBuilder()
36+
.append(bundle, that.bundle)
37+
.append(jar, that.jar)
38+
.isEquals();
39+
}
40+
41+
@Override
42+
public int hashCode() {
43+
return new HashCodeBuilder()
44+
.append(bundle)
45+
.append(jar)
46+
.toHashCode();
47+
}
48+
}

src/main/java/com/neva/felix/webconsole/plugins/search/core/OsgiExplorer.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ private String getBundleDir() {
7171
System.getProperty("user.dir") + "/" + BUNDLE_STORAGE_DIR_DEFAULT);
7272
}
7373

74+
public File findJar(String bundleId) {
75+
return findJar(Long.valueOf(bundleId));
76+
}
77+
7478
public File findJar(Long bundleId) {
7579
return findJar(findDir(bundleId));
7680
}
@@ -95,6 +99,36 @@ public int compare(File f1, File f2) {
9599
return null;
96100
}
97101

102+
public BundleJar findBundleJar(String bundleId) {
103+
BundleJar result = null;
104+
105+
File jar = findJar(bundleId);
106+
Bundle bundle = findBundle(bundleId);
107+
108+
if (jar != null && bundle != null) {
109+
result = new BundleJar(bundle, jar);
110+
}
111+
112+
return result;
113+
}
114+
115+
public Set<BundleJar> findBundleJars(List<String> bundleIds) {
116+
Set<BundleJar> result = Sets.newLinkedHashSet();
117+
118+
for (String bundleId : bundleIds) {
119+
BundleJar jar = findBundleJar(bundleId);
120+
if (jar != null) {
121+
result.add(jar);
122+
}
123+
}
124+
125+
return result;
126+
}
127+
128+
public String proposeJarName(Long bundleId) {
129+
return proposeJarName(String.valueOf(bundleId));
130+
}
131+
98132
public String proposeJarName(String bundleId) {
99133
String result = BUNDLE_JAR_FILE;
100134

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.neva.felix.webconsole.plugins.search.core.bundleassemble;
2+
3+
import com.neva.felix.webconsole.plugins.search.core.BundleJar;
4+
import com.neva.felix.webconsole.plugins.search.core.OsgiExplorer;
5+
import com.neva.felix.webconsole.plugins.search.core.SearchJob;
6+
import com.neva.felix.webconsole.plugins.search.rest.FileDownloadServlet;
7+
import java.io.File;
8+
import java.io.FileInputStream;
9+
import java.io.FileOutputStream;
10+
import java.io.IOException;
11+
import java.util.Set;
12+
import java.util.zip.ZipEntry;
13+
import java.util.zip.ZipOutputStream;
14+
import org.apache.commons.io.IOUtils;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
public class BundleAssembleJob extends SearchJob {
19+
20+
private static final Logger LOG = LoggerFactory.getLogger(BundleAssembleJob.class);
21+
22+
private static final String ZIP_FILE_PREFIX = "bundle-assemble_";
23+
24+
private static final String ZIP_SUFFIX = ".zip";
25+
26+
private static final String ZIP_NAME = "bundles.zip";
27+
28+
private transient final Set<BundleJar> bundles;
29+
30+
private String downloadUrl;
31+
32+
public BundleAssembleJob(OsgiExplorer osgiExplorer, Set<BundleJar> bundles) {
33+
super(osgiExplorer);
34+
this.bundles = bundles;
35+
this.step = "Gathering";
36+
}
37+
38+
@Override
39+
public void perform() {
40+
total = bundles.size();
41+
step = "Assembling";
42+
43+
ZipOutputStream out = null;
44+
File zipFile = null;
45+
46+
try {
47+
zipFile = File.createTempFile(ZIP_FILE_PREFIX, ZIP_SUFFIX);
48+
out = new ZipOutputStream(new FileOutputStream(zipFile));
49+
50+
for (BundleJar bundleJar: bundles) {
51+
try {
52+
final byte[] binary = IOUtils.toByteArray(new FileInputStream(bundleJar.getJar()));
53+
final String name = osgiExplorer.proposeJarName(bundleJar.getBundle().getBundleId());
54+
final ZipEntry entry = new ZipEntry(name);
55+
56+
out.putNextEntry(entry);
57+
out.write(binary);
58+
out.closeEntry();
59+
} catch (Exception e) {
60+
LOG.warn("Cannot assemble bundle {}", bundleJar.getBundle().getSymbolicName());
61+
}
62+
63+
increment();
64+
}
65+
} catch (IOException e) {
66+
LOG.error("IO error related with ZIP file in temporary directory.", e);
67+
} finally {
68+
IOUtils.closeQuietly(out);
69+
}
70+
71+
if (zipFile != null) {
72+
downloadUrl = FileDownloadServlet.url(osgiExplorer.getContext(), zipFile.getAbsolutePath(), ZIP_NAME);
73+
step = "Done";
74+
} else {
75+
step = "Ended with error";
76+
}
77+
}
78+
79+
public String getDownloadUrl() {
80+
return downloadUrl;
81+
}
82+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.neva.felix.webconsole.plugins.search.rest;
2+
3+
import static com.neva.felix.webconsole.plugins.search.utils.JsonUtils.MessageType;
4+
import static com.neva.felix.webconsole.plugins.search.utils.JsonUtils.writeMessage;
5+
6+
import com.google.common.collect.Lists;
7+
import com.neva.felix.webconsole.plugins.search.core.BundleJar;
8+
import com.neva.felix.webconsole.plugins.search.core.OsgiExplorer;
9+
import com.neva.felix.webconsole.plugins.search.core.SearchMonitor;
10+
import com.neva.felix.webconsole.plugins.search.core.bundleassemble.BundleAssembleJob;
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.util.List;
14+
import java.util.Set;
15+
import javax.servlet.ServletException;
16+
import javax.servlet.http.HttpServletRequest;
17+
import javax.servlet.http.HttpServletResponse;
18+
import org.osgi.framework.BundleContext;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
public class BundleAssembleServlet extends RestServlet {
23+
24+
private static final Logger LOG = LoggerFactory.getLogger(BundleAssembleServlet.class);
25+
26+
public static final String ALIAS_NAME = "bundle-assemble";
27+
28+
private final SearchMonitor<BundleAssembleJob> monitor;
29+
30+
private final OsgiExplorer osgiExplorer;
31+
32+
public BundleAssembleServlet(BundleContext bundleContext) {
33+
super(bundleContext);
34+
this.monitor = new SearchMonitor<>();
35+
this.osgiExplorer = new OsgiExplorer(bundleContext);
36+
}
37+
38+
@Override
39+
protected String getAliasName() {
40+
return ALIAS_NAME;
41+
}
42+
43+
@Override
44+
public void destroy() {
45+
monitor.shutdown();
46+
super.destroy();
47+
}
48+
49+
@Override
50+
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
51+
try {
52+
final RestParams params = RestParams.from(request);
53+
final Set<BundleJar> bundles = osgiExplorer.findBundleJars(params.getBundleIds());
54+
final BundleAssembleJob job = new BundleAssembleJob(osgiExplorer, bundles);
55+
56+
monitor.start(job);
57+
58+
writeMessage(response, MessageType.SUCCESS, "Job started properly.", job);
59+
} catch (Exception e) {
60+
LOG.error("Cannot assemble bundles.", e);
61+
writeMessage(response, MessageType.ERROR, "Internal error occurred.");
62+
}
63+
}
64+
65+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
66+
try {
67+
RestParams params = RestParams.from(request);
68+
String jobId = params.getJobId();
69+
BundleAssembleJob job = monitor.get(jobId);
70+
71+
if (job == null) {
72+
writeMessage(response, MessageType.ERROR, String.format("Job with ID '%s' is not running so it cannot be polled.", jobId));
73+
} else {
74+
writeMessage(response, MessageType.SUCCESS, "Job polled properly.", job);
75+
}
76+
} catch (Exception e) {
77+
LOG.error("Cannot search classes.", e);
78+
writeMessage(response, MessageType.ERROR, "Internal error occurred.");
79+
}
80+
}
81+
82+
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
83+
try {
84+
String jobId = RestParams.from(request).getJobId();
85+
BundleAssembleJob job = monitor.get(jobId);
86+
87+
if (job == null) {
88+
writeMessage(response, MessageType.ERROR, String.format("Job with ID '%s' is not running so it cannot be stopped.", jobId));
89+
} else {
90+
monitor.stop(job);
91+
writeMessage(response, MessageType.SUCCESS, "Job stopped properly.", job);
92+
}
93+
} catch (Exception e) {
94+
LOG.error("Cannot search classes.", e);
95+
writeMessage(response, MessageType.ERROR, "Internal error occurred.");
96+
}
97+
}
98+
99+
}

src/main/resources/search/plugin.html

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<a class="deselect-all" href="javascript:">Deselect all</a>
7777
<a class="search-classes" href="javascript:">Decompile &amp; search</a>
7878
<a class="source-generate" href="javascript:">Generate sources</a>
79+
<a class="bundle-assemble" href="javascript:">Assemble bundles</a>
7980
</span>
8081

8182
<span class="about" style="float: right">
@@ -170,11 +171,11 @@
170171
<tr>
171172
<td style="width: 10%">Actions</td>
172173
<td style="width: 90%" class="progress">
173-
<a href="javascript" class="class-decompile-start ui-button ui-state-default ui-corner-all" title="Start">
174+
<a href="javascript:" class="class-decompile-start ui-button ui-state-default ui-corner-all" title="Start">
174175
<span class="ui-icon ui-icon-play"> Start</span>
175176
</a>
176177

177-
<a href="javascript" class="class-decompile-stop ui-button ui-state-default ui-corner-all" title="Stop" style="display: none">
178+
<a href="javascript:" class="class-decompile-stop ui-button ui-state-default ui-corner-all" title="Stop" style="display: none">
178179
<span class="ui-icon ui-icon-stop"> Stop</span>
179180
</a>
180181
</td>
@@ -226,11 +227,11 @@
226227
<tr>
227228
<td style="width: 10%">Actions</td>
228229
<td style="width: 90%" class="progress">
229-
<a href="javascript" class="source-generate-start ui-button ui-state-default ui-corner-all" title="Start">
230+
<a href="javascript:" class="source-generate-start ui-button ui-state-default ui-corner-all" title="Start">
230231
<span class="ui-icon ui-icon-play"> Start</span>
231232
</a>
232233

233-
<a href="javascript" class="source-generate-stop ui-button ui-state-default ui-corner-all" title="Stop" style="display: none">
234+
<a href="javascript:" class="source-generate-stop ui-button ui-state-default ui-corner-all" title="Stop" style="display: none">
234235
<span class="ui-icon ui-icon-stop"> Stop</span>
235236
</a>
236237
</td>
@@ -255,4 +256,44 @@
255256

256257
<script id="source-generate-results-template" type="text/x-handlebars-template">
257258
<p>Sources generated. <a href="{{downloadUrl}}">Download ZIP</a></p>
259+
</script>
260+
261+
<script id="bundle-assemble-template" type="text/x-handlebars-template">
262+
<div class="dialog dialog-bundle-assemble">
263+
<form action="javascript:" method="post">
264+
<div class="ui-widget-content no-border">
265+
<table class="input nicetable ui-widget">
266+
<tbody class="ui-widget-content">
267+
<tr>
268+
<td style="width: 10%">Actions</td>
269+
<td style="width: 90%" class="progress">
270+
<a href="javascript:" class="bundle-assemble-start ui-button ui-state-default ui-corner-all" title="Start">
271+
<span class="ui-icon ui-icon-play"> Start</span>
272+
</a>
273+
274+
<a href="javascript:" class="bundle-assemble-stop ui-button ui-state-default ui-corner-all" title="Stop" style="display: none">
275+
<span class="ui-icon ui-icon-stop"> Stop</span>
276+
</a>
277+
</td>
278+
</tr>
279+
<tr>
280+
<td style="width: 10%">Progress</td>
281+
<td style="width: 90%" class="progress">
282+
<span class="spinner done"></span>
283+
<span class="text">Ready</span>
284+
</td>
285+
</tr>
286+
</tbody>
287+
</table>
288+
289+
<br/>
290+
291+
<div id="bundle-assemble-results"></div>
292+
</div>
293+
</form>
294+
</div>
295+
</script>
296+
297+
<script id="bundle-assemble-results-template" type="text/x-handlebars-template">
298+
<p>Bundles assembled. <a href="{{downloadUrl}}">Download ZIP</a></p>
258299
</script>

0 commit comments

Comments
 (0)