001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.ws.api;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.time.Instant;
022import javax.servlet.http.Cookie;
023import org.apache.wicket.request.Response;
024import org.apache.wicket.request.http.WebResponse;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * A {@link Response} used to cache the written data to the web socket client
030 * when Wicket thread locals are available.
031 *
032 * When the thread locals are not available then you can write directly to the {@link IWebSocketConnection}
033 * taken from {@link org.apache.wicket.protocol.ws.api.registry.IWebSocketConnectionRegistry}.
034 * In this case the response wont be cached.
035 *
036 * @since 6.0
037 */
038public class WebSocketResponse extends WebResponse
039{
040        private static final Logger LOG = LoggerFactory.getLogger(WebSocketResponse.class);
041        
042        private final IWebSocketConnection connection;
043
044        private StringBuilder text;
045
046        private ByteArrayOutputStream binary;
047
048        private boolean isRedirect = false;
049
050        private final boolean asynchronous;
051
052        private final long timeout;
053
054        public WebSocketResponse(final IWebSocketConnection conn)
055        {
056                this(conn, false, -1);
057        }
058
059        public WebSocketResponse(final IWebSocketConnection conn, boolean asynchronous, long timeout)
060        {
061                this.connection = conn;
062                this.asynchronous = asynchronous;
063                this.timeout = timeout;
064        }
065
066        @Override
067        public void write(CharSequence sequence)
068        {
069                if (text == null)
070                {
071                        text = new StringBuilder();
072                }
073                text.append(sequence);
074        }
075
076        @Override
077        public void write(byte[] array)
078        {
079                write(array, 0, array.length);
080        }
081
082        @Override
083        public void write(byte[] array, int offset, int length)
084        {
085                if (binary == null)
086                {
087                        binary = new ByteArrayOutputStream();
088                }
089                binary.write(array, offset, length);
090        }
091
092        @Override
093        public void close()
094        {
095                if (connection.isOpen())
096                {
097                        try
098                        {
099                                if (text != null)
100                                {
101                                        if (asynchronous)
102                                        {
103                                                connection.sendMessageAsync(text.toString(), timeout);
104                                        }
105                                        else
106                                        {
107                                                connection.sendMessage(text.toString());
108                                        }
109                                        text = null;
110                                }
111                                else if (binary != null)
112                                {
113                                        byte[] bytes = binary.toByteArray();
114                                        if (asynchronous)
115                                        {
116                       connection.sendMessageAsync(bytes, 0, bytes.length, timeout);
117                                        }
118                                        else
119                                        {
120                                                connection.sendMessage(bytes, 0, bytes.length);
121                                        }
122                                        binary.close();
123                                        binary = null;
124                                }
125
126                        } catch (IOException iox)
127                        {
128                                LOG.error("An error occurred while writing response to WebSocket client.", iox);
129                        }
130                }
131                super.close();
132        }
133
134        @Override
135        public void reset()
136        {
137                text = null;
138                if (binary != null)
139                {
140                        try
141                        {
142                                binary.close();
143                        } catch (IOException iox)
144                        {
145                                LOG.error("An error occurred while resetting the binary content", iox);
146                        }
147                        binary = null;
148                }
149                isRedirect = false;
150                super.reset();
151        }
152
153        @Override
154        public String encodeURL(CharSequence url)
155        {
156                return url.toString();
157        }
158
159        @Override
160        public final IWebSocketConnection getContainerResponse()
161        {
162                return connection;
163        }
164
165        @Override
166        public void addCookie(Cookie cookie)
167        {
168                throw new UnsupportedOperationException();
169        }
170
171        @Override
172        public void clearCookie(Cookie cookie)
173        {
174                throw new UnsupportedOperationException();
175        }
176
177        @Override
178        public boolean isHeaderSupported() {
179                return false;
180        }
181        
182        @Override
183        public void setHeader(String name, String value)
184        {
185                throw new UnsupportedOperationException();
186        }
187
188        @Override
189        public void addHeader(String name, String value)
190        {
191                throw new UnsupportedOperationException();
192        }
193
194        @Override
195        public void setDateHeader(String name, Instant date)
196        {
197                throw new UnsupportedOperationException();
198        }
199
200        @Override
201        public void setContentLength(long length)
202        {
203                throw new UnsupportedOperationException();
204        }
205
206        @Override
207        public void setContentType(String mimeType)
208        {
209                throw new UnsupportedOperationException();
210        }
211
212        @Override
213        public void setStatus(int sc)
214        {
215                throw new UnsupportedOperationException();
216        }
217
218        @Override
219        public void sendError(int sc, String msg)
220        {
221                LOG.warn("An HTTP error response in WebSocket communication would not be processed by the browser! " +
222                                "If you need to send the error code and message to the client then configure custom WebSocketResponse " +
223                                "via WebSocketSettings#newWebSocketResponse() factory method and override #sendError() method to write " +
224                                "them in an appropriate format for your application. " +
225                                "The ignored error code is '{}' and the message: '{}'.", sc, msg);
226        }
227
228        @Override
229        public String encodeRedirectURL(CharSequence url)
230        {
231                return url.toString();
232        }
233
234        @Override
235        public void sendRedirect(String url)
236        {
237                isRedirect = true;
238                url = encodeRedirectURL(url);
239
240                String ajaxRedirect = "<ajax-response><redirect><![CDATA[" + url + "]]></redirect></ajax-response>";
241                write(ajaxRedirect);
242        }
243
244        @Override
245        public boolean isRedirect()
246        {
247                return isRedirect;
248        }
249
250        @Override
251        public void flush()
252        {
253        }
254}