001/*
002The contents of this file are subject to the Mozilla Public License Version 1.1 
003(the "License"); you may not use this file except in compliance with the License. 
004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005Software distributed under the License is distributed on an "AS IS" basis, 
006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007specific language governing rights and limitations under the License. 
008
009The Original Code is "URLTransport.java".  Description: 
010"A TransportLayer that reads and writes from an URL." 
011
012The Initial Developer of the Original Code is University Health Network. Copyright (C) 
0132004.  All Rights Reserved. 
014
015Contributor(s): ______________________________________. 
016
017Alternatively, the contents of this file may be used under the terms of the 
018GNU General Public License (the "GPL"), in which case the provisions of the GPL are 
019applicable instead of those above.  If you wish to allow use of your version of this 
020file only under the terms of the GPL and not to allow others to use your version 
021of this file under the MPL, indicate your decision by deleting  the provisions above 
022and replace  them with the notice and other provisions required by the GPL License.  
023If you do not delete the provisions above, a recipient may use your version of 
024this file under either the MPL or the GPL. 
025*/
026
027package ca.uhn.hl7v2.protocol.impl;
028
029import java.io.BufferedInputStream;
030import java.io.BufferedOutputStream;
031import java.io.IOException;
032import java.io.InputStreamReader;
033import java.io.OutputStreamWriter;
034import java.io.Reader;
035import java.io.Writer;
036import java.net.URL;
037import java.net.URLConnection;
038
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042import ca.uhn.hl7v2.protocol.TransportException;
043import ca.uhn.hl7v2.protocol.TransportLayer;
044import ca.uhn.hl7v2.protocol.Transportable;
045
046/**
047 * A <code>TransportLayer</code> that reads and writes from an URL (for example
048 * over HTTP).    
049 * 
050 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
051 * @author <a href="mailto:alexei.guevara@uhn.on.ca">Alexei Guevara</a>
052 * @version $Revision: 1.1 $ updated on $Date: 2007-02-19 02:24:26 $ by $Author: jamesagnew $
053 */
054public class URLTransport extends AbstractTransport implements TransportLayer {
055    
056    private static final Logger log = LoggerFactory.getLogger(URLTransport.class);    
057
058    /**
059     * Key in Transportable metadata map under which URL is stored.  
060     */
061    public static final String URL_KEY = "URL";
062
063    private String myContentType = "application/hl7+doc+xml";
064    private final URL myURL;
065    private URLConnection myConnection;
066    protected final int myBufferSize = 3000;
067    
068    private final boolean myConnectOnSend;
069    private final boolean myConnectOnReceive;
070    private final boolean myConnectOnConnect;
071
072    /**
073     * The boolean configuration flags determine when new connections are made.  For example if this 
074     * transport is being used for query/response, you might set connectOnSend to true and
075     * the others to false, so that each query/response is done over a fresh connection.  If 
076     * you are using a transport just to read data from a URL, you might set connectOnReceive to 
077     * true and the others to false.  
078     *  
079     * @param theURL the URL at which messages are to be read and written 
080     * @param connectOnSend makes a new connection before each send  
081     * @param connectOnReceive makes a new connection before each receive 
082     * @param connectOnConnect makes a new connection when connect() is called 
083     */
084    public URLTransport(URL theURL, boolean connectOnSend, boolean connectOnReceive, boolean connectOnConnect) {
085        myURL = theURL;
086        getCommonMetadata().put(URL_KEY, theURL);
087        
088        myConnectOnSend = connectOnSend;
089        myConnectOnReceive = connectOnReceive;
090        myConnectOnConnect = connectOnConnect;
091    }
092
093    /** 
094     * Writes the given message to the URL. 
095     * 
096     * @param theMessage the message to send
097     */
098    public void doSend(Transportable theMessage) throws TransportException {
099        if (myConnectOnSend) {
100            makeConnection();
101        }
102
103        try {
104            Writer out = new OutputStreamWriter(new BufferedOutputStream(myConnection.getOutputStream()));
105            out.write(theMessage.getMessage());
106            out.flush();
107        } catch (IOException e) {
108            throw new TransportException(e);
109        }
110    }
111
112    public Transportable doReceive() throws TransportException {
113        
114        if (myConnectOnReceive) {
115            makeConnection();
116        }
117
118        StringBuilder response = new StringBuilder();
119
120        try {
121            log.debug("Getting InputStream from URLConnection");
122            Reader in = new InputStreamReader(new BufferedInputStream(myConnection.getInputStream()));
123            log.debug("Got InputStream from URLConnection");
124
125            char[] buf = new char[myBufferSize];
126            int bytesRead = 0;
127
128            IntRef bytesReadRef = new IntRef();
129
130            while (bytesRead >= 0) {
131
132                try {
133                    ReaderThread readerThread = new ReaderThread(in, buf, bytesReadRef);
134                    readerThread.start();
135                    readerThread.join(10000);
136
137                    bytesRead = bytesReadRef.getValue();
138
139                    if (bytesRead == 0) {
140                        throw new TransportException("Timeout waiting for response");
141                    }
142                }
143                catch (InterruptedException ignored) {
144                }
145
146                if (bytesRead > 0) {
147                    response.append(buf, 0, bytesRead);
148                }
149
150            }
151
152            in.close();
153        } catch (IOException e) {
154            log.error(e.getMessage(), e);
155        }
156
157        if (response.length() == 0) {
158            throw new TransportException("Timeout waiting for response");
159        }
160
161        return new TransportableImpl(response.toString());
162    }
163
164
165    /** 
166     * Calls openConnection() on the underlying URL and configures the connection, 
167     * if this transport is configured to connect when connect() is called (see 
168     * constructor params).
169     */
170    public void doConnect() throws TransportException {
171        if (myConnectOnConnect) {
172            makeConnection();
173        }
174    }
175    
176    //makes new connection 
177    private void makeConnection() throws TransportException {
178        try {
179            myConnection = myURL.openConnection();
180            myConnection.setDoOutput(true);
181            myConnection.setDoInput(true);
182            myConnection.setRequestProperty("Content-Type", getContentType());
183            myConnection.connect();
184        } catch (IOException e) {
185            throw new TransportException(e);
186        }     
187        log.debug("Made connection to {}", myURL.toExternalForm());
188    }
189    
190    /**
191     * @return the string used in the request property "Content-Type" (defaults to 
192     *      "application/hl7+doc+xml")
193     */
194    public String getContentType() {
195        return myContentType;
196    }
197    
198    /**
199     * @param theContentType the string to be used in the request property "Content-Type" 
200     *      (defaults to "application/hl7+doc+xml")
201     */
202    public void setContentType(String theContentType) {
203        myContentType = theContentType;
204    }
205
206    /** 
207     * @see ca.uhn.hl7v2.protocol.TransportLayer#disconnect()
208     */
209    public void doDisconnect() {
210        myConnection = null;
211    }
212    
213}