/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file 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 org.apache.ignite.internal.processors.rest.protocols.tcp; import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.concurrent.CountDownLatch; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.client.marshaller.GridClientMarshaller; import org.apache.ignite.internal.processors.rest.GridRestCommand; import org.apache.ignite.internal.processors.rest.GridRestProtocolHandler; import org.apache.ignite.internal.processors.rest.GridRestResponse; import org.apache.ignite.internal.processors.rest.client.message.GridClientAuthenticationRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeResponse; import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage; import org.apache.ignite.internal.processors.rest.client.message.GridClientPingPacket; import org.apache.ignite.internal.processors.rest.client.message.GridClientResponse; import org.apache.ignite.internal.processors.rest.client.message.GridClientStateRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientTaskRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientTopologyRequest; import org.apache.ignite.internal.processors.rest.handlers.cache.GridCacheRestMetrics; import org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisMessage; import org.apache.ignite.internal.processors.rest.protocols.tcp.redis.GridRedisNioListener; import org.apache.ignite.internal.processors.rest.request.GridRestCacheRequest; import org.apache.ignite.internal.processors.rest.request.GridRestChangeStateRequest; import org.apache.ignite.internal.processors.rest.request.GridRestRequest; import org.apache.ignite.internal.processors.rest.request.GridRestTaskRequest; import org.apache.ignite.internal.processors.rest.request.GridRestTopologyRequest; import org.apache.ignite.internal.util.nio.GridNioFuture; import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter; import org.apache.ignite.internal.util.nio.GridNioSession; import org.apache.ignite.internal.util.typedef.CI1; import org.apache.ignite.internal.util.typedef.internal.U; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_APPEND; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_CAS; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_GET_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_METRICS; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PREPEND; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PUT; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_PUT_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REMOVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REMOVE_ALL; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CACHE_REPLACE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.EXE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.NODE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.NOOP; import static org.apache.ignite.internal.processors.rest.GridRestCommand.TOPOLOGY; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_ACTIVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_INACTIVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_CURRENT_STATE; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.APPEND; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.CAS; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.GET; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.GET_ALL; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.METRICS; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.PREPEND; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.PUT; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.PUT_ALL; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.REPLACE; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.RMV; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.RMV_ALL; import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.MARSHALLER; /** * Listener for nio server that handles incoming tcp rest packets. */ public class GridTcpRestNioListener extends GridNioServerListenerAdapter<GridClientMessage> { /** Mapping of {@code GridCacheOperation} to {@code GridRestCommand}. */ private static final Map<GridClientCacheRequest.GridCacheOperation, GridRestCommand> cacheCmdMap = new EnumMap<>(GridClientCacheRequest.GridCacheOperation.class); /** Supported protocol versions. */ private static final Collection<Short> SUPP_VERS = new HashSet<>(); /** * Fills {@code cacheCmdMap}. */ static { cacheCmdMap.put(PUT, CACHE_PUT); cacheCmdMap.put(PUT_ALL, CACHE_PUT_ALL); cacheCmdMap.put(GET, CACHE_GET); cacheCmdMap.put(GET_ALL, CACHE_GET_ALL); cacheCmdMap.put(RMV, CACHE_REMOVE); cacheCmdMap.put(RMV_ALL, CACHE_REMOVE_ALL); cacheCmdMap.put(REPLACE, CACHE_REPLACE); cacheCmdMap.put(CAS, CACHE_CAS); cacheCmdMap.put(METRICS, CACHE_METRICS); cacheCmdMap.put(APPEND, CACHE_APPEND); cacheCmdMap.put(PREPEND, CACHE_PREPEND); SUPP_VERS.add((short)1); } /** */ private final CountDownLatch marshMapLatch = new CountDownLatch(1); /** Marshallers map. */ private Map<Byte, GridClientMarshaller> marshMap; /** Logger. */ private IgniteLogger log; /** Protocol. */ private GridTcpRestProtocol proto; /** Protocol handler. */ private GridRestProtocolHandler hnd; /** Handler for all memcache requests. */ private GridTcpMemcachedNioListener memcachedLsnr; /** Handler for all Redis requests. */ private GridRedisNioListener redisLsnr; /** * Creates listener which will convert incoming tcp packets to rest requests and forward them to * a given rest handler. * * @param log Logger to use. * @param proto Protocol. * @param hnd Rest handler. * @param ctx Context. */ public GridTcpRestNioListener(IgniteLogger log, GridTcpRestProtocol proto, GridRestProtocolHandler hnd, GridKernalContext ctx) { memcachedLsnr = new GridTcpMemcachedNioListener(log, hnd); redisLsnr = new GridRedisNioListener(log, hnd, ctx); this.log = log; this.proto = proto; this.hnd = hnd; } /** * @param marshMap Marshallers. */ void marshallers(Map<Byte, GridClientMarshaller> marshMap) { assert marshMap != null; this.marshMap = marshMap; marshMapLatch.countDown(); } /** {@inheritDoc} */ @Override public void onConnected(GridNioSession ses) { // No-op. } /** {@inheritDoc} */ @Override public void onDisconnected(GridNioSession ses, @Nullable Exception e) { if (e != null) { if (e instanceof RuntimeException) U.error(log, "Failed to process request from remote client: " + ses, e); else U.warn(log, "Closed client session due to exception [ses=" + ses + ", msg=" + e.getMessage() + ']'); } } /** {@inheritDoc} */ @SuppressWarnings("ConstantConditions") @Override public void onMessage(final GridNioSession ses, final GridClientMessage msg) { if (msg instanceof GridMemcachedMessage) memcachedLsnr.onMessage(ses, (GridMemcachedMessage)msg); else if (msg instanceof GridRedisMessage) redisLsnr.onMessage(ses, (GridRedisMessage)msg); else { if (msg instanceof GridClientPingPacket) ses.send(msg); else if (msg instanceof GridClientHandshakeRequest) { GridClientHandshakeRequest hs = (GridClientHandshakeRequest)msg; short ver = hs.version(); if (!SUPP_VERS.contains(ver)) { U.error(log, "Client protocol version is not supported [ses=" + ses + ", ver=" + ver + ", supported=" + SUPP_VERS + ']'); ses.close(); } else { byte marshId = hs.marshallerId(); if (marshMapLatch.getCount() > 0) U.awaitQuiet(marshMapLatch); GridClientMarshaller marsh = marshMap.get(marshId); if (marsh == null) { U.error(log, "Client marshaller ID is invalid. Note that .NET and C++ clients " + "are supported only in enterprise edition [ses=" + ses + ", marshId=" + marshId + ']'); ses.close(); } else { ses.addMeta(MARSHALLER.ordinal(), marsh); ses.send(GridClientHandshakeResponse.OK); } } } else { final GridRestRequest req = createRestRequest(ses, msg); if (req != null) hnd.handleAsync(req).listen(new CI1<IgniteInternalFuture<GridRestResponse>>() { @Override public void apply(IgniteInternalFuture<GridRestResponse> fut) { GridClientResponse res = new GridClientResponse(); res.requestId(msg.requestId()); res.clientId(msg.clientId()); try { GridRestResponse restRes = fut.get(); res.sessionToken(restRes.sessionTokenBytes()); res.successStatus(restRes.getSuccessStatus()); res.errorMessage(restRes.getError()); Object o = restRes.getResponse(); // In case of metrics a little adjustment is needed. if (o instanceof GridCacheRestMetrics) o = ((GridCacheRestMetrics)o).map(); res.result(o); } catch (IgniteCheckedException e) { U.error(log, "Failed to process client request: " + msg, e); res.successStatus(GridClientResponse.STATUS_FAILED); res.errorMessage("Failed to process client request: " + e.getMessage()); } GridNioFuture<?> sf = ses.send(res); // Check if send failed. sf.listen(new CI1<IgniteInternalFuture<?>>() { @Override public void apply(IgniteInternalFuture<?> fut) { try { fut.get(); } catch (IgniteCheckedException e) { U.error(log, "Failed to process client request [ses=" + ses + ", msg=" + msg + ']', e); } } }); } }); else U.error(log, "Failed to process client request (unknown packet type) [ses=" + ses + ", msg=" + msg + ']'); } } } /** * Creates a REST request object from client TCP binary packet. * * @param ses NIO session. * @param msg Request message. * @return REST request object. */ @Nullable private GridRestRequest createRestRequest(GridNioSession ses, GridClientMessage msg) { GridRestRequest restReq = null; if (msg instanceof GridClientAuthenticationRequest) { GridClientAuthenticationRequest req = (GridClientAuthenticationRequest)msg; restReq = new GridRestTaskRequest(); restReq.command(NOOP); restReq.credentials(req.credentials()); } else if (msg instanceof GridClientCacheRequest) { GridClientCacheRequest req = (GridClientCacheRequest)msg; GridRestCacheRequest restCacheReq = new GridRestCacheRequest(); restCacheReq.cacheName(req.cacheName()); restCacheReq.cacheFlags(req.cacheFlagsOn()); restCacheReq.key(req.key()); restCacheReq.value(req.value()); restCacheReq.value2(req.value2()); Map vals = req.values(); if (vals != null) restCacheReq.values(new HashMap<Object, Object>(vals)); restCacheReq.command(cacheCmdMap.get(req.operation())); restReq = restCacheReq; } else if (msg instanceof GridClientTaskRequest) { GridClientTaskRequest req = (GridClientTaskRequest)msg; GridRestTaskRequest restTaskReq = new GridRestTaskRequest(); restTaskReq.command(EXE); restTaskReq.taskName(req.taskName()); restTaskReq.params(Arrays.asList(req.argument())); restReq = restTaskReq; } else if (msg instanceof GridClientTopologyRequest) { GridClientTopologyRequest req = (GridClientTopologyRequest)msg; GridRestTopologyRequest restTopReq = new GridRestTopologyRequest(); restTopReq.includeMetrics(req.includeMetrics()); restTopReq.includeAttributes(req.includeAttributes()); if (req.nodeId() != null) { restTopReq.command(NODE); restTopReq.nodeId(req.nodeId()); } else if (req.nodeIp() != null) { restTopReq.command(NODE); restTopReq.nodeIp(req.nodeIp()); } else restTopReq.command(TOPOLOGY); restReq = restTopReq; }else if (msg instanceof GridClientStateRequest) { GridClientStateRequest req = (GridClientStateRequest)msg; GridRestChangeStateRequest restChangeReq = new GridRestChangeStateRequest(); if (req.isReqCurrentState()) { restChangeReq.reqCurrentState(); restChangeReq.command(CLUSTER_CURRENT_STATE); } else { restChangeReq.active(req.active()); restChangeReq.command(req.active() ? CLUSTER_ACTIVE : CLUSTER_INACTIVE); } restReq = restChangeReq; } if (restReq != null) { restReq.destinationId(msg.destinationId()); restReq.clientId(msg.clientId()); restReq.sessionToken(msg.sessionToken()); restReq.address(ses.remoteAddress()); } return restReq; } /** * Closes the session by timeout (i.e. inactivity within the configured period of time). * * @param ses Session, that was inactive. */ @Override public void onSessionIdleTimeout(GridNioSession ses) { ses.close(); } }