/**
 * @author Cameron INGRAM
 * @author Venkat DANDA
 */
public class SeamServiceInvoker extends ServiceInvoker<SeamServiceFactory> {

  private static final long serialVersionUID = 1L;

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

  public static final String CAPITALIZED_DESTINATION_ID = "{capitalized.destination.id}";
  public static final String DESTINATION_ID = "{destination.id}";

  public SeamServiceInvoker(Destination destination, SeamServiceFactory factory, Object instance)
      throws ServiceException {
    super(destination, factory);

    this.invokee = instance;
  }

  @Override
  protected void beforeInvocation(ServiceInvocationContext context) {
    log.debug("Before Invocation");
  }

  @Override
  protected Object afterInvocation(ServiceInvocationContext context, Object result) {
    log.debug("After Invocation");
    return result;
  }
}
/**
 * @author William DRAI
 * @author Franck WOLFF
 */
public class DefaultGravity implements Gravity, DefaultGravityMBean {

  ///////////////////////////////////////////////////////////////////////////
  // Fields.

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

  private final Map<String, Object> applicationMap = new HashMap<String, Object>();
  private final ConcurrentHashMap<String, TimeChannel<?>> channels =
      new ConcurrentHashMap<String, TimeChannel<?>>();

  private GravityConfig gravityConfig = null;
  private ServicesConfig servicesConfig = null;
  private GraniteConfig graniteConfig = null;
  private SharedContext sharedContext = null;

  private Channel serverChannel = null;
  private AdapterFactory adapterFactory = null;
  private GravityPool gravityPool = null;

  private UdpReceiverFactory udpReceiverFactory = null;

  private Timer channelsTimer;
  private boolean started;

  ///////////////////////////////////////////////////////////////////////////
  // Constructor.

  public DefaultGravity(
      GravityConfig gravityConfig,
      ServicesConfig servicesConfig,
      GraniteConfig graniteConfig,
      SharedContext sharedContext) {
    if (gravityConfig == null || servicesConfig == null || graniteConfig == null)
      throw new NullPointerException("All arguments must be non null.");

    this.gravityConfig = gravityConfig;
    this.servicesConfig = servicesConfig;
    this.graniteConfig = graniteConfig;
    this.sharedContext = sharedContext;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Properties.

  public GravityConfig getGravityConfig() {
    return gravityConfig;
  }

  public ServicesConfig getServicesConfig() {
    return servicesConfig;
  }

  public GraniteConfig getGraniteConfig() {
    return graniteConfig;
  }

  public SharedContext getSharedContext() {
    return sharedContext;
  }

  public boolean isStarted() {
    return started;
  }

  public ServiceAdapter getServiceAdapter(String messageType, String destinationId) {
    return adapterFactory.getServiceAdapter(messageType, destinationId);
  }

  ///////////////////////////////////////////////////////////////////////////
  // Starting/stopping.

  public void start() throws Exception {
    log.info("Starting Gravity...");
    synchronized (this) {
      if (!started) {
        adapterFactory = new AdapterFactory(this);
        internalStart();
        serverChannel = new ServerChannel(this, ServerChannel.class.getName(), null, null);

        if (gravityConfig.isUseUdp()) {
          ServiceLoader<UdpReceiverFactory> loader = ServiceLoader.load(UdpReceiverFactory.class);
          Iterator<UdpReceiverFactory> factories = loader.iterator();
          if (factories.hasNext()) {
            udpReceiverFactory = factories.next();
            udpReceiverFactory.setPort(gravityConfig.getUdpPort());
            udpReceiverFactory.setNio(gravityConfig.isUdpNio());
            udpReceiverFactory.setConnected(gravityConfig.isUdpConnected());
            udpReceiverFactory.setSendBufferSize(gravityConfig.getUdpSendBufferSize());
            udpReceiverFactory.start();
          } else log.warn("UDP receiver factory not found");
        }

        started = true;
      }
    }
    log.info("Gravity successfully started.");
  }

  protected void internalStart() {
    gravityPool = new GravityPool(gravityConfig);
    channelsTimer = new Timer();

    if (graniteConfig.isRegisterMBeans()) {
      try {
        ObjectName name =
            new ObjectName(
                "org.graniteds:type=Gravity,context=" + graniteConfig.getMBeanContextName());
        log.info("Registering MBean: %s", name);
        OpenMBean mBean = OpenMBean.createMBean(this);
        MBeanServerLocator.getInstance().register(mBean, name, true);
      } catch (Exception e) {
        log.error(
            e,
            "Could not register Gravity MBean for context: %s",
            graniteConfig.getMBeanContextName());
      }
    }
  }

  public void restart() throws Exception {
    synchronized (this) {
      stop();
      start();
    }
  }

  public void reconfigure(GravityConfig gravityConfig, GraniteConfig graniteConfig) {
    this.gravityConfig = gravityConfig;
    this.graniteConfig = graniteConfig;
    if (gravityPool != null) gravityPool.reconfigure(gravityConfig);
  }

  public void stop() throws Exception {
    stop(true);
  }

  public void stop(boolean now) throws Exception {
    log.info("Stopping Gravity (now=%s)...", now);
    synchronized (this) {
      if (adapterFactory != null) {
        try {
          adapterFactory.stopAll();
        } catch (Exception e) {
          log.error(e, "Error while stopping adapter factory");
        }
        adapterFactory = null;
      }

      if (serverChannel != null) {
        try {
          removeChannel(serverChannel.getId(), false);
        } catch (Exception e) {
          log.error(e, "Error while removing server channel: %s", serverChannel);
        }
        serverChannel = null;
      }

      if (channelsTimer != null) {
        try {
          channelsTimer.cancel();
        } catch (Exception e) {
          log.error(e, "Error while cancelling channels timer");
        }
        channelsTimer = null;
      }

      if (gravityPool != null) {
        try {
          if (now) gravityPool.shutdownNow();
          else gravityPool.shutdown();
        } catch (Exception e) {
          log.error(e, "Error while stopping thread pool");
        }
        gravityPool = null;
      }

      if (udpReceiverFactory != null) {
        try {
          udpReceiverFactory.stop();
        } catch (Exception e) {
          log.error(e, "Error while stopping udp receiver factory");
        }
        udpReceiverFactory = null;
      }

      started = false;
    }
    log.info("Gravity sucessfully stopped.");
  }

  ///////////////////////////////////////////////////////////////////////////
  // GravityMBean attributes implementation.

  public String getGravityFactoryName() {
    return gravityConfig.getGravityFactory();
  }

  public long getChannelIdleTimeoutMillis() {
    return gravityConfig.getChannelIdleTimeoutMillis();
  }

  public void setChannelIdleTimeoutMillis(long channelIdleTimeoutMillis) {
    gravityConfig.setChannelIdleTimeoutMillis(channelIdleTimeoutMillis);
  }

  public boolean isRetryOnError() {
    return gravityConfig.isRetryOnError();
  }

  public void setRetryOnError(boolean retryOnError) {
    gravityConfig.setRetryOnError(retryOnError);
  }

  public long getLongPollingTimeoutMillis() {
    return gravityConfig.getLongPollingTimeoutMillis();
  }

  public void setLongPollingTimeoutMillis(long longPollingTimeoutMillis) {
    gravityConfig.setLongPollingTimeoutMillis(longPollingTimeoutMillis);
  }

  public int getMaxMessagesQueuedPerChannel() {
    return gravityConfig.getMaxMessagesQueuedPerChannel();
  }

  public void setMaxMessagesQueuedPerChannel(int maxMessagesQueuedPerChannel) {
    gravityConfig.setMaxMessagesQueuedPerChannel(maxMessagesQueuedPerChannel);
  }

  public long getReconnectIntervalMillis() {
    return gravityConfig.getReconnectIntervalMillis();
  }

  public int getReconnectMaxAttempts() {
    return gravityConfig.getReconnectMaxAttempts();
  }

  public int getCorePoolSize() {
    if (gravityPool != null) return gravityPool.getCorePoolSize();
    return gravityConfig.getCorePoolSize();
  }

  public void setCorePoolSize(int corePoolSize) {
    gravityConfig.setCorePoolSize(corePoolSize);
    if (gravityPool != null) gravityPool.setCorePoolSize(corePoolSize);
  }

  public long getKeepAliveTimeMillis() {
    if (gravityPool != null) return gravityPool.getKeepAliveTimeMillis();
    return gravityConfig.getKeepAliveTimeMillis();
  }

  public void setKeepAliveTimeMillis(long keepAliveTimeMillis) {
    gravityConfig.setKeepAliveTimeMillis(keepAliveTimeMillis);
    if (gravityPool != null) gravityPool.setKeepAliveTimeMillis(keepAliveTimeMillis);
  }

  public int getMaximumPoolSize() {
    if (gravityPool != null) return gravityPool.getMaximumPoolSize();
    return gravityConfig.getMaximumPoolSize();
  }

  public void setMaximumPoolSize(int maximumPoolSize) {
    gravityConfig.setMaximumPoolSize(maximumPoolSize);
    if (gravityPool != null) gravityPool.setMaximumPoolSize(maximumPoolSize);
  }

  public int getQueueCapacity() {
    if (gravityPool != null) return gravityPool.getQueueCapacity();
    return gravityConfig.getQueueCapacity();
  }

  public int getQueueRemainingCapacity() {
    if (gravityPool != null) return gravityPool.getQueueRemainingCapacity();
    return gravityConfig.getQueueCapacity();
  }

  public int getQueueSize() {
    if (gravityPool != null) return gravityPool.getQueueSize();
    return 0;
  }

  public boolean hasUdpReceiverFactory() {
    return udpReceiverFactory != null;
  }

  public UdpReceiverFactory getUdpReceiverFactory() {
    return udpReceiverFactory;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Channel's operations.

  protected <C extends Channel> C createChannel(ChannelFactory<C> channelFactory, String clientId) {
    C channel = null;
    if (clientId != null) {
      channel = getChannel(channelFactory, clientId);
      if (channel != null) return channel;
    }

    String clientType = GraniteContext.getCurrentInstance().getClientType();
    channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType);
    TimeChannel<C> timeChannel = new TimeChannel<C>(channel);
    for (int i = 0; channels.putIfAbsent(channel.getId(), timeChannel) != null; i++) {
      if (i >= 10)
        throw new RuntimeException("Could not find random new clientId after 10 iterations");
      channel.destroy(false);
      channel = channelFactory.newChannel(UUIDUtil.randomUUID(), clientType);
      timeChannel = new TimeChannel<C>(channel);
    }

    String channelId = channel.getId();

    // Save channel id in distributed data (clustering).
    try {
      DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
      if (gdd != null) {
        log.debug("Saving channel id in distributed data: %s", channelId);
        gdd.addChannelId(channelId, channelFactory.getClass().getName(), clientType);
      }
    } catch (Exception e) {
      log.error(e, "Could not add channel id in distributed data: %s", channelId);
    }

    // Initialize timer task.
    access(channelId);

    return channel;
  }

  @SuppressWarnings("unchecked")
  public <C extends Channel> C getChannel(ChannelFactory<C> channelFactory, String clientId) {
    if (clientId == null) return null;

    TimeChannel<C> timeChannel = (TimeChannel<C>) channels.get(clientId);
    if (timeChannel == null) {
      // Look for existing channel id/subscriptions in distributed data (clustering).
      log.debug("Lookup channel %s in distributed data", clientId);
      try {
        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
        if (gdd != null && gdd.hasChannelId(clientId)) {
          log.debug("Found channel id in distributed data: %s", clientId);
          String channelFactoryClassName = gdd.getChannelFactoryClassName(clientId);
          String clientType = gdd.getChannelClientType(clientId);
          channelFactory =
              (ChannelFactory<C>)
                  TypeUtil.newInstance(
                      channelFactoryClassName, new Class<?>[] {Gravity.class}, new Object[] {this});
          C channel = channelFactory.newChannel(clientId, clientType);
          timeChannel = new TimeChannel<C>(channel);
          if (channels.putIfAbsent(clientId, timeChannel) == null) {
            for (CommandMessage subscription : gdd.getSubscriptions(clientId)) {
              log.debug("Resubscribing channel: %s - %s", clientId, subscription);
              handleSubscribeMessage(channelFactory, subscription, false);
            }
            access(clientId);
          }
        }
      } catch (Exception e) {
        log.error(
            e, "Could not recreate channel/subscriptions from distributed data: %s", clientId);
      }
    }

    return (timeChannel != null ? timeChannel.getChannel() : null);
  }

  public Channel removeChannel(String channelId, boolean timeout) {
    if (channelId == null) return null;

    // Remove existing channel id/subscriptions in distributed data (clustering).
    try {
      DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
      if (gdd != null) {
        log.debug("Removing channel id from distributed data: %s", channelId);
        gdd.removeChannelId(channelId);
      }
    } catch (Exception e) {
      log.error(e, "Could not remove channel id from distributed data: %s", channelId);
    }

    TimeChannel<?> timeChannel = channels.get(channelId);
    Channel channel = null;
    if (timeChannel != null) {
      try {
        if (timeChannel.getTimerTask() != null) timeChannel.getTimerTask().cancel();
      } catch (Exception e) {
        // Should never happen...
      }

      channel = timeChannel.getChannel();

      try {
        for (Subscription subscription : channel.getSubscriptions()) {
          try {
            Message message = subscription.getUnsubscribeMessage();
            handleMessage(channel.getFactory(), message, true);
          } catch (Exception e) {
            log.error(
                e,
                "Error while unsubscribing channel: %s from subscription: %s",
                channel,
                subscription);
          }
        }
      } finally {
        channels.remove(channelId);
        channel.destroy(timeout);
      }
    }
    return channel;
  }

  public boolean access(String channelId) {
    if (channelId != null) {
      TimeChannel<?> timeChannel = channels.get(channelId);
      if (timeChannel != null) {
        synchronized (timeChannel) {
          TimerTask timerTask = timeChannel.getTimerTask();
          if (timerTask != null) {
            log.debug("Canceling TimerTask: %s", timerTask);
            timerTask.cancel();
            timeChannel.setTimerTask(null);
          }

          timerTask = new ChannelTimerTask(this, channelId);
          timeChannel.setTimerTask(timerTask);

          long timeout = gravityConfig.getChannelIdleTimeoutMillis();
          log.debug("Scheduling TimerTask: %s for %s ms.", timerTask, timeout);
          channelsTimer.schedule(timerTask, timeout);
          return true;
        }
      }
    }
    return false;
  }

  public void execute(AsyncChannelRunner runner) {
    if (gravityPool == null) {
      runner.reset();
      throw new NullPointerException("Gravity not started or pool disabled");
    }
    gravityPool.execute(runner);
  }

  public boolean cancel(AsyncChannelRunner runner) {
    if (gravityPool == null) {
      runner.reset();
      throw new NullPointerException("Gravity not started or pool disabled");
    }
    return gravityPool.remove(runner);
  }

  ///////////////////////////////////////////////////////////////////////////
  // Incoming message handling.

  public Message handleMessage(final ChannelFactory<?> channelFactory, Message message) {
    return handleMessage(channelFactory, message, false);
  }

  public Message handleMessage(
      final ChannelFactory<?> channelFactory, final Message message, boolean skipInterceptor) {

    AMF3MessageInterceptor interceptor = null;
    if (!skipInterceptor)
      interceptor =
          GraniteContext.getCurrentInstance().getGraniteConfig().getAmf3MessageInterceptor();

    Message reply = null;
    boolean publish = false;

    try {
      if (interceptor != null) interceptor.before(message);

      if (message instanceof CommandMessage) {
        CommandMessage command = (CommandMessage) message;

        switch (command.getOperation()) {
          case CommandMessage.LOGIN_OPERATION:
          case CommandMessage.LOGOUT_OPERATION:
            return handleSecurityMessage(command);

          case CommandMessage.CLIENT_PING_OPERATION:
            return handlePingMessage(channelFactory, command);
          case CommandMessage.CONNECT_OPERATION:
            return handleConnectMessage(channelFactory, command);
          case CommandMessage.DISCONNECT_OPERATION:
            return handleDisconnectMessage(channelFactory, command);
          case CommandMessage.SUBSCRIBE_OPERATION:
            return handleSubscribeMessage(channelFactory, command);
          case CommandMessage.UNSUBSCRIBE_OPERATION:
            return handleUnsubscribeMessage(channelFactory, command);

          default:
            throw new UnsupportedOperationException("Unsupported command operation: " + command);
        }
      }

      reply = handlePublishMessage(channelFactory, (AsyncMessage) message);
      publish = true;
    } finally {
      if (interceptor != null) interceptor.after(message, reply);
    }

    if (reply != null) {
      GraniteContext context = GraniteContext.getCurrentInstance();
      if (context.getSessionId() != null) {
        reply.setHeader("org.granite.sessionId", context.getSessionId());
        if (publish
            && context instanceof ServletGraniteContext
            && ((ServletGraniteContext) context).getSession(false) != null) {
          long serverTime = new Date().getTime();
          ((ServletGraniteContext) context)
              .getSession()
              .setAttribute(GraniteContext.SESSION_LAST_ACCESSED_TIME_KEY, serverTime);
          reply.setHeader("org.granite.time", serverTime);
          reply.setHeader(
              "org.granite.sessionExp",
              ((ServletGraniteContext) context).getSession().getMaxInactiveInterval());
        }
      }
    }

    return reply;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Other Public API methods.

  public GraniteContext initThread(String sessionId, String clientType) {
    GraniteContext context = GraniteContext.getCurrentInstance();
    if (context == null)
      context =
          SimpleGraniteContext.createThreadInstance(
              graniteConfig, servicesConfig, sessionId, applicationMap, clientType);
    return context;
  }

  public void releaseThread() {
    GraniteContext.release();
  }

  public Message publishMessage(AsyncMessage message) {
    return publishMessage(serverChannel, message);
  }

  public Message publishMessage(Channel fromChannel, AsyncMessage message) {
    initThread(
        null, fromChannel != null ? fromChannel.getClientType() : serverChannel.getClientType());

    return handlePublishMessage(null, message, fromChannel != null ? fromChannel : serverChannel);
  }

  private Message handlePingMessage(ChannelFactory<?> channelFactory, CommandMessage message) {

    Channel channel = createChannel(channelFactory, (String) message.getClientId());

    AsyncMessage reply = new AcknowledgeMessage(message);
    reply.setClientId(channel.getId());
    Map<String, Object> advice = new HashMap<String, Object>();
    advice.put(RECONNECT_INTERVAL_MS_KEY, Long.valueOf(gravityConfig.getReconnectIntervalMillis()));
    advice.put(RECONNECT_MAX_ATTEMPTS_KEY, Long.valueOf(gravityConfig.getReconnectMaxAttempts()));
    advice.put(ENCODE_MESSAGE_BODY_KEY, Boolean.valueOf(gravityConfig.isEncodeMessageBody()));
    reply.setBody(advice);
    reply.setDestination(message.getDestination());

    log.debug("handshake.handle: reply=%s", reply);

    return reply;
  }

  private Message handleSecurityMessage(CommandMessage message) {
    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();

    Message response = null;

    if (!config.hasSecurityService())
      log.warn(
          "Ignored security operation (no security settings in granite-config.xml): %s", message);
    else if (!config.getSecurityService().acceptsContext())
      log.info(
          "Ignored security operation (security service does not handle this kind of granite context)",
          message);
    else {
      SecurityService securityService = config.getSecurityService();
      try {
        if (message.isLoginOperation())
          securityService.login(
              message.getBody(), (String) message.getHeader(Message.CREDENTIALS_CHARSET_HEADER));
        else securityService.logout();
      } catch (Exception e) {
        if (e instanceof SecurityServiceException)
          log.debug(e, "Could not process security operation: %s", message);
        else log.error(e, "Could not process security operation: %s", message);
        response = new ErrorMessage(message, e, true);
      }
    }

    if (response == null) {
      response = new AcknowledgeMessage(message, true);
      // For SDK 2.0.1_Hotfix2.
      if (message.isSecurityOperation()) response.setBody("success");
    }

    return response;
  }

  private Message handleConnectMessage(
      final ChannelFactory<?> channelFactory, CommandMessage message) {
    Channel client = getChannel(channelFactory, (String) message.getClientId());

    if (client == null) return handleUnknownClientMessage(message);

    return null;
  }

  private Message handleDisconnectMessage(
      final ChannelFactory<?> channelFactory, CommandMessage message) {
    Channel client = getChannel(channelFactory, (String) message.getClientId());
    if (client == null) return handleUnknownClientMessage(message);

    removeChannel(client.getId(), false);

    AcknowledgeMessage reply = new AcknowledgeMessage(message);
    reply.setDestination(message.getDestination());
    reply.setClientId(client.getId());
    return reply;
  }

  private Message handleSubscribeMessage(
      final ChannelFactory<?> channelFactory, final CommandMessage message) {
    return handleSubscribeMessage(channelFactory, message, true);
  }

  private Message handleSubscribeMessage(
      final ChannelFactory<?> channelFactory,
      final CommandMessage message,
      final boolean saveMessageInSession) {

    final GraniteContext context = GraniteContext.getCurrentInstance();

    // Get and check destination.
    final Destination destination =
        context
            .getServicesConfig()
            .findDestinationById(message.getMessageRefType(), message.getDestination());

    if (destination == null) return getInvalidDestinationError(message);

    GravityInvocationContext invocationContext =
        new GravityInvocationContext(message, destination) {
          @Override
          public Object invoke() throws Exception {
            // Subscribe...
            Channel channel = getChannel(channelFactory, (String) message.getClientId());
            if (channel == null) return handleUnknownClientMessage(message);

            String subscriptionId =
                (String) message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
            if (subscriptionId == null) {
              subscriptionId = UUIDUtil.randomUUID();
              message.setHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER, subscriptionId);
            }

            DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
            if (gdd != null) {
              if (!gdd.hasChannelId(channel.getId())) {
                gdd.addChannelId(
                    channel.getId(),
                    channel.getFactory().getClass().getName(),
                    context.getClientType());
                log.debug("Stored channel %s in distributed data", channel.getId());
              }

              if (Boolean.TRUE
                  .toString()
                  .equals(destination.getProperties().get("session-selector"))) {
                String selector = gdd.getDestinationSelector(destination.getId());
                log.debug("Session selector found: %s", selector);
                if (selector != null) message.setHeader(CommandMessage.SELECTOR_HEADER, selector);
              }
            }

            ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);

            AsyncMessage reply = (AsyncMessage) adapter.manage(channel, message);

            postManage(channel);

            if (saveMessageInSession && !(reply instanceof ErrorMessage)) {
              // Save subscription message in distributed data (clustering).
              try {
                if (gdd != null) {
                  log.debug(
                      "Saving new subscription message for channel: %s - %s",
                      channel.getId(), message);
                  gdd.addSubcription(channel.getId(), message);
                }
              } catch (Exception e) {
                log.error(
                    e,
                    "Could not add subscription in distributed data: %s - %s",
                    channel.getId(),
                    subscriptionId);
              }
            }

            reply.setDestination(message.getDestination());
            reply.setClientId(channel.getId());
            reply.getHeaders().putAll(message.getHeaders());

            if (gdd != null && message.getDestination() != null) {
              gdd.setDestinationClientId(message.getDestination(), channel.getId());
              gdd.setDestinationSubscriptionId(message.getDestination(), subscriptionId);
            }

            return reply;
          }
        };

    // Check security 1 (destination).
    if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
      try {
        ((GravityDestinationSecurizer) destination.getSecurizer()).canSubscribe(invocationContext);
      } catch (Exception e) {
        return new ErrorMessage(message, e);
      }
    }

