Package org.red5.server.so

Source Code of org.red5.server.so.SharedObjectScope

/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2014 by respective authors (see below). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.red5.server.so;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantLock;

import org.red5.server.BaseConnection;
import org.red5.server.api.IAttributeStore;
import org.red5.server.api.IContext;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.event.IEventListener;
import org.red5.server.api.persistence.IPersistenceStore;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.ScopeType;
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.so.ISharedObjectListener;
import org.red5.server.api.so.ISharedObjectSecurity;
import org.red5.server.api.so.ISharedObjectSecurityService;
import org.red5.server.api.statistics.ISharedObjectStatistics;
import org.red5.server.net.rtmp.status.StatusCodes;
import org.red5.server.scheduling.QuartzSchedulingService;
import org.red5.server.scope.BasicScope;
import org.red5.server.service.ReflectionUtils;
import org.red5.server.util.ScopeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Special scope for shared objects
*/
public class SharedObjectScope extends BasicScope implements ISharedObject,  StatusCodes {

  private Logger log = LoggerFactory.getLogger(SharedObjectScope.class);

  /**
   * Lock to synchronize shared object updates from multiple threads
   */
  private final ReentrantLock lock = new ReentrantLock();

  /**
   * Server-side listeners
   */
  private CopyOnWriteArraySet<ISharedObjectListener> serverListeners = new CopyOnWriteArraySet<ISharedObjectListener>();

  /**
   * Event handlers
   */
  private ConcurrentMap<String, Object> handlers = new ConcurrentHashMap<String, Object>(1, 0.9f, 1);

  /**
   * Security handlers
   */
  private CopyOnWriteArraySet<ISharedObjectSecurity> securityHandlers = new CopyOnWriteArraySet<ISharedObjectSecurity>();

  /**
   * Scoped shared object
   */
  protected volatile SharedObject so;

  /**
   * Time to linger before checking for disposal
   */
  private long lingerPeriod = 5000L;

  /**
   * Linger job name
   */
  private String lingerJobName;

  /**
   * Creates shared object with given parent scope, name, persistence flag
   * state and store object
   *
   * @param parent
   *            Parent scope
   * @param name
   *            Name
   * @param persistent
   *            Persistence flag state
   * @param store
   *            Persistence store
   */
  public SharedObjectScope(IScope parent, String name, boolean persistent, IPersistenceStore store) {
    super(parent, ScopeType.SHARED_OBJECT, name, persistent);
    // create shared object wrapper around the attributes
    String path = parent.getContextPath();
    if ("".equals(path) || path.charAt(0) != '/') {
      path = '/' + path;
    }
    log.trace("Path+name: {}/{}", path, name);
    // Load SO
    so = (SharedObject) store.load(ScopeType.SHARED_OBJECT + path + '/'
        + name);
    // Create if it doesn't exist
    if (so == null) {
      so = new SharedObject(name, path, persistent, store);
      // Save
      store.save(so);
    } else {
      // set path
      so.setPath(path);
    }
  }

  /** {@inheritDoc} */
  public void registerSharedObjectSecurity(ISharedObjectSecurity handler) {
    securityHandlers.add(handler);
  }

  /** {@inheritDoc} */
  public void unregisterSharedObjectSecurity(ISharedObjectSecurity handler) {
    securityHandlers.remove(handler);
  }

  /** {@inheritDoc} */
  public Set<ISharedObjectSecurity> getSharedObjectSecurity() {
    return Collections.unmodifiableSet(securityHandlers);
  }

  /** {@inheritDoc} */
  @Override
  public IPersistenceStore getStore() {
    return so.getStore();
  }

  /** {@inheritDoc} */
  @Override
  public String getName() {
    return so.getName();
  }

  /** {@inheritDoc} */
  @Override
  public String getPath() {
    return so.getPath();
  }

  public void setPath(String path) {
    so.setPath(path);
  }

  /** {@inheritDoc} */
  public boolean isPersistent() {
    return so.isPersistent();
  }

  /** {@inheritDoc} */
  public void beginUpdate() {
    // Make sure only one thread can update the SO
    lock.lock();
    so.beginUpdate();
  }

