All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.granite.gravity.AbstractChannel Maven / Gradle / Ivy

There is a newer version: 3.0.0.M3
Show newest version
/*
  GRANITE DATA SERVICES
  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.

  This file is part of Granite Data Services.

  Granite Data Services is free software; you can redistribute it and/or modify
  it under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  Granite Data Services is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, see .
*/

package org.granite.gravity;

import java.io.IOException;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.granite.context.AMFContextImpl;
import org.granite.context.GraniteContext;
import org.granite.logging.Logger;
import org.granite.messaging.amf.AMF0Message;
import org.granite.messaging.webapp.HttpGraniteContext;

import flex.messaging.messages.AsyncMessage;

/**
 * @author Franck WOLFF
 */
public abstract class AbstractChannel implements Channel {
    
    ///////////////////////////////////////////////////////////////////////////
    // Fields.

    private static final Logger log = Logger.getLogger(AbstractChannel.class);

    protected final String id;
    protected final ServletConfig servletConfig;
    
    protected final ConcurrentMap subscriptions = new ConcurrentHashMap();
    
    protected LinkedList publishedQueue = new LinkedList();
    protected final Lock publishedQueueLock = new ReentrantLock();

    protected LinkedList receivedQueue = new LinkedList();
    protected final Lock receivedQueueLock = new ReentrantLock();
    
    protected final AsyncPublisher publisher;
    protected final AsyncReceiver receiver;
    
    ///////////////////////////////////////////////////////////////////////////
    // Constructor.

    protected AbstractChannel(ServletConfig servletConfig, GravityConfig gravityConfig, String id) {        
        if (id == null)
        	throw new NullPointerException("id cannot be null");
        
        this.id = id;
        this.servletConfig = servletConfig;
        
        this.publisher = new AsyncPublisher(this);
        this.receiver = new AsyncReceiver(this);
    }
    
    ///////////////////////////////////////////////////////////////////////////
    // Abstract protected method.
	
	protected abstract boolean hasAsyncHttpContext();	
	protected abstract AsyncHttpContext acquireAsyncHttpContext();
	protected abstract void releaseAsyncHttpContext(AsyncHttpContext context);
    
    ///////////////////////////////////////////////////////////////////////////
    // Channel interface implementation.

	public String getId() {
        return id;
    }
	
	public Gravity getGravity() {
		return GravityManager.getGravity(getServletContext());
	}

    public Subscription addSubscription(String destination, String subTopicId, String subscriptionId, boolean noLocal) {
    	Subscription subscription = new Subscription(this, destination, subTopicId, subscriptionId, noLocal);
    	Subscription present = subscriptions.putIfAbsent(subscriptionId, subscription);
    	return (present != null ? present : subscription);
    }

    public Collection getSubscriptions() {
    	return subscriptions.values();
    }
    
    public Subscription removeSubscription(String subscriptionId) {
    	return subscriptions.remove(subscriptionId);
    }

	public void publish(AsyncPublishedMessage message) throws MessagePublishingException {
		if (message == null)
			throw new NullPointerException("message cannot be null");
		
		publishedQueueLock.lock();
		try {
			publishedQueue.add(message);
		}
		finally {
			publishedQueueLock.unlock();
		}

		publisher.queue(getGravity());
	}
	
	public boolean hasPublishedMessage() {
		publishedQueueLock.lock();
		try {
			return !publishedQueue.isEmpty();
		}
		finally {
			publishedQueueLock.unlock();
		}
	}
	
	public boolean runPublish() {
		LinkedList publishedCopy = null;
		
		publishedQueueLock.lock();
		try {
			if (publishedQueue.isEmpty())
				return false;
			publishedCopy = publishedQueue;
			publishedQueue = new LinkedList();
		}
		finally {
			publishedQueueLock.unlock();
		}
		
		for (AsyncPublishedMessage message : publishedCopy) {
			try {
				message.publish(this);
			}
			catch (Exception e) {
				log.error(e, "Error while trying to publish message: %s", message);
			}
		}
		
		return true;
	}

	public void receive(AsyncMessage message) throws MessageReceivingException {
		if (message == null)
			throw new NullPointerException("message cannot be null");
		
		Gravity gravity = getGravity();
		
		receivedQueueLock.lock();
		try {
			if (receivedQueue.size() + 1 > gravity.getGravityConfig().getMaxMessagesQueuedPerChannel())
				throw new MessageReceivingException(message, "Could not queue message (channel's queue is full) for channel: " + this);
			
			receivedQueue.add(message);
		}
		finally {
			receivedQueueLock.unlock();
		}

		if (hasAsyncHttpContext())
			receiver.queue(gravity);
	}
	
	public boolean hasReceivedMessage() {
		receivedQueueLock.lock();
		try {
			return !receivedQueue.isEmpty();
		}
		finally {
			receivedQueueLock.unlock();
		}
	}

