/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt.http; import nxt.http.EventListener.EventListenerException; import nxt.http.EventListener.PendingEvent; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import javax.servlet.http.HttpServletRequest; import java.util.List; /** * <p>The EventWait API will wait for one of the server events * registered by EventRegister. EventWait will return immediately * if one or more events have occurred since the last time EventWait * was called. All pending events will be returned in a single response. * The events remain registered so successive calls to EventWait can * be made without another call to EventRegister.</p> * * <p>Only one EventWait can be outstanding for the same network address. * If a second EventWait is issued, the current EventWait will be replaced * by the new EventWait.</p> * * <p>Request parameters:</p> * <ul> * <li>timeout - Number of seconds to wait for an event. The EventWait * will complete normally if no event is received within the timeout interval. * nxt.apiEventTimeout will be used if no timeout value is specified or * if the requested timeout is greater than nxt.apiEventTimeout.</li> * </ul> * * <p>Response parameters:</p> * <ul> * <li>events - An array of event objects</li> * </ul> * * <p>Error Response parameters:</p> * <ul> * <li>errorCode - API error code</li> * <li>errorDescription - API error description</li> * </ul> * * <p>Event object:</p> * <ul> * <li>name - The event name</li> * <li>ids - An array of event object identifiers</li> * </ul> * * <p>Event names:</p> * <ul> * <li>Block.BLOCK_GENERATED</li> * <li>Block.BLOCK_POPPED</li> * <li>Block.BLOCK_PUSHED</li> * <li>Ledger.ADD_ENTRY.account - The account suffix will be Reed-Solomon identifier * of the account associated with the ledger entry.</li> * <li>Peer.ADD_INBOUND</li> * <li>Peer.ADDED_ACTIVE_PEER</li> * <li>Peer.BLACKLIST</li> * <li>Peer.CHANGED_ACTIVE_PEER</li> * <li>Peer.DEACTIVATE</li> * <li>Peer.NEW_PEER</li> * <li>Peer.REMOVE</li> * <li>Peer.REMOVE_INBOUND</li> * <li>Peer.UNBLACKLIST</li> * <li>Transaction.ADDED_CONFIRMED_TRANSACTIONS</li> * <li>Transaction.ADDED_UNCONFIRMED_TRANSACTIONS</li> * <li>Transaction.REJECT_PHASED_TRANSACTION</li> * <li>Transaction.RELEASE_PHASED_TRANSACTION</li> * <li>Transaction.REMOVE_UNCONFIRMED_TRANSACTIONS</li> * </ul> * * <p>Event object identifiers:</p> * <ul> * <li>Block string identifier for a Block event</li> * <li>Peer network address for a Peer event</li> * <li>Transaction string identifier for a Transaction event</li> * </ul> */ public class EventWait extends APIServlet.APIRequestHandler { /** EventWait instance */ static final EventWait instance = new EventWait(); /** Incorrect timeout */ private static final JSONObject incorrectTimeout = new JSONObject(); static { incorrectTimeout.put("errorCode", 4); incorrectTimeout.put("errorDescription", "Wait timeout is not valid"); } /** No events registered */ private static final JSONObject noEventsRegistered = new JSONObject(); static { noEventsRegistered.put("errorCode", 8); noEventsRegistered.put("errorDescription", "No events registered"); } /** * Create the EventWait instance */ private EventWait() { super(new APITag[] {APITag.INFO}, "timeout"); } /** * Process the EventWait API request * * The response will be returned immediately if there are any * pending events. Otherwise, an asynchronous context will * be created and the response will be returned after the wait * has completed. By using an asynchronous context, we avoid * tying up the Jetty servlet thread while waiting for an event. * * @param req API request * @return API response or null */ @Override JSONStreamAware processRequest(HttpServletRequest req) { JSONObject response = null; // // Get the timeout value // long timeout = EventListener.eventTimeout; String value = req.getParameter("timeout"); if (value != null) { try { timeout = Math.min(Long.valueOf(value), timeout); } catch (NumberFormatException exc) { response = incorrectTimeout; } } // // Wait for an event // if (response == null) { EventListener listener = EventListener.eventListeners.get(req.getRemoteAddr()); if (listener == null) { response = noEventsRegistered; } else { try { List<PendingEvent> events = listener.eventWait(req, timeout); if (events != null) response = formatResponse(events); } catch (EventListenerException exc) { response = new JSONObject(); response.put("errorCode", 7); response.put("errorDescription", "Unable to wait for an event: "+exc.getMessage()); } } } return response; } @Override final boolean requirePost() { return true; } /** * No required block parameters * * @return FALSE to disable the required block parameters */ @Override boolean allowRequiredBlockParameters() { return false; } /** * Format the EventWait response * * @param events Event list * @return JSON stream */ static JSONObject formatResponse(List<PendingEvent> events) { JSONArray eventsJSON = new JSONArray(); events.forEach(event -> { JSONArray idsJSON = new JSONArray(); if (event.isList()) idsJSON.addAll(event.getIdList()); else idsJSON.add(event.getId()); JSONObject eventJSON = new JSONObject(); eventJSON.put("name", event.getName()); eventJSON.put("ids", idsJSON); eventsJSON.add(eventJSON); }); JSONObject response = new JSONObject(); response.put("events", eventsJSON); return response; } }