Dan
Dan

Reputation: 8513

Client is blocked after unmarshalling xml from socket using JAXB

I'm trying to send some xml from a Client to a Server and have the Server send back a message to the Client after receiving the data. If it is just sending xml then it runs smoothly, but when the client is expecting a response, it blocks forever. I've read from a different post that unmarshalling closes the socket so I used a filterStream instead but it doesn't seem to solve the problem. Any ideas?

Here's my code:

Client

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.net.Socket;
import javax.xml.bind.annotation.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Client {

    public static void main(String args[]) throws JAXBException, IOException {
        JAXBContext context = JAXBContext.newInstance(Product.class);
        Socket sock = new Socket("localhost", 4000);
        Marshaller m = context.createMarshaller();
        BufferedReader br = new BufferedReader(new InputStreamReader(new MyInputStream(sock.getInputStream())));
        PrintStream ps = new PrintStream(sock.getOutputStream());
        Product object = new Product();
        object.setCode("WI1");
        object.setName("Widget Number One");
        object.setPrice(BigDecimal.valueOf(300.00));
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        m.setProperty(Marshaller.JAXB_FRAGMENT, true);

        System.out.println("Data Sent");
        m.marshal(object, ps);

        System.out.println("Waiting for response from server");
        String msg = br.readLine(); // runs smoothly if client doesn't wait for response
        if (msg != null) {
            System.out.println(msg);
        }
        br.close();
        sock.close();
    }
}

@XmlRootElement
class Product {

    private String code;
    private String name;
    private BigDecimal price;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

Server:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.math.BigDecimal;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.stream.StreamSource;

public class Server {

    public static void main(String a[]) throws IOException, JAXBException {
        Socket sock;
        ServerSocket servsock = new ServerSocket(4000, 1);
        while (true) {
            sock = servsock.accept();
            new WorkerThread(sock).start();
        }
    }
}

class WorkerThread extends Thread {

    Socket sock;
    WorkerThread(Socket s) {
        sock = s;
    }

    public void run() {
        try {
            PrintStream out = new PrintStream(sock.getOutputStream()); // to send ack back to client;
            BufferedReader in = new BufferedReader(new InputStreamReader(new MyInputStream(sock.getInputStream())));;
            JAXBContext context = JAXBContext.newInstance(Product.class);  
            Unmarshaller um = context.createUnmarshaller();
            XMLInputFactory xif = XMLInputFactory.newFactory();
                StreamSource ss = new StreamSource(in);
                XMLStreamReader xsr = xif.createXMLStreamReader(ss);
                xsr.nextTag();

                JAXBElement<Product> xmlToJava = um.unmarshal(xsr, Product.class);

                Product product = xmlToJava.getValue();
                System.out.println("closing");
                xsr.close();

                System.out.println(product.getName());
                System.out.println(product.getCode());
            System.out.println(product.getPrice());
            out.println("data recieved");
            out.flush();
        } catch (IOException ex) {
            Logger.getLogger(WorkerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (JAXBException ex) {
            Logger.getLogger(WorkerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (XMLStreamException ex) {
            Logger.getLogger(WorkerThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

class MyInputStream extends FilterInputStream {

    public MyInputStream(InputStream in) {
        super(in);
    }

    @Override
    public void close() {
    }
}

@XmlRootElement
class Product {

    private String code;
    private String name;
    private BigDecimal price;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}

Upvotes: 0

Views: 1968

Answers (3)

mr mcwolf
mr mcwolf

Reputation: 2859

I have the same problem. I decided it buffered in string.

@Override
public void run() {
    try {
        while(true) {
            try(
                Socket socket = ss.accept();
                DataInputStream is = new DataInputStream(socket.getInputStream());
                DataOutputStream os = new DataOutputStream(socket.getOutputStream());
            ) {
                socket.setSoTimeout(1000);

                Unmarshaller unmarshaller = JAXBContext.newInstance(Sale.class).createUnmarshaller();
                unmarshaller.setEventHandler(new ValidationEventHandler() {
                    @Override
                    public boolean handleEvent(ValidationEvent event) {
                        if(event.getSeverity() == ValidationEvent.FATAL_ERROR) {
                            InvoiceSrv.logMessage(event.getMessage());
                        }

                        return true;
                    }
                });

                Sale sale = (Sale)unmarshaller.unmarshal(new StringReader(is.readUTF()));
                Response response = getResponse(sale);

                StringWriter writer = new StringWriter();
                Marshaller marshaller = JAXBContext.newInstance(Response.class).createMarshaller();
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                marshaller.marshal(response, writer);

                os.writeUTF(writer.toString());
            }
        }
    }
    catch(SocketException e) { e.printStackTrace(); } // for close !!!
    catch(IOException | JAXBException e) {
        InvoiceSrv.logMessage(e);
    }
}

and client

public static Response send(Sale sale) throws DeviceException {
    try(
        Socket socket = new Socket(addr, 9999);
        DataInputStream is = new DataInputStream(socket.getInputStream());
        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
    ) {
        StringWriter writer = new StringWriter();
        Marshaller marshaller = JAXBContext.newInstance(Sale.class).createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(sale, writer);

        os.writeUTF(writer.toString());

        Unmarshaller unmarshaller = JAXBContext.newInstance(Response.class).createUnmarshaller();
        Response response = (Response)unmarshaller.unmarshal(new StringReader(is.readUTF()));

        return response;
    }
    catch(IOException | JAXBException e) {
        e.printStackTrace();
    }

    throw new DeviceException();
}

Upvotes: 0

Dan
Dan

Reputation: 8513

For the client, after marshaling, you have to add sock.shutdownOuput(); It seems that marshaling doesn't know when to end so you have to manually close the output stream.

Upvotes: 0

Stephen C
Stephen C

Reputation: 719229

As far as I can make out, the server is neither writing a response, or closing the socket before the run() method returns. So the socket will just sit there ... and the client will remain blocked in the read.

The simple solution is for the server's run() method to close sock ... preferably in a finally block so that it ALWAYS happens.


(The above is valid, even if it isn't the cause of your immediate problem!)

UPDATE

There are a couple of things to look into.

  1. Your use of PrintStream is unnecessary. You can (and should) marshal to the socket output stream directly. It is possible that this is confusing the server end and resulting in it blocking the client. Even if not, the PrintStream serves no useful purpose.

  2. It is possible that the server is throwing some unchecked exception, resulting in the thread exiting silently. That could leave the client side blocked ... waiting for the server read or close the socket. My previous suggestion would (at least) prevent the client from blocking. But you also need to install an uncaught exception handler to diagnose any unchecked exceptions and errors.

Upvotes: 1

Related Questions