    // Check security 2 (security service).
    GraniteConfig config = context.getGraniteConfig();
    try {
      if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
        return (Message) config.getSecurityService().authorize(invocationContext);

      return (Message) invocationContext.invoke();
    } catch (Exception e) {
      return new ErrorMessage(message, e, true);
    }
  }

  private Message handleUnsubscribeMessage(
      final ChannelFactory<?> channelFactory, CommandMessage message) {
    Channel channel = getChannel(channelFactory, (String) message.getClientId());
    if (channel == null) return handleUnknownClientMessage(message);

    AsyncMessage reply = null;

    ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);

    reply = (AcknowledgeMessage) adapter.manage(channel, message);

    postManage(channel);

    if (!(reply instanceof ErrorMessage)) {
      // Remove subscription message in distributed data (clustering).
      try {
        DistributedData gdd = graniteConfig.getDistributedDataFactory().getInstance();
        if (gdd != null) {
          String subscriptionId =
              (String) message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER);
          log.debug(
              "Removing subscription message from channel info: %s - %s",
              channel.getId(), subscriptionId);
          gdd.removeSubcription(channel.getId(), subscriptionId);
        }
      } catch (Exception e) {
        log.error(
            e,
            "Could not remove subscription from distributed data: %s - %s",
            channel.getId(),
            message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER));
      }
    }

    reply.setDestination(message.getDestination());
    reply.setClientId(channel.getId());
    reply.getHeaders().putAll(message.getHeaders());

    return reply;
  }

  protected void postManage(Channel channel) {}

  private Message handlePublishMessage(
      final ChannelFactory<?> channelFactory, final AsyncMessage message) {
    return handlePublishMessage(channelFactory, message, null);
  }

  private Message handlePublishMessage(
      final ChannelFactory<?> channelFactory, final AsyncMessage message, final Channel channel) {

    GraniteContext context = GraniteContext.getCurrentInstance();

    // Get and check destination.
    Destination destination =
        context
            .getServicesConfig()
            .findDestinationById(message.getClass().getName(), message.getDestination());

    if (destination == null) return getInvalidDestinationError(message);

    if (message.getMessageId() == null) message.setMessageId(UUIDUtil.randomUUID());
    message.setTimestamp(System.currentTimeMillis());
    if (channel != null) message.setClientId(channel.getId());

    GravityInvocationContext invocationContext =
        new GravityInvocationContext(message, destination) {
          @Override
          public Object invoke() throws Exception {
            // Publish...
            Channel fromChannel = channel;
            if (fromChannel == null)
              fromChannel = getChannel(channelFactory, (String) message.getClientId());
            if (fromChannel == null) return handleUnknownClientMessage(message);

            ServiceAdapter adapter = adapterFactory.getServiceAdapter(message);

            AsyncMessage reply = (AsyncMessage) adapter.invoke(fromChannel, message);

            reply.setDestination(message.getDestination());
            reply.setClientId(fromChannel.getId());

            return reply;
          }
        };

    // Check security 1 (destination).
    if (destination.getSecurizer() instanceof GravityDestinationSecurizer) {
      try {
        ((GravityDestinationSecurizer) destination.getSecurizer()).canPublish(invocationContext);
      } catch (Exception e) {
        return new ErrorMessage(message, e, true);
      }
    }

    // Check security 2 (security service).
    GraniteConfig config = context.getGraniteConfig();
    try {
      if (config.hasSecurityService() && config.getSecurityService().acceptsContext())
        return (Message) config.getSecurityService().authorize(invocationContext);

      return (Message) invocationContext.invoke();
    } catch (Exception e) {
      return new ErrorMessage(message, e, true);
    }
  }

  private Message handleUnknownClientMessage(Message message) {
    ErrorMessage reply = new ErrorMessage(message, true);
    reply.setFaultCode("Server.Call.UnknownClient");
    reply.setFaultString("Unknown client");
    return reply;
  }

  ///////////////////////////////////////////////////////////////////////////
  // Utilities.

  private ErrorMessage getInvalidDestinationError(Message message) {

    String messageType = message.getClass().getName();
    if (message instanceof CommandMessage)
      messageType += '[' + ((CommandMessage) message).getMessageRefType() + ']';

    ErrorMessage reply = new ErrorMessage(message, true);
    reply.setFaultCode("Server.Messaging.InvalidDestination");
    reply.setFaultString(
        "No configured destination for id: "
            + message.getDestination()
            + " and message type: "
            + messageType);
    return reply;
  }

  private static class ServerChannel extends AbstractChannel implements Serializable {

    private static final long serialVersionUID = 1L;

    public ServerChannel(
        Gravity gravity,
        String channelId,
        ChannelFactory<ServerChannel> factory,
        String clientType) {
      super(gravity, channelId, factory, clientType);
    }

    @Override
    public Gravity getGravity() {
      return gravity;
    }

    public void close() {}

    @Override
    public void receive(AsyncMessage message) throws MessageReceivingException {}

    @Override
    protected boolean hasAsyncHttpContext() {
      return false;
    }

    @Override
    protected AsyncHttpContext acquireAsyncHttpContext() {
      return null;
    }

    @Override
    protected void releaseAsyncHttpContext(AsyncHttpContext context) {}
  }
}
/**
 * @author Bouiaw
 * @author wdrai
 */
