/****************************************************************************** * 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.EventRegistration; import nxt.util.Convert; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; /** * <p>The EventRegister API will create an event listener and register * one or more server events. * The 'add' and 'remove' parameters must be omitted or must both be false * in order to create a new event listener.</p> * * <p>After calling EventRegister, the application needs to call the * EventWait API to wait for one of the registered events to occur. * The events will remain registered so successive calls to EventWait can * be made without another call to EventRegister.</p> * * <p>When the event listener is no longer needed, the application should call * EventRegister with an empty event list and 'remove=true'. An outstanding * event wait will be completed and the event listener will be canceled. * </p> * * <p>An existing event list can be modified by calling EventRegister with * either 'add=true' or 'remove=true'. The current event list will be replaced * if both parameters are omitted or are false.</p> * * <p>Event registration will be canceled if the application does not * issue an EventWait before the time interval specified by nxt.apiEventTimeout * expires. The timer is reset each time an EventWait is processed.</p> * * <p>An application cannot register events if the maximum number of event users * specified by nxt.apiMaxEventUsers has been reached.</p> * * <p>Request parameters:</p> * <ul> * <li>event - Event name. The 'event' parameter can be * repeated to specify multiple events. All events will be included * if the 'event' parameter is not specified.</li> * <li>add - Specify 'true' to add the events to an existing event list.</li> * <li>remove - Specify 'true' to remove the events from an existing event list.</li> * </ul> * * <p>Response parameters:</p> * <ul> * <li>registered - Set to 'true' if the events were processed.</li> * </ul> * * <p>Error Response parameters:</p> * <ul> * <li>errorCode - API error code</li> * <li>errorDescription - API error description</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 - Changes to all accounts will be reported.</li> * <li>Ledger.ADD_ENTRY.account - Only changes to the specified account will be reported. 'account' * may be the numeric identifier or the Reed-Solomon identifier * of the account to monitor for updates. Specifying an account identifier of 0 is the same as * not specifying an account.</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> */ public class EventRegister extends APIServlet.APIRequestHandler { /** EventRegister instance */ static final EventRegister instance = new EventRegister(); /** Events registers */ private static final JSONObject eventsRegistered = new JSONObject(); static { eventsRegistered.put("registered", true); } /** Mutually exclusive parameters */ private static final JSONObject exclusiveParams = new JSONObject(); static { exclusiveParams.put("errorCode", 4); exclusiveParams.put("errorDescription", "Mutually exclusive 'add' and 'remove'"); } /** Incorrect event */ private static final JSONObject incorrectEvent = new JSONObject(); static { incorrectEvent.put("errorCode", 4); incorrectEvent.put("errorDescription", "Incorrect event name format"); } /** Unknown event */ private static final JSONObject unknownEvent = new JSONObject(); static { unknownEvent.put("errorCode", 5); unknownEvent.put("errorDescription", "Unknown event name"); } /** No events registered */ private static final JSONObject noEventsRegistered = new JSONObject(); static { noEventsRegistered.put("errorCode", 8); noEventsRegistered.put("errorDescription", "No events registered"); } /** * Create the EventRegister instance */ private EventRegister() { super(new APITag[] {APITag.INFO}, "event", "event", "event", "add", "remove"); } /** * Process the EventRegister API request * * @param req API request * @return API response */ @Override JSONStreamAware processRequest(HttpServletRequest req) { JSONObject response; // // Get 'add' and 'remove' parameters // boolean addEvents = Boolean.valueOf(req.getParameter("add")); boolean removeEvents = Boolean.valueOf(req.getParameter("remove")); if (addEvents && removeEvents) return exclusiveParams; // // Build the event list from the 'event' parameters // List<EventRegistration> events = new ArrayList<>(); String[] params = req.getParameterValues("event"); if (params == null) { // // Add all events if no events are supplied // EventListener.peerEvents.forEach(event -> events.add(new EventRegistration(event, 0))); EventListener.blockEvents.forEach(event -> events.add(new EventRegistration(event, 0))); EventListener.txEvents.forEach(event -> events.add(new EventRegistration(event, 0))); EventListener.ledgerEvents.forEach(event -> events.add(new EventRegistration(event, 0))); } else { for (String param : params) { // // The Ledger event can have 2 or 3 parts. All other events have 2 parts. // long accountId = 0; String[] parts = param.split("\\."); if (parts[0].equals("Ledger")) { if (parts.length == 3) { try { accountId = Convert.parseAccountId(parts[2]); } catch (RuntimeException e) { return incorrectEvent; } } else if (parts.length != 2) { return incorrectEvent; } } else if (parts.length != 2) { return incorrectEvent; } // // Add the event // List<? extends Enum> eventList; switch (parts[0]) { case "Block": eventList = EventListener.blockEvents; break; case "Peer": eventList = EventListener.peerEvents; break; case "Transaction": eventList = EventListener.txEvents; break; case "Ledger": eventList = EventListener.ledgerEvents; break; default: return unknownEvent; } boolean eventAdded = false; for (Enum<? extends Enum> event : eventList) { if (event.name().equals(parts[1])) { events.add(new EventRegistration(event, accountId)); eventAdded = true; break; } } if (!eventAdded) return unknownEvent; } } // // Register the event listener // try { if (addEvents || removeEvents) { EventListener listener = EventListener.eventListeners.get(req.getRemoteAddr()); if (listener != null) { if (addEvents) listener.addEvents(events); else listener.removeEvents(events); response = eventsRegistered; } else { response = noEventsRegistered; } } else { EventListener listener = new EventListener(req.getRemoteAddr()); listener.activateListener(events); response = eventsRegistered; } } catch (EventListenerException exc) { response = new JSONObject(); response.put("errorCode", 7); response.put("errorDescription", "Unable to register events: "+exc.getMessage()); } // // Return the response // 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; } }