001/* 002 * To change this template, choose Tools | Templates 003 * and open the template in the editor. 004 */ 005package org.anarres.qemu.qapi.common; 006 007import com.fasterxml.jackson.databind.JsonNode; 008import com.fasterxml.jackson.databind.ObjectMapper; 009import java.io.BufferedReader; 010import java.io.Closeable; 011import java.io.EOFException; 012import java.io.IOException; 013import java.io.InputStreamReader; 014import java.io.OutputStreamWriter; 015import java.io.Writer; 016import java.net.InetAddress; 017import java.net.InetSocketAddress; 018import java.net.Socket; 019import java.nio.charset.Charset; 020import javax.annotation.Nonnegative; 021import javax.annotation.Nonnull; 022import org.anarres.qemu.qapi.api.VersionInfo; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026/** 027 * A connection to a QEmu process. 028 * 029 * @author shevek 030 */ 031public class QApiConnection implements Closeable { 032 033 private static final Logger LOG = LoggerFactory.getLogger(QApiConnection.class); 034 private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); 035 private final ObjectMapper mapper = new ObjectMapper(); 036 private final Socket socket; 037 private final Writer output; 038 private final BufferedReader input; 039 private final QApiGreeting greeting; 040 041 public QApiConnection(@Nonnull Socket socket) throws IOException { 042 this.socket = socket; 043 this.output = new OutputStreamWriter(socket.getOutputStream(), ISO_8859_1); 044 this.input = new BufferedReader(new InputStreamReader(socket.getInputStream(), ISO_8859_1)); 045 this.greeting = read(QApiGreeting.class); 046 /* QmpCapabilitiesCommand.Response capabilities = */ invoke(new QmpCapabilitiesCommand()); 047 // assert !capabilities.isError(); 048 } 049 050 public QApiConnection(@Nonnull InetAddress address, @Nonnegative int port) throws IOException { 051 this(new Socket(address, port)); 052 } 053 054 public QApiConnection(@Nonnull String address, @Nonnegative int port) throws IOException { 055 this(InetAddress.getByName(address), port); 056 } 057 058 public QApiConnection(@Nonnull InetSocketAddress address) throws IOException { 059 this(address.getAddress(), address.getPort()); 060 } 061 062 @Nonnull 063 public QApiGreeting getGreeting() { 064 return greeting; 065 } 066 067 @Nonnull 068 public VersionInfo getQEmuVersion() { 069 return getGreeting().QMP.version; 070 } 071 072 @Nonnull 073 private <T> T read(@Nonnull Class<T> type) throws IOException { 074 for (;;) { 075 String line = input.readLine(); 076 if (line == null) 077 throw new EOFException(); 078 if (LOG.isDebugEnabled()) 079 LOG.debug("<<<" + line); 080 JsonNode tree = mapper.readTree(line); 081 if (tree.get("event") != null) // Could parse a QApiEvent. 082 continue; 083 return mapper.treeToValue(tree, type); 084 } 085 // return mapper.readValue(line, type); 086 } 087 088 @Nonnull 089 public <Response extends QApiResponse<?>> Response invoke(@Nonnull QApiCommand<?, Response> command) throws IOException { 090 String line = mapper.writeValueAsString(command); 091 if (LOG.isDebugEnabled()) 092 LOG.debug(">>>" + line); 093 output.write(line); 094 output.write('\n'); 095 output.flush(); 096 Class<Response> returnType = command.getReturnType(); 097 return read(returnType); 098 } 099 100 @Nonnull 101 public <Result, Response extends QApiResponse<Result>> Result call(@Nonnull QApiCommand<?, Response> command) throws IOException, QApiException { 102 return invoke(command).getResult(); 103 } 104 105 @Override 106 public void close() throws IOException { 107 socket.close(); 108 } 109}