blob: 3d8e0e4fbc0f328e6d26a28b8dfa940b0b273cbf [file] [log] [blame]
Michael Hanl19390652016-01-16 11:01:24 +01001package de.ids_mannheim.korap.web.service.full;
Michael Hanlfb839b92015-09-19 21:32:34 +02002
3import com.fasterxml.jackson.databind.JsonNode;
4import com.fasterxml.jackson.databind.node.ObjectNode;
5import com.sun.jersey.spi.container.ContainerRequest;
6import com.sun.jersey.spi.container.ResourceFilters;
7import de.ids_mannheim.korap.config.BeanConfiguration;
8import de.ids_mannheim.korap.config.Scopes;
9import de.ids_mannheim.korap.config.URIParam;
10import de.ids_mannheim.korap.exceptions.KustvaktException;
11import de.ids_mannheim.korap.exceptions.StatusCodes;
12import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
Michael Hanlfb839b92015-09-19 21:32:34 +020013import de.ids_mannheim.korap.user.*;
14import de.ids_mannheim.korap.utils.JsonUtils;
Michael Hanlfb839b92015-09-19 21:32:34 +020015import de.ids_mannheim.korap.utils.StringUtils;
16import de.ids_mannheim.korap.utils.TimeUtils;
17import de.ids_mannheim.korap.web.KustvaktServer;
18import de.ids_mannheim.korap.web.filter.AuthFilter;
Michael Hanl19390652016-01-16 11:01:24 +010019import de.ids_mannheim.korap.web.filter.BlockingFilter;
Michael Hanlfb839b92015-09-19 21:32:34 +020020import de.ids_mannheim.korap.web.filter.DefaultFilter;
21import de.ids_mannheim.korap.web.filter.PiwikFilter;
Michael Hanl482f30d2015-09-25 12:39:46 +020022import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
23import de.ids_mannheim.korap.web.utils.KustvaktResponseHandler;
Michael Hanlfb839b92015-09-19 21:32:34 +020024import org.slf4j.Logger;
Michael Hanlf1e85e72016-01-21 16:55:45 +010025import org.slf4j.LoggerFactory;
Michael Hanlfb839b92015-09-19 21:32:34 +020026
27import javax.ws.rs.*;
28import javax.ws.rs.core.*;
Michael Hanlfb839b92015-09-19 21:32:34 +020029import java.util.*;
30
31/**
32 * @author hanl
33 * @date 29/01/2014
34 */
35@Path(KustvaktServer.API_VERSION + "/user")
36@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
37@ResourceFilters({ PiwikFilter.class })
38public class UserService {
39
Michael Hanlf1e85e72016-01-21 16:55:45 +010040 private static Logger jlog = LoggerFactory.getLogger(UserService.class);
41 // private static Logger jlog = KustvaktLogger
42 // .getLogger(KustvaktLogger.SECURITY_LOG);
Michael Hanlfb839b92015-09-19 21:32:34 +020043 private AuthenticationManagerIface controller;
Michael Hanlfb839b92015-09-19 21:32:34 +020044
45 private
46 @Context
47 UriInfo info;
48
49 public UserService() {
50 this.controller = BeanConfiguration.getBeans()
51 .getAuthenticationManager();
Michael Hanlfb839b92015-09-19 21:32:34 +020052 }
53
54 // fixme: json contains password in clear text. Encrypt request?
55 // fixme: should also collect service exception, not just db exception!
56 @POST
57 @Path("register")
58 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
59 public Response signUp(
60 @HeaderParam(ContainerRequest.USER_AGENT) String agent,
61 @HeaderParam(ContainerRequest.HOST) String host,
Michael Hanle17eaa52016-01-22 20:55:05 +010062 @Context Locale locale,
63 MultivaluedMap<String, String> form_values) {
Michael Hanlf1e85e72016-01-21 16:55:45 +010064 Map<String, String> wrapper = FormRequestWrapper
Michael Hanl19390652016-01-16 11:01:24 +010065 .toMap(form_values, true);
Michael Hanlfb839b92015-09-19 21:32:34 +020066
67 wrapper.put(Attributes.HOST, host);
68 wrapper.put(Attributes.USER_AGENT, agent);
69 UriBuilder uriBuilder;
70 User user;
Michael Hanl19390652016-01-16 11:01:24 +010071 if (wrapper.get(Attributes.EMAIL) == null)
Michael Hanl482f30d2015-09-25 12:39:46 +020072 throw KustvaktResponseHandler
Michael Hanlfb839b92015-09-19 21:32:34 +020073 .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
74 "email");
75
76 try {
77 uriBuilder = info.getBaseUriBuilder();
78 uriBuilder.path(KustvaktServer.API_VERSION).path("user")
79 .path("confirm");
80
Michael Hanl19390652016-01-16 11:01:24 +010081 user = controller.createUserAccount(wrapper, true);
Michael Hanlfb839b92015-09-19 21:32:34 +020082
83 }catch (KustvaktException e) {
Michael Hanl482f30d2015-09-25 12:39:46 +020084 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +020085 }
86 URIParam uri = user.getField(URIParam.class);
87 if (uri.hasValues()) {
88 uriBuilder.queryParam(Attributes.QUERY_PARAM_URI,
89 uri.getUriFragment())
90 .queryParam(Attributes.QUERY_PARAM_USER,
91 user.getUsername());
92 jlog.info("registration was successful for user '{}'",
93 form_values.get(Attributes.USERNAME));
94 Map object = new HashMap();
95 object.put("confirm_uri", uriBuilder.build());
96 object.put("uri_expiration",
97 TimeUtils.format(uri.getUriExpiration()));
98 return Response.ok(JsonUtils.toJSON(object)).build();
99 }else {
100 // todo: return error or warning
Michael Hanl19390652016-01-16 11:01:24 +0100101 throw KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
102 "failed to validate uri paramter", "confirmation fragment");
Michael Hanlfb839b92015-09-19 21:32:34 +0200103 }
104
105 }
106
Michael Hanle17eaa52016-01-22 20:55:05 +0100107 //todo: password update in special function? --> password reset only!
Michael Hanlfb839b92015-09-19 21:32:34 +0200108 @POST
109 @Path("update")
110 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
111 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
112 PiwikFilter.class })
113 public Response updateAccount(@Context SecurityContext ctx, String json) {
114 TokenContext context = (TokenContext) ctx.getUserPrincipal();
115 try {
116 User user = controller.getUser(context.getUsername());
117
118 JsonNode node = JsonUtils.readTree(json);
119 KorAPUser ident = (KorAPUser) user;
120 KorAPUser values = User.UserFactory.toUser(json);
121 // user = controller
122 // .checkPasswordAllowance(ident, values.getPassword(),
123 // node.path("new_password").asText());
Michael Hanle17eaa52016-01-22 20:55:05 +0100124 // controller.updateAccount(user);
Michael Hanlfb839b92015-09-19 21:32:34 +0200125 }catch (KustvaktException e) {
Michael Hanl482f30d2015-09-25 12:39:46 +0200126 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200127 }
128 return Response.ok().build();
129 }
130
131 @GET
132 @Path("confirm")
133 @Produces(MediaType.TEXT_HTML)
134 public Response confirmRegistration(@QueryParam("uri") String uritoken,
135 @Context Locale locale, @QueryParam("user") String username) {
Michael Hanl19390652016-01-16 11:01:24 +0100136 if (uritoken == null || uritoken.isEmpty())
Michael Hanl482f30d2015-09-25 12:39:46 +0200137 throw KustvaktResponseHandler
Michael Hanlfb839b92015-09-19 21:32:34 +0200138 .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
Michael Hanl19390652016-01-16 11:01:24 +0100139 "uri parameter");
140 if (username == null || username.isEmpty())
Michael Hanl482f30d2015-09-25 12:39:46 +0200141 throw KustvaktResponseHandler
Michael Hanlfb839b92015-09-19 21:32:34 +0200142 .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
143 "Username");
144
145 try {
146 controller.confirmRegistration(uritoken, username);
147 }catch (KustvaktException e) {
Michael Hanl19390652016-01-16 11:01:24 +0100148 e.printStackTrace();
149 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200150 }
151 return Response.ok("success").build();
152 }
153
154 // todo: auditing!
155 @POST
156 @Path("requestReset")
157 @Produces(MediaType.TEXT_HTML)
158 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
159 public Response requestPasswordReset(@Context Locale locale, String json) {
160 JsonNode node = JsonUtils.readTree(json);
161 StringBuilder builder = new StringBuilder();
162 String username, email;
163 username = node.path(Attributes.USERNAME).asText();
164 email = node.path(Attributes.EMAIL).asText();
165
166 // deprecated --> depends on the client!
167 // String url = config.getMailProperties()
168 // .getProperty("korap.frontend.url", "");
169 // if (url.isEmpty())
170 // return Response.ok("URLException: Missing source URL").build();
171
172 // URIUtils utils = new URIUtils(info);
173 // may inject the actual REST url in a redirect request?!
174 // UriBuilder uriBuilder = UriBuilder.fromUri(url).fragment("reset");
175 Object[] objects;
176 try {
177 builder.append("?");
178 // just append the endpint fragment plus the query parameter.
179 // the address by which the data is handled depends on the frontend
180 objects = controller.validateResetPasswordRequest(username, email);
181 builder.append(Attributes.QUERY_PARAM_URI).append("=")
182 .append(objects[0]);
183 builder.append(Attributes.QUERY_PARAM_USER).append("=")
184 .append(username);
185 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100186 jlog.error("Eoxception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200187 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200188 }
189
190 ObjectNode obj = JsonUtils.createObjectNode();
191 obj.put(Attributes.URI, builder.toString());
192 obj.put(Attributes.URI_EXPIRATION, String.valueOf(objects[1]));
193 return Response.ok(JsonUtils.toJSON(obj)).build();
194 }
195
196 @POST
197 @Path("reset")
198 @Produces(MediaType.TEXT_HTML)
199 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
200 public Response resetPassword(
201 @QueryParam(Attributes.QUERY_PARAM_URI) String uri,
202 @QueryParam(Attributes.QUERY_PARAM_USER) String username,
203 @Context HttpHeaders headers, String passphrase) {
204 try {
205 controller.resetPassword(uri, username, passphrase);
206 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100207 jlog.error("Exception encountered!", e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200208 return Response.notModified().build();
209 }
210 return Response.ok().build();
211 }
212
213 @GET
214 @Path("info")
Michael Hanl19390652016-01-16 11:01:24 +0100215 @ResourceFilters({ AuthFilter.class, DefaultFilter.class, PiwikFilter.class,
216 BlockingFilter.class })
Michael Hanlfb839b92015-09-19 21:32:34 +0200217 public Response getStatus(@Context SecurityContext context,
Michael Hanl19390652016-01-16 11:01:24 +0100218 @QueryParam("scopes") String scopes) {
Michael Hanlfb839b92015-09-19 21:32:34 +0200219 TokenContext ctx = (TokenContext) context.getUserPrincipal();
220 User user;
221 try {
222 user = controller.getUser(ctx.getUsername());
Michael Hanl7d925612016-01-28 16:59:30 +0100223 Userdata data = controller.getUserData(user, Userdetails2.class);
Michael Hanlc2a9f622016-01-28 16:40:06 +0100224 user.addUserData(data);
225
Michael Hanl19390652016-01-16 11:01:24 +0100226 Set<String> base_scope = StringUtils.toSet(scopes, " ");
227 if (scopes != null)
228 base_scope.retainAll(StringUtils.toSet(scopes));
229 scopes = StringUtils.toString(base_scope);
Michael Hanlfb839b92015-09-19 21:32:34 +0200230 }catch (KustvaktException e) {
Michael Hanl482f30d2015-09-25 12:39:46 +0200231 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200232 }
Michael Hanlf1e85e72016-01-21 16:55:45 +0100233 Scopes m = Scopes.mapScopes(scopes, user.getDetails());
234 return Response.ok(m.toEntity()).build();
Michael Hanlfb839b92015-09-19 21:32:34 +0200235 }
236
237 @GET
238 @Path("settings")
239 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
240 PiwikFilter.class })
241 public Response getUserSettings(@Context SecurityContext context,
242 @Context Locale locale) {
243 TokenContext ctx = (TokenContext) context.getUserPrincipal();
Michael Hanl7d925612016-01-28 16:59:30 +0100244 String result;
Michael Hanlfb839b92015-09-19 21:32:34 +0200245 try {
Michael Hanl7d925612016-01-28 16:59:30 +0100246 User user = controller.getUser(ctx.getUsername());
247 Userdata data = controller.getUserData(user, UserSettings2.class);
248 data.addField(Attributes.USERNAME, ctx.getUsername());
249 result = data.data();
Michael Hanlfb839b92015-09-19 21:32:34 +0200250 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100251 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200252 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200253 }
Michael Hanl7d925612016-01-28 16:59:30 +0100254 return Response.ok(result).build();
Michael Hanlfb839b92015-09-19 21:32:34 +0200255 }
256
257 // todo: test
258 @POST
259 @Path("settings")
260 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
261 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
262 PiwikFilter.class })
263 public Response updateSettings(@Context SecurityContext context,
Michael Hanlf1e85e72016-01-21 16:55:45 +0100264 @Context Locale locale, MultivaluedMap<String, String> form) {
Michael Hanlfb839b92015-09-19 21:32:34 +0200265 TokenContext ctx = (TokenContext) context.getUserPrincipal();
Michael Hanlf1e85e72016-01-21 16:55:45 +0100266 Map<String, String> settings = FormRequestWrapper.toMap(form, false);
Michael Hanlfb839b92015-09-19 21:32:34 +0200267
268 try {
269 User user = controller.getUser(ctx.getUsername());
Michael Hanl7d925612016-01-28 16:59:30 +0100270 if (user.isDemo())
271 return Response.notModified().build();
272
273 Userdata data = controller.getUserData(user, UserSettings2.class);
Michael Hanl19390652016-01-16 11:01:24 +0100274 // todo: check setting only within the scope of user settings permissions; not foundry range. Latter is part of
275 // frontend which only displays available foundries and
Michael Hanlfb839b92015-09-19 21:32:34 +0200276 // SecurityManager.findbyId(us.getDefaultConstfoundry(), user, Foundry.class);
277 // SecurityManager.findbyId(us.getDefaultLemmafoundry(), user, Foundry.class);
278 // SecurityManager.findbyId(us.getDefaultPOSfoundry(), user, Foundry.class);
279 // SecurityManager.findbyId(us.getDefaultRelfoundry(), user, Foundry.class);
Michael Hanl7d925612016-01-28 16:59:30 +0100280 Userdata new_data = new UserSettings2(user.getId());
281 new_data.setData(JsonUtils.toJSON(settings));
282 data.update(new_data);
Michael Hanlf1e85e72016-01-21 16:55:45 +0100283
Michael Hanl7d925612016-01-28 16:59:30 +0100284 controller.updateUserData(data);
Michael Hanlfb839b92015-09-19 21:32:34 +0200285 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100286 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200287 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200288 }
289
290 return Response.ok().build();
291 }
292
293 @GET
294 @Path("details")
295 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
296 PiwikFilter.class })
297 public Response getDetails(@Context SecurityContext context,
298 @Context Locale locale) {
299 TokenContext ctx = (TokenContext) context.getUserPrincipal();
300 User user;
301 try {
302 user = controller.getUser(ctx.getUsername());
Michael Hanl7d925612016-01-28 16:59:30 +0100303 Userdata data = controller.getUserData(user, Userdetails2.class);
Michael Hanlc2a9f622016-01-28 16:40:06 +0100304 user.addUserData(data);
305
Michael Hanlfb839b92015-09-19 21:32:34 +0200306 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100307 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200308 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200309 }
310
Michael Hanle17eaa52016-01-22 20:55:05 +0100311 Map m = user.getDetails().toMap();
312 m.put(Attributes.USERNAME, ctx.getUsername());
313 return Response.ok(JsonUtils.toJSON(m)).build();
Michael Hanlfb839b92015-09-19 21:32:34 +0200314 }
315
316 @POST
317 @Path("details")
318 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
319 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
320 PiwikFilter.class })
321 public Response updateDetails(@Context SecurityContext context,
Michael Hanl19390652016-01-16 11:01:24 +0100322 @Context Locale locale, MultivaluedMap form) {
Michael Hanlfb839b92015-09-19 21:32:34 +0200323 TokenContext ctx = (TokenContext) context.getUserPrincipal();
Michael Hanl19390652016-01-16 11:01:24 +0100324
Michael Hanlf1e85e72016-01-21 16:55:45 +0100325 Map<String, String> wrapper = FormRequestWrapper.toMap(form, true);
Michael Hanlfb839b92015-09-19 21:32:34 +0200326
327 try {
328 User user = controller.getUser(ctx.getUsername());
Michael Hanlfb839b92015-09-19 21:32:34 +0200329 if (user.isDemo())
330 return Response.notModified().build();
Michael Hanl7d925612016-01-28 16:59:30 +0100331
332 Userdetails2 new_data = new Userdetails2(user.getId());
333 new_data.setData(JsonUtils.toJSON(wrapper));
334
335 Userdetails2 det = controller.getUserData(user, Userdetails2.class);
336 det.update(new_data);
337 controller.updateUserData(det);
Michael Hanlfb839b92015-09-19 21:32:34 +0200338 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100339 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200340 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200341 }
342
343 return Response.ok().build();
344 }
345
346 //fixme: if policy allows, foreign user might be allowed to change search!
347 @POST
348 @Path("queries")
349 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
350 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
351 PiwikFilter.class })
352 public Response updateQueries(@Context SecurityContext context,
353 String json) {
354 TokenContext ctx = (TokenContext) context.getUserPrincipal();
355 Collection<UserQuery> add = new HashSet<>();
356 try {
357 User user = controller.getUser(ctx.getUsername());
358 List<UserQuery> userQuieres = new ArrayList<>();
359 JsonNode nodes = JsonUtils.readTree(json);
360 Iterator<JsonNode> node = nodes.elements();
361 while (node.hasNext()) {
362 JsonNode cursor = node.next();
363 UserQuery query = new UserQuery(cursor.path("id").asInt(),
364 user.getId());
365 query.setQueryLanguage(cursor.path("queryLanguage").asText());
366 query.setQuery(cursor.path("query").asText());
367 query.setDescription(cursor.path("description").asText());
368 userQuieres.add(query);
369 }
370
371 //1: add all that are new, update all that are retained, delete the rest
372 // Set<UserQuery> resources = ResourceFinder
373 // .search(user, UserQuery.class);
374 //
375 // add.addAll(userQuieres);
376 // add.removeAll(resources);
377 // Collection<UserQuery> update = new HashSet<>(userQuieres);
378 // update.retainAll(resources);
379 // resources.removeAll(userQuieres);
380 //
381 // if (!update.isEmpty()) {
382 // resourceHandler.updateResources(user,
383 // update.toArray(new UserQuery[update.size()]));
384 // }
385 // if (!add.isEmpty()) {
386 // resourceHandler.storeResources(user,
387 // add.toArray(new UserQuery[add.size()]));
388 // }
389 // if (!resources.isEmpty()) {
390 // resourceHandler.deleteResources(user,
391 // resources.toArray(new UserQuery[resources.size()]));
392 // }
393 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100394 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200395 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200396 }
397 return Response.ok(JsonUtils.toJSON(add)).build();
398 }
399
400 @DELETE
401 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
402 PiwikFilter.class })
403 public Response deleteUser(@Context SecurityContext context) {
404 TokenContext ctx = (TokenContext) context.getUserPrincipal();
405 try {
406 User user = controller.getUser(ctx.getUsername());
407 if (user.isDemo())
408 return Response.notModified().build();
409 controller.deleteAccount(user);
410 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100411 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200412 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200413 }
414 return Response.ok().build();
415 }
416
417 @GET
418 @Path("queries")
419 @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
420 PiwikFilter.class })
421 public Response getQueries(@Context SecurityContext context,
422 @Context Locale locale) {
423 TokenContext ctx = (TokenContext) context.getUserPrincipal();
424 String queryStr;
425 try {
426 User user = controller.getUser(ctx.getUsername());
427 // Set<UserQuery> queries = ResourceFinder
428 // .search(user, UserQuery.class);
429 // queryStr = JsonUtils.toJSON(queries);
430 //todo:
431 queryStr = "";
432 }catch (KustvaktException e) {
Michael Hanlf1e85e72016-01-21 16:55:45 +0100433 jlog.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200434 throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200435 }
436 return Response.ok(queryStr).build();
437 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200438}