public class SpringSecurityService extends AbstractSecurityService {

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

  private static final String FILTER_APPLIED =
      "__spring_security_filterSecurityInterceptor_filterApplied";

  private AbstractSpringSecurityInterceptor securityInterceptor = null;

  public SpringSecurityService() {
    log.debug("Starting Spring Security Service!");
  }

  public void configure(Map<String, String> params) {
    log.debug("Configuring with parameters (NOOP) %s: ", params);
  }

  public void setSecurityInterceptor(AbstractSpringSecurityInterceptor securityInterceptor) {
    this.securityInterceptor = securityInterceptor;
  }

  public Principal login(Object credentials, String charset) {
    List<String> decodedCredentials = Arrays.asList(decodeBase64Credentials(credentials, charset));

    HttpGraniteContext context = (HttpGraniteContext) GraniteContext.getCurrentInstance();
    HttpServletRequest httpRequest = context.getRequest();

    String user = decodedCredentials.get(0);
    String password = decodedCredentials.get(1);
    Authentication auth = new UsernamePasswordAuthenticationToken(user, password);
    Principal principal = null;

    ApplicationContext ctx =
        WebApplicationContextUtils.getWebApplicationContext(
            httpRequest.getSession().getServletContext());
    if (ctx != null) {
      AbstractAuthenticationManager authenticationManager =
          BeanFactoryUtils.beanOfTypeIncludingAncestors(ctx, AbstractAuthenticationManager.class);
      try {
        Authentication authentication = authenticationManager.authenticate(auth);
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);
        principal = authentication;
        SecurityContextHolder.setContext(securityContext);
        saveSecurityContextInSession(securityContext, 0);

        endLogin(credentials, charset);
      } catch (AuthenticationException e) {
        handleAuthenticationExceptions(e);
      }
    }

    log.debug("User %s logged in", user);

    return principal;
  }

  protected void handleAuthenticationExceptions(AuthenticationException e) {
    if (e instanceof BadCredentialsException || e instanceof UsernameNotFoundException)
      throw SecurityServiceException.newInvalidCredentialsException(e.getMessage());

    throw SecurityServiceException.newAuthenticationFailedException(e.getMessage());
  }

  public Object authorize(AbstractSecurityContext context) throws Exception {
    log.debug("Authorize: %s", context);
    log.debug(
        "Is %s secured? %b",
        context.getDestination().getId(), context.getDestination().isSecured());

    startAuthorization(context);

    HttpGraniteContext graniteContext = (HttpGraniteContext) GraniteContext.getCurrentInstance();

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    SecurityContext securityContextBefore = null;
    int securityContextHashBefore = 0;
    if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
      securityContextBefore = loadSecurityContextFromSession();
      if (securityContextBefore == null) securityContextBefore = SecurityContextHolder.getContext();
      else securityContextHashBefore = securityContextBefore.hashCode();
      SecurityContextHolder.setContext(securityContextBefore);
      authentication = securityContextBefore.getAuthentication();
    }

    if (context.getDestination().isSecured()) {
      if (!isAuthenticated(authentication)
          || authentication instanceof AnonymousAuthenticationToken) {
        log.debug("Is not authenticated!");
        throw SecurityServiceException.newNotLoggedInException("User not logged in");
      }
      if (!userCanAccessService(context, authentication)) {
        log.debug("Access denied for: %s", authentication.getName());
        throw SecurityServiceException.newAccessDeniedException("User not in required role");
      }
    }

    try {
      Object returnedObject =
          securityInterceptor != null
              ? securityInterceptor.invoke(context)
              : endAuthorization(context);

      return returnedObject;
    } catch (AccessDeniedException e) {
      throw SecurityServiceException.newAccessDeniedException(e.getMessage());
    } catch (InvocationTargetException e) {
      handleAuthorizationExceptions(e);
      throw e;
    } finally {
      if (graniteContext.getRequest().getAttribute(FILTER_APPLIED) == null) {
        // Do this only when not already filtered by Spring Security
        SecurityContext securityContextAfter = SecurityContextHolder.getContext();
        SecurityContextHolder.clearContext();
        saveSecurityContextInSession(securityContextAfter, securityContextHashBefore);
      }
    }
  }

  public void logout() {
    HttpGraniteContext context = (HttpGraniteContext) GraniteContext.getCurrentInstance();
    HttpSession session = context.getSession(false);
    if (session != null
        && session.getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY)
            != null) session.invalidate();

    SecurityContextHolder.clearContext();
  }

  protected boolean isUserInRole(Authentication authentication, String role) {
    for (GrantedAuthority ga : authentication.getAuthorities()) {
      if (ga.getAuthority().matches(role)) return true;
    }
    return false;
  }

  protected boolean isAuthenticated(Authentication authentication) {
    return authentication != null && authentication.isAuthenticated();
  }

  protected boolean userCanAccessService(
      AbstractSecurityContext context, Authentication authentication) {
    log.debug("Is authenticated as: %s", authentication.getName());

    for (String role : context.getDestination().getRoles()) {
      if (isUserInRole(authentication, role)) {
        log.debug("Allowed access to %s in role %s", authentication.getName(), role);
        return true;
      }
      log.debug("Access denied for %s not in role %s", authentication.getName(), role);
    }
    return false;
  }

  protected SecurityContext loadSecurityContextFromSession() {
    HttpGraniteContext context = (HttpGraniteContext) GraniteContext.getCurrentInstance();
    HttpServletRequest request = context.getRequest();
    return (SecurityContext)
        request
            .getSession()
            .getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
  }

  protected void saveSecurityContextInSession(
      SecurityContext securityContext, int securityContextHashBefore) {
    if (securityContext.hashCode() != securityContextHashBefore
        && !(securityContext.getAuthentication() instanceof AnonymousAuthenticationToken)) {
      HttpGraniteContext context = (HttpGraniteContext) GraniteContext.getCurrentInstance();
      HttpServletRequest request = context.getRequest();
      request
          .getSession()
          .setAttribute(
              HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY, securityContext);
    }
  }

  protected void handleAuthorizationExceptions(InvocationTargetException e) {
    for (Throwable t = e; t != null; t = t.getCause()) {
      // Don't create a dependency to javax.ejb in SecurityService...
      if (t instanceof SecurityException
          || t instanceof AccessDeniedException
          || "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
        throw SecurityServiceException.newAccessDeniedException(t.getMessage());
    }
  }
}
/** @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 String sessionId;
  protected final String clientType;
  protected final GravityInternal gravity;
  protected final ChannelFactory<? extends Channel> factory;
  // protected final ServletConfig servletConfig;

  protected final ConcurrentMap<String, Subscription> subscriptions =
      new ConcurrentHashMap<String, Subscription>();
  protected Principal userPrincipal;

  protected LinkedList<AsyncPublishedMessage> publishedQueue =
      new LinkedList<AsyncPublishedMessage>();
  protected final Lock publishedQueueLock = new ReentrantLock();

  protected LinkedList<AsyncMessage> receivedQueue = new LinkedList<AsyncMessage>();
  protected final Lock receivedQueueLock = new ReentrantLock();

  protected final AsyncPublisher publisher;
  protected final AsyncReceiver receiver;

  protected UdpReceiver udpReceiver = null;

  ///////////////////////////////////////////////////////////////////////////
  // Constructor.

  protected AbstractChannel(
      GravityInternal gravity,
      String id,
      ChannelFactory<? extends Channel> factory,
      String clientType) {
    if (id == null) throw new NullPointerException("id cannot be null");

    this.id = id;
    GraniteContext graniteContext = GraniteContext.getCurrentInstance();
    this.clientType = clientType;
    this.sessionId = graniteContext != null ? graniteContext.getSessionId() : null;
    this.gravity = gravity;
    this.factory = factory;

    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 String getClientType() {
    return clientType;
  }

  public ChannelFactory<? extends Channel> getFactory() {
    return factory;
  }

  public GravityInternal getGravity() {
    return gravity;
  }

  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<Subscription> getSubscriptions() {
    return subscriptions.values();
  }

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

  public boolean isConnected() {
    return true;
  }

  public boolean isAuthenticated() {
    return userPrincipal != null;
  }

  public Principal getUserPrincipal() {
    return userPrincipal;
  }

  public void setUserPrincipal(Principal principal) {
    this.userPrincipal = principal;

    gravity.notifyAuthenticated(this, principal);
  }

  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<AsyncPublishedMessage> publishedCopy = null;

    publishedQueueLock.lock();
    try {
      if (publishedQueue.isEmpty()) return false;
      publishedCopy = publishedQueue;
      publishedQueue = new LinkedList<AsyncPublishedMessage>();
    } 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");

    GravityInternal gravity = getGravity();

    if (udpReceiver != null) {
      if (udpReceiver.isClosed()) return;

      try {
        udpReceiver.receive(message);
      } catch (MessageReceivingException e) {
        if (e.getCause() instanceof SocketException) {
          log.debug(e, "Closing unreachable UDP channel %s", getId());
          udpReceiver.close(false);
        } else log.error(e, "Cannot access UDP channel %s", getId());
      }
      return;
    }

    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);

      log.debug(
          "Channel %s queue message %s for client %s",
          getId(), message.getMessageId(), message.getClientId());
      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);
  }

  protected void createUdpReceiver(UdpReceiverFactory factory, AsyncHttpContext asyncHttpContext) {
    OutputStream os = null;
    try {
      Message connectMessage = asyncHttpContext.getConnectMessage();

      if (udpReceiver == null || udpReceiver.isClosed())
        udpReceiver = factory.newReceiver(this, asyncHttpContext.getRequest(), connectMessage);

      AsyncMessage reply = udpReceiver.acknowledge(connectMessage);

      HttpServletRequest request = asyncHttpContext.getRequest();
      HttpServletResponse response = asyncHttpContext.getResponse();

      GraniteContext context =
          HttpGraniteContext.createThreadIntance(
              gravity.getGraniteConfig(), gravity.getServicesConfig(), null, request, response);
      ((AMFContextImpl) context.getAMFContext())
          .setCurrentAmf3Message(asyncHttpContext.getConnectMessage());

      GravityServletUtil.serialize(
          gravity,
          response,
          new AsyncMessage[] {reply},
          ContentType.forMimeType(request.getContentType()));
    } catch (ServletException e) {
      log.error(e, "Could not send UDP connect acknowledgement to channel: %s", this);
    } catch (IOException e) {
      log.error(e, "Could not send UDP connect acknowledgement to channel: %s", this);
    } finally {
      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 {
        releaseAsyncHttpContext(asyncHttpContext);
      }
    }
  }

  public boolean runReceived(AsyncHttpContext asyncHttpContext) {

    GravityInternal gravity = getGravity();

    if (asyncHttpContext != null && gravity.hasUdpReceiverFactory()) {
      UdpReceiverFactory factory = gravity.getUdpReceiverFactory();

      if (factory.isUdpConnectRequest(asyncHttpContext.getConnectMessage())) {
        createUdpReceiver(factory, asyncHttpContext);
        return true;
      }

      if (udpReceiver != null) {
        if (!udpReceiver.isClosed()) udpReceiver.close(false);
        udpReceiver = null;
      }
    }

    boolean httpAsParam = (asyncHttpContext != null);
    LinkedList<AsyncMessage> 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<AsyncMessage>();
      } 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)
      GraniteContext context =
          HttpGraniteContext.createThreadIntance(
              gravity.getGraniteConfig(), gravity.getServicesConfig(), null, request, response);
      ((AMFContextImpl) context.getAMFContext())
          .setCurrentAmf3Message(asyncHttpContext.getConnectMessage());

      // Write messages to response output stream.
      GravityServletUtil.serialize(
          gravity, response, messagesArray, ContentType.forMimeType(request.getContentType()));

      return true; // Messages were delivered, http context isn't valid anymore.
    } catch (ServletException e) {
      log.error(e, "Configuration error for channel: %s", getId());

      return true;
    } catch (IOException e) {
      log.warn(e, "Could not send messages to channel: %s (retrying later)", getId());

      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)",
                getId(), 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 final void destroy() {
    destroy(false);
  }

  public void destroy(boolean timeout) {
    try {
      GravityInternal gravity = getGravity();
      gravity.cancel(publisher);
      gravity.cancel(receiver);

      subscriptions.clear();
    } finally {
      if (udpReceiver != null) {
        if (!udpReceiver.isClosed()) udpReceiver.close(timeout);
        udpReceiver = null;
      }
    }
  }

  ///////////////////////////////////////////////////////////////////////////
  // Protected utilities.

  protected boolean queueReceiver() {
    if (hasReceivedMessage()) {
      receiver.queue(getGravity());
      return true;
    }
    return false;
  }

  ///////////////////////////////////////////////////////////////////////////
  // 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() + "}";
  }
}
/**
 * @author <a href="mailto:[email protected]">[email protected]</a>
 * @since 1.1.0
 */
public class AMFServiceAdaptor extends HttpServlet {

  private static final long serialVersionUID = 4777538296260511097L;
  private static final Logger log = Logger.getLogger(AMFServiceAdaptor.class);

  private GraniteConfig graniteConfig = null;
  private ServicesConfig servicesConfig = null;
  BundleContext context;

  public AMFServiceAdaptor(BundleContext context) {
    this.context = context;
  }
  /*
   * (non-Javadoc)
   * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
   */
  @Override
  public void init(ServletConfig config) {
    try {
      super.init(config);
      Configuration configuration = Activator.getConfigurationService();
      getServletContext()
          .setAttribute(ServletGraniteConfig.GRANITE_CONFIG_CONFIGURATION_KEY, configuration);
      graniteConfig = ServletGraniteConfig.loadConfig(getServletContext());
      servicesConfig = ServletServicesConfig.loadConfig(getServletContext());

      // register EventHandler ServicesConfig handle Add or Remove dataservice
      Dictionary<String, Object> properties = new Hashtable<String, Object>();
      String[] topics =
          new String[] {
            OSGIConstants.TOPIC_GDS_ADD_SERVICE, OSGIConstants.TOPIC_GDS_REMOVE_SERVICE
          };
      properties.put(EventConstants.EVENT_TOPIC, topics);
      context.registerService(
          EventHandler.class.getName(), new ServiceEventHandler(servicesConfig), properties);

    } catch (ServletException e) {
      log.error(e, "Could initialize OSGi service adaptor");
    }
  }

  public ServicesConfig getServicesConfig() {
    return servicesConfig;
  }
  /*
   * (non-Javadoc)
   * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    if (log.isInfoEnabled())
      try {
        GraniteContext context =
            HttpGraniteContext.createThreadIntance(
                graniteConfig, servicesConfig, getServletContext(), request, response);
        if (context == null) throw new ServletException("GraniteContext not Initialized!!");

        // AMFContextImpl amf = (AMFContextImpl) context.getAMFContext();
        // Phase1 Deserializing AMF0 request
        if (log.isInfoEnabled())
          log.info(">>>>> Deserializing AMF0 request from..." + request.getRequestURI());
        AMF0Deserializer deserializer =
            new AMF0Deserializer(new DataInputStream(request.getInputStream()));
        AMF0Message amf0Request = deserializer.getAMFMessage();

        // Phase2 Processing AMF0 request
        if (log.isInfoEnabled()) log.info(">>>>> Processing AMF0 request: " + amf0Request);
        AMF0Message amf0Response = AMF0MessageProcessor.process(amf0Request);
        if (log.isInfoEnabled()) log.info("<<<<< Returning AMF0 response: " + amf0Response);

        // Phase3 Send back response to the client
        response.setContentType(AMF0Message.CONTENT_TYPE);
        AMF0Serializer serializer =
            new AMF0Serializer(new DataOutputStream(response.getOutputStream()));
        serializer.serializeMessage(amf0Response);
        if (log.isInfoEnabled()) log.info("...End of Processing AMF Request......");
      } catch (Exception e) {
        log.error(e, "Could not handle AMF request");
        throw new ServletException(e);
      }
  }
}
/** @author Franck WOLFF */
public class StandardRemoteAliasScanner implements RemoteAliasScanner {

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

  @Override
  public Set<Class<?>> scan(Set<String> packageNames) {
    Set<Class<?>> classes = new HashSet<Class<?>>();

    Scanner scanner =
        ScannerFactory.createScanner(new MessagingScannedItemHandler(packageNames, classes), null);
    try {
      scanner.scan();
    } catch (Exception e) {
      log.error(e, "Could not scan classpath for @RemoteAlias");
    }

    return classes;
  }

  class MessagingScannedItemHandler implements ScannedItemHandler {

    final String[] packageNames;
    final Set<Class<?>> classes;

    MessagingScannedItemHandler(Set<String> packageNames, Set<Class<?>> classes) {
      this.packageNames = new String[packageNames.size()];
      int i = 0;
      for (String packageName : packageNames)
        this.packageNames[i++] = packageName.replace('.', '/') + '/';

      this.classes = classes;
    }

    @Override
    public boolean handleMarkerItem(ScannedItem item) {
      return false;
    }

    @Override
    public void handleScannedItem(ScannedItem item) {
      if ("class".equals(item.getExtension())) {
        boolean scan = false;

        String path = item.getRelativePath();
        for (String packageName : packageNames) {
          if (path.startsWith(packageName)) {
            scan = true;
            break;
          }
        }

        if (scan) {
          try {
            Class<?> cls = item.loadAsClass();
            RemoteAlias alias = cls.getAnnotation(RemoteAlias.class);
            if (alias != null) classes.add(cls);
          } catch (ClassFormatError e) {
          } catch (ClassNotFoundException e) {
          } catch (IOException e) {
            log.error(e, "Could not load class: %s", item);
          }
        }
      }
    }
  }
}
/**
 * @author Venkat DANDA
 * @author Cameron INGRAM
 *     <p>Update services-config.xml to use the seam service exception handler <factory
 *     id="tideSeamFactory" class="org.granite.tide.seam.SeamServiceFactory" > <properties>
 *     <service-exception-handler>org.granite.tide.seam.SeamServiceExceptionHandler</service-exception-handler>
 *     </properties> </factory>
 */
public class ExtendedServiceExceptionHandler extends DefaultServiceExceptionHandler {

  private static final long serialVersionUID = -1L;
  private static final Logger log = Logger.getLogger(ExtendedServiceExceptionHandler.class);

  public static final Class<?> JAVAX_EJB_EXCEPTION;

  static {
    Class<?> exception = null;
    try {
      exception =
          Thread.currentThread().getContextClassLoader().loadClass("javax.ejb.EJBException");
    } catch (Exception e) {
    }
    JAVAX_EJB_EXCEPTION = exception;
  }

  public ExtendedServiceExceptionHandler() {
    this(true);
  }

  public ExtendedServiceExceptionHandler(boolean logException) {
    super(logException);
  }

  @Override
  protected ServiceException getServiceException(
      Message request, Destination destination, String method, Throwable t) {
    if (t == null) throw new NullPointerException("Parameter t cannot be null");

    Map<String, Object> extendedData = new HashMap<String, Object>();

    if (t instanceof ServiceException) {
      ((ServiceException) t).getExtendedData().putAll(extendedData);
      return (ServiceException) t;
    }

    List<Throwable> causes = new ArrayList<Throwable>();
    for (Throwable cause = t; cause != null; cause = getCause(cause)) causes.add(cause);

    String detail =
        "\n"
            + "- destination: "
            + (destination != null ? destination.getId() : "")
            + "\n"
            + "- method: "
            + method
            + "\n"
            + "- exception: "
            + t.toString()
            + "\n";

    for (int i = causes.size() - 1; i >= 0; i--) {
      Throwable cause = causes.get(i);
      for (ExceptionConverter ec :
          GraniteContext.getCurrentInstance().getGraniteConfig().getExceptionConverters()) {
        if (ec.accepts(cause, t)) return ec.convert(cause, detail, extendedData);
      }
    }

    if (getLogException()) log.error(t, "Could not process remoting message: %s", request);

    // Default exception handler
    ServiceException se =
        new ServiceException(
            t.getClass().getSimpleName() + ".Call.Failed", t.getMessage(), detail, t);
    se.getExtendedData().putAll(extendedData);
    return se;
  }

  public static Throwable getCause(Throwable t) {
    Throwable cause = null;
    try {
      if (JAVAX_EJB_EXCEPTION != null && JAVAX_EJB_EXCEPTION.isInstance(t)) {
        Method m = JAVAX_EJB_EXCEPTION.getMethod("getCausedByException");
        cause = (Throwable) m.invoke(t);
      } else if (t instanceof ServletException) cause = ((ServletException) t).getRootCause();
      else cause = t.getCause();
    } catch (Exception x) {
      return null;
    }
    return cause == t ? null : (Throwable) cause;
  }
}
/** @author William DRAI */
public class EjbServiceContext extends TideServiceContext {

  private static final long serialVersionUID = 1L;

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

  public static final String CAPITALIZED_DESTINATION_ID = "{capitalized.component.name}";
  public static final String DESTINATION_ID = "{component.name}";

  private final transient Map<String, EjbComponent> ejbLookupCache =
      new ConcurrentHashMap<String, EjbComponent>();
  private final Set<String> remoteObservers = new HashSet<String>();

  private final String lookup;

  private final EjbIdentity identity;

  private String entityManagerFactoryJndiName = null;
  private String entityManagerJndiName = null;

  public EjbServiceContext() throws ServiceException {
    super();
    lookup = "";
    identity = new EjbIdentity();
  }

  public EjbServiceContext(String lookup) throws ServiceException {
    super();
    this.lookup = lookup;
    identity = new EjbIdentity();
  }

  @Override
  protected AsyncPublisher getAsyncPublisher() {
    return null;
  }

  public void setEntityManagerFactoryJndiName(String entityManagerFactoryJndiName) {
    this.entityManagerFactoryJndiName = entityManagerFactoryJndiName;
  }

  public void setEntityManagerJndiName(String entityManagerJndiName) {
    this.entityManagerJndiName = entityManagerJndiName;
  }

  /**
   * Create a TidePersistenceManager
   *
   * @param create create if not existent (can be false for use in entity merge)
   * @return a TidePersistenceManager
   */
  @Override
  protected TidePersistenceManager getTidePersistenceManager(boolean create) {
    if (!create) return null;

    EntityManager em = getEntityManager();
    if (em == null) return null;

    return new JPAPersistenceManager(em);
  }

  /**
   * Find the entity manager using the jndi names stored in the bean.
   *
   * @return The found entity manager
   */
  private EntityManager getEntityManager() {
    try {
      InitialContext jndiContext = new InitialContext();

      if (entityManagerFactoryJndiName != null) {
        EntityManagerFactory factory =
            (EntityManagerFactory) jndiContext.lookup(entityManagerFactoryJndiName);
        return factory.createEntityManager();
      } else if (entityManagerJndiName != null) {
        return (EntityManager) jndiContext.lookup(entityManagerJndiName);
      }
    } catch (NamingException e) {
      if (entityManagerFactoryJndiName != null)
        throw new RuntimeException(
            "Unable to find a EntityManagerFactory  for jndiName " + entityManagerFactoryJndiName);
      else if (entityManagerJndiName != null)
        throw new RuntimeException(
            "Unable to find a EntityManager for jndiName " + entityManagerJndiName);
    }

    return null;
  }

  public Object callComponent(Method method, Object... args) throws Exception {
    String name = method.getDeclaringClass().getSimpleName();
    name = name.substring(0, 1).toLowerCase() + name.substring(1);
    if (name.endsWith("Bean")) name = name.substring(0, name.length() - "Bean".length());
    Object invokee = findComponent(name, null);
    method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
    return method.invoke(invokee, args);
  }

  public Set<String> getRemoteObservers() {
    return remoteObservers;
  }

  /* (non-Javadoc)
   * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponent(java.lang.String)
   */
  @Override
  public Object findComponent(String componentName, Class<?> componentClass) {
    if ("identity".equals(componentName)) return identity;

    EjbComponent component = ejbLookupCache.get(componentName);
    if (component != null) return component.ejbInstance;

    // Compute EJB JNDI binding.
    String name = componentName;
    if (lookup != null) {
      name = lookup;
      if (lookup.contains(CAPITALIZED_DESTINATION_ID))
        name = lookup.replace(CAPITALIZED_DESTINATION_ID, capitalize(componentName));
      if (lookup.contains(DESTINATION_ID)) name = lookup.replace(DESTINATION_ID, componentName);
    }

    InitialContext ic = null;
    try {
      ic = new InitialContext();
    } catch (Exception e) {
      throw new ServiceException("Could not get InitialContext", e);
    }

    log.debug(">> New EjbServiceInvoker looking up: %s", name);

    try {
      component = new EjbComponent();
      component.ejbInstance = ic.lookup(name);
      component.ejbClasses = new HashSet<Class<?>>();
      Class<?> scannedClass = null;
      EjbScannedItemHandler itemHandler = EjbScannedItemHandler.instance();
      for (Class<?> i : component.ejbInstance.getClass().getInterfaces()) {
        if (itemHandler.getScannedClasses().containsKey(i)) {
          scannedClass = itemHandler.getScannedClasses().get(i);
          break;
        }
      }
      if (scannedClass == null)
        scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass());
      // GDS-768: handle of proxied no-interface EJBs in GlassFish v3
      if (scannedClass == null && component.ejbInstance.getClass().getSuperclass() != null)
        scannedClass =
            itemHandler.getScannedClasses().get(component.ejbInstance.getClass().getSuperclass());

      if (scannedClass != null) {
        component.ejbClasses.add(scannedClass);
        for (Map.Entry<Class<?>, Class<?>> me : itemHandler.getScannedClasses().entrySet()) {
          if (me.getValue().equals(scannedClass)) component.ejbClasses.add(me.getKey());
        }
        component.ejbMetadata =
            new EjbServiceMetadata(scannedClass, component.ejbInstance.getClass());
      } else
        log.warn(
            "Ejb "
                + componentName
                + " was not scanned: remove method will not be called if it is a Stateful bean. Add META-INF/services-config.properties if needed.");

      ejbLookupCache.put(componentName, component);

      return component.ejbInstance;
    } catch (NamingException e) {
      log.error("EJB not found " + name + ": " + e.getMessage());
      throw new ServiceException("Could not lookup for: " + name, e);
    }
  }

