/* * Copyright (c) 2016 Couchbase, Inc. * * Licensed 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 com.couchbase.client.core.endpoint.search; import com.couchbase.client.core.ResponseEvent; import com.couchbase.client.core.endpoint.AbstractEndpoint; import com.couchbase.client.core.endpoint.AbstractGenericHandler; import com.couchbase.client.core.endpoint.ResponseStatusConverter; import com.couchbase.client.core.logging.CouchbaseLogger; import com.couchbase.client.core.logging.CouchbaseLoggerFactory; import com.couchbase.client.core.message.AbstractCouchbaseRequest; import com.couchbase.client.core.message.AbstractCouchbaseResponse; import com.couchbase.client.core.message.CouchbaseRequest; import com.couchbase.client.core.message.CouchbaseResponse; import com.couchbase.client.core.message.ResponseStatus; import com.couchbase.client.core.message.search.GetSearchIndexRequest; import com.couchbase.client.core.message.search.GetSearchIndexResponse; import com.couchbase.client.core.message.search.UpsertSearchIndexRequest; import com.couchbase.client.core.message.search.UpsertSearchIndexResponse; import com.couchbase.client.core.message.search.RemoveSearchIndexRequest; import com.couchbase.client.core.message.search.RemoveSearchIndexResponse; import com.couchbase.client.core.message.search.SearchQueryRequest; import com.couchbase.client.core.message.search.SearchQueryResponse; import com.couchbase.client.core.message.search.SearchRequest; import com.couchbase.client.core.service.ServiceType; import com.lmax.disruptor.EventSink; import com.lmax.disruptor.RingBuffer; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.CharsetUtil; import java.util.Queue; /** * The {@link SearchHandler} is responsible for encoding {@link SearchRequest}s into lower level * {@link HttpRequest}s as well as decoding {@link HttpObject}s into * {@link CouchbaseResponse}s. * * @author Sergey Avseyev * @since 1.2 */ public class SearchHandler extends AbstractGenericHandler<HttpObject, HttpRequest, SearchRequest> { private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(SearchHandler.class); /** * Contains the current pending response header if set. */ private HttpResponse responseHeader; /** * Contains the accumulating buffer for the response content. */ private ByteBuf responseContent; public SearchHandler(AbstractEndpoint endpoint, EventSink<ResponseEvent> responseBuffer, boolean isTransient, final boolean pipeline) { super(endpoint, responseBuffer, isTransient, pipeline); } /** * Creates a new {@link SearchHandler} with a custom queue for requests (suitable for tests). * * @param endpoint the {@link AbstractEndpoint} to coordinate with. * @param responseBuffer the {@link RingBuffer} to push responses into. * @param queue the queue which holds all outstanding open requests. */ SearchHandler(AbstractEndpoint endpoint, RingBuffer<ResponseEvent> responseBuffer, Queue<SearchRequest> queue, boolean isTransient, final boolean pipeline) { super(endpoint, responseBuffer, queue, isTransient, pipeline); } @Override protected HttpRequest encodeRequest(ChannelHandlerContext ctx, SearchRequest msg) throws Exception { HttpMethod httpMethod = HttpMethod.GET; if (msg instanceof UpsertSearchIndexRequest) { httpMethod = HttpMethod.PUT; } else if (msg instanceof RemoveSearchIndexRequest) { httpMethod = HttpMethod.DELETE; } else if (msg instanceof SearchQueryRequest) { httpMethod = HttpMethod.POST; } ByteBuf content; if (msg instanceof UpsertSearchIndexRequest) { content = Unpooled.copiedBuffer(((UpsertSearchIndexRequest) msg).payload(), CharsetUtil.UTF_8); } else if (msg instanceof SearchQueryRequest) { content = Unpooled.copiedBuffer(((SearchQueryRequest) msg).payload(), CharsetUtil.UTF_8); } else { content = Unpooled.EMPTY_BUFFER; } FullHttpRequest request; if (msg instanceof KeepAliveRequest) { request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, msg.path(), content); request.headers().set(HttpHeaders.Names.USER_AGENT, env().userAgent()); request.headers().set(HttpHeaders.Names.HOST, remoteHttpHost(ctx)); } else { request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, msg.path(), content); request.headers().set(HttpHeaders.Names.USER_AGENT, env().userAgent()); if (msg instanceof UpsertSearchIndexRequest || msg instanceof SearchQueryRequest) { request.headers().set(HttpHeaders.Names.ACCEPT, "*/*"); request.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/json"); } request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes()); request.headers().set(HttpHeaders.Names.HOST, remoteHttpHost(ctx)); addHttpBasicAuth(ctx, request, msg.username(), msg.password()); } return request; } @Override protected CouchbaseResponse decodeResponse(ChannelHandlerContext ctx, HttpObject msg) throws Exception { SearchRequest request = currentRequest(); CouchbaseResponse response = null; if (msg instanceof HttpResponse) { responseHeader = (HttpResponse) msg; if (responseContent != null) { responseContent.clear(); } else { responseContent = ctx.alloc().buffer(); } } if (msg instanceof HttpContent) { responseContent.writeBytes(((HttpContent) msg).content()); } if (currentRequest() instanceof KeepAliveRequest) { if (msg instanceof LastHttpContent) { response = new KeepAliveResponse(ResponseStatusConverter.fromHttp(responseHeader.getStatus().code()), currentRequest()); responseContent.clear(); responseContent.discardReadBytes(); finishedDecoding(); } } else if (msg instanceof LastHttpContent) { ResponseStatus status = ResponseStatusConverter.fromHttp(responseHeader.getStatus().code()); String body = responseContent.readableBytes() > 0 ? responseContent.toString(CHARSET) : responseHeader.getStatus().reasonPhrase(); if (request instanceof UpsertSearchIndexRequest) { response = new UpsertSearchIndexResponse(body, status); } else if (request instanceof GetSearchIndexRequest) { response = new GetSearchIndexResponse(body, status); } else if (request instanceof RemoveSearchIndexRequest) { response = new RemoveSearchIndexResponse(body, status); } else if (request instanceof SearchQueryRequest) { //TODO if more parsing is implemented, add a RawSearchQueryRequest similar to what was done in JVMCBC-357 response = new SearchQueryResponse(body, status); } finishedDecoding(); } return response; } @Override protected ServiceType serviceType() { return ServiceType.SEARCH; } @Override protected CouchbaseRequest createKeepAliveRequest() { return new KeepAliveRequest(); } protected static class KeepAliveRequest extends AbstractCouchbaseRequest implements SearchRequest { protected KeepAliveRequest() { super(null, null); } @Override public String path() { return "/api/ping"; } } protected static class KeepAliveResponse extends AbstractCouchbaseResponse { protected KeepAliveResponse(ResponseStatus status, CouchbaseRequest request) { super(status, request); } } }