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}