package net.unit8.wscl;

import net.unit8.wscl.dto.ResourceRequest;
import net.unit8.wscl.dto.ResourceResponse;
import net.unit8.wscl.handler.ResourceRequestWriteHandler;
import net.unit8.wscl.handler.ResourceResponseReadHandler;
import net.unit8.wscl.util.FressianUtils;
import net.unit8.wscl.util.PropertyUtils;
import org.fressian.FressianReader;
import org.fressian.FressianWriter;
import org.fressian.handlers.ILookup;
import org.fressian.handlers.ReadHandler;
import org.fressian.handlers.WriteHandler;
import org.fressian.impl.ByteBufferInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author kawasima
 */
@ClientEndpoint
public class ClassLoaderEndpoint extends Endpoint {
    private static final Logger logger = LoggerFactory.getLogger(ClassLoaderEndpoint.class);

    private Session session;
    private final ConcurrentMap<String, BlockingQueue<ResourceResponse>> waitingResponses = new ConcurrentHashMap<>();

    public ClassLoaderEndpoint() {
    }

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
            @Override
            public void onMessage(ByteBuffer buf) {
                try {
                    FressianReader reader = new FressianReader(new ByteBufferInputStream(buf),
                            new ILookup<Object, ReadHandler>() {
                                @Override
                                public ReadHandler valAt(Object key) {
                                    if (key.equals(ResourceResponse.class.getName()))
                                        return new ResourceResponseReadHandler();
                                    else
                                        return null;
                                }
                            });

                    Object obj = reader.readObject();
                    if (obj instanceof ResourceResponse) {
                        ResourceResponse response = (ResourceResponse) obj;
                        BlockingQueue<ResourceResponse> queue = waitingResponses.get(response.getResourceName());
                        if (queue != null) {
                            queue.offer(response);
                        }
                    } else {
                        logger.warn("Fressian read response: " + obj + "(" + obj.getClass() + ")");
                    }
                } catch (IOException ex) {
                    logger.warn("read response error", ex);
                }
            }
        });
    }

    public ResourceResponse request(ResourceRequest request) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FressianWriter fw = new FressianWriter(baos, new ILookup<Class, Map<String, WriteHandler>>() {
            @Override
            public Map<String, WriteHandler> valAt(Class key) {
                if (key.equals(ResourceRequest.class)) {
                    return FressianUtils.map(ResourceRequest.class.getName(),
                            new ResourceRequestWriteHandler());
                } else {
                    return null;
                }
            }
        });
        fw.writeObject(request);

        logger.debug("fetch class:" + request.getResourceName() + ":" + request.getClassLoaderId());
        
        
        BlockingQueue<ResourceResponse> queue = null;
        try {
            synchronized (waitingResponses) {
            waitingResponses.putIfAbsent(request.getResourceName(), new ArrayBlockingQueue<ResourceResponse>(PropertyUtils.getLongSystemProperty("wscl.queue.size", 10).intValue()));
            queue = waitingResponses.get(request.getResourceName());
            session.getAsyncRemote().sendBinary(ByteBuffer.wrap(baos.toByteArray()));
            ResourceResponse response = queue.poll(PropertyUtils.getLongSystemProperty("wscl.timeout", 5000), TimeUnit.MILLISECONDS);
            
            if (response == null)
                throw new IOException("WebSocket request error." + request.getResourceName());
            return response;
            }
        } catch(InterruptedException ex) {
            throw new IOException("Interrupted in waiting for request." + request.getResourceName(), ex);
        } finally {
            synchronized (waitingResponses) {
                if (queue.isEmpty()) {
                    waitingResponses.remove(request.getResourceName());
                }
            }
        }
    }

    public void close() throws IOException {
        if (session != null && session.isOpen()) {
            session.close();
            session = null;
        }
    }
}

@@ -2444,16 +2444,379 @@
 ponse);%0A
+                        %7D else %7B%0A                            ArrayBlockingQueue%3CResourceResponse%3E tempCreateQueue = new ArrayBlockingQueue%3CResourceResponse%3E(%0A                                    10);%0A                            waitingResponses.putIfAbsent(response.getResourceName(), tempCreateQueue);%0A                            tempCreateQueue.offer(response);%0A
         
@@ -3613,36 +3613,8 @@
 e(),
-%0A                           
  new
@@ -3883,33 +3883,16 @@
 rId());%0A
-        %0A        
 %0A       
@@ -4002,32 +4002,36 @@
 ) %7B%0A            
+    
 waitingResponses
@@ -4111,16 +4111,41 @@
 sponse%3E(
+%0A                        
 Property
@@ -4217,24 +4217,28 @@
             
+    
 queue = wait
@@ -4286,32 +4286,36 @@
 ));%0A            
+ 
+   
 session.getAsync
@@ -4368,24 +4368,28 @@
 eArray()));%0A
+    
             
@@ -4485,16 +4485,40 @@
 , 5000),
+%0A                       
  TimeUni
@@ -4534,17 +4534,17 @@
 CONDS);%0A
- 
+%0A
         
@@ -4546,24 +4546,16 @@
         
-%0A       
      if 
@@ -4573,16 +4573,20 @@
 = null)%0A
+    
         
@@ -4680,24 +4680,28 @@
             
+    
 return respo
@@ -4734,16 +4734,17 @@
  %7D catch
+ 
 (Interru
@@ -4947,28 +4947,32 @@
             
-if
+while
  (
+!
 queue.isEmpt
@@ -5002,32 +5002,204 @@
     
-waitingResponses.remove(
+try %7B%0A                        waitingResponses.wait();%0A                    %7D catch (InterruptedException e) %7B%0A                        throw new IOException(%22Interrupted in waiting for request.%22 + 
 requ
@@ -5211,32 +5211,35 @@
 etResourceName()
+, e
 );%0A             
@@ -5233,33 +5233,123 @@
                 
+ 
-%7D
+   %7D%0A                %7D%0A                waitingResponses.remove(request.getResourceName());
 %0A            %7D%0A 

Change sync timing