	public boolean runReceive() {
		return runReceived(null);
	}
	
	public boolean runReceived(AsyncHttpContext asyncHttpContext) {
		
		boolean httpAsParam = (asyncHttpContext != null); 
		LinkedList messages = null;
		OutputStream os = null;

		try {
			receivedQueueLock.lock();
			try {
				// Do we have any pending messages? 
				if (receivedQueue.isEmpty())
					return false;
				
				// Do we have a valid http context?
				if (asyncHttpContext == null) {
					asyncHttpContext = acquireAsyncHttpContext();
					if (asyncHttpContext == null)
						return false;
				}
				
				// Both conditions are ok, get all pending messages.
				messages = receivedQueue;
				receivedQueue = new LinkedList();
			}
			finally {
				receivedQueueLock.unlock();
			}
			
			HttpServletRequest request = asyncHttpContext.getRequest();
			HttpServletResponse response = asyncHttpContext.getResponse();
			
			// Set response messages correlation ids to connect request message id.
			String correlationId = asyncHttpContext.getConnectMessage().getMessageId();
			AsyncMessage[] messagesArray = new AsyncMessage[messages.size()];
			int i = 0;
			for (AsyncMessage message : messages) {
				message.setCorrelationId(correlationId);
				messagesArray[i++] = message;
			}
			
			// Setup serialization context (thread local)
			Gravity gravity = getGravity();
	        GraniteContext context = HttpGraniteContext.createThreadIntance(
	            gravity.getGraniteConfig(), gravity.getServicesConfig(),
	            null, request, response
	        );
	        ((AMFContextImpl)context.getAMFContext()).setCurrentAmf3Message(asyncHttpContext.getConnectMessage());
	
	        // Write messages to response output stream.

	        response.setStatus(HttpServletResponse.SC_OK);
	        response.setContentType(AMF0Message.CONTENT_TYPE);
	        response.setDateHeader("Expire", 0L);
	        response.setHeader("Cache-Control", "no-store");
	        
	        os = response.getOutputStream();
	        ObjectOutput amf3Serializer = context.getGraniteConfig().newAMF3Serializer(os);
	        
	        log.debug("<< [MESSAGES for channel=%s] %s", this, messagesArray);
	        
	        amf3Serializer.writeObject(messagesArray);
	        
	        os.flush();
	        response.flushBuffer();
	        
	        return true; // Messages were delivered, http context isn't valid anymore.
		}
		catch (IOException e) {
			log.warn(e, "Could not send messages to channel: %s (retrying later)", this);
			
			GravityConfig gravityConfig = getGravity().getGravityConfig();
			if (gravityConfig.isRetryOnError()) {
				receivedQueueLock.lock();
				try {
					if (receivedQueue.size() + messages.size() > gravityConfig.getMaxMessagesQueuedPerChannel()) {
						log.warn(
							"Channel %s has reached its maximum queue capacity %s (throwing %s messages)",
							this,
							gravityConfig.getMaxMessagesQueuedPerChannel(),
							messages.size()
						);
					}
					else
						receivedQueue.addAll(0, messages);
				}
				finally {
					receivedQueueLock.unlock();
				}
			}
			
			return true; // Messages weren't delivered, but http context isn't valid anymore.
		}
		finally {
			
			// Cleanup serialization context (thread local)
			try {
				GraniteContext.release();
			}
			catch (Exception e) {
				// should never happen...
			}
			
			// Close output stream.
			try {
				if (os != null) {
					try {
						os.close();
					}
					catch (IOException e) {
						log.warn(e, "Could not close output stream (ignored)");
					}
				}
			}
			finally {
				// Cleanup http context (only if this method wasn't explicitly called with a non null
				// AsyncHttpContext from the servlet).
				if (!httpAsParam)
					releaseAsyncHttpContext(asyncHttpContext);
			}
		}
	}

    public void destroy() {
    	Gravity gravity = getGravity();
		gravity.cancel(publisher);
		gravity.cancel(receiver);

    	subscriptions.clear();
	}
    
    ///////////////////////////////////////////////////////////////////////////
    // Protected utilities.
	
	protected boolean queueReceiver() {
		if (hasReceivedMessage()) {
			receiver.queue(getGravity());
			return true;
		}
		return false;
	}
	
	protected ServletConfig getServletConfig() {
		return servletConfig;
	}
	
	protected ServletContext getServletContext() {
		return servletConfig.getServletContext();
	}
    
    ///////////////////////////////////////////////////////////////////////////
    // Object overwritten methods.

	@Override
    public boolean equals(Object obj) {
        return (obj instanceof Channel && id.equals(((Channel)obj).getId()));
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

	@Override
    public String toString() {
        return getClass().getName() + " {id=" + id + ", subscriptions=" + subscriptions.values() + "}";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy