Java tutorial
/* * Copyright 2011 John Muellerleile * * This file is licensed to you under the Apache License, version 2.0 * (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package cc.osint.graphd.server; import java.lang.ref.*; import java.net.InetAddress; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.concurrent.*; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.ChannelState; import org.jboss.netty.channel.Channel; import org.jetlang.channels.MemoryChannel; import org.jetlang.fibers.Fiber; import org.jetlang.fibers.PoolFiberFactory; import org.json.*; import cc.osint.graphd.graph.*; import cc.osint.graphd.processes.*; public class GraphServerHandler extends SimpleChannelUpstreamHandler { private static final Logger log = Logger.getLogger(GraphServerHandler.class.getName()); /* name -> graph registry */ final private static ConcurrentHashMap<String, Graph> nameGraphMap; static { nameGraphMap = new ConcurrentHashMap<String, Graph>(); } /* name -> GraphCommandExecutor registry */ final private static ConcurrentHashMap<String, GraphCommandExecutor> graphCommandExecutorMap; static { graphCommandExecutorMap = new ConcurrentHashMap<String, GraphCommandExecutor>(); } /* executors */ final private static ExecutorService graphCommandExecutorService; final private static ExecutorService inboundChannelExecutorService; static { graphCommandExecutorService = Executors.newCachedThreadPool(); inboundChannelExecutorService = Executors.newCachedThreadPool(); } /* inboundChannel fiberFactory */ final private static PoolFiberFactory fiberFactory; static { fiberFactory = new PoolFiberFactory(inboundChannelExecutorService); } /* clientId -> inboundChannel registry */ final private static ConcurrentHashMap<String, WeakReference<InboundChannelProcess>> inboundChannelMap; static { inboundChannelMap = new ConcurrentHashMap<String, WeakReference<InboundChannelProcess>>(); } /* clientId -> client state registry */ final private static ConcurrentHashMap<String, ConcurrentHashMap<String, String>> clientStateMap; static { clientStateMap = new ConcurrentHashMap<String, ConcurrentHashMap<String, String>>(); } /* client id -> netty channel registry */ final private static ConcurrentHashMap<String, Channel> clientIdChannelMap; static { clientIdChannelMap = new ConcurrentHashMap<String, Channel>(); } /* clientStateMap keys */ final protected static String ST_DB = "cur_db"; // clientState: current db (via CMD_USE) final protected static String ST_NAMECON = "name_con"; // clientState: connection name (via CMD_USE) /* defaults */ final protected static String DEFAULT_CONNECTION_NAME = "client"; // default connection name /* netty handlers */ @Override public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { if (e instanceof ChannelStateEvent) { String clientId = "" + e.getChannel().getId(); ChannelState state = ((ChannelStateEvent) e).getState(); if (state == state.CONNECTED && ((ChannelStateEvent) e).getValue() == null) { // TODO: send client disconnection messages to any related // or interested parties (processes, watched objects, // watching objects, etc.) clientStateMap.remove(clientId); clientIdChannelMap.remove(clientId); InboundChannelProcess inboundChannelProcess = inboundChannelMap.get(clientId).get(); if (null != inboundChannelProcess) { inboundChannelProcess.kill(); inboundChannelProcess = null; } inboundChannelMap.remove(clientId); log.info("DISCONNECTED: " + clientId); } else { //log.info("NETTY: handleUpstream: " + e.toString()); } } super.handleUpstream(ctx, e); } @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { String clientId = "" + e.getChannel().getId(); if (null == clientStateMap.get(clientId)) { ConcurrentHashMap<String, String> clientState = new ConcurrentHashMap<String, String>(); clientState.put(ST_NAMECON, DEFAULT_CONNECTION_NAME + "-" + clientId); clientStateMap.put(clientId, clientState); clientIdChannelMap.put(clientId, e.getChannel()); /* * start "client fiber & channel" & connect them */ Fiber fiber = fiberFactory.create(); fiber.start(); org.jetlang.channels.Channel<JSONObject> inboundChannel = new MemoryChannel<JSONObject>(); InboundChannelProcess inboundChannelProcess = new InboundChannelProcess(clientId, fiber, inboundChannel, // jetlang channel e.getChannel()); // netty channel inboundChannelMap.put(clientId, new WeakReference<InboundChannelProcess>(inboundChannelProcess)); log.info(clientId + ": " + inboundChannelMap.get(clientId)); inboundChannel.subscribe(fiber, inboundChannelProcess); } e.getChannel().write("-graphd " + InetAddress.getLocalHost().getHostName() + GraphServerProtocol.SPACE + clientId + GraphServerProtocol.NL); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { if (e.getCause() instanceof java.nio.channels.ClosedChannelException) { // client disconnected before response to command has been sent back } else { log.log(Level.WARNING, "Unexpected exception from downstream.", e.getCause()); e.getChannel().close(); } } /* message/command processors */ @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) { String clientId = "" + e.getChannel().getId(); String request = (String) e.getMessage(); String response; boolean close = false; ConcurrentHashMap<String, String> clientState = clientStateMap.get(clientId); //log.info(clientId + ": " + request); if (request.length() == 0) { response = GraphServerProtocol.R_OK; } else if (request.toLowerCase().equals(GraphServerProtocol.CMD_GOODBYE)) { response = GraphServerProtocol.R_OK; close = true; } else { try { response = executeRequest(e.getChannel(), clientId, clientState, request); if (null == response) return; } catch (Exception ex) { ex.printStackTrace(); response = GraphServerProtocol.R_ERR + GraphServerProtocol.SPACE + ex.getMessage(); } } ChannelFuture future = e.getChannel().write(response.trim() + GraphServerProtocol.NL); if (close) { future.addListener(ChannelFutureListener.CLOSE); } } public String executeRequest(Channel responseChannel, String clientId, ConcurrentHashMap<String, String> clientState, String request) throws Exception { StringBuffer rsb = new StringBuffer(); String cmd; String[] args; if (request.indexOf(GraphServerProtocol.SPACE) != -1) { cmd = request.substring(0, request.indexOf(GraphServerProtocol.SPACE)).trim().toLowerCase(); args = request.substring(request.indexOf(GraphServerProtocol.SPACE)).trim() .split(GraphServerProtocol.SPACE); } else { cmd = request.trim().toLowerCase(); args = new String[0]; } // USE GRAPH: use <graphName> if (cmd.equals(GraphServerProtocol.CMD_USE)) { if (null == nameGraphMap.get(args[0])) { rsb.append(GraphServerProtocol.R_ERR); rsb.append(" DB_NOT_EXIST"); } else { if (null != clientState.get(ST_DB)) clientState.remove(ST_DB); clientState.put(ST_DB, args[0]); rsb.append(GraphServerProtocol.R_OK); } // CREATE GRAPH: create <graphName> } else if (cmd.equals(GraphServerProtocol.CMD_CREATE)) { if (null != nameGraphMap.get(args[0])) { rsb.append(GraphServerProtocol.R_ERR); rsb.append(" DB_ALREADY_EXISTS"); } else { Graph newGraph = new Graph(args[0]); nameGraphMap.put(args[0], newGraph); WeakReference<Graph> graphRef = new WeakReference<Graph>(newGraph); GraphCommandExecutor graphCommandExecutor = new GraphCommandExecutor(args[0], graphRef); graphCommandExecutorService.execute(graphCommandExecutor); graphCommandExecutorMap.put(args[0], graphCommandExecutor); rsb.append(GraphServerProtocol.R_OK); } // DROP GRAPH: drop <graphName> } else if (cmd.equals(GraphServerProtocol.CMD_DROP)) { if (null == nameGraphMap.get(args[0])) { rsb.append(GraphServerProtocol.R_ERR); rsb.append(" DB_NOT_EXIST"); } else { nameGraphMap.remove(args[0]); graphCommandExecutorMap.remove(args[0]); // TODO: DROP <KEY> // TODO: KILL graphCommandExecutor (via poisonPill message) rsb.append(GraphServerProtocol.R_OK); } // NAME THIS CONNECTION: namecon <name> } else if (cmd.equals(GraphServerProtocol.CMD_NAMECON)) { clientState.put(ST_NAMECON, args[0] + "-" + clientId); rsb.append(clientState.get(ST_NAMECON)); rsb.append(GraphServerProtocol.NL); rsb.append(GraphServerProtocol.R_OK); // DUMP CLIENT STATE: clstate } else if (cmd.equals(GraphServerProtocol.CMD_CLSTATE)) { JSONObject result = new JSONObject(clientState); rsb.append(result.toString()); rsb.append(GraphServerProtocol.NL); rsb.append(GraphServerProtocol.R_OK); // SERVER STATUS: sstat } else if (cmd.equals(GraphServerProtocol.CMD_SSTAT)) { JSONObject result = new JSONObject(); JSONObject names = new JSONObject(); names.put("TYPE_FIELD", Graph.TYPE_FIELD); names.put("KEY_FIELD", Graph.KEY_FIELD); names.put("WEIGHT_FIELD", Graph.WEIGHT_FIELD); names.put("EDGE_SOURCE_FIELD", Graph.EDGE_SOURCE_FIELD); names.put("EDGE_TARGET_FIELD", Graph.EDGE_TARGET_FIELD); names.put("RELATION_FIELD", Graph.RELATION_FIELD); names.put("VERTEX_TYPE", Graph.VERTEX_TYPE); names.put("EDGE_TYPE", Graph.EDGE_TYPE); result.put("names", names); rsb.append(result.toString()); rsb.append(GraphServerProtocol.NL); rsb.append(GraphServerProtocol.R_OK); // LIST NAMED GRAPHS } else if (cmd.equals(GraphServerProtocol.CMD_LISTG)) { for (String name : nameGraphMap.keySet()) { rsb.append(name); rsb.append(GraphServerProtocol.NL); } rsb.append(GraphServerProtocol.R_OK); // GRAPH STATUS: gstat <name> } else if (cmd.equals(GraphServerProtocol.CMD_GSTAT)) { Graph gr0 = nameGraphMap.get(args[0]); if (null == gr0) { rsb.append(GraphServerProtocol.R_NOT_EXIST); } else { JSONObject result = new JSONObject(); result.put("vertex_count", gr0.numVertices()); result.put("edge_count", gr0.numEdges()); rsb.append(result.toString()); rsb.append(GraphServerProtocol.NL); rsb.append(GraphServerProtocol.R_OK); } } else { // // graph-specific operations // WeakReference<InboundChannelProcess> inboundChannelProcessRef = inboundChannelMap.get(clientId); // // async override handler // if (cmd.startsWith("&")) { cmd = cmd.substring(1); request = request.substring(1); responseChannel.write(graphCommandExecutorMap.get(clientState.get(GraphServerHandler.ST_DB)) .execute(responseChannel, clientId, clientState, inboundChannelProcessRef.get(), request, cmd, args) + GraphServerProtocol.NL); } else { // // graph-specific, queue-driven ordered operations // GraphCommand graphCommand = new GraphCommand(); graphCommand.responseChannel = responseChannel; graphCommand.clientId = clientId; graphCommand.clientState = clientState; graphCommand.inboundChannelProcess = inboundChannelProcessRef.get(); graphCommand.request = request; graphCommand.cmd = cmd; graphCommand.args = args; graphCommand.poisonPill = false; graphCommandExecutorMap.get(clientState.get(GraphServerHandler.ST_DB)).queue(graphCommand); } // a null return value indicates it's been queued for execution // by the appropriate GraphCommandExecutor return null; } // unknown request if (rsb.toString().equals("")) { log.info("GraphServerProtocol.R_UNK: " + cmd); rsb.append(GraphServerProtocol.R_UNK); rsb.append(GraphServerProtocol.SPACE); rsb.append(cmd); } rsb.append(GraphServerProtocol.NL); return rsb.toString(); } }