  /* (non-Javadoc)
   * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponentClass(java.lang.String)
   */
  @Override
  public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) {
    if ("identity".equals(componentName)) {
      Set<Class<?>> classes = new HashSet<Class<?>>(1);
      classes.add(EjbIdentity.class);
      return classes;
    }

    EjbComponent component = ejbLookupCache.get(componentName);
    if (component == null) findComponent(componentName, componentClass);
    return ejbLookupCache.get(componentName).ejbClasses;
  }

  private String capitalize(String s) {
    if (s == null || s.length() == 0) return s;
    if (s.length() == 1) return s.toUpperCase();
    return s.substring(0, 1).toUpperCase() + s.substring(1);
  }

  /* (non-Javadoc)
   * @see org.granite.tide.ejb.EJBServiceContextIntf#prepareCall(org.granite.messaging.service.ServiceInvocationContext, org.granite.tide.IInvocationCall, java.lang.String)
   */
  @Override
  public void prepareCall(
      ServiceInvocationContext context,
      IInvocationCall c,
      String componentName,
      Class<?> componentClass) {
    if ((c instanceof InvocationCall) && ((InvocationCall) c).getListeners() != null)
      remoteObservers.addAll(((InvocationCall) c).getListeners());
    Context.create(this);
  }

  private static class EjbComponent {
    public Object ejbInstance;
    public Set<Class<?>> ejbClasses;
    public EjbServiceMetadata ejbMetadata;
  }

  /* (non-Javadoc)
   * @see org.granite.tide.ejb.EJBServiceContextIntf#postCall(org.granite.messaging.service.ServiceInvocationContext, java.lang.Object, java.lang.String)
   */
  @Override
  public IInvocationResult postCall(
      ServiceInvocationContext context,
      Object result,
      String componentName,
      Class<?> componentClass) {
    try {
      AbstractContext threadContext = AbstractContext.instance();

      List<ContextUpdate> results = new ArrayList<ContextUpdate>(threadContext.size());
      DataContext dataContext = DataContext.get();
      Set<Object[]> dataUpdates = dataContext != null ? dataContext.getDataUpdates() : null;
      Object[][] updates = null;
      if (dataUpdates != null && !dataUpdates.isEmpty())
        updates = dataUpdates.toArray(new Object[dataUpdates.size()][]);

      for (Map.Entry<String, Object> entry : threadContext.entrySet())
        results.add(new ContextUpdate(entry.getKey(), null, entry.getValue(), 3, false));

      InvocationResult ires = new InvocationResult(result, results);
      if (context.getBean() != null) {
        if (context.getBean().getClass().isAnnotationPresent(BypassTideMerge.class))
          ires.setMerge(false);
        else {
          try {
            Method m =
                context
                    .getBean()
                    .getClass()
                    .getMethod(
                        context.getMethod().getName(), context.getMethod().getParameterTypes());
            if (m.isAnnotationPresent(BypassTideMerge.class)) ires.setMerge(false);
          } catch (Exception e) {
            log.warn("Could not find bean method", e);
          }
        }
      }

      ires.setUpdates(updates);
      ires.setEvents(new ArrayList<ContextEvent>(threadContext.getRemoteEvents()));

      if (componentName != null) {
        EjbComponent component = ejbLookupCache.get(componentName);
        if (component != null
            && component.ejbMetadata != null
            && component.ejbMetadata.isStateful()
            && component.ejbMetadata.isRemoveMethod(context.getMethod()))
          ejbLookupCache.remove(componentName);
      }

      return ires;
    } finally {
      AbstractContext.remove();
    }
  }

  /* (non-Javadoc)
   * @see org.granite.tide.ejb.EJBServiceContextIntf#postCallFault(org.granite.messaging.service.ServiceInvocationContext, java.lang.Throwable, java.lang.String)
   */
  @Override
  public void postCallFault(
      ServiceInvocationContext context,
      Throwable t,
      String componentName,
      Class<?> componentClass) {
    try {
      if (componentName != null) {
        EjbComponent component = ejbLookupCache.get(componentName);
        if (t instanceof NoSuchEJBException
            || (component != null
                && component.ejbMetadata != null
                && (component.ejbMetadata.isStateful()
                    && component.ejbMetadata.isRemoveMethod(context.getMethod())
                    && !component.ejbMetadata.getRetainIfException(context.getMethod())))) {
          ejbLookupCache.remove(componentName);
        }
      }
    } finally {
      AbstractContext.remove();
    }
  }
}
/** @author Franck WOLFF */
public class EntityCodec implements ExtendedObjectCodec {

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

  private final ConcurrentMap<Class<?>, SerializableProxyAdapter> serializableProxyAdapters =
      new ConcurrentHashMap<Class<?>, SerializableProxyAdapter>();

  static class SerializableProxyAdapter {

    private final Object serializableProxy;
    private final Field idField;
    private final Method readResolveMethod;

    public SerializableProxyAdapter(HibernateProxy proxy)
        throws NoSuchMethodException, SecurityException, NoSuchFieldException {
      this.serializableProxy = proxy.writeReplace();

      this.idField = serializableProxy.getClass().getDeclaredField("id");
      this.idField.setAccessible(true);

      this.readResolveMethod = serializableProxy.getClass().getDeclaredMethod("readResolve");
      this.readResolveMethod.setAccessible(true);
    }

    public HibernateProxy newProxy(Serializable id)
        throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
      synchronized (serializableProxy) {
        idField.set(serializableProxy, id);
        return (HibernateProxy) readResolveMethod.invoke(serializableProxy);
      }
    }
  }

  public boolean canEncode(ExtendedObjectOutput out, Object v) {
    Class<?> cls = getClass(out, v);
    return (cls.isAnnotationPresent(Entity.class)
        || cls.isAnnotationPresent(MappedSuperclass.class));
  }

  public String getEncodedClassName(ExtendedObjectOutput out, Object v) {
    return getClass(out, v).getName();
  }

  public void encode(ExtendedObjectOutput out, Object v)
      throws IOException, IllegalAccessException, InvocationTargetException {
    String detachedState = null;

    if (v instanceof HibernateProxy) {
      HibernateProxy proxy = (HibernateProxy) v;

      // Only write initialized flag, detachedState & id if v is an uninitialized proxy.
      if (proxy.getHibernateLazyInitializer().isUninitialized()) {

        Class<?> persistentClass = proxy.getHibernateLazyInitializer().getPersistentClass();
        if (!serializableProxyAdapters.containsKey(persistentClass)) {
          try {
            SerializableProxyAdapter proxyAdapter = new SerializableProxyAdapter(proxy);
            serializableProxyAdapters.putIfAbsent(persistentClass, proxyAdapter);
          } catch (Exception e) {
            throw new IOException("Could not create SerializableProxyAdapter for: " + proxy);
          }
        }

        Serializable id = proxy.getHibernateLazyInitializer().getIdentifier();
        log.debug("Writing uninitialized HibernateProxy %s with id %s", detachedState, id);

        out.writeBoolean(false);
        out.writeUTF(null);
        out.writeObject(id);
        return;
      }

      // Proxy is initialized, get the underlying persistent object.
      log.debug("Writing initialized HibernateProxy...");
      v = proxy.getHibernateLazyInitializer().getImplementation();
    }

    // Write initialized flag & detachedState.
    out.writeBoolean(true);
    out.writeUTF(null);

    // Write all properties in lexical order.
    List<Property> properties = out.getReflection().findSerializableProperties(v.getClass());
    for (Property property : properties) out.getAndWriteProperty(v, property);
  }

  public boolean canDecode(ExtendedObjectInput in, String className) throws ClassNotFoundException {
    Class<?> cls = in.getReflection().loadClass(className);
    return (cls.isAnnotationPresent(Entity.class)
        || cls.isAnnotationPresent(MappedSuperclass.class));
  }

  public String getDecodedClassName(ExtendedObjectInput in, String className) {
    return in.getAlias(className);
  }

  public Object newInstance(ExtendedObjectInput in, String className)
      throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
          InvocationTargetException, SecurityException, NoSuchMethodException, IOException {

    Class<?> cls = in.getReflection().loadClass(className);

    // Read initialized flag & detachedState.
    boolean initialized = in.readBoolean();
    in.readUTF();

    if (initialized) return in.getReflection().newInstance(cls);

    // Create an HibernateProxy.
    SerializableProxyAdapter proxyAdapter = serializableProxyAdapters.get(cls);
    if (proxyAdapter == null)
      throw new IOException("Could not find SerializableProxyAdapter for: " + cls);
    Serializable id = (Serializable) in.readObject();
    return proxyAdapter.newProxy(id);
  }

  public void decode(ExtendedObjectInput in, Object v)
      throws IOException, ClassNotFoundException, IllegalAccessException,
          InvocationTargetException {
    if (!(v instanceof HibernateProxy)) {
      List<Property> properties = in.getReflection().findSerializableProperties(v.getClass());
      for (Property property : properties) in.readAndSetProperty(v, property);
    }
  }

  protected Class<?> getClass(ExtendedObjectOutput out, Object v) {
    Class<?> cls = v.getClass();

    if (v instanceof HibernateProxy) {
      LazyInitializer initializer = ((HibernateProxy) v).getHibernateLazyInitializer();

      String className =
          (initializer.isUninitialized()
              ? initializer.getEntityName()
              : initializer.getImplementation().getClass().getName());

      if (className != null && className.length() > 0) {
        try {
          cls = out.getReflection().loadClass(className);
        } catch (ClassNotFoundException e) {
          cls = initializer.getPersistentClass();
        }
      }
    }

    return cls;
  }
}
@Component
@Provides
public class OSGiServiceSimple extends SimpleService {

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

  @ServiceProperty(name = "ID")
  private String ID;

  @Requires(proxy = false)
  private ServicesConfig servicesConfig;

  //
  private boolean started = false;

  //
  public OSGiServiceSimple() {
    super(
        null, null, null, null, new HashMap<String, Adapter>(), new HashMap<String, Destination>());
  }

  @Property(name = "id", mandatory = true)
  private void setId(String id) {
    this.id = id;
    this.ID = id;
  }

  @Property(name = "messageTypes", mandatory = true)
  private void setMessageTypes(String messageTypes) {
    this.messageTypes = messageTypes;
  }

  @Property(name = "class", mandatory = false, value = "flex.messaging.services.RemotingService")
  private void setClass(String className) {
    this.className = className;
  }

  @Validate
  public void start() {
    log.debug("Start OSGiServiceSimple: " + toString());

    if (servicesConfig.findServiceById(id) == null) {
      // Clear destinations
      destinations.clear();

      servicesConfig.addService(this);
      started = true;
    } else {
      log.error("Service \"" + id + "\" already registered");
    }
  }

  @Invalidate
  public void stop() {
    log.debug("Stop OSGiServiceSimple: " + toString());
    if (servicesConfig != null) {
      servicesConfig.removeService(id);
      started = false;
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    OSGiServiceSimple that = (OSGiServiceSimple) o;

    if (this != that) return false;

    return true;
  }

  @Override
  public String toString() {
    return "Service{"
        + "id="
        + id
        + ", class="
        + className
        + ", messageTypes="
        + messageTypes
        + '}';
  }
}
/** @author Franck WOLFF */
public class JMFServletContextListener implements ServletContextListener {

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

  private static final String[] CODEC_EXTENSION_FACTORY_NAMES = {
    "org.granite.hibernate.jmf.Hibernate3CodecExtensionFactory"
  };

  public static final String EXTENDED_OBJECT_CODECS_PARAM = "jmf-extended-object-codecs";
  public static final String DEFAULT_STORED_STRINGS_PARAM = "jmf-default-stored-strings";

  public static final String SHARED_CONTEXT_KEY = SharedContext.class.getName();
  public static final String DUMP_SHARED_CONTEXT_KEY = SharedContext.class.getName() + ":DUMP";

  public static SharedContext getSharedContext(ServletContext servletContext) {
    return (SharedContext) servletContext.getAttribute(SHARED_CONTEXT_KEY);
  }

  public static SharedContext getDumpSharedContext(ServletContext servletContext) {
    return (SharedContext) servletContext.getAttribute(DUMP_SHARED_CONTEXT_KEY);
  }

  public static ServletException newSharedContextNotInitializedException() {
    return new ServletException(
        "JMF shared context not initialized (configure "
            + JMFServletContextListener.class.getName()
            + " in your web.xml)");
  }

  public void contextInitialized(ServletContextEvent event) {
    log.info("Loading JMF shared context");

    ServletContext servletContext = event.getServletContext();

    List<ExtendedObjectCodec> extendedObjectCodecs = loadExtendedObjectCodecs(servletContext);
    List<String> defaultStoredStrings = loadDefaultStoredStrings(servletContext);

    SharedContext sharedContext =
        new DefaultSharedContext(
            new DefaultCodecRegistry(extendedObjectCodecs), null, defaultStoredStrings);
    servletContext.setAttribute(SHARED_CONTEXT_KEY, sharedContext);

    SharedContext dumpSharedContext =
        new DefaultSharedContext(new DefaultCodecRegistry(), null, defaultStoredStrings);
    servletContext.setAttribute(DUMP_SHARED_CONTEXT_KEY, dumpSharedContext);

    log.info("JMF shared context loaded");
  }

  protected List<ExtendedObjectCodec> loadExtendedObjectCodecs(ServletContext servletContext) {
    String extendedObjectCodecsParam =
        servletContext.getInitParameter(EXTENDED_OBJECT_CODECS_PARAM);

    if (extendedObjectCodecsParam == null) return detectExtendedObjectCodecs();

    ClassLoader classLoader = getClass().getClassLoader();

    List<ExtendedObjectCodec> extendedObjectCodecs = new ArrayList<ExtendedObjectCodec>();
    for (String className : extendedObjectCodecsParam.trim().split("\\s*\\,\\s*")) {
      if (className.length() == 0) continue;

      log.info("Loading JMF extended object codec: %s", className);
      try {
        @SuppressWarnings("unchecked")
        Class<? extends ExtendedObjectCodec> cls =
            (Class<? extends ExtendedObjectCodec>) classLoader.loadClass(className);
        extendedObjectCodecs.add(cls.getDeclaredConstructor().newInstance());
      } catch (Throwable t) {
        log.warn(t, "Could not load JMF codec: %s", className);
      }
    }
    return extendedObjectCodecs;
  }

