@Override public ResponseMessage createObject(final Map<String, Object> data) { final Map<String, Object> status = (Map<String, Object>) data.get(SerTokens.TOKEN_STATUS); final Map<String, Object> result = (Map<String, Object>) data.get(SerTokens.TOKEN_RESULT); return ResponseMessage.build(UUID.fromString(data.get(SerTokens.TOKEN_REQUEST).toString())) .code(ResponseStatusCode.getFromValue((Integer) status.get(SerTokens.TOKEN_CODE))) .statusMessage(status.get(SerTokens.TOKEN_MESSAGE).toString()) .statusAttributes((Map<String, Object>) status.get(SerTokens.TOKEN_ATTRIBUTES)) .result(result.get(SerTokens.TOKEN_DATA)) .responseMetaData((Map<String, Object>) result.get(SerTokens.TOKEN_META)) .create(); } }
private static Optional<Map<String, String>> validatedAliases(final RequestMessage message) throws OpProcessorException { final Optional<Map<String, String>> aliases = message.optionalArgs(Tokens.ARGS_ALIASES); if (!aliases.isPresent()) { final String msg = String.format("A message with [%s] op code requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_ALIASES); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } if (aliases.get().size() != 1 || !aliases.get().containsKey(Tokens.VAL_TRAVERSAL_SOURCE_ALIAS)) { final String msg = String.format("A message with [%s] op code requires the [%s] argument to be a Map containing one alias assignment named '%s'.", Tokens.OPS_BYTECODE, Tokens.ARGS_ALIASES, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } return aliases; }
private static void validateTraversalSourceAlias(final Context ctx, final RequestMessage message, final Map<String, String> aliases) throws OpProcessorException { final String traversalSourceBindingForAlias = aliases.values().iterator().next(); if (!ctx.getGraphManager().getTraversalSourceNames().contains(traversalSourceBindingForAlias)) { final String msg = String.format("The traversal source [%s] for alias [%s] is not configured on the server.", traversalSourceBindingForAlias, Tokens.VAL_TRAVERSAL_SOURCE_ALIAS); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } }
@Override public ResponseMessage deserializeResponse(final ByteBuf msg) throws SerializationException { try { final byte[] payload = new byte[msg.readableBytes()]; msg.readBytes(payload); final Map<String, Object> responseData = mapper.readValue(payload, mapTypeReference); final Map<String, Object> status = (Map<String, Object>) responseData.get(SerTokens.TOKEN_STATUS); final Map<String, Object> result = (Map<String, Object>) responseData.get(SerTokens.TOKEN_RESULT); return ResponseMessage.build(UUID.fromString(responseData.get(SerTokens.TOKEN_REQUEST).toString())) .code(ResponseStatusCode.getFromValue((Integer) status.get(SerTokens.TOKEN_CODE))) .statusMessage(status.get(SerTokens.TOKEN_MESSAGE).toString()) .statusAttributes((Map<String, Object>) status.get(SerTokens.TOKEN_ATTRIBUTES)) .result(result.get(SerTokens.TOKEN_DATA)) .responseMetaData((Map<String, Object>) result.get(SerTokens.TOKEN_META)) .create(); } catch (Exception ex) { logger.warn("Response [{}] could not be deserialized by {}.", msg, AbstractGraphSONMessageSerializerV1d0.class.getName()); throw new SerializationException(ex); } }
@Override public ResponseMessage deserializeResponse(final String msg) throws SerializationException { try { final Map<String, Object> responseData = mapper.readValue(msg, mapTypeReference); final Map<String, Object> status = (Map<String, Object>) responseData.get(SerTokens.TOKEN_STATUS); final Map<String, Object> result = (Map<String, Object>) responseData.get(SerTokens.TOKEN_RESULT); return ResponseMessage.build(UUID.fromString(responseData.get(SerTokens.TOKEN_REQUEST).toString())) .code(ResponseStatusCode.getFromValue((Integer) status.get(SerTokens.TOKEN_CODE))) .statusMessage(status.get(SerTokens.TOKEN_MESSAGE).toString()) .statusAttributes((Map<String, Object>) status.get(SerTokens.TOKEN_ATTRIBUTES)) .result(result.get(SerTokens.TOKEN_DATA)) .responseMetaData((Map<String, Object>) result.get(SerTokens.TOKEN_META)) .create(); } catch (Exception ex) { logger.warn("Response [{}] could not be deserialized by {}.", msg, AbstractGraphSONMessageSerializerV1d0.class.getName()); throw new SerializationException(ex); } }
private static Map<String, String> validateTraversalRequest(final RequestMessage message) throws OpProcessorException { if (!message.optionalArgs(Tokens.ARGS_GREMLIN).isPresent()) { final String msg = String.format("A message with [%s] op code requires a [%s] argument.", Tokens.OPS_BYTECODE, Tokens.ARGS_GREMLIN); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } return validatedAliases(message).get(); }
@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).statusMessage(msg).create()); } return Optional.empty(); }
protected Optional<ThrowingConsumer<Context>> validateEvalMessage(final RequestMessage message) throws OpProcessorException { if (!message.optionalArgs(Tokens.ARGS_GREMLIN).isPresent()) { final String msg = String.format("A message with an [%s] op code requires a [%s] argument.", Tokens.OPS_EVAL, Tokens.ARGS_GREMLIN); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } if (message.optionalArgs(Tokens.ARGS_BINDINGS).isPresent()) { final Map bindings = (Map) message.getArgs().get(Tokens.ARGS_BINDINGS); if (IteratorUtils.anyMatch(bindings.keySet().iterator(), k -> null == k || !(k instanceof String))) { final String msg = String.format("The [%s] message is using one or more invalid binding keys - they must be of type String and cannot be null", Tokens.OPS_EVAL); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } final Set<String> badBindings = IteratorUtils.set(IteratorUtils.<String>filter(bindings.keySet().iterator(), INVALID_BINDINGS_KEYS::contains)); if (!badBindings.isEmpty()) { final String msg = String.format("The [%s] message supplies one or more invalid parameters key of [%s] - these are reserved names.", Tokens.OPS_EVAL, badBindings); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } // ignore control bindings that get passed in with the "#jsr223" prefix - those aren't used in compilation if (IteratorUtils.count(IteratorUtils.filter(bindings.keySet().iterator(), k -> !k.toString().startsWith("#jsr223"))) > maxParameters) { final String msg = String.format("The [%s] message contains %s bindings which is more than is allowed by the server %s configuration", Tokens.OPS_EVAL, bindings.size(), maxParameters); throw new OpProcessorException(msg, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS).statusMessage(msg).create()); } } return Optional.empty(); }
/** * 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).statusMessage(msg).create()); } final boolean force = requestMessage.<Boolean>optionalArgs(Tokens.ARGS_FORCE).orElse(false); 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).statusMessage(msg).create()); } sessionToClose.manualKill(force); // send back a confirmation of the close ctx.getChannelHandlerContext().writeAndFlush(ResponseMessage.build(requestMessage) .code(ResponseStatusCode.NO_CONTENT) .create()); }); } else { return Optional.empty(); } }
@Override protected void channelRead0(final ChannelHandlerContext ctx, final Pair<RequestMessage, ThrowingConsumer<Context>> objects) throws Exception { final RequestMessage msg = objects.getValue0(); final ThrowingConsumer<Context> op = objects.getValue1(); final Context gremlinServerContext = new Context(msg, ctx, settings, graphManager, gremlinExecutor, scheduledExecutorService); try { op.accept(gremlinServerContext); } catch (OpProcessorException ope) { // Ops may choose to throw OpProcessorException or write the error ResponseMessage down the line // themselves logger.warn(ope.getMessage(), ope); ctx.writeAndFlush(ope.getResponseMessage()); } catch (Exception ex) { // It is possible that an unplanned exception might raise out of an OpProcessor execution. Build a general // error to send back to the client logger.warn(ex.getMessage(), ex); ctx.writeAndFlush(ResponseMessage.build(msg) .code(ResponseStatusCode.SERVER_ERROR) .statusAttributeException(ex) .statusMessage(ex.getMessage()).create()); } finally { ReferenceCountUtil.release(objects); } } }
protected void evalOp(final Context context) throws OpProcessorException { final RequestMessage msg = context.getRequestMessage(); final Session session = getSession(context, msg); // check if the session is still accepting requests - if not block further requests if (!session.acceptingRequests()) { final String sessionClosedMessage = String.format("Session %s is no longer accepting requests as it has been closed", session.getSessionId()); final ResponseMessage response = ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR) .statusMessage(sessionClosedMessage).create(); throw new OpProcessorException(sessionClosedMessage, response); } // 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, getBindingMaker(session).apply(context)); }
@Override public ThrowingConsumer<Context> select(final Context ctx) throws OpProcessorException { final RequestMessage message = ctx.getRequestMessage(); logger.debug("Selecting processor for RequestMessage {}", message); final ThrowingConsumer<Context> op; switch (message.getOp()) { case Tokens.OPS_EVAL: op = validateEvalMessage(message).orElse(getEvalOp()); break; case Tokens.OPS_INVALID: final String msgInvalid = String.format("Message could not be parsed. Check the format of the request. [%s]", message); throw new OpProcessorException(msgInvalid, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST).statusMessage(msgInvalid).create()); default: op = selectOther(message).orElseThrow(() -> { final String msgDefault = String.format("Message with op code [%s] is not recognized.", message.getOp()); return new OpProcessorException(msgDefault, ResponseMessage.build(message).code(ResponseStatusCode.REQUEST_ERROR_MALFORMED_REQUEST).statusMessage(msgDefault).create()); }); } return op; }
@Override protected void decode(final ChannelHandlerContext ctx, final RequestMessage msg, final List<Object> objects) throws Exception { final Context gremlinServerContext = new Context(msg, ctx, settings, graphManager, gremlinExecutor, this.scheduledExecutorService); try { // choose a processor to do the work based on the request message. final Optional<OpProcessor> processor = OpLoader.getProcessor(msg.getProcessor()); if (processor.isPresent()) // the processor is known so use it to evaluate the message objects.add(Pair.with(msg, processor.get().select(gremlinServerContext))); else { // invalid op processor selected so write back an error by way of OpProcessorException. final String errorMessage = String.format("Invalid OpProcessor requested [%s]", msg.getProcessor()); throw new OpProcessorException(errorMessage, ResponseMessage.build(msg) .code(ResponseStatusCode.REQUEST_ERROR_INVALID_REQUEST_ARGUMENTS) .statusMessage(errorMessage).create()); } } catch (OpProcessorException ope) { logger.warn(ope.getMessage(), ope); ctx.writeAndFlush(ope.getResponseMessage()); } }
/** * A generalized implementation of the "eval" operation. It handles script evaluation and iteration of results * so as to write {@link ResponseMessage} objects down the Netty pipeline. It also handles script timeouts, * iteration timeouts, metrics and building bindings. Note that result iteration is delegated to the * {@link #handleIterator} method, so those extending this class could override that method for better control * over result iteration. * * @param context The current Gremlin Server {@link Context} * @param gremlinExecutorSupplier A function that returns the {@link GremlinExecutor} to use in executing the * script evaluation. * @param bindingsSupplier A function that returns the {@link Bindings} to provide to the * {@link GremlinExecutor#eval} method. * @see #evalOpInternal(ResponseHandlerContext, Supplier, BindingSupplier) */ protected void evalOpInternal(final Context context, final Supplier<GremlinExecutor> gremlinExecutorSupplier, final BindingSupplier bindingsSupplier) throws OpProcessorException { final ResponseHandlerContext rhc = new ResponseHandlerContext(context); try { evalOpInternal(rhc, gremlinExecutorSupplier, bindingsSupplier); } catch (Exception ex) { // Exceptions may occur on after the script started executing, therefore corresponding errors must be // reported via the ResponseHandlerContext. logger.warn("Unable to process script evaluation request: " + ex, ex); rhc.writeAndFlush(ResponseMessage.build(context.getRequestMessage()) .code(ResponseStatusCode.SERVER_ERROR) .statusAttributeException(ex) .statusMessage(ex.getMessage()).create()); } }
@Override public ByteBuf serializeResponseAsBinary(final ResponseMessage responseMessage, final ByteBufAllocator allocator) throws SerializationException { ByteBuf encodedMessage = null; try { final Kryo kryo = kryoThreadLocal.get(); try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) { final Output output = new Output(baos, bufferSize); final ResponseMessage msgToWrite = !serializeToString ? responseMessage : ResponseMessage.build(responseMessage.getRequestId()) .code(responseMessage.getStatus().getCode()) .statusAttributes(responseMessage.getStatus().getAttributes()) .responseMetaData(responseMessage.getResult().getMeta()) .result(serializeResultToString(responseMessage)) .statusMessage(responseMessage.getStatus().getMessage()).create(); kryo.writeObject(output, msgToWrite); final long size = output.total(); if (size > Integer.MAX_VALUE) throw new SerializationException(String.format("Message size of %s exceeds allocatable space", size)); output.flush(); encodedMessage = allocator.buffer((int) size); encodedMessage.writeBytes(baos.toByteArray()); } return encodedMessage; } catch (Exception ex) { if (encodedMessage != null) ReferenceCountUtil.release(encodedMessage); logger.warn(String.format("Response [%s] could not be serialized by %s.", responseMessage, AbstractGryoMessageSerializerV3d0.class.getName()), ex); throw new SerializationException(ex); } }
final ResponseMessage clientSideError = ResponseMessage.build(response.getRequestId()) .code(ResponseStatusCode.FORBIDDEN).statusMessage(saslException.getMessage()).create(); channelHandlerContext.fireChannelRead(clientSideError);
@Override public ResponseMessage deserializeResponse(final ByteBuf msg) throws SerializationException { try { final Kryo kryo = kryoThreadLocal.get(); final byte[] payload = new byte[msg.capacity()]; msg.readBytes(payload); try (final Input input = new Input(payload)) { final UUID requestId = kryo.readObjectOrNull(input, UUID.class); final int status = input.readShort(); final String statusMsg = input.readString(); final Map<String,Object> statusAttributes = (Map<String,Object>) kryo.readClassAndObject(input); final Object result = kryo.readClassAndObject(input); final Map<String,Object> metaAttributes = (Map<String,Object>) kryo.readClassAndObject(input); return ResponseMessage.build(requestId) .code(ResponseStatusCode.getFromValue(status)) .statusMessage(statusMsg) .statusAttributes(statusAttributes) .result(result) .responseMetaData(metaAttributes) .create(); } } catch (Exception ex) { logger.warn(String.format("Response [%s] could not be deserialized by %s.", msg, AbstractGryoMessageSerializerV1d0.class.getName()), ex); throw new SerializationException(ex); } }
try { if (useBinary) { return new Frame(serializer.serializeResponseAsBinary(ResponseMessage.build(msg) .code(code) .statusAttributes(statusAttributes) return new Frame(textSerializer.serializeResponseAsString(ResponseMessage.build(msg) .code(code) .statusAttributes(statusAttributes) logger.warn("The result [{}] in the request {} could not be serialized and returned.", aggregate, msg.getRequestId(), ex); final String errorMessage = String.format("Error during serialization: %s", ExceptionHelper.getMessageFromExceptionOrCause(ex)); final ResponseMessage error = ResponseMessage.build(msg.getRequestId()) .statusMessage(errorMessage) .statusAttributeException(ex)
public ResponseMessage readValue(final ByteBuf buffer, final GraphBinaryReader context, final boolean nullable) throws SerializationException { final int version = buffer.readByte(); assert version >>> 31 == 1; return ResponseMessage.build(context.readValue(buffer, UUID.class, true)) .code(ResponseStatusCode.getFromValue(context.readValue(buffer, Integer.class, false))) .statusMessage(context.readValue(buffer, String.class, true)) .statusAttributes(context.readValue(buffer, Map.class, false)) .responseMetaData(context.readValue(buffer, Map.class, false)) .result(context.read(buffer)) .create(); }
@Override public <I extends InputShim> ResponseMessage read(final KryoShim<I, ?> kryo, final I input, final Class<ResponseMessage> clazz) { final UUID requestId = kryo.readObjectOrNull(input, UUID.class); final int status = input.readShort(); final String statusMsg = input.readString(); final Map<String,Object> statusAttributes = (Map<String,Object>) kryo.readClassAndObject(input); final Object result = kryo.readClassAndObject(input); final Map<String,Object> metaAttributes = (Map<String,Object>) kryo.readClassAndObject(input); return ResponseMessage.build(requestId) .code(ResponseStatusCode.getFromValue(status)) .statusMessage(statusMsg) .statusAttributes(statusAttributes) .result(result) .responseMetaData(metaAttributes) .create(); } }