1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.kernel.bundleinfo;
23
24 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
25 import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
26 import static org.opencastproject.util.EqualsUtil.ne;
27 import static org.opencastproject.util.Jsons.arr;
28 import static org.opencastproject.util.Jsons.obj;
29 import static org.opencastproject.util.Jsons.p;
30 import static org.opencastproject.util.Jsons.stringVal;
31 import static org.opencastproject.util.RestUtil.R.notFound;
32 import static org.opencastproject.util.RestUtil.R.ok;
33 import static org.opencastproject.util.data.Collections.set;
34 import static org.opencastproject.util.data.Collections.toArray;
35 import static org.opencastproject.util.data.Monadics.mlist;
36
37 import org.opencastproject.util.Jsons;
38 import org.opencastproject.util.data.Function;
39 import org.opencastproject.util.data.Monadics;
40 import org.opencastproject.util.data.Option;
41 import org.opencastproject.util.data.functions.Functions;
42 import org.opencastproject.util.doc.rest.RestParameter;
43 import org.opencastproject.util.doc.rest.RestQuery;
44 import org.opencastproject.util.doc.rest.RestResponse;
45 import org.opencastproject.util.doc.rest.RestService;
46
47 import org.osgi.service.component.ComponentContext;
48 import org.osgi.service.component.annotations.Activate;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import java.util.List;
53 import java.util.Set;
54
55 import javax.servlet.http.HttpServletResponse;
56 import javax.ws.rs.DELETE;
57 import javax.ws.rs.DefaultValue;
58 import javax.ws.rs.GET;
59 import javax.ws.rs.Path;
60 import javax.ws.rs.Produces;
61 import javax.ws.rs.QueryParam;
62 import javax.ws.rs.core.Response;
63
64
65 @RestService(
66 name = "systemInfo",
67 title = "System Bundle Info",
68 notes = { "This is used to display the version information on the login page." },
69 abstractText = "The system bundle info endpoint yields information about the running OSGi bundles of Opencast.")
70 public abstract class BundleInfoRestEndpoint {
71
72 private static final Logger logger = LoggerFactory.getLogger(BundleInfoRestEndpoint.class);
73
74 private static final String DEFAULT_BUNDLE_PREFIX = "opencast";
75
76 protected abstract BundleInfoDb getDb();
77
78 private long lastModified = 0;
79
80 @Activate
81 public void activate(ComponentContext cc) {
82 lastModified = cc.getBundleContext().getBundle().getLastModified();
83 }
84
85 @GET
86
87
88 @Path("bundles/list")
89 @Produces(APPLICATION_JSON)
90 @RestQuery(
91 name = "list",
92 description = "Return a list of all running bundles on the whole cluster.",
93 responses = {
94 @RestResponse(description = "A list of bundles.", responseCode = HttpServletResponse.SC_OK) },
95 returnDescription = "The search results, expressed as xml or json.")
96 public Response getVersions() {
97 final Monadics.ListMonadic<Jsons.Val> bundleInfos = mlist(getDb().getBundles()).map(
98 Functions.<BundleInfo, Jsons.Val> co(bundleInfo));
99 return ok(obj(p("bundleInfos", arr(bundleInfos)), p("count", bundleInfos.value().size())));
100 }
101
102
103 @GET
104 @Path("bundles/check")
105 @RestQuery(
106 name = "check",
107 description = "Check if all bundles throughout the cluster have the same OSGi bundle version and build number.",
108 restParameters = {
109 @RestParameter(
110 name = "prefix",
111 description = "The bundle name prefixes to check. Defaults to '" + DEFAULT_BUNDLE_PREFIX + "'.",
112 isRequired = false,
113 defaultValue = DEFAULT_BUNDLE_PREFIX,
114 type = RestParameter.Type.STRING) },
115 responses = {
116 @RestResponse(description = "true/false", responseCode = HttpServletResponse.SC_OK),
117 @RestResponse(description = "cannot find any bundles with the given prefix", responseCode = HttpServletResponse.SC_NOT_FOUND) },
118 returnDescription = "The search results, expressed as xml or json.")
119 public Response checkBundles(@DefaultValue(DEFAULT_BUNDLE_PREFIX) @QueryParam("prefix") List<String> prefixes) {
120 return withBundles(prefixes, new Function<List<BundleInfo>, Response>() {
121 @Override
122 public Response apply(List<BundleInfo> infos) {
123 final String bundleVersion = infos.get(0).getBundleVersion();
124 final Option<String> buildNumber = infos.get(0).getBuildNumber();
125 for (BundleInfo a : infos) {
126 if (ne(a.getBundleVersion(), bundleVersion) || ne(a.getBuildNumber(), buildNumber))
127 return ok(TEXT_PLAIN_TYPE, "false");
128 }
129 return ok(TEXT_PLAIN_TYPE, "true");
130 }
131 });
132 }
133
134
135 @GET
136 @Path("bundles/version")
137 @Produces(APPLICATION_JSON)
138 @RestQuery(
139 name = "bundleVersion",
140 description = "Return the common OSGi build version and build number of all bundles matching the given prefix.",
141 restParameters = {
142 @RestParameter(
143 name = "prefix",
144 description = "The bundle name prefixes to check. Defaults to '" + DEFAULT_BUNDLE_PREFIX + "'.",
145 isRequired = false,
146 defaultValue = DEFAULT_BUNDLE_PREFIX,
147 type = RestParameter.Type.STRING) },
148 responses = {
149 @RestResponse(description = "Version structure", responseCode = HttpServletResponse.SC_OK),
150 @RestResponse(description = "No bundles with the given prefix", responseCode = HttpServletResponse.SC_NOT_FOUND) },
151 returnDescription = "The search results as json.")
152 public Response getBundleVersion(@DefaultValue(DEFAULT_BUNDLE_PREFIX) @QueryParam("prefix") List<String> prefixes) {
153 return withBundles(prefixes, new Function<List<BundleInfo>, Response>() {
154 @Override
155 public Response apply(List<BundleInfo> infos) {
156 final Set<BundleVersion> versions = set();
157 for (BundleInfo bundle : infos) {
158 versions.add(bundle.getVersion());
159 }
160 final BundleInfo example = infos.get(0);
161 switch (versions.size()) {
162 case 0:
163
164 throw new Error("bug");
165 case 1:
166
167 return ok(obj(p("consistent", true))
168 .append(fullVersionJson.apply(example.getVersion()))
169 .append(obj(p("last-modified", lastModified))));
170 default:
171
172 return ok(obj(p("consistent", false),
173 p("versions",
174 arr(mlist(versions.iterator())
175 .map(Functions.<BundleVersion, Jsons.Val> co(fullVersionJson))))));
176 }
177 }
178 });
179 }
180
181 @DELETE
182 @Path("bundles/host")
183 @RestQuery(
184 name = "clearHost",
185 description = "Removes the tracked bundles for a host. This is done automatically when you shut down "
186 + "Opencast. But this endpoint can be used to force this in case e.g. a machine got dropped. Make sure the "
187 + "host is actually gone! The database will be automatically rebuilt when Opencast on that host is "
188 + "(re)started.",
189 restParameters = {
190 @RestParameter(
191 name = "host",
192 description = "The name of the host to clear",
193 isRequired = true,
194 type = RestParameter.Type.STRING,
195 defaultValue = "") },
196 responses = {
197 @RestResponse(description = "Version structure", responseCode = HttpServletResponse.SC_NO_CONTENT) },
198 returnDescription = "No data is returned.")
199 public Response clearHost(@QueryParam("host") String host) {
200 logger.debug("Removing tracked bundles of host: {}", host);
201 getDb().clear(host);
202 return Response.noContent().build();
203 }
204
205 public static final Function<BundleVersion, Jsons.Obj> fullVersionJson = new Function<BundleVersion, Jsons.Obj>() {
206 @Override
207 public Jsons.Obj apply(BundleVersion version) {
208 return obj(p("version", version.getBundleVersion()), p("buildNumber", version.getBuildNumber().map(stringVal)));
209 }
210 };
211
212 public static Jsons.Obj bundleInfoJson(BundleInfo bundle) {
213 return obj(p("host", bundle.getHost()), p("bundleSymbolicName", bundle.getBundleSymbolicName()),
214 p("bundleId", bundle.getBundleId())).append(fullVersionJson.apply(bundle.getVersion()));
215 }
216
217 public static final Function<BundleInfo, Jsons.Obj> bundleInfo = new Function<BundleInfo, Jsons.Obj>() {
218 @Override
219 public Jsons.Obj apply(BundleInfo bundle) {
220 return bundleInfoJson(bundle);
221 }
222 };
223
224
225 private Response withBundles(List<String> prefixes, Function<List<BundleInfo>, Response> f) {
226 final List<BundleInfo> info = getDb().getBundles(toArray(String.class, prefixes));
227 if (info.size() > 0) {
228 return f.apply(info);
229 } else {
230 return notFound("No bundles match one of the given prefixes");
231 }
232 }
233 }