  protected List<ExtendedObjectCodec> detectExtendedObjectCodecs() {
    log.info("Auto detecting extended object codec...");

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    for (String factoryName : CODEC_EXTENSION_FACTORY_NAMES) {
      try {
        CodecExtensionFactory factory =
            (CodecExtensionFactory) classLoader.loadClass(factoryName).newInstance();
        List<ExtendedObjectCodec> extendedObjectCodecs = factory.getCodecs();
        log.info("Using %s: %s", factoryName, extendedObjectCodecs);
        return extendedObjectCodecs;
      } catch (Throwable t) {
        log.debug(t, "Could not load factory: %s", factoryName);
      }
    }

    log.info("No extended object codec detected");
    return Collections.emptyList();
  }

  protected List<String> loadDefaultStoredStrings(ServletContext servletContext) {
    List<String> defaultStoredStrings =
        new ArrayList<String>(JMFAMFUtil.AMF_DEFAULT_STORED_STRINGS);

    String defaultStoredStringsParam =
        servletContext.getInitParameter(DEFAULT_STORED_STRINGS_PARAM);

    if (defaultStoredStringsParam != null) {
      for (String value : defaultStoredStringsParam.trim().split("\\s*\\,\\s*")) {
        if (value.length() > 0) defaultStoredStrings.add(value);
      }
    }

    return defaultStoredStrings;
  }

  public void contextDestroyed(ServletContextEvent event) {
    ServletContext servletContext = event.getServletContext();

    servletContext.removeAttribute(SHARED_CONTEXT_KEY);
  }
}
/** @author Franck WOLFF */
public class DumpFilter implements Filter {

  private static final Logger log = Logger.getLogger(DumpFilter.class);
  private static final String HEXS = "0123456789ABCDEF";
  private static final String DUMP_DIR = "dumpDir";

  File dumpDir = null;

  public void init(FilterConfig config) throws ServletException {
    String dumpDirString = ServletParams.get(config, DUMP_DIR, String.class, null);
    if (dumpDirString != null) {
      File dumpDir = new File(dumpDirString);
      if (!dumpDir.exists() || !dumpDir.isDirectory() || !dumpDir.canWrite())
        log.warn("Ignoring dump directory (is it a writable directory?): %s", dumpDir);
      else this.dumpDir = dumpDir;
    }
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    DumpRequestWrapper requestWrapper = new DumpRequestWrapper((HttpServletRequest) request);
    DumpResponseWrapper responseWrapper = new DumpResponseWrapper((HttpServletResponse) response);

    dumpBytes("request", requestWrapper.getBytes());
    chain.doFilter(requestWrapper, responseWrapper);
    dumpBytes("response", responseWrapper.getBytes());
  }

  public void destroy() {
    dumpDir = null;
  }

  private void dumpBytes(String label, byte[] bytes) {
    StringBuilder hexSb = new StringBuilder();
    StringBuilder charSb = new StringBuilder();

    for (int i = 0; i < bytes.length; i++) {
      int b = bytes[i] & 0xFF;
      if (hexSb.length() > 0) {
        hexSb.append(' ');
        charSb.append(' ');
      }
      hexSb.append(HEXS.charAt(b >> 4)).append(HEXS.charAt(b & 0x0F));
      if (b >= 0x20 && b <= 0x7e) charSb.append(' ').append((char) b);
      else charSb.append("##");
    }

    log.info("[RAW %s] {\n%s\n%s\n}", label.toUpperCase(), hexSb.toString(), charSb.toString());

    if (dumpDir != null) {
      File file =
          new File(
              dumpDir.getPath()
                  + File.separator
                  + label
                  + "_"
                  + System.currentTimeMillis()
                  + ".amf");
      for (int i = 1; i < 100 && file.exists(); i++)
        file = new File(file.getAbsolutePath() + "." + i);

      OutputStream os = null;
      try {
        os = new FileOutputStream(file);
        os.write(bytes);
      } catch (Exception e) {
        log.error(e, "Could not write dump file: %s", file);
      } finally {
        if (os != null)
          try {
            os.close();
          } catch (Exception e) {
          }
      }
    }
  }

  class DumpRequestWrapper extends HttpServletRequestWrapper {

    private byte[] bytes = null;

    public DumpRequestWrapper(HttpServletRequest request) throws IOException {
      super(request);

      setCharacterEncoding("UTF-8");

      InputStream is = null;
      try {
        is = request.getInputStream();

        ByteArrayOutputStream out = new ByteArrayOutputStream(128);
        for (int b = is.read(); b != -1; b = is.read()) out.write(b);

        this.bytes = out.toByteArray();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

      final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

      return new ServletInputStream() {
        @Override
        public int read() throws IOException {
          return bais.read();
        }
      };
    }

    public byte[] getBytes() {
      return bytes;
    }
  }

  class DumpResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
    private ServletOutputStream out = null;

    public DumpResponseWrapper(HttpServletResponse response) throws IOException {
      super(response);
      this.out = response.getOutputStream();
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {

      return new ServletOutputStream() {
        @Override
        public void write(int b) throws IOException {
          baos.write(b);
          out.write(b);
        }
      };
    }

    public byte[] getBytes() {
      return baos.toByteArray();
    }
  }
}
/** @author Franck WOLFF */
public class GraniteConfig
    implements ConvertersConfig,
        AliasRegistryConfig,
        AMF3Config,
        ExternalizersConfig,
        ScannedItemHandler {

  public enum JMF_EXTENSIONS_MODE {
    PREPPEND,
    APPEND,
    REPLACE
  }

  ///////////////////////////////////////////////////////////////////////////
  // Static fields.

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

  private static final String GRANITE_CONFIG_PUBLIC_ID =
      "-//Granite Data Services//DTD granite-config internal//EN";
  private static final String GRANITE_CONFIG_PROPERTIES = "META-INF/granite-config.properties";

  final ExternalizerFactory EXTERNALIZER_FACTORY = new ExternalizerFactory();
  private static final Externalizer LONG_EXTERNALIZER = new LongExternalizer();
  private static final Externalizer BIGINTEGER_EXTERNALIZER = new BigIntegerExternalizer();
  private static final Externalizer BIGDECIMAL_EXTERNALIZER = new BigDecimalExternalizer();
  private static final Externalizer MAP_EXTERNALIZER = new MapExternalizer();

  final ActionScriptClassDescriptorFactory ASC_DESCRIPTOR_FACTORY =
      new ActionScriptClassDescriptorFactory();
  final JavaClassDescriptorFactory JC_DESCRIPTOR_FACTORY = new JavaClassDescriptorFactory();
  final TideComponentMatcherFactory TIDE_COMPONENT_MATCHER_FACTORY =
      new TideComponentMatcherFactory();

  ///////////////////////////////////////////////////////////////////////////
  // Instance fields.

  // Should we scan classpath for auto-configured services/externalizers?
  private boolean scan = false;

  private AliasRegistry aliasRegistry = new DefaultAliasRegistry();

  private String MBeanContextName = null;

  // Custom AMF3 (De)Serializer configuration.
  private Constructor<AMF3Serializer> amf3SerializerConstructor = null;
  private Constructor<AMF3Deserializer> amf3DeserializerConstructor = null;

  private AMF3DeserializerSecurizer amf3DeserializerSecurizer = null;

  // Custom AMF3 message interceptor configuration.
  private AMF3MessageInterceptor amf3MessageInterceptor = null;

  // Converters configuration.
  private List<Class<? extends Converter>> converterClasses =
      new ArrayList<Class<? extends Converter>>();
  private Converters converters = null;

  // MethodMatcher configuration.
  private MethodMatcher methodMatcher = new DefaultMethodMatcher();

  // Invocation listener configuration.
  private ServiceInvocationListener invocationListener = null;

  // Instantiators configuration.
  private final Map<String, String> instantiators = new HashMap<String, String>();

  // Class getter configuration.
  private ClassGetter classGetter = new DefaultClassGetter();
  private boolean classGetterSet = false;

  // Externalizers configuration.
  private XMap externalizersConfiguration = null;
  private final List<Externalizer> scannedExternalizers = new ArrayList<Externalizer>();
  private final ConcurrentHashMap<String, Externalizer> externalizersByType =
      new ConcurrentHashMap<String, Externalizer>();
  private final Map<String, String> externalizersByInstanceOf = new HashMap<String, String>();
  private final Map<String, String> externalizersByAnnotatedWith = new HashMap<String, String>();

  // JMF extended codecs.
  private JMF_EXTENSIONS_MODE jmfExtendedCodecsMode = JMF_EXTENSIONS_MODE.APPEND;
  private final List<ExtendedObjectCodec> jmfExtendedCodecs = new ArrayList<ExtendedObjectCodec>();

  // JMF default stored strings.
  private JMF_EXTENSIONS_MODE jmfDefaultStoredStringsMode = JMF_EXTENSIONS_MODE.APPEND;
  private final List<String> jmfDefaultStoredStrings = new ArrayList<String>();

  // JMF reflection.
  private Reflection jmfReflection = null;

  // JMF
  private SharedContext sharedContext;

  // Java descriptors configuration.
  private final ConcurrentHashMap<String, JavaClassDescriptor> javaDescriptorsCache =
      new ConcurrentHashMap<String, JavaClassDescriptor>();
  private final ConcurrentHashMap<String, Class<? extends JavaClassDescriptor>>
      javaDescriptorsByType = new ConcurrentHashMap<String, Class<? extends JavaClassDescriptor>>();
  private final Map<String, String> javaDescriptorsByInstanceOf = new HashMap<String, String>();

  // AS3 descriptors configuration.
  private final ConcurrentHashMap<String, Class<? extends ActionScriptClassDescriptor>>
      as3DescriptorsByType =
          new ConcurrentHashMap<String, Class<? extends ActionScriptClassDescriptor>>();
  private final Map<String, String> as3DescriptorsByInstanceOf = new HashMap<String, String>();

  // Exception converters
  private final List<ExceptionConverter> exceptionConverters = new ArrayList<ExceptionConverter>();

  // Tide-enabled Components configuration.
  private final ConcurrentHashMap<String, Object[]> enabledTideComponentsByName =
      new ConcurrentHashMap<String, Object[]>();
  private final ConcurrentHashMap<String, Object[]> disabledTideComponentsByName =
      new ConcurrentHashMap<String, Object[]>();
  private final List<TideComponentMatcher> tideComponentMatchers =
      new ArrayList<TideComponentMatcher>();

  // Security service configuration.
  private SecurityService securityService = null;

  // MessageSelector configuration.
  private Constructor<?> messageSelectorConstructor;

  // Gravity configuration.
  private XMap gravityConfig;

  // Clustering
  private DistributedDataFactory distributedDataFactory;

  ///////////////////////////////////////////////////////////////////////////
  // Constructor.

  public GraniteConfig(
      String stdConfig,
      InputStream customConfigIs,
      Configuration configuration,
      String MBeanContextName)
      throws IOException, SAXException {
    try {
      amf3SerializerConstructor =
          TypeUtil.getConstructor(AMF3Serializer.class, new Class<?>[] {OutputStream.class});
      amf3DeserializerConstructor =
          TypeUtil.getConstructor(AMF3Deserializer.class, new Class<?>[] {InputStream.class});
    } catch (Exception e) {
      throw new GraniteConfigException("Could not get constructor for AMF3 (de)serializers", e);
    }

    this.MBeanContextName = MBeanContextName;

    ClassLoader loader = GraniteConfig.class.getClassLoader();

    final ByteArrayInputStream dtd =
        StreamUtil.getResourceAsStream("org/granite/config/granite-config.dtd", loader);
    final EntityResolver resolver =
        new EntityResolver() {
          public InputSource resolveEntity(String publicId, String systemId)
              throws SAXException, IOException {
            if (GRANITE_CONFIG_PUBLIC_ID.equals(publicId)) {
              dtd.reset();
              InputSource source = new InputSource(dtd);
              source.setPublicId(publicId);
              return source;
            }
            return null;
          }
        };

    // Load standard config.
    InputStream is = null;
    try {
      is = StreamUtil.getResourceAsStream("org/granite/config/granite-config.xml", loader);
      XMap doc = new XMap(is, resolver);
      forElement(doc, false, null);
    } finally {
      if (is != null) is.close();
    }

    if (stdConfig != null) {
      try {
        is = StreamUtil.getResourceAsStream(stdConfig, loader);
        XMap doc = new XMap(is, resolver);
        forElement(doc, false, null);
      } finally {
        if (is != null) is.close();
      }
    }

    // Load custom config (override).
    if (customConfigIs != null) {
      XMap doc = new XMap(customConfigIs, resolver);
      forElement(
          doc, true, configuration != null ? configuration.getGraniteConfigProperties() : null);
    }

    if (amf3DeserializerSecurizer == null)
      log.warn(
          "You should configure a deserializer securizer in your granite-config.xml file in order to prevent potential security exploits!");
  }

  ///////////////////////////////////////////////////////////////////////////
  // Classpath scan initialization.

  private void scanConfig(String graniteConfigProperties) {
    // if config overriding exists
    Scanner scanner =
        ScannerFactory.createScanner(
            this,
            graniteConfigProperties != null ? graniteConfigProperties : GRANITE_CONFIG_PROPERTIES);
    try {
      scanner.scan();
    } catch (Exception e) {
      log.error(e, "Could not scan classpath for configuration");
    }
  }

  public boolean handleMarkerItem(ScannedItem item) {
    try {
      return handleProperties(item.loadAsProperties());
    } catch (Exception e) {
      log.error(e, "Could not load properties: %s", item);
    }
    return true;
  }

  public void handleScannedItem(ScannedItem item) {
    if ("class".equals(item.getExtension()) && item.getName().indexOf('$') == -1) {
      try {
        handleClass(item.loadAsClass());
      } catch (NoClassDefFoundError e) {
        // Ignore errors with Tide classes depending on Gravity
      } catch (LinkageError e) {
        // Ignore errors with GraniteDS/Hibernate classes depending on Hibernate 3 when using
        // Hibernate 4
      } catch (Throwable t) {
        log.error(t, "Could not load class: %s", item);
      }
    }
  }

  private boolean handleProperties(Properties properties) {
    if (properties.getProperty("dependsOn") != null) {
      String dependsOn = properties.getProperty("dependsOn");
      try {
        TypeUtil.forName(dependsOn);
      } catch (ClassNotFoundException e) {
        // Class not found, skip scan for this package
        return true;
      }
    }

    String classGetterName = properties.getProperty("classGetter");
    if (!classGetterSet && classGetterName != null) {
      try {
        classGetter = TypeUtil.newInstance(classGetterName, ClassGetter.class);
      } catch (Throwable t) {
        log.error(t, "Could not create instance of: %s", classGetterName);
      }
    }

    String amf3MessageInterceptorName = properties.getProperty("amf3MessageInterceptor");
    if (amf3MessageInterceptor == null && amf3MessageInterceptorName != null) {
      try {
        amf3MessageInterceptor =
            TypeUtil.newInstance(amf3MessageInterceptorName, AMF3MessageInterceptor.class);
      } catch (Throwable t) {
        log.error(t, "Could not create instance of: %s", amf3MessageInterceptorName);
      }
    }

    for (Map.Entry<?, ?> me : properties.entrySet()) {
      if (me.getKey().toString().startsWith("converter.")) {
        String converterName = me.getValue().toString();
        try {
          converterClasses.add(TypeUtil.forName(converterName, Converter.class));
        } catch (Exception e) {
          throw new GraniteConfigException(
              "Could not get converter class for: " + converterName, e);
        }
      }
    }

    return false;
  }

  private void handleClass(Class<?> clazz) {
    if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) return;

    if (Externalizer.class.isAssignableFrom(clazz)) {
      try {
        scannedExternalizers.add(TypeUtil.newInstance(clazz, Externalizer.class));
      } catch (Exception e) {
        log.error(e, "Could not create new instance of: %s", clazz);
      }
    }

    if (ExceptionConverter.class.isAssignableFrom(clazz)) {
      try {
        exceptionConverters.add(TypeUtil.newInstance(clazz, ExceptionConverter.class));
      } catch (Exception e) {
        if (!clazz
            .getName()
            .equals("org.granite.tide.hibernate.HibernateValidatorExceptionConverter")) // GDS-582
        log.error(e, "Could not create new instance of: %s", clazz);
      }
    }
  }

