/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 io.undertow.websockets.jsr; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EncodeException; import javax.websocket.Encoder; import io.undertow.servlet.api.InstanceHandle; /** * Manages all encoders and decoders for an endpoint instance * * @author Stuart Douglas */ public class Encoding implements Closeable { private final Map<Class<?>, List<InstanceHandle<? extends Encoder>>> binaryEncoders; private final Map<Class<?>, List<InstanceHandle<? extends Decoder>>> binaryDecoders; private final Map<Class<?>, List<InstanceHandle<? extends Encoder>>> textEncoders; private final Map<Class<?>, List<InstanceHandle<? extends Decoder>>> textDecoders; public Encoding(final Map<Class<?>, List<InstanceHandle<? extends Encoder>>> binaryEncoders, final Map<Class<?>, List<InstanceHandle<? extends Decoder>>> binaryDecoders, final Map<Class<?>, List<InstanceHandle<? extends Encoder>>> textEncoders, final Map<Class<?>, List<InstanceHandle<? extends Decoder>>> textDecoders) { this.binaryEncoders = binaryEncoders; this.binaryDecoders = binaryDecoders; this.textEncoders = textEncoders; this.textDecoders = textDecoders; } public boolean canEncodeText(final Class<?> type) { if(textEncoders.containsKey(type)) { return true; } for(Class<?> key : textEncoders.keySet()) { if(key.isAssignableFrom(type)) { return true; } } if (EncodingFactory.isPrimitiveOrBoxed(type)) { Class<?> primType = boxedType(type); return !binaryEncoders.containsKey(primType) && !binaryEncoders.containsKey(Object.class); //don't use a built in coding if a user supplied binary one is present } return false; } public boolean canDecodeText(final Class<?> type) { if(textDecoders.containsKey(type)) { return true; } if (EncodingFactory.isPrimitiveOrBoxed(type)) { Class<?> primType = boxedType(type); return !binaryDecoders.containsKey(primType) && !binaryEncoders.containsKey(Object.class); //don't use a built in coding if a user supplied binary one is present } return false; } public boolean canEncodeBinary(final Class<?> type) { if(binaryEncoders.containsKey(type)) { return true; } for(Class<?> key : binaryEncoders.keySet()) { if(key.isAssignableFrom(type)) { return true; } } return false; } public boolean canDecodeBinary(final Class<?> type) { return binaryDecoders.containsKey(type); } public Object decodeText(final Class<?> targetType, final String message) throws DecodeException { if (EncodingFactory.isPrimitiveOrBoxed(targetType)) { return decodePrimitive(targetType, message); } List<InstanceHandle<? extends Decoder>> decoders = textDecoders.get(targetType); if (decoders != null) { for (InstanceHandle<? extends Decoder> decoderHandle : decoders) { Decoder decoder = decoderHandle.getInstance(); if (decoder instanceof Decoder.Text) { if (((Decoder.Text) decoder).willDecode(message)) { return ((Decoder.Text) decoder).decode(message); } } else { try { return ((Decoder.TextStream) decoder).decode(new StringReader(message)); } catch (IOException e) { throw new DecodeException(message, "Could not decode string", e); } } } } throw new DecodeException(message, "Could not decode string"); } private Object decodePrimitive(final Class<?> targetType, final String message) throws DecodeException { if (targetType == Boolean.class || targetType == boolean.class) { return Boolean.valueOf(message); } else if (targetType == Character.class || targetType == char.class) { return message.charAt(0); } else if (targetType == Byte.class || targetType == byte.class) { return Byte.valueOf(message); } else if (targetType == Short.class || targetType == short.class) { return Short.valueOf(message); } else if (targetType == Integer.class || targetType == int.class) { return Integer.valueOf(message); } else if (targetType == Long.class || targetType == long.class) { return Long.valueOf(message); } else if (targetType == Float.class || targetType == float.class) { return Float.valueOf(message); } else if (targetType == Double.class || targetType == double.class) { return Double.valueOf(message); } return null; // impossible } public Object decodeBinary(final Class<?> targetType, final byte[] bytes) throws DecodeException { List<InstanceHandle<? extends Decoder>> decoders = binaryDecoders.get(targetType); if (decoders != null) { for (InstanceHandle<? extends Decoder> decoderHandle : decoders) { Decoder decoder = decoderHandle.getInstance(); if (decoder instanceof Decoder.Binary) { if (((Decoder.Binary) decoder).willDecode(ByteBuffer.wrap(bytes))) { return ((Decoder.Binary) decoder).decode(ByteBuffer.wrap(bytes)); } } else { try { return ((Decoder.BinaryStream) decoder).decode(new ByteArrayInputStream(bytes)); } catch (IOException e) { throw new DecodeException(ByteBuffer.wrap(bytes), "Could not decode binary", e); } } } } throw new DecodeException(ByteBuffer.wrap(bytes), "Could not decode binary"); } public String encodeText(final Object o) throws EncodeException { List<InstanceHandle<? extends Encoder>> encoders = textEncoders.get(o.getClass()); if(encoders == null) { for(Map.Entry<Class<?>, List<InstanceHandle<? extends Encoder>>> entry : textEncoders.entrySet()) { if(entry.getKey().isAssignableFrom(o.getClass())) { encoders = entry.getValue(); break; } } } if (encoders != null) { for (InstanceHandle<? extends Encoder> decoderHandle : encoders) { Encoder decoder = decoderHandle.getInstance(); if (decoder instanceof Encoder.Text) { return ((Encoder.Text) decoder).encode(o); } else { try { StringWriter out = new StringWriter(); ((Encoder.TextStream) decoder).encode(o, out); return out.toString(); } catch (IOException e) { throw new EncodeException(o, "Could not encode text", e); } } } } if (EncodingFactory.isPrimitiveOrBoxed(o.getClass())) { return o.toString(); } throw new EncodeException(o, "Could not encode text"); } public ByteBuffer encodeBinary(final Object o) throws EncodeException { List<InstanceHandle<? extends Encoder>> encoders = binaryEncoders.get(o.getClass()); if(encoders == null) { for(Map.Entry<Class<?>, List<InstanceHandle<? extends Encoder>>> entry : binaryEncoders.entrySet()) { if(entry.getKey().isAssignableFrom(o.getClass())) { encoders = entry.getValue(); break; } } } if (encoders != null) { for (InstanceHandle<? extends Encoder> decoderHandle : encoders) { Encoder decoder = decoderHandle.getInstance(); if (decoder instanceof Encoder.Binary) { return ((Encoder.Binary) decoder).encode(o); } else { try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ((Encoder.BinaryStream) decoder).encode(o, out); return ByteBuffer.wrap(out.toByteArray()); } catch (IOException e) { throw new EncodeException(o, "Could not encode binary", e); } } } } throw new EncodeException(o, "Could not encode binary"); } @Override public void close() { for (Map.Entry<Class<?>, List<InstanceHandle<? extends Decoder>>> entry : binaryDecoders.entrySet()) { for (InstanceHandle<? extends Decoder> val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry<Class<?>, List<InstanceHandle<? extends Decoder>>> entry : textDecoders.entrySet()) { for (InstanceHandle<? extends Decoder> val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry<Class<?>, List<InstanceHandle<? extends Encoder>>> entry : binaryEncoders.entrySet()) { for (InstanceHandle<? extends Encoder> val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } for (Map.Entry<Class<?>, List<InstanceHandle<? extends Encoder>>> entry : textEncoders.entrySet()) { for (InstanceHandle<? extends Encoder> val : entry.getValue()) { val.getInstance().destroy(); val.release(); } } } private static Class<?> boxedType(final Class<?> targetType) { if (targetType == Boolean.class || targetType == boolean.class) { return Boolean.class; } else if (targetType == Character.class || targetType == char.class) { return Character.class; } else if (targetType == Byte.class || targetType == byte.class) { return Byte.class; } else if (targetType == Short.class || targetType == short.class) { return Short.class; } else if (targetType == Integer.class || targetType == int.class) { return Integer.class; } else if (targetType == Long.class || targetType == long.class) { return Long.class; } else if (targetType == Float.class || targetType == float.class) { return Float.class; } else if (targetType == Double.class || targetType == double.class) { return Double.class; } return targetType; } }