package org.red5.server.so; /* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright (c) 2006-2010 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.red5.server.api.event.IEventListener; import org.red5.server.net.rtmp.event.IRTMPEvent; import org.red5.server.net.rtmp.message.SharedObjectTypeMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.flazr.amf.Amf0Value; import com.flazr.rtmp.RtmpHeader; import com.flazr.rtmp.message.AbstractMessage; import com.flazr.rtmp.message.MessageType; /** * Shared object event */ public class SharedObjectMessage extends AbstractMessage implements ISharedObjectMessage, IRTMPEvent { private static final Logger logger = LoggerFactory.getLogger(SharedObjectMessage.class); private static final long serialVersionUID = -8128704039659990049L; /** * SO event name */ private String name; /** * SO events chain */ private ConcurrentLinkedQueue<ISharedObjectEvent> events; /** * SO version, used for synchronization purposes */ private int version; /** * Whether SO persistent */ private boolean persistent; public SharedObjectMessage() { } /** * Creates Shared Object event with given name, version and persistence flag * * @param name Event name * @param version SO version * @param persistent SO persistence flag */ public SharedObjectMessage(String name, int version, boolean persistent) { this(null, name, version, persistent); } /** * Creates Shared Object event with given listener, name, SO version and * persistence flag * * @param source Event listener * @param name Event name * @param version SO version * @param persistent SO persistence flag */ public SharedObjectMessage(IEventListener source, String name, int version, boolean persistent) { this.name = name; this.version = version; this.persistent = persistent; this.events = new ConcurrentLinkedQueue<ISharedObjectEvent>(); } public SharedObjectMessage(RtmpHeader header, ChannelBuffer in) { super(header, in); } /** {@inheritDoc} */ public int getVersion() { return version; } /** * Setter for version * * @param version * New version */ protected void setVersion(int version) { this.version = version; } /** {@inheritDoc} */ public String getName() { return name; } /** * Setter for name * * @param name * Event name */ protected void setName(String name) { this.name = name; } /** {@inheritDoc} */ public boolean isPersistent() { return persistent; } /** * Setter for persistence flag * * @param persistent * Persistence flag */ protected void setIsPersistent(boolean persistent) { this.persistent = persistent; } /** {@inheritDoc} */ public void addEvent(ISharedObjectEvent event) { events.add(event); } public void addEvents(List<ISharedObjectEvent> events) { this.events.addAll(events); } public void addEvents(Queue<ISharedObjectEvent> events) { this.events.addAll(events); } /** {@inheritDoc} */ public ConcurrentLinkedQueue<ISharedObjectEvent> getEvents() { return events; } /** {@inheritDoc} */ public void addEvent(ISharedObjectEvent.Type type, String key, Object value) { events.add(new SharedObjectEvent(type, key, value)); } /** {@inheritDoc} */ public void clear() { events.clear(); } /** {@inheritDoc} */ public boolean isEmpty() { return events.isEmpty(); } /** {@inheritDoc} */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("SharedObjectMessage: ").append(name).append(" { "); final Iterator<ISharedObjectEvent> it = events.iterator(); while (it.hasNext()) { sb.append(it.next()); if (it.hasNext()) { sb.append(" , "); } } sb.append(" } "); return sb.toString(); } @Override public MessageType getMessageType() { return MessageType.SHARED_OBJECT_AMF0; } /** * Encode a string without the string type byte * @param out * @param s */ void encodeString(ChannelBuffer out, String s) { out.writeShort((short) s.length()); out.writeBytes(s.getBytes()); } @Override public ChannelBuffer encode() { ChannelBuffer out = ChannelBuffers.dynamicBuffer(); encodeString(out, name); // SO version out.writeInt(version); // Encoding (this always seems to be 2 for persistent shared objects) out.writeInt(persistent? 2 : 0); // unknown field out.writeInt(0); int mark, len; for (ISharedObjectEvent event : events) { byte type = SharedObjectTypeMapping.toByte(event.getType()); switch (event.getType()) { case SERVER_CONNECT: case SERVER_DISCONNECT: case CLIENT_INITIAL_DATA: case CLIENT_CLEAR_DATA: out.writeByte(type); out.writeInt(0); break; case SERVER_DELETE_ATTRIBUTE: case CLIENT_DELETE_DATA: case CLIENT_UPDATE_ATTRIBUTE: out.writeByte(type); mark = out.writerIndex(); out.writeInt(0); // we will be back encodeString(out, event.getKey()); len = out.writerIndex() - mark - 4; out.markWriterIndex(); out.writerIndex(mark); out.writeInt(len); out.resetWriterIndex(); // for some reason, it's needed to write an integer at the end out.writeInt(0); break; case SERVER_SET_ATTRIBUTE: case CLIENT_UPDATE_DATA: if (event.getKey() == null) { // Update multiple attributes in one request Map<?, ?> initialData = (Map<?, ?>) event.getValue(); for (Object o : initialData.keySet()) { out.writeByte(type); mark = out.writerIndex(); out.writeInt(0); // we will be back String key = (String) o; encodeString(out, key); Amf0Value.encode(out, initialData.get(key)); len = out.writerIndex() - mark - 4; out.writerIndex(mark); out.writeInt(len); } } else { out.writeByte(type); mark = out.writerIndex(); out.writeInt(0); // we will be back encodeString(out, event.getKey()); Amf0Value.encode(out, event.getValue()); out.markWriterIndex(); len = out.writerIndex() - mark - 4; out.writerIndex(mark); out.writeInt(len); out.resetWriterIndex(); out.writeInt(0); } break; case CLIENT_SEND_MESSAGE: case SERVER_SEND_MESSAGE: // Send method name and value out.writeByte(type); mark = out.writerIndex(); out.writeInt(0); // we will be back // Serialize name of the handler to call... Amf0Value.encode(out, event.getKey()); // ...and the arguments for (Object arg : (List<?>) event.getValue()) { Amf0Value.encode(out, arg); } out.markWriterIndex(); len = out.writerIndex() - mark - 4; out.writerIndex(mark); out.writeInt(len); out.resetWriterIndex(); out.writeInt(0); break; case CLIENT_STATUS: out.writeByte(type); final String status = event.getKey(); final String message = (String) event.getValue(); out.writeInt(message.length() + status.length() + 4); encodeString(out, message); encodeString(out, status); break; default: logger.error("Unknown event " + event.getType()); out.writeByte(type); mark = out.writerIndex(); out.writeInt(0); // we will be back encodeString(out, event.getKey()); Amf0Value.encode(out, event.getValue()); len = out.writerIndex() - mark - 4; out.writerIndex(mark); out.writeInt(len); break; } } return out; } /** * Read a string without the string type byte * @param in * @return a decoded string */ String decodeString(ChannelBuffer in) { int length = in.readShort(); byte[] str = new byte[length]; in.readBytes(str); return new String(str); } @Override public void decode(ChannelBuffer in) { name = decodeString(in); version = in.readInt(); persistent = in.readInt() == 2; in.skipBytes(4); if (events == null) events = new ConcurrentLinkedQueue<ISharedObjectEvent>(); while (in.readableBytes() > 0) { ISharedObjectEvent.Type type = SharedObjectTypeMapping.toType(in.readByte()); if (type == null) { in.skipBytes(in.readableBytes()); continue; } String key = null; Object value = null; int length = in.readInt(); if (type == ISharedObjectEvent.Type.CLIENT_STATUS) { // Status code key = decodeString(in); // Status level value = decodeString(in); } else if (type == ISharedObjectEvent.Type.CLIENT_UPDATE_DATA) { key = null; // Map containing new attribute values final Map<String, Object> map = new HashMap<String, Object>(); final int start = in.readerIndex(); while (in.readerIndex() - start < length) { String tmp = decodeString(in); map.put(tmp, Amf0Value.decode(in)); } value = map; } else if (type != ISharedObjectEvent.Type.SERVER_SEND_MESSAGE && type != ISharedObjectEvent.Type.CLIENT_SEND_MESSAGE) { if (length > 0) { key = decodeString(in); if (length > key.length() + 2) { value = Amf0Value.decode(in); } } } else { final int start = in.readerIndex(); // the "send" event seems to encode the handler name // as complete AMF string including the string type byte key = (String) Amf0Value.decode(in); // read parameters final List<Object> list = new LinkedList<Object>(); // while loop changed for JIRA CODECS-9 while (in.readerIndex() - start < length) { list.add(Amf0Value.decode(in)); } value = list; } addEvent(type, key, value); } } /* @SuppressWarnings({ "unchecked" }) @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); name = (String) in.readObject(); version = in.readInt(); persistent = in.readBoolean(); Object o = in.readObject(); if (o != null && o instanceof ConcurrentLinkedQueue) { events = (ConcurrentLinkedQueue<ISharedObjectEvent>) o; } } @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeObject(name); out.writeInt(version); out.writeBoolean(persistent); out.writeObject(events); } */ @Override public byte getDataType() { // TODO Auto-generated method stub return 0; } @Override public byte getSourceType() { // TODO Auto-generated method stub return 0; } @Override public int getTimestamp() { // TODO Auto-generated method stub return 0; } @Override public void release() { // TODO Auto-generated method stub } @Override public void retain() { // TODO Auto-generated method stub } @Override public void setHeader(RtmpHeader header) { // TODO Auto-generated method stub } @Override public void setSource(IEventListener source) { // TODO Auto-generated method stub } @Override public void setSourceType(byte sourceType) { // TODO Auto-generated method stub } @Override public void setTimestamp(int timestamp) { // TODO Auto-generated method stub } @Override public Object getObject() { // TODO Auto-generated method stub return null; } @Override public IEventListener getSource() { // TODO Auto-generated method stub return null; } @Override public Type getType() { return Type.SHARED_OBJECT; } @Override public boolean hasSource() { // TODO Auto-generated method stub return false; } }