  /** {@inheritDoc} */
  public void beginUpdate(IEventListener listener) {
    // Make sure only one thread can update the SO
    lock.lock();
    // start updates
    so.beginUpdate(listener);
  }

  /** {@inheritDoc} */
  public void endUpdate() {
    // end update of SO
    try {
      so.endUpdate();
    } catch (Exception ex) {
      log.warn("Exception on so.endUpdate", ex);
    } finally {
      lock.unlock();
    }
  }

  /** {@inheritDoc} */
  public int getVersion() {
    return so.getVersion();
  }

  /** {@inheritDoc} */
  public void sendMessage(String handler, List<?> arguments) {
    if (so != null) {
      beginUpdate();
      try {
        so.sendMessage(handler, arguments);
      } catch (Exception ex) {
        log.warn("Exception on so.sendMessage", ex);
      } finally {
        endUpdate();
      }
      // Invoke method on registered handler
      String serviceName, serviceMethod;
      // Find out last dot position
      int dotPos = handler.lastIndexOf('.');
      // If any, split service name and service method name
      if (dotPos != -1) {
        serviceName = handler.substring(0, dotPos);
        serviceMethod = handler.substring(dotPos + 1);
      } else {
        // Otherwise only service method name is available
        serviceName = "";
        serviceMethod = handler;
      }
      // Get previously registered handler for service
      Object soHandler = getServiceHandler(serviceName);
      if (soHandler == null && hasParent()) {
        // No custom handler, check for service defined in the scope's context
        IContext context = getParent().getContext();
        String serviceId = null;
        try {
          // The bean must have a name of
          // "<SharedObjectName>.<DottedServiceName>.soservice"
          serviceId = so.getName() + '.' + serviceName + ".soservice";
          if (context.hasBean(serviceId)) {
            soHandler = context.getBean(serviceId);
          }
        } catch (Exception err) {
          log.debug("No such bean: {}", serviceId);
        }
      }
      // Once handler is found, find matching method
      if (soHandler != null) {
        // With exact params...
        Object[] methodResult = ReflectionUtils.findMethodWithExactParameters(soHandler, serviceMethod, arguments);
        // Or at least with suitable list params
        if (methodResult.length == 0 || methodResult[0] == null) {
          methodResult = ReflectionUtils.findMethodWithListParameters(soHandler, serviceMethod, arguments);
        }
        // If method is found...
        if (methodResult.length > 0 && methodResult[0] != null) {
          Method method = (Method) methodResult[0];
          Object[] params = (Object[]) methodResult[1];
          // ...try to invoke it and handle exceptions
          try {
            method.invoke(soHandler, params);
          } catch (Exception err) {
            log.error("Error while invoking method {} on shared object handler {}", new Object[] { serviceMethod, handler }, err);
          }
        }
      }
      // notify server listeners
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectSend(this, handler, arguments);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeAttribute(String name) {
    boolean success = false;
    // begin update of shared object
    beginUpdate();
    try {
      // try to remove attribute
      success = so.removeAttribute(name);
    } catch (Exception ex) {
      log.warn("Exception on so.removeAttribute", ex);
    } finally {
      // end update of SO
      endUpdate();
    }
    // notify listeners on success and return true
    if (success) {
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectDelete(this, name);
      }
    }
    return success;
  }

  /** {@inheritDoc} */
  @Override
  public void removeAttributes() {
    beginUpdate();
    try {
      // remove all attributes
      so.removeAttributes();
    } catch (Exception ex) {
      log.warn("Exception on so.removeAttributes", ex);
    } finally {
      endUpdate();
    }
    // notify listeners on attributes clear
    for (ISharedObjectListener listener : serverListeners) {
      listener.onSharedObjectClear(this);
    }
  }

  /** {@inheritDoc} */
  public int size() {
    return so != null ? so.getAttributeNames().size() : 0;
  }

  /** {@inheritDoc} */
  @Override
  public boolean addEventListener(IEventListener listener) {
    boolean result = super.addEventListener(listener) && so.register(listener);
    for (ISharedObjectListener soListener : serverListeners) {
      soListener.onSharedObjectConnect(this);
    }
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public boolean removeEventListener(IEventListener listener) {
    // remove the listener from the so
    so.unregister(listener);
    // if we have not been released by all that acquired then keep on
    // disconnection of the last listener
    if (so.isAcquired()) {
      log.debug("Shared object has been aquired so setting keep on disconnect");
      keepOnDisconnect = true;
    }
    // remove the listener
    boolean result = super.removeEventListener(listener);
    // notify other listeners that someone has stopped listening
    for (ISharedObjectListener soListener : serverListeners) {
      soListener.onSharedObjectDisconnect(this);
    }
    // check that linger job has be set
    if (lingerJobName == null) {
      // start a job to allow the so to linger for just a few ticks
      QuartzSchedulingService scheduler = (QuartzSchedulingService) getParent().getContext().getBean(QuartzSchedulingService.BEAN_NAME);
      IScheduledJob job = new IScheduledJob() {
        public void execute(ISchedulingService service) {
          if (so != null && !so.isClosed()) {
            so.checkRelease();
          }
        }
      };
      lingerJobName = scheduler.addScheduledOnceJob(lingerPeriod, job);
    }
    // check acquire
    if (so.isClosed()) {
      log.debug("Removing scope: {}", this);
      getParent().removeChildScope(this);
    }
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public boolean hasAttribute(String name) {
    return so.hasAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Object getAttribute(String name) {
    return so.getAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Object getAttribute(String name, Object value) {
    beginUpdate();
    try {
      return so.getAttribute(name, value);
    } catch (Exception ex) {
      log.warn("Exception on so.getAttribute", ex);
    } finally {
      endUpdate();
    }
    return null;
  }

  /** {@inheritDoc} */
  @Override
  public Map<String, Object> getAttributes() {
    return so.getAttributes();
  }

  /** {@inheritDoc} */
  @Override
  public Set<String> getAttributeNames() {
    return so.getAttributeNames();
  }

  /** {@inheritDoc} */
  @Override
  public Boolean getBoolAttribute(String name) {
    return so.getBoolAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Byte getByteAttribute(String name) {
    return so.getByteAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Double getDoubleAttribute(String name) {
    return so.getDoubleAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Integer getIntAttribute(String name) {
    return so.getIntAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public List<?> getListAttribute(String name) {
    return so.getListAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Long getLongAttribute(String name) {
    return so.getLongAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Map<?, ?> getMapAttribute(String name) {
    return so.getMapAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Set<?> getSetAttribute(String name) {
    return so.getSetAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public Short getShortAttribute(String name) {
    return so.getShortAttribute(name);
  }

  /** {@inheritDoc} */
  @Override
  public String getStringAttribute(String name) {
    return so.getStringAttribute(name);
  }

  /** {@inheritDoc} */
  public Map<String, Object> getData() {
    return so.getData();
  }

  /**
   * Return security handlers for this shared object or <code>null</code> if none are found.
   *
   * @return set of security handlers
   */
  private Set<ISharedObjectSecurity> getSecurityHandlers() {
    ISharedObjectSecurityService security = (ISharedObjectSecurityService) ScopeUtils.getScopeService(getParent(), ISharedObjectSecurityService.class);
    if (security == null) {
      return null;
    }
    return security.getSharedObjectSecurity();
  }

  /**
   * Call handlers and check if connection to the existing SO is allowed.
   *
   * @return is connection allowed
   */
  protected boolean isConnectionAllowed() {
    // Check internal handlers first
    for (ISharedObjectSecurity handler : securityHandlers) {
      if (!handler.isConnectionAllowed(this)) {
        return false;
      }
    }
    // Check global SO handlers next
    final Set<ISharedObjectSecurity> handlers = getSecurityHandlers();
    if (handlers == null) {
      return true;
    }
    for (ISharedObjectSecurity handler : handlers) {
      if (!handler.isConnectionAllowed(this)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Call handlers and check if writing to the SO is allowed.
   *
   * @param key
   *            key
   * @param value
   *            value
   * @return is write allowed
   */
  protected boolean isWriteAllowed(String key, Object value) {
    // check internal handlers first
    for (ISharedObjectSecurity handler : securityHandlers) {
      if (!handler.isWriteAllowed(this, key, value)) {
        return false;
      }
    }
    // check global SO handlers next
    final Set<ISharedObjectSecurity> handlers = getSecurityHandlers();
    if (handlers == null) {
      return true;
    }
    for (ISharedObjectSecurity handler : handlers) {
      if (!handler.isWriteAllowed(this, key, value)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Call handlers and check if deleting a property from the SO is allowed.
   *
   * @param key
   *            key
   * @return is delete allowed
   */
  protected boolean isDeleteAllowed(String key) {
    // check internal handlers first
    for (ISharedObjectSecurity handler : securityHandlers) {
      if (!handler.isDeleteAllowed(this, key)) {
        return false;
      }
    }
    // check global SO handlers next
    final Set<ISharedObjectSecurity> handlers = getSecurityHandlers();
    if (handlers == null) {
      return true;
    }
    for (ISharedObjectSecurity handler : handlers) {
      if (!handler.isDeleteAllowed(this, key)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Call handlers and check if sending a message to the clients connected to
   * the SO is allowed.
   *
   * @param message
   *            message
   * @param arguments
   *            arguments
   * @return is send allowed
   */
  protected boolean isSendAllowed(String message, List<?> arguments) {
    // check internal handlers first
    for (ISharedObjectSecurity handler : securityHandlers) {
      if (!handler.isSendAllowed(this, message, arguments)) {
        return false;
      }
    }
    // check global SO handlers next
    final Set<ISharedObjectSecurity> handlers = getSecurityHandlers();
    if (handlers == null) {
      return true;
    }
    for (ISharedObjectSecurity handler : handlers) {
      if (!handler.isSendAllowed(this, message, arguments)) {
        return false;
      }
    }
    return true;
  }

  /** {@inheritDoc} */
  @Override
  public void dispatchEvent(IEvent e) {
    if (e instanceof ISharedObjectMessage
        || e.getType() == IEvent.Type.SHARED_OBJECT) {
      ISharedObjectMessage msg = (ISharedObjectMessage) e;
      if (msg.hasSource()) {
        beginUpdate(msg.getSource());
      } else {
        beginUpdate();
      }
      try {
        for (ISharedObjectEvent event : msg.getEvents()) {
          final String key = event.getKey();
          switch (event.getType()) {
          case SERVER_CONNECT:
            if (!isConnectionAllowed()) {
              so.returnError(SO_NO_READ_ACCESS);
            } else if (msg.hasSource()) {
              IEventListener source = msg.getSource();
              if (source instanceof BaseConnection) {
                ((BaseConnection) source)
                    .registerBasicScope(this);
              } else {
                addEventListener(source);
              }
            }
            break;
          case SERVER_DISCONNECT:
            if (msg.hasSource()) {
              IEventListener source = msg.getSource();
              if (source instanceof BaseConnection) {
                ((BaseConnection) source)
                    .unregisterBasicScope(this);
              } else {
                removeEventListener(source);
              }
            }
            break;
          case SERVER_SET_ATTRIBUTE:
            final Object value = event.getValue();
            if (!isWriteAllowed(key, value)) {
              so.returnAttributeValue(key);
              so.returnError(SO_NO_WRITE_ACCESS);
            } else {
              setAttribute(key, value);
            }
            break;
          case SERVER_DELETE_ATTRIBUTE:
            if (!isDeleteAllowed(key)) {
              so.returnAttributeValue(key);
              so.returnError(SO_NO_WRITE_ACCESS);
            } else {
              removeAttribute(key);
            }
            break;
          case SERVER_SEND_MESSAGE:
            final List<?> arguments = (List<?>) event.getValue();
            // Ignore request silently if not allowed
            if (isSendAllowed(key, arguments)) {
              sendMessage(key, arguments);
            } else {
              log.debug("Send is not allowed for {}", key);
            }
            break;
          default:
            log.warn("Unknown SO event: {}", event.getType());
          }
        }
      } catch (Exception ex) {
        log.warn("Exception on dispatchEvent", ex);
      } finally {
        endUpdate();
      }
    } else {
      // don't know how to handle this event.
      super.dispatchEvent(e);
    }
  }

  /** {@inheritDoc} */
  @Override
  public boolean setAttribute(String name, Object value) {
    boolean success = false;
    beginUpdate();
    try {
      success = so.setAttribute(name, value);
    } catch (Exception ex) {
      log.warn("Exception on so.setAttribute", ex);
    } finally {
      endUpdate();
    }
    if (success) {
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectUpdate(this, name, value);
      }
    }
    return success;
  }

  /** {@inheritDoc} */
  @Override
  public boolean setAttributes(IAttributeStore values) {
    boolean success = false;
    beginUpdate();
    try {
      success = so.setAttributes(values);
    } catch (Exception ex) {
      log.warn("Exception on so.setAttributes", ex);
    } finally {
      endUpdate();
    }
    if (success) {
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectUpdate(this, values);
      }
    }
    return success;
  }

  /** {@inheritDoc} */
  @Override
  public boolean setAttributes(Map<String, Object> values) {
    boolean success = false;
    beginUpdate();
    try {
      success = so.setAttributes(values);
    } catch (Exception ex) {
      log.warn("Exception on so.setAttributes", ex);
    } finally {
      endUpdate();
    }
    if (success) {
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectUpdate(this, values);
      }
    }
    return success;
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return "SharedObjectScope: " + getName();
  }

  /** {@inheritDoc} */
  public void addSharedObjectListener(ISharedObjectListener listener) {
    serverListeners.add(listener);
  }

  /** {@inheritDoc} */
  public void removeSharedObjectListener(ISharedObjectListener listener) {
    serverListeners.remove(listener);
  }

  /** {@inheritDoc} */
  public void registerServiceHandler(Object handler) {
    registerServiceHandler("", handler);
  }

  /** {@inheritDoc} */
  public void registerServiceHandler(String name, Object handler) {
    if (name == null) {
      name = "";
    }
    handlers.put(name, handler);
  }

  public void unregisterServiceHandler() {
    unregisterServiceHandler("");
  }

  /** {@inheritDoc} */
  public void unregisterServiceHandler(String name) {
    if (name == null) {
      name = "";
    }
    handlers.remove(name);
  }

  /** {@inheritDoc} */
  public Object getServiceHandler(String name) {
    if (name == null) {
      name = "";
    }
    return handlers.get(name);
  }

  /** {@inheritDoc} */
  public Set<String> getServiceHandlerNames() {
    return Collections.unmodifiableSet(handlers.keySet());
  }

  /**
   * Locks the shared object instance. Prevents any changes to this object by
   * clients until the SharedObject.unlock() method is called.
   */
  public void lock() {
    lock.lock();
  }

  /**
   * Unlocks a shared object instance that was locked with
   * SharedObject.lock().
   */
  public void unlock() {
    lock.unlock();
  }

  /**
   * Returns the locked state of this SharedObject.
   *
   * @return true if in a locked state; false otherwise
   */
  public boolean isLocked() {
    return lock.isLocked();
  }

  /** {@inheritDoc} */
  public boolean clear() {
    boolean success = false;
    beginUpdate();
    try {
      success = so.clear();
    } catch (Exception ex) {
      log.warn("Exception on so.clear", ex);
    } finally {
      endUpdate();
    }
    if (success) {
      for (ISharedObjectListener listener : serverListeners) {
        listener.onSharedObjectClear(this);
      }
    }
    return success;
  }

  /** {@inheritDoc} */
  public void close() {
    // close the internal SO
    so.close();
    // remove from the parent
    parent.removeChildScope(this);
    // clear the reference
    so = null;
  }

  /** {@inheritDoc} */
  public void acquire() {
    so.acquire();
  }

  /** {@inheritDoc} */
  public boolean isAcquired() {
    return so.isAcquired();
  }

  /** {@inheritDoc} */
  public void release() {
    so.release();
  }

  /** {@inheritDoc} */
  public ISharedObjectStatistics getStatistics() {
    return so;
  }

}
TOP

Related Classes of org.red5.server.so.SharedObjectScope

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.