package org.yamcs.web.rest.processor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.InvalidIdentification; import org.yamcs.NoPermissionException; import org.yamcs.Processor; import org.yamcs.alarms.AlarmServer; import org.yamcs.alarms.CouldNotAcknowledgeAlarmException; import org.yamcs.parameter.ParameterRequestManagerImpl; import org.yamcs.parameter.ParameterValueWithId; import org.yamcs.parameter.ParameterWithIdConsumer; import org.yamcs.parameter.ParameterWithIdRequestHelper; import org.yamcs.parameter.SoftwareParameterManager; import org.yamcs.parameter.Value; import org.yamcs.protobuf.Pvalue.ParameterValue; import org.yamcs.protobuf.Rest.BulkGetParameterValueRequest; import org.yamcs.protobuf.Rest.BulkGetParameterValueResponse; import org.yamcs.protobuf.Rest.BulkSetParameterValueRequest; import org.yamcs.protobuf.Rest.BulkSetParameterValueRequest.SetParameterValueRequest; import org.yamcs.protobuf.Rest.EditAlarmRequest; import org.yamcs.protobuf.SchemaPvalue; import org.yamcs.protobuf.SchemaRest; import org.yamcs.protobuf.SchemaYamcs; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.security.AuthenticationToken; import org.yamcs.security.Privilege; import org.yamcs.utils.ValueUtility; import org.yamcs.web.BadRequestException; import org.yamcs.web.ForbiddenException; import org.yamcs.web.HttpException; import org.yamcs.web.InternalServerErrorException; import org.yamcs.web.rest.RestHandler; import org.yamcs.web.rest.RestRequest; import org.yamcs.web.rest.Route; import org.yamcs.xtce.Parameter; import org.yamcs.xtce.XtceDb; import org.yamcs.xtceproc.XtceDbFactory; public class ProcessorParameterRestHandler extends RestHandler { private final static Logger log = LoggerFactory.getLogger(ProcessorParameterRestHandler.class); @Route(path = "/api/processors/:instance/:processor/parameters/:name*/alarms/:seqnum", method = { "PATCH", "PUT", "POST" }) public void patchParameterAlarm(RestRequest req) throws HttpException { Processor processor = verifyProcessor(req, req.getRouteParam("instance"), req.getRouteParam("processor")); AlarmServer alarmServer = verifyAlarmServer(processor); XtceDb mdb = XtceDbFactory.getInstance(processor.getInstance()); Parameter p = verifyParameter(req, mdb, req.getRouteParam("name")); int seqNum = req.getIntegerRouteParam("seqnum"); String state = null; String comment = null; EditAlarmRequest request = req.bodyAsMessage(SchemaRest.EditAlarmRequest.MERGE).build(); if (request.hasState()) { state = request.getState(); } if (request.hasComment()) { comment = request.getComment(); } // URI can override body if (req.hasQueryParameter("state")) { state = req.getQueryParameter("state"); } if (req.hasQueryParameter("comment")) { comment = req.getQueryParameter("comment"); } if(state==null) { throw new BadRequestException("No state specified"); } switch (state.toLowerCase()) { case "acknowledged": try { // TODO permissions on AlarmServer alarmServer.acknowledge(p, seqNum, req.getUsername(), processor.getCurrentTime(), comment); completeOK(req); } catch (CouldNotAcknowledgeAlarmException e) { log.debug("Did not acknowledge alarm " + seqNum + ". " + e.getMessage()); throw new BadRequestException(e.getMessage()); } default: throw new BadRequestException("Unsupported state '" + state + "'"); } } @Route(path = "/api/processors/:instance/:processor/parameters/:name*", method = { "PUT", "POST" }) public void setSingleParameterValue(RestRequest req) throws HttpException { Processor processor = verifyProcessor(req, req.getRouteParam("instance"), req.getRouteParam("processor")); SoftwareParameterManager mgr = verifySoftwareParameterManager(processor); XtceDb mdb = XtceDbFactory.getInstance(processor.getInstance()); Parameter p = verifyParameter(req, mdb, req.getRouteParam("name")); Value v = ValueUtility.fromGpb(req.bodyAsMessage(SchemaYamcs.Value.MERGE).build()); try { mgr.updateParameter(p, v); } catch (IllegalArgumentException e) { throw new BadRequestException(e.getMessage()); } completeOK(req); } @Route(path = "/api/processors/:instance/:processor/parameters/mset", method = { "POST", "PUT" }, priority=true) public void setParameterValues(RestRequest req) throws HttpException { Processor processor = verifyProcessor(req, req.getRouteParam("instance"), req.getRouteParam("processor")); SoftwareParameterManager mgr = verifySoftwareParameterManager(processor); BulkSetParameterValueRequest request = req.bodyAsMessage(SchemaRest.BulkSetParameterValueRequest.MERGE).build(); // check permission ParameterRequestManagerImpl prm = processor.getParameterRequestManager(); for(SetParameterValueRequest r : request.getRequestList()) { try { String parameterName = prm.getParameter(r.getId()).getQualifiedName(); if(!Privilege.getInstance().hasPrivilege1(req.getAuthToken(), Privilege.Type.TM_PARAMETER_SET, parameterName)) { throw new ForbiddenException("User " + req.getAuthToken() + " has no 'set' permission for parameter " + parameterName); } } catch (InvalidIdentification e) { throw new BadRequestException("InvalidIdentification: " + e.getMessage()); } } // Yamcs uses ParameterValue, so map to that structure List<ParameterValue> pvals = new ArrayList<>(); for (SetParameterValueRequest r : request.getRequestList()) { ParameterValue.Builder pvalb = ParameterValue.newBuilder(); pvalb.setId(r.getId()); pvalb.setEngValue(r.getValue()); pvals.add(pvalb.build()); } try { mgr.updateParameters(pvals); } catch (IllegalArgumentException e) { throw new BadRequestException(e.getMessage()); } completeOK(req); } @Route(path = "/api/processors/:instance/:processor/parameters/:name*", method = "GET") public void getParameterValue(RestRequest req) throws HttpException { Processor processor = verifyProcessor(req, req.getRouteParam("instance"), req.getRouteParam("processor")); XtceDb mdb = XtceDbFactory.getInstance(processor.getInstance()); Parameter p = verifyParameter(req, mdb, req.getRouteParam("name")); if (!Privilege.getInstance().hasPrivilege1(req.getAuthToken(), Privilege.Type.TM_PARAMETER, p.getQualifiedName())) { log.warn("Parameter Info for {} not authorized for token {}", p.getQualifiedName(), req.getAuthToken()); throw new BadRequestException("Invalid parameter name specified"); } long timeout = 10000; boolean fromCache = true; if (req.hasQueryParameter("timeout")) { timeout = req.getQueryParameterAsLong("timeout"); } if (req.hasQueryParameter("fromCache")) { fromCache = req.getQueryParameterAsBoolean("fromCache"); } NamedObjectId id = NamedObjectId.newBuilder().setName(p.getQualifiedName()).build(); List<NamedObjectId> ids = Arrays.asList(id); List<ParameterValue> pvals = doGetParameterValues(processor, req.getAuthToken(), ids, fromCache, timeout); ParameterValue pval; if (pvals.isEmpty()) { pval = ParameterValue.newBuilder().setId(id).build(); } else { pval = pvals.get(0); } completeOK(req, pval, SchemaPvalue.ParameterValue.WRITE); } @Route(path = "/api/processors/:instance/:processor/parameters/mget", method = {"GET", "POST"}, priority=true) public void getParameterValues(RestRequest req) throws HttpException { Processor processor = verifyProcessor(req, req.getRouteParam("instance"), req.getRouteParam("processor")); BulkGetParameterValueRequest request = req.bodyAsMessage(SchemaRest.BulkGetParameterValueRequest.MERGE).build(); if (request.getIdCount() == 0) { throw new BadRequestException("Empty parameter list"); } long timeout = 10000; boolean fromCache = true; // Consider body params first if (request.hasTimeout()) { timeout = request.getTimeout(); } if (request.hasFromCache()) { fromCache = request.getFromCache(); } // URI params override body if (req.hasQueryParameter("timeout")) { timeout = req.getQueryParameterAsLong("timeout"); } if (req.hasQueryParameter("fromCache")) { fromCache = req.getQueryParameterAsBoolean("fromCache"); } List<NamedObjectId> ids = request.getIdList(); List<ParameterValue> pvals = doGetParameterValues(processor, req.getAuthToken(), ids, fromCache, timeout); BulkGetParameterValueResponse.Builder responseb = BulkGetParameterValueResponse.newBuilder(); responseb.addAllValue(pvals); completeOK(req, responseb.build(), SchemaRest.BulkGetParameterValueResponse.WRITE); } private List<ParameterValue> doGetParameterValues(Processor processor, AuthenticationToken authToken, List<NamedObjectId> ids, boolean fromCache, long timeout) throws HttpException { if (timeout > 60000) { throw new BadRequestException("Invalid timeout specified. Maximum is 60.000 milliseconds"); } ParameterRequestManagerImpl prm = processor.getParameterRequestManager(); MyConsumer myConsumer = new MyConsumer(); ParameterWithIdRequestHelper pwirh = new ParameterWithIdRequestHelper(prm, myConsumer); List<ParameterValue> pvals = new ArrayList<>(); try { if(fromCache) { if(!prm.hasParameterCache()) { throw new BadRequestException("ParameterCache not activated for this processor"); } List<ParameterValueWithId> l; l = pwirh.getValuesFromCache(ids, authToken); for(ParameterValueWithId pvwi: l) { pvals.add(pvwi.toGbpParameterValue()); } } else { int reqId = pwirh.addRequest(ids, authToken); long t0 = System.currentTimeMillis(); long t1; while(true) { t1 = System.currentTimeMillis(); long remaining = timeout - (t1-t0); List<ParameterValueWithId> l = myConsumer.queue.poll(remaining, TimeUnit.MILLISECONDS); if(l==null) { break; } for(ParameterValueWithId pvwi: l) { pvals.add(pvwi.toGbpParameterValue()); } //TODO: this may not be correct: if we get a parameter multiple times, we stop here before receiving all parameters if(pvals.size() == ids.size()) { break; } } pwirh.removeRequest(reqId); } } catch (InvalidIdentification e) { //TODO - send the invalid parameters in a parsable form throw new BadRequestException("Invalid parameters: "+e.getInvalidParameters().toString()); } catch (InterruptedException e) { throw new InternalServerErrorException("Interrupted while waiting for parameters"); } catch (NoPermissionException e) { throw new ForbiddenException(e.getMessage(), e); } return pvals; } private static class MyConsumer implements ParameterWithIdConsumer { LinkedBlockingQueue<List<ParameterValueWithId>> queue = new LinkedBlockingQueue<>(); @Override public void update(int subscriptionId, List<ParameterValueWithId> params) { queue.add(params); } } private SoftwareParameterManager verifySoftwareParameterManager(Processor processor) throws BadRequestException { SoftwareParameterManager mgr = processor.getParameterRequestManager().getSoftwareParameterManager(); if (mgr == null) { throw new BadRequestException("SoftwareParameterManager not activated for this processor"); } else { return mgr; } } }