private void evalOp(final Context context) throws OpProcessorException { if (logger.isDebugEnabled()) { final RequestMessage msg = context.getRequestMessage(); logger.debug( "Sessionless request {} for eval in thread {}", msg.getRequestId(), Thread.currentThread().getName()); } evalOpInternal(context, context::getGremlinExecutor, bindingMaker.apply(context)); }
/** * Examines the {@link RequestMessage} and extracts the session token. The session is then either * found or a new one is created. */ protected static Session getSession(final Context context, final RequestMessage msg) { final String sessionId = (String) msg.getArgs().get(Tokens.ARGS_SESSION); logger.debug( "In-session request {} for eval for session {} in thread {}", msg.getRequestId(), sessionId, Thread.currentThread().getName()); final Session session = sessions.computeIfAbsent(sessionId, k -> new Session(k, context, sessions)); session.touch(); return session; }
@Override public RequestMessage deserializeRequest(final ByteBuf msg) throws SerializationException { try { final Kryo kryo = kryoThreadLocal.get(); final byte[] payload = new byte[msg.readableBytes()]; msg.readBytes(payload); try (final Input input = new Input(payload)) { // by the time the message gets here, the mime length/type have been already read, so this // part just // needs to process the payload. final UUID id = kryo.readObject(input, UUID.class); final String processor = input.readString(); final String op = input.readString(); final RequestMessage.Builder builder = RequestMessage.build(op).overrideRequestId(id).processor(processor); final Map<String, Object> args = kryo.readObject(input, HashMap.class); args.forEach(builder::addArg); return builder.create(); } } catch (Exception ex) { logger.warn( "Request [{}] could not be deserialized by {}.", msg, GryoMessageSerializerV1d0.class.getName()); throw new SerializationException(ex); } }
/** * Session based requests accept a "close" operator in addition to "eval". A close will trigger * the session to be killed and any uncommitted transaction to be rolled-back. */ @Override public Optional<ThrowingConsumer<Context>> selectOther(final RequestMessage requestMessage) throws OpProcessorException { if (requestMessage.getOp().equals(Tokens.OPS_CLOSE)) { // this must be an in-session request if (!requestMessage.optionalArgs(Tokens.ARGS_SESSION).isPresent()) { final String msg = String.format( "A message with an [%s] op code requires a [%s] argument", Tokens.OPS_CLOSE, Tokens.ARGS_SESSION); throw new OpProcessorException( msg, ResponseMessage.build(requestMessage) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .result(msg) .create()); } return Optional.of( ctx -> { // validate the session is present and then remove it if it is. final Session sessionToClose = sessions.get(requestMessage.getArgs().get(Tokens.ARGS_SESSION).toString()); if (null == sessionToClose) { final String msg = String.format( "There was no session named %s to close", requestMessage.getArgs().get(Tokens.ARGS_SESSION).toString()); throw new OpProcessorException( msg, ResponseMessage.build(requestMessage) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .result(msg) .create()); } sessionToClose.kill(); }); } else { return Optional.empty(); } }
@Override public ByteBuf serializeRequestAsBinary( final RequestMessage requestMessage, final ByteBufAllocator allocator) throws SerializationException { ByteBuf encodedMessage = null; try { final Kryo kryo = kryoThreadLocal.get(); try (final OutputStream baos = new ByteArrayOutputStream()) { final Output output = new Output(baos); final String mimeType = serializeToString ? MIME_TYPE_STRINGD : MIME_TYPE; output.writeByte(mimeType.length()); output.write(mimeType.getBytes(UTF8)); kryo.writeObject(output, requestMessage.getRequestId()); output.writeString(requestMessage.getProcessor()); output.writeString(requestMessage.getOp()); kryo.writeObject(output, requestMessage.getArgs()); final long size = output.total(); if (size > Integer.MAX_VALUE) throw new SerializationException( String.format("Message size of %s exceeds allocatable space", size)); encodedMessage = allocator.buffer((int) size); encodedMessage.writeBytes(output.toBytes()); } return encodedMessage; } catch (Exception ex) { if (encodedMessage != null) ReferenceCountUtil.release(encodedMessage); logger.warn( "Request [{}] could not be serialized by {}.", requestMessage.toString(), GryoMessageSerializerV1d0.class.getName()); throw new SerializationException(ex); } }
protected void evalOp(final Context context) throws OpProcessorException { final RequestMessage msg = context.getRequestMessage(); final Session session = getSession(context, msg); // place the session on the channel context so that it can be used during serialization. in // this way // the serialization can occur on the same thread used to execute the gremlin within the // session. this // is important given the threadlocal nature of Graph implementation transactions. context.getChannelHandlerContext().channel().attr(StateKey.SESSION).set(session); evalOpInternal( context, session::getGremlinExecutor, () -> { final Bindings bindings = session.getBindings(); // parameter bindings override session bindings if present Optional.ofNullable((Map<String, Object>) msg.getArgs().get(Tokens.ARGS_BINDINGS)) .ifPresent(bindings::putAll); return bindings; }); }
@Override protected Optional<ThrowingConsumer<Context>> validateEvalMessage(final RequestMessage message) throws OpProcessorException { super.validateEvalMessage(message); if (!message.optionalArgs(Tokens.ARGS_SESSION).isPresent()) { final String msg = String.format( "A message with an [%s] op code requires a [%s] argument", Tokens.OPS_EVAL, Tokens.ARGS_SESSION); throw new OpProcessorException( msg, ResponseMessage.build(message) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .result(msg) .create()); } return Optional.empty(); }
/** * A useful method for those extending this class, where the means for binding construction can be * supplied to this class. This function is used in {@link #evalOp(Context)} to create the final * argument to {@link super#evalOpInternal(Context, Supplier, BindingSupplier)}. In this way an * extending class can use the default {@link BindingSupplier} which carries a lot of re-usable * functionality or provide a new one to override the existing approach. */ protected Function<Context, BindingSupplier> getBindingMaker() { return context -> () -> { final RequestMessage msg = context.getRequestMessage(); final Bindings bindings = new SimpleBindings(); // don't allow both rebindings and aliases parameters as they are the same thing. aliases // were introduced // as of 3.1.0 as a replacement for rebindings. this check can be removed when rebindings // are completely // removed from the protocol final boolean hasRebindings = msg.getArgs().containsKey(Tokens.ARGS_REBINDINGS); final boolean hasAliases = msg.getArgs().containsKey(Tokens.ARGS_ALIASES); if (hasRebindings && hasAliases) { final String error = "Prefer use of the 'aliases' parameter over 'rebindings' and do not use both"; throw new OpProcessorException( error, ResponseMessage.build(msg) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .result(error) .create()); } final String rebindingOrAliasParameter = hasRebindings ? Tokens.ARGS_REBINDINGS : Tokens.ARGS_ALIASES; // alias any global bindings to a different variable. if (msg.getArgs().containsKey(rebindingOrAliasParameter)) { final Map<String, String> rebinds = (Map<String, String>) msg.getArgs().get(rebindingOrAliasParameter); for (Map.Entry<String, String> kv : rebinds.entrySet()) { boolean found = false; final Map<String, Graph> graphs = context.getGraphManager().getGraphs(); if (graphs.containsKey(kv.getValue())) { bindings.put(kv.getKey(), graphs.get(kv.getValue())); found = true; } if (!found) { final Map<String, TraversalSource> traversalSources = context.getGraphManager().getTraversalSources(); if (traversalSources.containsKey(kv.getValue())) { bindings.put(kv.getKey(), traversalSources.get(kv.getValue())); found = true; } } if (!found) { final String error = String.format( "Could not alias [%s] to [%s] as [%s] not in the Graph or TraversalSource global bindings", kv.getKey(), kv.getValue(), kv.getValue()); throw new OpProcessorException( error, ResponseMessage.build(msg) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .result(error) .create()); } } } // add any bindings to override any other supplied Optional.ofNullable((Map<String, Object>) msg.getArgs().get(Tokens.ARGS_BINDINGS)) .ifPresent(bindings::putAll); return bindings; }; }