  ///////////////////////////////////////////////////////////////////////////
  // Property getters.

  public boolean getScan() {
    return scan;
  }

  public boolean isRegisterMBeans() {
    return MBeanContextName != null;
  }

  public String getMBeanContextName() {
    return MBeanContextName;
  }

  public ObjectOutput newAMF3Serializer(OutputStream out) {
    try {
      return amf3SerializerConstructor.newInstance(new Object[] {out});
    } catch (Exception e) {
      throw new GraniteConfigException(
          "Could not create serializer instance with: " + amf3SerializerConstructor, e);
    }
  }

  public Constructor<?> getAmf3SerializerConstructor() {
    return amf3SerializerConstructor;
  }

  public ObjectInput newAMF3Deserializer(InputStream in) {
    try {
      return amf3DeserializerConstructor.newInstance(new Object[] {in});
    } catch (Exception e) {
      throw new GraniteConfigException(
          "Could not create deserializer instance with: " + amf3DeserializerConstructor, e);
    }
  }

  public Constructor<?> getAmf3DeserializerConstructor() {
    return amf3DeserializerConstructor;
  }

  public AMF3DeserializerSecurizer getAmf3DeserializerSecurizer() {
    return amf3DeserializerSecurizer;
  }

  public void setAmf3DeserializerSecurizer(AMF3DeserializerSecurizer amf3DeserializerSecurizer) {
    this.amf3DeserializerSecurizer = amf3DeserializerSecurizer;
  }

  public AMF3MessageInterceptor getAmf3MessageInterceptor() {
    return amf3MessageInterceptor;
  }

  public void setAmf3MessageInterceptor(AMF3MessageInterceptor amf3MessageInterceptor) {
    this.amf3MessageInterceptor = amf3MessageInterceptor;
  }

  public Map<String, String> getInstantiators() {
    return instantiators;
  }

  public Converters getConverters() {
    return converters;
  }

  public MethodMatcher getMethodMatcher() {
    return methodMatcher;
  }

  public ServiceInvocationListener getInvocationListener() {
    return invocationListener;
  }

  public String getInstantiator(String type) {
    return instantiators.get(type);
  }

  public ClassGetter getClassGetter() {
    return classGetter;
  }

  public XMap getExternalizersConfiguration() {
    return externalizersConfiguration;
  }

  public void setExternalizersConfiguration(XMap externalizersConfiguration) {
    this.externalizersConfiguration = externalizersConfiguration;
  }

  public Externalizer getExternalizer(String type) {
    Externalizer externalizer =
        getElementByType(
            type,
            EXTERNALIZER_FACTORY,
            externalizersByType,
            externalizersByInstanceOf,
            externalizersByAnnotatedWith,
            scannedExternalizers);
    if (externalizer != null) return externalizer;

    if ("java".equals(GraniteContext.getCurrentInstance().getClientType())) {
      // Force use of number externalizers when serializing from/to a Java client
      if (Long.class.getName().equals(type)) return LONG_EXTERNALIZER;
      else if (BigInteger.class.getName().equals(type)) return BIGINTEGER_EXTERNALIZER;
      else if (BigDecimal.class.getName().equals(type)) return BIGDECIMAL_EXTERNALIZER;
      else {
        try {
          Class<?> clazz = TypeUtil.forName(type);
          if (Map.class.isAssignableFrom(clazz) && !Externalizable.class.isAssignableFrom(clazz))
            return MAP_EXTERNALIZER;
        } catch (Exception e) {

        }
      }
    }

    return null;
  }

  public void registerExternalizer(Externalizer externalizer) {
    scannedExternalizers.add(externalizer);
  }

  public Map<String, Externalizer> getExternalizersByType() {
    return externalizersByType;
  }

  public Map<String, String> getExternalizersByInstanceOf() {
    return externalizersByInstanceOf;
  }

  public Map<String, String> getExternalizersByAnnotatedWith() {
    return externalizersByAnnotatedWith;
  }

  public List<Externalizer> getScannedExternalizers() {
    return scannedExternalizers;
  }

  public JMF_EXTENSIONS_MODE getJmfExtendedCodecsMode() {
    return jmfExtendedCodecsMode;
  }

  public List<ExtendedObjectCodec> getJmfExtendedCodecs() {
    return jmfExtendedCodecs;
  }

  public JMF_EXTENSIONS_MODE getJmfDefaultStoredStringsMode() {
    return jmfDefaultStoredStringsMode;
  }

  public Reflection getJmfReflection() {
    return jmfReflection;
  }

  public List<String> getJmfDefaultStoredStrings() {
    return jmfDefaultStoredStrings;
  }

  public Class<? extends ActionScriptClassDescriptor> getActionScriptDescriptor(String type) {
    return getElementByType(
        type, ASC_DESCRIPTOR_FACTORY, as3DescriptorsByType, as3DescriptorsByInstanceOf, null, null);
  }

  public Map<String, Class<? extends ActionScriptClassDescriptor>> getAs3DescriptorsByType() {
    return as3DescriptorsByType;
  }

  public Map<String, String> getAs3DescriptorsByInstanceOf() {
    return as3DescriptorsByInstanceOf;
  }

  public ConcurrentMap<String, JavaClassDescriptor> getJavaDescriptorsCache() {
    return javaDescriptorsCache;
  }

  public Class<? extends JavaClassDescriptor> getJavaDescriptor(String type) {
    return getElementByType(
        type,
        JC_DESCRIPTOR_FACTORY,
        javaDescriptorsByType,
        javaDescriptorsByInstanceOf,
        null,
        null);
  }

  public Map<String, Class<? extends JavaClassDescriptor>> getJavaDescriptorsByType() {
    return javaDescriptorsByType;
  }

  public Map<String, String> getJavaDescriptorsByInstanceOf() {
    return javaDescriptorsByInstanceOf;
  }

  public boolean isComponentTideEnabled(
      String componentName, Set<Class<?>> componentClasses, Object instance) {
    return TideComponentMatcherFactory.isComponentTideEnabled(
        enabledTideComponentsByName,
        tideComponentMatchers,
        componentName,
        componentClasses,
        instance);
  }

  public boolean isComponentTideDisabled(
      String componentName, Set<Class<?>> componentClasses, Object instance) {
    return TideComponentMatcherFactory.isComponentTideDisabled(
        disabledTideComponentsByName,
        tideComponentMatchers,
        componentName,
        componentClasses,
        instance);
  }

  public List<ExceptionConverter> getExceptionConverters() {
    return exceptionConverters;
  }

  public void registerExceptionConverter(
      Class<? extends ExceptionConverter> exceptionConverterClass) {
    registerExceptionConverter(exceptionConverterClass, false);
  }

  public void registerExceptionConverter(
      Class<? extends ExceptionConverter> exceptionConverterClass, boolean first) {
    for (ExceptionConverter ec : exceptionConverters) {
      if (ec.getClass() == exceptionConverterClass) return;
    }
    try {
      ExceptionConverter exceptionConverter =
          TypeUtil.newInstance(exceptionConverterClass, ExceptionConverter.class);
      if (first) exceptionConverters.add(0, exceptionConverter);
      else exceptionConverters.add(exceptionConverter);
    } catch (Exception e) {
      log.error(e, "Could not instantiate exception converter: %s", exceptionConverterClass);
    }
  }

  public void registerExceptionConverter(ExceptionConverter exceptionConverter, boolean first) {
    for (ExceptionConverter ec : exceptionConverters) {
      if (ec.getClass() == exceptionConverter.getClass()) return;
    }
    if (first) exceptionConverters.add(0, exceptionConverter);
    else exceptionConverters.add(exceptionConverter);
  }

  public boolean hasSecurityService() {
    return securityService != null;
  }

  public SecurityService getSecurityService() {
    return securityService;
  }

  public List<TideComponentMatcher> getTideComponentMatchers() {
    return tideComponentMatchers;
  }

  public Map<String, Object[]> getEnabledTideComponentsByName() {
    return enabledTideComponentsByName;
  }

  public Map<String, Object[]> getDisabledTideComponentsByName() {
    return disabledTideComponentsByName;
  }

  public XMap getGravityConfig() {
    return gravityConfig;
  }

  public DistributedDataFactory getDistributedDataFactory() {
    return distributedDataFactory;
  }

  public Constructor<?> getMessageSelectorConstructor() {
    return messageSelectorConstructor;
  }

  public Externalizer setExternalizersByType(String type, String externalizerType) {
    return externalizersByType.put(type, EXTERNALIZER_FACTORY.getInstance(externalizerType, this));
  }

  public String putExternalizersByInstanceOf(String instanceOf, String externalizerType) {
    return externalizersByInstanceOf.put(instanceOf, externalizerType);
  }

  public String putExternalizersByAnnotatedWith(String annotatedWith, String externalizerType) {
    return externalizersByAnnotatedWith.put(annotatedWith, externalizerType);
  }

  ///////////////////////////////////////////////////////////////////////////
  // Static GraniteConfig loading helpers.

  private void forElement(XMap element, boolean custom, String graniteConfigProperties) {
    String scan = element.get("@scan");

    this.scan = Boolean.TRUE.toString().equals(scan);

    loadCustomAMF3Serializer(element, custom);
    loadCustomAMF3DeserializerSecurizer(element, custom);
    loadCustomAMF3MessageInterceptor(element, custom);
    loadCustomConverters(element, custom);
    loadCustomMethodMatcher(element, custom);
    loadCustomInvocationListener(element, custom);
    loadCustomInstantiators(element, custom);
    loadCustomClassGetter(element, custom);
    loadCustomExternalizers(element, custom);
    loadCustomJMFExtendedCodecs(element, custom);
    loadCustomJMFDefaultStoredStrings(element, custom);
    loadCustomJMFReflection(element, custom);
    loadCustomDescriptors(element, custom);
    loadCustomExceptionConverters(element, custom);
    loadCustomTideComponents(element, custom);
    loadCustomSecurity(element, custom);
    loadCustomMessageSelector(element, custom);
    loadCustomGravity(element, custom);
    loadCustomDistributedDataFactory(element, custom);

    if (this.scan) scanConfig(graniteConfigProperties);

    finishCustomConverters(custom);
  }

  private void loadCustomAMF3Serializer(XMap element, boolean custom) {
    XMap amf3Serializer = element.getOne("amf3-serializer");
    if (amf3Serializer != null) {
      String type = amf3Serializer.get("@type");
      try {
        Class<AMF3Serializer> amf3SerializerClass = TypeUtil.forName(type, AMF3Serializer.class);
        amf3SerializerConstructor =
            TypeUtil.getConstructor(amf3SerializerClass, new Class<?>[] {OutputStream.class});
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not get constructor for AMF3 serializer: " + type, e);
      }
    }

    XMap amf3Deserializer = element.getOne("amf3-deserializer");
    if (amf3Deserializer != null) {
      String type = amf3Deserializer.get("@type");
      try {
        Class<AMF3Deserializer> amf3DeserializerClass =
            TypeUtil.forName(type, AMF3Deserializer.class);
        amf3DeserializerConstructor =
            TypeUtil.getConstructor(amf3DeserializerClass, new Class<?>[] {InputStream.class});
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not get constructor for AMF3 deserializer: " + type, e);
      }
    }
  }

  private void loadCustomAMF3DeserializerSecurizer(XMap element, boolean custom) {
    XMap securizer = element.getOne("amf3-deserializer-securizer");
    if (securizer != null) {
      String type = securizer.get("@type");
      try {
        amf3DeserializerSecurizer = (AMF3DeserializerSecurizer) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not construct amf3 deserializer securizer: " + type, e);
      }
      String param = securizer.get("@param");
      try {
        amf3DeserializerSecurizer.setParam(param);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not set param of amf3 deserializer securizer: " + type + ", param: " + param, e);
      }
    }
  }

