/******************************************************************************* * Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University * as Operator of the SLAC National Accelerator Laboratory. * Copyright (c) 2011 Brookhaven National Laboratory. * EPICS archiver appliance is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. *******************************************************************************/ package org.epics.archiverappliance.mgmt.bpl; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedList; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.epics.archiverappliance.common.BPLAction; import org.epics.archiverappliance.config.ApplianceInfo; import org.epics.archiverappliance.config.ConfigService; import org.epics.archiverappliance.config.PVNames; import org.epics.archiverappliance.config.PVTypeInfo; import org.epics.archiverappliance.utils.ui.GetUrlContent; import org.epics.archiverappliance.utils.ui.MimeTypeConstants; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; /** * Get the status of a PV. * * @epics.BPLAction - Get the status of a PV. * @epics.BPLActionParam pv - The name(s) of the pv for which status is to be determined. If a pv is not being archived, you should get back a simple JSON object with a status string of "Not being archived." You can also pass in GLOB wildcards here and multiple PVs as a comma separated list. If you have more PVs that can fit in a GET, send the pv's as a CSV <code>pv=pv1,pv2,pv3</code> as the body of a POST. * @epics.BPLActionEnd * * @author mshankar * */ public class GetPVStatusAction implements BPLAction { private static final Logger logger = Logger.getLogger(GetPVStatusAction.class); @SuppressWarnings("unchecked") @Override public void execute(HttpServletRequest req, HttpServletResponse resp, ConfigService configService) throws IOException { logger.info("Getting the status of pv(s) " + req.getParameter("pv")); LinkedList<String> pvNames = PVsMatchingParameter.getMatchingPVs(req, configService, true, -1); HashMap<String, String> pvStatuses = new HashMap<String, String>(); HashMap<String, LinkedList<String>> pvNamesToAskEngineForStatus = new HashMap<String, LinkedList<String>>(); HashMap<String, PVTypeInfo> typeInfosForEngineRequests = new HashMap<String, PVTypeInfo>(); HashMap<String, LinkedList<String>> realName2NameFromRequest = new HashMap<String, LinkedList<String>>(); for(String pvName : pvNames) { String pvNameFromRequest = pvName; // Get rid of .VAL and the V4 prefix pvName = PVNames.normalizePVName(pvName); pvName = PVNames.stripPrefixFromName(pvName); addInverseNameMapping(pvNameFromRequest, pvName, realName2NameFromRequest); ApplianceInfo info = PVNames.determineAppropriateApplianceInfo(pvName, configService); if(info == null) { if(configService.doesPVHaveArchiveRequestInWorkflow(pvName)) { pvStatuses.put(pvNameFromRequest, "{ \"pvName\": \"" + pvNameFromRequest + "\", \"status\": \"Initial sampling\" }"); } else { pvStatuses.put(pvNameFromRequest, "{ \"pvName\": \"" + pvNameFromRequest + "\", \"status\": \"Not being archived\" }"); } } else { PVTypeInfo typeInfoForPV = PVNames.determineAppropriatePVTypeInfo(pvName, configService); if(typeInfoForPV != null) { pvName = typeInfoForPV.getPvName(); addInverseNameMapping(pvNameFromRequest, pvName, realName2NameFromRequest); } if(!pvNamesToAskEngineForStatus.containsKey(info.getEngineURL())) { pvNamesToAskEngineForStatus.put(info.getEngineURL(), new LinkedList<String>()); } pvNamesToAskEngineForStatus.get(info.getEngineURL()).add(pvName); typeInfosForEngineRequests.put(pvName, typeInfoForPV); } } for(String engineURL : pvNamesToAskEngineForStatus.keySet()) { LinkedList<String> pvNamesToAskEngine = pvNamesToAskEngineForStatus.get(engineURL); JSONArray engineStatuses = null; boolean instanceDown = false; try { engineStatuses = GetUrlContent.postStringListAndGetContentAsJSONArray(engineURL + "/status", "pv", pvNamesToAskEngine); } catch(IOException ex) { instanceDown = true; logger.warn("Exception getting status from engine " + engineURL, ex); } // Convert list of statuses from engine to hashmap HashMap<String, JSONObject> computedEngineStatueses = new HashMap<String, JSONObject>(); if(engineStatuses != null) { for(Object engineStatusObj : engineStatuses) { JSONObject engineStatus = (JSONObject) engineStatusObj; computedEngineStatueses.put((String) engineStatus.get("pvName"), engineStatus); } } for(String pvNameToAskEngine : pvNamesToAskEngine) { PVTypeInfo typeInfo = typeInfosForEngineRequests.get(pvNameToAskEngine); JSONObject pvStatus = computedEngineStatueses.get(pvNameToAskEngine); LinkedList<String> pvNamesForResult = new LinkedList<String>(); if(pvNames.contains(pvNameToAskEngine)) { // User made a status request using the same name we sent to the engine. pvNamesForResult.add(pvNameToAskEngine); } if(realName2NameFromRequest.containsKey(pvNameToAskEngine)) { // Add all the aliases/field names etc. pvNamesForResult.addAll(realName2NameFromRequest.get(pvNameToAskEngine)); } for(String pvNameForResult : pvNamesForResult) { if(pvStatus != null && !pvStatus.isEmpty()) { pvStatus.put("appliance", typeInfo.getApplianceIdentity()); pvStatus.put("pvName", pvNameForResult); pvStatus.put("pvNameOnly", pvNameToAskEngine); pvStatuses.put(pvNameForResult, pvStatus.toJSONString()); } else { if(typeInfo != null && typeInfo.isPaused()) { HashMap<String, String> tempStatus = new HashMap<String, String>(); tempStatus.put("appliance", typeInfo.getApplianceIdentity()); tempStatus.put("pvName", pvNameForResult); tempStatus.put("pvNameOnly", pvNameToAskEngine); tempStatus.put("status", "Paused"); pvStatuses.put(pvNameForResult, JSONValue.toJSONString(tempStatus)); } else { // Here we have a PVTypeInfo but no status from the engine. if(instanceDown) { // It could mean that the engine component archiving this PV is down. pvStatuses.put(pvNameToAskEngine, "{ \"pvName\": \"" + pvNameForResult + "\", \"status\": \"Appliance Down\" }"); } else { // It could be that we are in that transient period between persisting the PVTypeInfo and opening the CA channel. pvStatuses.put(pvNameToAskEngine, "{ \"pvName\": \"" + pvNameForResult + "\", \"status\": \"Appliance assigned\" }"); } } } } } } resp.setContentType(MimeTypeConstants.APPLICATION_JSON); try (PrintWriter out = resp.getWriter()) { out.println("["); boolean isFirst = true; for(String pvName : pvNames) { if(pvStatuses.containsKey(pvName)) { if(isFirst) { isFirst = false; } else { out.println(","); } out.print(pvStatuses.get(pvName)); } } out.println("]"); } } /** * There is a 1-many mapping between the name the user asks for and the internals. * When sending the data back, we need to take the "real" information and create an entry for each user request. * This method maintains this list. * @param pvNameFromRequest * @param pvName * @param realName2NameFromRequest */ private static void addInverseNameMapping(String pvNameFromRequest, String pvName, HashMap<String, LinkedList<String>> realName2NameFromRequest) { if(!pvName.equals(pvNameFromRequest)) { if(!realName2NameFromRequest.containsKey(pvName)) { realName2NameFromRequest.put(pvName, new LinkedList<String>()); } realName2NameFromRequest.get(pvName).add(pvNameFromRequest); } } }