001package ca.uhn.hl7v2.hoh.encoder; 002 003import static ca.uhn.hl7v2.hoh.util.StringUtils.*; 004 005import java.io.ByteArrayOutputStream; 006import java.io.IOException; 007import java.io.OutputStream; 008import java.io.OutputStreamWriter; 009import java.text.DateFormat; 010import java.text.SimpleDateFormat; 011import java.util.Date; 012import java.util.LinkedHashMap; 013import java.util.Map; 014import java.util.zip.GZIPOutputStream; 015 016import ca.uhn.hl7v2.hoh.api.EncodeException; 017import ca.uhn.hl7v2.hoh.api.ISendable; 018import ca.uhn.hl7v2.hoh.sign.SignatureFailureException; 019import ca.uhn.hl7v2.hoh.util.GZipUtils; 020import ca.uhn.hl7v2.hoh.util.HTTPUtils; 021 022/** 023 * Base class that creates HL7 over HTTP requests. This class is intended to be 024 * single use, so please create a new instance for each message. 025 */ 026public abstract class AbstractHl7OverHttpEncoder extends AbstractHl7OverHttp { 027 028 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractHl7OverHttpEncoder.class); 029 private static DateFormat ourRfc1123DateFormat; 030 031 static { 032 ourRfc1123DateFormat = new SimpleDateFormat("EEE, dd MMM yy HH:mm:ss z"); 033 } 034 035 private String myActionLine; 036 private boolean myGzipData; 037 private ISendable<?> mySendable; 038 039 /** 040 * Constructor 041 */ 042 public AbstractHl7OverHttpEncoder() { 043 super(); 044 } 045 046 /** 047 * @throws EncodeException 048 * 049 */ 050 public void encode() throws EncodeException { 051 ourLog.trace("Entering encode()"); 052 verifyNotUsed(); 053 054 if (isBlank(getMessage()) && mySendable == null) { 055 throw new IllegalStateException("Either Message or Sendable must be set"); 056 } 057 if (getMessage() != null) { 058 byte[] bytes = getMessage().getBytes(getCharset()); 059 if (myGzipData) { 060 try { 061 bytes = GZipUtils.compress(bytes); 062 } catch (IOException e) { 063 throw new EncodeException("Failed to apply GZip coding", e); 064 } 065 } 066 setData(bytes); 067 } else { 068 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 069 OutputStream os; 070 if (myGzipData) { 071 try { 072 os = new GZIPOutputStream(bos); 073 } catch (IOException e) { 074 throw new EncodeException("Failed to create GZip encoder", e); 075 } 076 } else { 077 os = bos; 078 } 079 080 OutputStreamWriter w = new OutputStreamWriter(os, getCharset()); 081 try { 082 mySendable.writeMessage(w); 083 } catch (IOException e) { 084 throw new EncodeException("Failed to convert message to sendable bytes"); 085 } 086 setData(bos.toByteArray()); 087 } 088 089 setActionLineAppropriately(); 090 091 setHeaders(new LinkedHashMap<String, String>()); 092 093 StringBuilder ctBuilder = new StringBuilder(); 094 if (mySendable != null) { 095 ctBuilder.append(mySendable.getEncodingStyle().getContentType()); 096 } else { 097 ctBuilder.append(EncodingStyle.detect(getMessage()).getContentType()); 098 } 099 ctBuilder.append("; charset="); 100 ctBuilder.append(getCharset().name()); 101 getHeaders().put("Content-Type", ctBuilder.toString()); 102 103 getHeaders().put("Content-Length", Integer.toString(getData().length)); 104 105 addSpecificHeaders(); 106 107 synchronized (ourRfc1123DateFormat) { 108 getHeaders().put("Date", ourRfc1123DateFormat.format(new Date())); 109 } 110 111 if (getSigner() != null) { 112 try { 113 getHeaders().put(HTTP_HEADER_HL7_SIGNATURE, getSigner().sign(getData())); 114 } catch (SignatureFailureException e) { 115 throw new EncodeException(e); 116 } 117 } 118 119 ourLog.trace("Exiting encode()"); 120 } 121 122 public void encodeToOutputStream(OutputStream theOutputStream) throws IOException, EncodeException { 123 encode(); 124 125 ourLog.debug("Writing HTTP action: {}", getActionLine()); 126 127 OutputStreamWriter w = new OutputStreamWriter(theOutputStream, HTTPUtils.DEFAULT_CHARSET); 128 w.write(getActionLine()); 129 w.write("\r\n"); 130 131 for (Map.Entry<String, String> next : getHeaders().entrySet()) { 132 ourLog.debug("Writing HTTP header- {}: {}", next.getKey(), next.getValue()); 133 134 w.write(next.getKey()); 135 w.write(": "); 136 w.write(next.getValue()); 137 w.write("\r\n"); 138 } 139 140 w.write("\r\n"); 141 w.flush(); 142 143 ourLog.debug("Writing {} bytes of actual data", getData().length); 144 theOutputStream.write(getData()); 145 146 } 147 148 /** 149 * @return the actionLine 150 */ 151 public String getActionLine() { 152 return myActionLine; 153 } 154 155 /** 156 * @param theActionLine 157 * the actionLine to set 158 */ 159 public void setActionLine(String theActionLine) { 160 myActionLine = theActionLine; 161 } 162 163 /** 164 * Provide the message to send with a {@link ISendable} instance. Either 165 * this method OR {@link #setMessage(String)} must be called, but not both. 166 */ 167 public void setDataProvider(ISendable<?> theSendable) { 168 if (getMessage() != null) { 169 throw new IllegalStateException("Message already provided"); 170 } 171 mySendable = theSendable; 172 } 173 174 /** 175 * Provide the message to send with a String. Either this method OR 176 * {@link #setDataProvider(ISendable)} must be called, but not both. 177 */ 178 @Override 179 public void setMessage(String theData) { 180 if (mySendable != null) { 181 throw new IllegalStateException("Data provider already provided"); 182 } 183 super.setMessage(theData); 184 } 185 186 protected abstract void addSpecificHeaders(); 187 188 protected abstract void setActionLineAppropriately(); 189 190 boolean isGzipData() { 191 return myGzipData; 192 } 193 194 /** 195 * Should the encoded data be GZipped? Note that this doesn't set any 196 * headers indicating this fact, so it's up to callers of this method to 197 * take care of that. 198 */ 199 void setGzipData(boolean theGzipData) { 200 myGzipData = theGzipData; 201 } 202 203}