/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.dataservice.json; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.log4j.Logger; import org.helios.apmrouter.server.ServerComponentBean; import org.helios.apmrouter.util.StringHelper; import org.jboss.netty.channel.Channel; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.jmx.export.annotation.ManagedAttribute; /** * <p>Title: JSONRequestRouter</p> * <p>Description: Examines JSON requests and routes them to the correct {@link JSONDataService} instance.</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.dataservice.json.JSONRequestRouter</code></p> */ public class JSONRequestRouter extends ServerComponentBean { /** A map of {@link JSONDataService}s keyed by the service name */ protected final Map<String, Map<String, JSONRequestHandlerImpl>> services = new ConcurrentHashMap<String, Map<String, JSONRequestHandlerImpl>>(); /** Instance logger */ protected final Logger log = Logger.getLogger(getClass()); /** The key indicating a request is being sent (as opposed to .... ) */ public static final String REQUEST_FLAG = "t"; /** The request ID sent by the client which will be returned with every response */ public static final String REQUEST_ID = "rid"; /** The correlation id in reference to the original request. i.e. if a subscription request comes in as <code>43</code>, * all of the published events for that subscription will have a <b><code>rerid</code></b> of <code>43</code> */ public static final String RE_REQUEST_ID = "rerid"; /** The name of the json data service the client wishes to invoke */ public static final String SERVICE_NAME = "svc"; /** The name of the json data service operation the client wishes to invoke */ public static final String OP_NAME = "op"; /** The name of the json key for the client submitted arguments to the op */ public static final String ARGS_NAME = "args"; /** The name of the json key for a response error message */ public static final String ERR_NAME = "err"; /** * Common JSON request parser * @param request The JSON request * @param channel The channel the request was received from (and where the response should be written back to) * @return the parsed request * @throws JSONException thrown on any JSON unmarshalling error */ protected JsonRequest parse(JSONObject request, Channel channel) throws JSONException { try { String flag = request.getString(REQUEST_FLAG); long requestId = request.getLong(REQUEST_ID); String service = request.getString(SERVICE_NAME); String op = request.getString(OP_NAME); JsonRequest jreq = new JsonRequest(channel, flag, requestId, service, op, request); if(request.has(ARGS_NAME)) { JSONArray arr = null; try { arr = request.getJSONArray(ARGS_NAME); for(int i = 0; i < arr.length(); i++) { jreq.addArg(i, arr.get(i)); } } catch (Exception ex) {} if(arr==null) { JSONObject map = request.getJSONObject(ARGS_NAME); for(Iterator<?> iter = map.keys(); iter.hasNext();) { Object key = iter.next(); jreq.addArg(key.toString(), map.get(key.toString())); } } } return jreq; } catch (JSONException ex) { throw ex; } catch (Exception ex) { log.error("Unexpected exception parsing request [" + request + "]", ex); throw new RuntimeException("Unexpected exception parsing request [" + request + "]", ex); } } /** * {@inheritDoc} * @see org.helios.apmrouter.server.ApplicationContextLifecycleListener#onApplicationContextRefresh(org.springframework.context.event.ContextRefreshedEvent) */ @Override public void onApplicationContextRefresh(ContextRefreshedEvent event) { Map<String, Object> handlerServices = applicationContext.getBeansWithAnnotation(JSONRequestHandler.class); info("Processing [", handlerServices.size(), "] JSON Handler Services"); for(Map.Entry<String, Object> entry: handlerServices.entrySet()) { Map<String, JSONRequestHandlerImpl> ops = JSONRequestHandlerImpl.generateHandlers(entry.getKey(), entry.getValue()); if(ops.size()<1) { warn("JSON Service [", entry.getKey(), "] had zero ops"); } else { services.put(entry.getKey(), ops); info("Added JSON Handler Service [", entry.getKey(), "]"); } } } /** * Returns a map of service names and ops * @return A map of arrays of ops keyed by the service name */ @ManagedAttribute(description="A map of arrays of ops keyed by the service name") public Map<String, String> getServiceOps() { Map<String, String> map = new HashMap<String, String>(services.size()); for(Map.Entry<String, Map<String, JSONRequestHandlerImpl>> entry: services.entrySet()) { map.put(entry.getKey(), entry.getValue().keySet().toString()); } return map; } /** * Attempts to decode and invoke the passed request * @param request A JSON encoded request * @param channel The channel to respond on */ public void invoke(JSONObject request, Channel channel) { try { JsonRequest req = parse(request, channel); Map<String, JSONRequestHandlerImpl> service = services.get(req.serviceName); if(service!=null) { JSONRequestHandlerImpl handler = service.get(req.opName); handler.processRequest(req, channel); } else { req.error(StringHelper.fastConcat("JSON Invocation from [", channel.toString(), "] for req [", req.serviceName, "/", req.opName, "] failed. Could not locate service/op")).send(channel); } } catch (Exception ex) { error("Failed to invoke request", ex); long requestId = -1; try { requestId = request.getLong(REQUEST_ID); } catch (Exception e) {} sendError(ex, requestId, channel); } } /** * Formats a throwable into a JSON error message and sends to the caller * @param t The throwable to report * @param requestId The request Id that failed * @param channel The channel to write to */ protected void sendError(Throwable t, long requestId, Channel channel) { try { JSONObject msg = new JSONObject(); msg.put(REQUEST_FLAG, "err"); msg.put(REQUEST_ID, requestId); msg.put(ERR_NAME, t.toString()); channel.write(msg); } catch (Exception ex) { error("Failed to write error message to client [", t, "]", ex); } } }