Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* The MIT License
* Copyright (c) 2015 Alexander Sova ([email protected])
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.codeminders.socketio.protocol;
import com.codeminders.socketio.server.SocketIOProtocolException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.text.ParsePosition;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Implementation of Socket.IO Protocol version 4
*
* @author Alexander Sova ([email protected])
*/
public final class SocketIOProtocol
{
private static final Logger LOGGER = Logger.getLogger(SocketIOProtocol.class.getName());
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
public static final String DEFAULT_NAMESPACE = "/";
private static final String ATTACHMENTS_DELIMITER = "-";
private static final String NAMESPACE_PREFIX = "/";
static final String NAMESPACE_DELIMITER = ",";
private SocketIOProtocol()
{
}
private static class EmptyPacket extends SocketIOPacket
{
public EmptyPacket(Type type, String ns)
{
super(type, ns);
}
@Override
protected String encodeArgs()
{
return "";
}
}
private static class PlainACKPacket extends ACKPacket
{
public PlainACKPacket(int id, String ns, Object[] args)
{
super(Type.ACK, id, ns, args);
}
}
private static class PlainEventPacket extends EventPacket
{
public PlainEventPacket(int id, String ns, String name, Object[] args)
{
super(Type.EVENT, id, ns, name, args);
}
}
public static SocketIOPacket decode(String data)
throws SocketIOProtocolException
{
assert (data != null);
if (data.length() < 1)
throw new SocketIOProtocolException("Empty SIO packet");
try
{
ParsePosition pos = new ParsePosition(0);
SocketIOPacket.Type type = decodePacketType(data, pos);
int attachments = 0;
if (type == SocketIOPacket.Type.BINARY_ACK || type == SocketIOPacket.Type.BINARY_EVENT)
attachments = decodeAttachments(data, pos);
String ns = decodeNamespace(data, pos);
int packet_id = decodePacketId(data, pos);
Object json = decodeArgs(data, pos);
List args = null;
String eventName = "";
if (type == SocketIOPacket.Type.EVENT ||
type == SocketIOPacket.Type.BINARY_EVENT ||
type == SocketIOPacket.Type.ACK ||
type == SocketIOPacket.Type.BINARY_ACK)
{
if (!(json instanceof List))
throw new SocketIOProtocolException("Array payload is expected");
args = (List)json;
if (type == SocketIOPacket.Type.EVENT || type == SocketIOPacket.Type.BINARY_EVENT)
{
if (args.size() == 0)
throw new SocketIOProtocolException("Missing event name");
eventName = args.get(0).toString();
args.remove(0);
}
}
switch (type)
{
case CONNECT:
return createConnectPacket(ns);
case DISCONNECT:
return createDisconnectPacket(ns);
case EVENT:
return new PlainEventPacket(packet_id, ns, eventName, args.toArray());
case ACK:
return new PlainACKPacket(packet_id, ns, args.toArray());
case ERROR:
return createErrorPacket(ns, json);
case BINARY_EVENT:
return new BinaryEventPacket(packet_id, ns, eventName, args.toArray(), attachments);
case BINARY_ACK:
assert (args != null); //just to make IDEA to shut up about possible NPE
return new BinaryACKPacket(packet_id, ns, args.toArray(), attachments);
default:
throw new SocketIOProtocolException("Unsupported packet type " + type);
}
}
catch (NumberFormatException | SocketIOProtocolException e)
{
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, "Invalid SIO packet: " + data, e);
throw new SocketIOProtocolException("Invalid SIO packet: " + data, e);
}
}
public static SocketIOPacket createErrorPacket(String namespace, final Object args)
{
return new SocketIOPacket(SocketIOPacket.Type.ERROR, namespace)
{
@Override
protected String encodeArgs()
throws SocketIOProtocolException
{
return toJSON(args);
}
};
}
/*
* This method could create either EventPacket or BinaryEventPacket based
* on the content of args parameter.
* If args has any InputStream inside then SockeIOBinaryEventPacket will be created
*/
public static SocketIOPacket createEventPacket(int packet_id, String ns, String name, Object[] args)
{
if (hasBinary(args))
return new BinaryEventPacket(packet_id, ns, name, args);
else
return new PlainEventPacket(packet_id, ns, name, args);
}
public static SocketIOPacket createACKPacket(int id, String ns, Object[] args)
{
if (hasBinary(args))
return new BinaryACKPacket(id, ns, args);
else
return new PlainACKPacket(id, ns, args);
}
public static SocketIOPacket createDisconnectPacket(String ns)
{
return new EmptyPacket(SocketIOPacket.Type.DISCONNECT, ns);
}
public static SocketIOPacket createConnectPacket(String ns)
{
return new EmptyPacket(SocketIOPacket.Type.CONNECT, ns);
}
static String toJSON(Object o)
throws SocketIOProtocolException
{
try
{
return mapper.writeValueAsString(o);
}
catch (JsonProcessingException e)
{
throw new SocketIOProtocolException("Cannot convert object to JSON", e);
}
}
static Object fromJSON(String s)
throws SocketIOProtocolException
{
try
{
if(s == null || s.isEmpty())
return null;
return mapper.readValue(s, Object.class);
}
catch (IOException e)
{
throw new SocketIOProtocolException("Cannot parse JSON", e);
}
}
static String decodeNamespace(String data, ParsePosition pos)
{
String ns = DEFAULT_NAMESPACE;
if (data.startsWith(NAMESPACE_PREFIX, pos.getIndex()))
{
int idx = data.indexOf(NAMESPACE_DELIMITER, pos.getIndex());
if (idx < 0)
{
ns = data.substring(pos.getIndex());
pos.setIndex(data.length());
}
else
{
ns = data.substring(pos.getIndex(), idx);
pos.setIndex(idx + 1);
}
}
return ns;
}
static int decodeAttachments(String data, ParsePosition pos)
throws SocketIOProtocolException
{
Number n = new DecimalFormat("#").parse(data, pos);
if (n == null || n.intValue() == 0)
throw new SocketIOProtocolException("No attachments defined in BINARY packet: " + data);
pos.setIndex(pos.getIndex() + 1); //skipping '-' delimiter
return n.intValue();
}
static int decodePacketId(String data, ParsePosition pos)
{
Number id = new DecimalFormat("#").parse(data, pos);
if (id == null)
return -1;
return id.intValue();
}
static SocketIOPacket.Type decodePacketType(String data, ParsePosition pos)
throws SocketIOProtocolException
{
int idx = pos.getIndex();
SocketIOPacket.Type type = SocketIOPacket.Type.fromInt(Integer.parseInt(data.substring(idx, idx + 1)));
pos.setIndex(idx + 1);
return type;
}
//TODO: pass what type (Array, Map, Object) is expected?
static Object decodeArgs(String data, ParsePosition pos)
throws SocketIOProtocolException
{
Object json = fromJSON(data.substring(pos.getIndex()));
pos.setIndex(data.length());
return json;
}
static String encodeNamespace(String namespace, boolean addDelimiter)
{
if(namespace.equals(SocketIOProtocol.DEFAULT_NAMESPACE))
return "";
return namespace + (addDelimiter ? SocketIOProtocol.NAMESPACE_DELIMITER : "");
}
static String encodeAttachments(int size)
{
return String.valueOf(size) + ATTACHMENTS_DELIMITER;
}
private static boolean hasBinary(Object args)
{
if (args.getClass().isArray())
{
for (Object o : (Object[]) args)
if (hasBinary(o))
return true;
}
else if (args instanceof Map)
{
for (Object o : ((Map) args).values())
if (hasBinary(o))
return true;
}
else
{
return (args instanceof InputStream);
}
return false;
}
/**
* Extracts binary objects (InputStream) from JSON and replaces it with
* placeholder objects {@code {"_placeholder":true,"num":1} }
* This method to be used before sending the packet
*
* @param json JSON object
* @param attachments container for extracted binary object
* @return modified JSON object
*/
@SuppressWarnings("unchecked")
static Object extractBinaryObjects(Object json, List attachments)
{
//TODO: what about Collection? for now only array is supported
if (json.getClass().isArray())
{
ArrayList