  private void loadCustomAMF3MessageInterceptor(XMap element, boolean custom) {
    XMap interceptor = element.getOne("amf3-message-interceptor");
    if (interceptor != null) {
      String type = interceptor.get("@type");
      try {
        amf3MessageInterceptor = (AMF3MessageInterceptor) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not construct amf3 message interceptor: " + type, e);
      }
    }
  }

  private void loadCustomDistributedDataFactory(XMap element, boolean custom) {
    XMap distributedDataFactory = element.getOne("distributed-data-factory");
    if (distributedDataFactory != null) {
      String type = distributedDataFactory.get("@type");
      try {
        this.distributedDataFactory = (DistributedDataFactory) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not construct build distributed data factory: " + type, e);
      }
    }
  }

  private void loadCustomConverters(XMap element, boolean custom) {
    XMap converters = element.getOne("converters");
    if (converters != null) {
      // Should we override standard config converters?
      String override = converters.get("@override");
      if (Boolean.TRUE.toString().equals(override)) converterClasses.clear();

      int i = 0;
      for (XMap converter : converters.getAll("converter")) {
        String type = converter.get("@type");
        try {
          // For custom config, shifts any standard converters to the end of the list...
          converterClasses.add(i++, TypeUtil.forName(type, Converter.class));
        } catch (Exception e) {
          throw new GraniteConfigException("Could not get converter class for: " + type, e);
        }
      }
    }
  }

  private void finishCustomConverters(boolean custom) {
    try {
      converters = new Converters(converterClasses);
    } catch (Exception e) {
      throw new GraniteConfigException("Could not construct new Converters instance", e);
    }

    // Cleanup...
    if (custom) converterClasses = null;
  }

  private void loadCustomMethodMatcher(XMap element, boolean custom) {
    XMap methodMatcher = element.getOne("method-matcher");
    if (methodMatcher != null) {
      String type = methodMatcher.get("@type");
      try {
        this.methodMatcher = (MethodMatcher) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException("Could not construct method matcher: " + type, e);
      }
    }
  }

  private void loadCustomInvocationListener(XMap element, boolean custom) {
    XMap invocationListener = element.getOne("invocation-listener");
    if (invocationListener != null) {
      String type = invocationListener.get("@type");
      try {
        this.invocationListener = (ServiceInvocationListener) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not instantiate ServiceInvocationListener: " + type, e);
      }
    }
  }

  private void loadCustomInstantiators(XMap element, boolean custom) {
    XMap instantiators = element.getOne("instantiators");
    if (instantiators != null) {
      for (XMap instantiator : instantiators.getAll("instantiator"))
        this.instantiators.put(instantiator.get("@type"), instantiator.get("."));
    }
  }

  private void loadCustomClassGetter(XMap element, boolean custom) {
    XMap classGetter = element.getOne("class-getter");
    if (classGetter != null) {
      String type = classGetter.get("@type");
      try {
        this.classGetter = (ClassGetter) TypeUtil.newInstance(type);
        classGetterSet = true;
      } catch (Exception e) {
        throw new GraniteConfigException("Could not instantiate ClassGetter: " + type, e);
      }
    }
  }

  private void loadCustomExternalizers(XMap element, boolean custom) {
    externalizersConfiguration = element.getOne("externalizers/configuration");

    for (XMap externalizer : element.getAll("externalizers/externalizer")) {
      String externalizerType = externalizer.get("@type");

      for (XMap include : externalizer.getAll("include")) {
        String type = include.get("@type");
        if (type != null)
          externalizersByType.put(type, EXTERNALIZER_FACTORY.getInstance(externalizerType, this));
        else {
          String instanceOf = include.get("@instance-of");
          if (instanceOf != null) externalizersByInstanceOf.put(instanceOf, externalizerType);
          else {
            String annotatedWith = include.get("@annotated-with");
            if (annotatedWith == null)
              throw new GraniteConfigException(
                  "Element 'include' has no attribute 'type', 'instance-of' or 'annotated-with'");
            externalizersByAnnotatedWith.put(annotatedWith, externalizerType);
          }
        }
      }
    }
  }

  private void loadCustomJMFExtendedCodecs(XMap element, boolean custom) {
    String jmfExtendedCodecsMode = element.get("jmf-extended-codecs/@mode");
    if (jmfExtendedCodecsMode != null) {
      try {
        this.jmfExtendedCodecsMode =
            JMF_EXTENSIONS_MODE.valueOf(jmfExtendedCodecsMode.toLowerCase());
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Illegal JMF extended codecs mode: " + jmfExtendedCodecsMode, e);
      }
    }

    for (XMap codec : element.getAll("jmf-extended-codecs/jmf-extended-codec")) {
      String codecType = codec.get("@type");

      try {
        jmfExtendedCodecs.add((ExtendedObjectCodec) TypeUtil.newInstance(codecType));
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not instantiate JMF extended codec: " + codecType, e);
      }
    }
  }

  private void loadCustomJMFDefaultStoredStrings(XMap element, boolean custom) {
    String jmfDefaultStoredStringsMode = element.get("jmf-default-stored-strings/@mode");
    if (jmfDefaultStoredStringsMode != null) {
      try {
        this.jmfDefaultStoredStringsMode =
            JMF_EXTENSIONS_MODE.valueOf(jmfDefaultStoredStringsMode.toLowerCase());
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Illegal JMF default stored strings mode: " + jmfDefaultStoredStringsMode, e);
      }
    }

    for (XMap codec : element.getAll("jmf-default-stored-strings/jmf-default-stored-string"))
      jmfDefaultStoredStrings.add(codec.get("@value"));
  }

  private void loadCustomJMFReflection(XMap element, boolean custom) {
    String jmfReflection = element.get("jmf-reflection/@type");
    if (jmfReflection == null)
      this.jmfReflection = new Reflection(Thread.currentThread().getContextClassLoader());
    else {
      try {
        this.jmfReflection = (Reflection) TypeUtil.newInstance(jmfReflection);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not instantiate JMF reflection: " + jmfReflection, e);
      }
    }
  }

  /**
   * Read custom class descriptors. Descriptor must have 'type' or 'instanceof' attribute and one of
   * 'java' or 'as3' attributes specified.
   */
  private void loadCustomDescriptors(XMap element, boolean custom) {
    for (XMap descriptor : element.getAll("descriptors/descriptor")) {
      String type = descriptor.get("@type");
      if (type != null) {
        String java = descriptor.get("@java");
        String as3 = descriptor.get("@as3");
        if (java == null && as3 == null)
          throw new GraniteConfigException(
              "Element 'descriptor' has no attributes 'java' or 'as3'\n" + descriptor);
        if (java != null)
          javaDescriptorsByType.put(type, JC_DESCRIPTOR_FACTORY.getInstance(java, this));
        if (as3 != null)
          as3DescriptorsByType.put(type, ASC_DESCRIPTOR_FACTORY.getInstance(as3, this));
      } else {
        String instanceOf = descriptor.get("@instance-of");
        if (instanceOf == null)
          throw new GraniteConfigException(
              "Element 'descriptor' has no attribute 'type' or 'instance-of'\n" + descriptor);
        String java = descriptor.get("@java");
        String as3 = descriptor.get("@as3");
        if (java == null && as3 == null) {
          throw new GraniteConfigException(
              "Element 'descriptor' has no attributes 'java' or 'as3' in:\n" + descriptor);
        }
        if (java != null) javaDescriptorsByInstanceOf.put(instanceOf, java);
        if (as3 != null) as3DescriptorsByInstanceOf.put(instanceOf, as3);
      }
    }
  }

  public void setAliasRegistry(AliasRegistry aliasRegistry) {
    this.aliasRegistry = aliasRegistry;
  }

  public AliasRegistry getAliasRegistry() {
    return aliasRegistry;
  }

  public void setSharedContext(SharedContext sharedContext) {
    this.sharedContext = sharedContext;
  }

  public SharedContext getSharedContext() {
    return sharedContext;
  }

  /** Read custom class exception converters Converter must have 'type' attribute */
  private void loadCustomExceptionConverters(XMap element, boolean custom) {
    for (XMap exceptionConverter : element.getAll("exception-converters/exception-converter")) {
      String type = exceptionConverter.get("@type");
      ExceptionConverter converter = null;
      try {
        converter = (ExceptionConverter) TypeUtil.newInstance(type);
        exceptionConverters.add(converter);
      } catch (Exception e) {
        throw new GraniteConfigException("Could not construct exception converter: " + type, e);
      }
    }
  }

  private void loadCustomTideComponents(XMap element, boolean custom) {
    for (XMap component : element.getAll("tide-components/tide-component")) {
      boolean disabled = Boolean.TRUE.toString().equals(component.get("@disabled"));
      String type = component.get("@type");
      if (type != null)
        tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getTypeMatcher(type, disabled));
      else {
        String name = component.get("@name");
        if (name != null)
          tideComponentMatchers.add(TIDE_COMPONENT_MATCHER_FACTORY.getNameMatcher(name, disabled));
        else {
          String instanceOf = component.get("@instance-of");
          if (instanceOf != null)
            tideComponentMatchers.add(
                TIDE_COMPONENT_MATCHER_FACTORY.getInstanceOfMatcher(instanceOf, disabled));
          else {
            String annotatedWith = component.get("@annotated-with");
            if (annotatedWith == null)
              throw new GraniteConfigException(
                  "Element 'component' has no attribute 'type', 'name', 'instance-of' or 'annotated-with'");
            tideComponentMatchers.add(
                TIDE_COMPONENT_MATCHER_FACTORY.getAnnotatedWithMatcher(annotatedWith, disabled));
          }
        }
      }
    }
  }

  private void loadCustomSecurity(XMap element, boolean custom) {
    XMap security = element.getOne("security");
    if (security != null) {
      String type = security.get("@type");
      try {
        securityService = (SecurityService) TypeUtil.newInstance(type);
      } catch (Exception e) {
        throw new GraniteConfigException("Could not instantiate SecurityService: " + type, e);
      }

      Map<String, String> params = new HashMap<String, String>();
      for (XMap param : security.getAll("param")) {
        String name = param.get("@name");
        String value = param.get("@value");
        params.put(name, value);
      }
      try {
        securityService.configure(params);
      } catch (Exception e) {
        throw new GraniteConfigException(
            "Could not configure SecurityService " + type + " with: " + params, e);
      }
    }
  }

  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  private void loadCustomMessageSelector(XMap element, boolean custom) {
    XMap selector = element.getOne("message-selector");
    if (selector != null) {
      String type = selector.get("@type");
      try {
        messageSelectorConstructor = TypeUtil.getConstructor(type, new Class<?>[] {String.class});
      } catch (Exception e) {
        throw new GraniteConfigException("Could not construct message selector: " + type, e);
      }
    }
  }

  private void loadCustomGravity(XMap element, boolean custom) {
    gravityConfig = element.getOne("gravity");
  }

  ///////////////////////////////////////////////////////////////////////////
  // Other helpers.

  @SuppressWarnings("unchecked")
  private <T, C extends Config> T getElementByType(
      String type,
      ConfigurableFactory<T, C> factory,
      ConcurrentHashMap<String, T> elementsByType,
      Map<String, String> elementsByInstanceOf,
      Map<String, String> elementsByAnnotatedWith,
      List<T> scannedConfigurables) {

    // This NULL object is a Java null placeholder: ConcurrentHashMap doesn't allow
    // null values...
    final T NULL = factory.getNullInstance();

    T element = elementsByType.get(type);
    if (element != null) return (NULL == element ? null : element);
    element = NULL;

    Class<?> typeClass = null;
    try {
      typeClass = TypeUtil.forName(type);
    } catch (Exception e) {
      throw new GraniteConfigException("Could not load class: " + type, e);
    }

    if (elementsByAnnotatedWith != null && NULL == element) {
      for (Map.Entry<String, String> entry : elementsByAnnotatedWith.entrySet()) {
        String annotation = entry.getKey();
        try {
          Class<Annotation> annotationClass = TypeUtil.forName(annotation, Annotation.class);
          if (typeClass.isAnnotationPresent(annotationClass)) {
            element = factory.getInstance(entry.getValue(), (C) this);
            break;
          }
        } catch (Exception e) {
          throw new GraniteConfigException("Could not load class: " + annotation, e);
        }
      }
    }

    if (elementsByInstanceOf != null && NULL == element) {
      for (Map.Entry<String, String> entry : elementsByInstanceOf.entrySet()) {
        String instanceOf = entry.getKey();
        try {
          Class<?> instanceOfClass = TypeUtil.forName(instanceOf);
          if (instanceOfClass.isAssignableFrom(typeClass)) {
            element = factory.getInstance(entry.getValue(), (C) this);
            break;
          }
        } catch (Exception e) {
          throw new GraniteConfigException("Could not load class: " + instanceOf, e);
        }
      }
    }

    if (NULL == element)
      element = factory.getInstanceForBean(scannedConfigurables, typeClass, (C) this);

    T previous = elementsByType.putIfAbsent(type, element);
    if (previous != null) element = previous;

    return (NULL == element ? null : element);
  }
}
Exemple #14
0
/** @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<String, Subscription> subscriptions =
      new ConcurrentHashMap<String, Subscription>();

  protected LinkedList<AsyncPublishedMessage> publishedQueue =
      new LinkedList<AsyncPublishedMessage>();
  protected final Lock publishedQueueLock = new ReentrantLock();

  protected LinkedList<AsyncMessage> receivedQueue = new LinkedList<AsyncMessage>();
  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<Subscription> 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<AsyncPublishedMessage> publishedCopy = null;

    publishedQueueLock.lock();
    try {
      if (publishedQueue.isEmpty()) return false;
      publishedCopy = publishedQueue;
      publishedQueue = new LinkedList<AsyncPublishedMessage>();
    } 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<AsyncMessage> 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<AsyncMessage>();
      } 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 (messages != null && 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() + "}";
  }
}