/* * Copyright WSO2, Inc. (http://wso2.com) * * 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 org.wso2.carbon.cloud.gateway.transport.server; import org.apache.axis2.AxisFault; import org.apache.axis2.transport.base.threads.WorkerPool; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.thrift.TException; import org.wso2.carbon.cloud.gateway.common.CGUtils; import org.wso2.carbon.cloud.gateway.common.thrift.gen.CloudGatewayService; import org.wso2.carbon.cloud.gateway.common.thrift.gen.Message; import org.wso2.carbon.cloud.gateway.common.thrift.gen.NotAuthorizedException; import org.wso2.carbon.core.common.AuthenticationException; import org.wso2.carbon.core.services.authentication.AuthenticationAdmin; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Semaphore; /** * This implements the handler for Thrift server. This uses static objects(for buffers) in order to make * sure that standalone deployment and Stratos based deployment will work. * Since static objects can be accessed from anywhere make sure to review (in order to avoid stealing * from this buffers) any custom code deployed into the same JVM. */ public class CGThriftServerHandler implements CloudGatewayService.Iface { private static Log log = LogFactory.getLog(CGThriftServerHandler.class); private WorkerPool workerPool; public CGThriftServerHandler(WorkerPool workerPool) { this.workerPool = workerPool; } /** * A list of semaphores for two way messages */ private static Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<String, Semaphore>(); /** * When the back end worker task received a response that will be placed in this buffer to pick * up by the blocked thread. This buffer is used to communicate between two threads */ private static Map<String, Message> middleBuffer = new ConcurrentHashMap<String, Message>(); /** * Keep track of authorized list of queues in the form of queueName->SecureUUID */ private static Map<String, String> authorizedQueues = new ConcurrentHashMap<String, String>(); /** * List of request buffers which holds the request messages in the form of * SecureUUID->BlockingQueue buffer */ private static Map<String, BlockingQueue<Message>> requestBuffers = new ConcurrentHashMap<String, BlockingQueue<Message>>(); /** * The response message buffer */ private static BlockingQueue<Message> responseBuffer = new LinkedBlockingQueue<Message>(); /** * Allow access to user userName for the buffer queueName and return the token or throw * exception if user can't allow access to the buffer * * @param userName user name of the client * @param password password of the client * @param queueName the name of the buffer to use should allow access to * @return a token to use for server buffer access * @throws NotAuthorizedException throws in case of illegal access * @throws TException throws in case of an connection error */ public String login(String userName, String password, String queueName) throws NotAuthorizedException, TException { // check if this user is configured AuthenticationAdmin authAdmin = new AuthenticationAdmin(); try { if (!authAdmin.login(userName, password, "localhost")) { throw new NotAuthorizedException("User '" + userName + "' not authorized to access" + " buffers"); } } catch (AuthenticationException e) { throw new NotAuthorizedException(e.getMessage()); } SecureRandom rand = new SecureRandom(); String token = Integer.toString(rand.nextInt()); if (authorizedQueues.containsKey(queueName)) { // an already authorized user try to login again let him/her login again String oldToken = authorizedQueues.get(queueName); authorizedQueues.remove(queueName); if (requestBuffers.containsKey(oldToken)) { BlockingQueue<Message> oldBuffer = requestBuffers.remove(oldToken); // initialize the new buffer with the existing messages requestBuffers.put(token, new LinkedBlockingQueue<Message>(oldBuffer)); } } else { // initialize the buffer for this request requestBuffers.put(token, new LinkedBlockingQueue<Message>()); } authorizedQueues.put(queueName, token); return token; } /** * This will perform the exchange of data buffers between the client and the server. * This must do it's all operations without blocking as much as possible. * * @param responseMessageList The response buffer from thrift client * @param blockSize size of the message bulk that need to return client * @param token the token to authorize the exchange operation * @return a message bulk of size, 'size' if possible * @throws NotAuthorizedException in case the provided token is invalid * @throws TException in case of an error */ public List<Message> exchange(List<Message> responseMessageList, int blockSize, String token) throws NotAuthorizedException, TException { if (!authorizedQueues.containsValue(token)) { throw new NotAuthorizedException("You don't have required permission to access the buffers"); } // if there is any response messages copy the response from the client to server's response // buffer and then hand over the copy/move operations into separate threads because // the processing of buffers are independent of the exchange operation if (responseMessageList.size() > 0) { workerPool.execute(new MessageCopyTask(responseMessageList, responseBuffer)); } List<Message> requestMsgList = new ArrayList<Message>(); // if there is any request messages send them to the client if (requestBuffers.size() > 0) { BlockingQueue<Message> requestBuffer = requestBuffers.get(token); if (requestBuffer != null) { try { CGUtils.moveElements(requestBuffer, requestMsgList, blockSize); } catch (AxisFault axisFault) { log.error("Error while moving elements :", axisFault); } } } return requestMsgList; } /** * Add a message into the server's request buffer, wait if the buffer is full. * * @param msg the new Thrift message * @param token the token to look up the real buffer * @throws AxisFault in case of an error - for e.g. out of space in the queue */ public static void addRequestMessage(Message msg, String token) throws AxisFault { try { BlockingQueue<Message> buffer = requestBuffers.get(token); if (buffer == null) { throw new AxisFault("The requested buffer is not found"); } buffer.put(msg); } catch (Exception e) { throw new AxisFault(e.getMessage(), e); } } /** * Return the head of the response buffer, not that if there are no message it'll * block if no messages available. * <p/> * Note that since the buffers are static objects anybody has the access to buffers (and * the data inside them) but before deploy any custom be warned to review them! * * @return the message if there is any or null in case of this thread is interrupted */ public static Message getResponseMessage() { try { // block if there is no messages return responseBuffer.take(); } catch (InterruptedException e) { // ignore } return null; } /** * Return the request message buffer * <p/> * Note that since the buffers are static objects anybody has the access to buffers (and * the data inside them) but before deploy any custom be warned to review them! * * @param token the token to the buffer is bound to * @return request message buffer */ public BlockingQueue<Message> getRequestBuffer(String token) { return requestBuffers.get(token); } /** * Return the token give the queue name * * @param queueName queue name * @return the secure token that this queue is bound */ public static String getSecureUUID(String queueName) { return authorizedQueues.get(queueName); } /** * Return the list of request buffers * <p/> * Note that since the buffers are static objects anybody has the access to buffers (and * the data inside them) but before deploy any custom be warned to review them! * * @return request buffer list */ public static Map<String, BlockingQueue<Message>> getRequestBuffers() { return requestBuffers; } /** * Add a new request buffer to the list of request buffers * * @param token the token for the new buffer */ public static void addNewRequestBuffer(final String token) { requestBuffers.put(token, new LinkedBlockingQueue<Message>()); } public static Map<String, Semaphore> getSemaphoreMap() { return semaphoreMap; } public static Map<String, Message> getMiddleBuffer() { return middleBuffer; } /** * An asynchronous message copy task among buffers */ private class MessageCopyTask implements Runnable { private List<Message> src; private BlockingQueue<Message> dest; private MessageCopyTask(List<Message> src, BlockingQueue<Message> dest) { this.src = src; this.dest = dest; } public void run() { copyElements(this.src, this.dest); } private void copyElements(final List<Message> src, BlockingQueue<Message> dest) { dest.addAll(src); } } }