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.IOException; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.concurrent.CompletableFuture; 023import java.util.concurrent.Future; 024 025import org.apache.wicket.Component; 026import org.apache.wicket.Page; 027import org.apache.wicket.core.request.handler.AbstractPartialPageRequestHandler; 028import org.apache.wicket.core.request.handler.logger.PageLogData; 029import org.apache.wicket.markup.head.IHeaderResponse; 030import org.apache.wicket.page.PartialPageUpdate; 031import org.apache.wicket.page.XmlPartialPageUpdate; 032import org.apache.wicket.request.ILogData; 033import org.apache.wicket.request.IRequestCycle; 034import org.apache.wicket.request.component.IRequestablePage; 035import org.apache.wicket.request.mapper.parameter.PageParameters; 036import org.apache.wicket.util.lang.Args; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * A handler of WebSocket requests. 042 * 043 * @since 6.0 044 */ 045public class WebSocketRequestHandler extends AbstractPartialPageRequestHandler implements IWebSocketRequestHandler 046{ 047 private static final Logger LOG = LoggerFactory.getLogger(WebSocketRequestHandler.class); 048 049 050 private final IWebSocketConnection connection; 051 052 private PartialPageUpdate update; 053 054 private PageLogData logData; 055 056 public WebSocketRequestHandler(final Component component, final IWebSocketConnection connection) 057 { 058 super(Args.notNull(component, "component").getPage()); 059 this.connection = Args.notNull(connection, "connection"); 060 } 061 062 @Override 063 public void push(CharSequence message) 064 { 065 if (connection.isOpen()) 066 { 067 Args.notNull(message, "message"); 068 try 069 { 070 connection.sendMessage(message.toString()); 071 } catch (IOException iox) 072 { 073 LOG.error("An error occurred while pushing text message.", iox); 074 } 075 } 076 else 077 { 078 LOG.warn("The websocket connection is already closed. Cannot push the text message '{}'", message); 079 } 080 } 081 082 @Override 083 public Future<Void> pushAsync(CharSequence message, long timeout) 084 { 085 if (connection.isOpen()) 086 { 087 Args.notNull(message, "message"); 088 return connection.sendMessageAsync(message.toString(), timeout); 089 } 090 else 091 { 092 LOG.warn("The websocket connection is already closed. Cannot push the text message '{}'", message); 093 } 094 return CompletableFuture.completedFuture(null); 095 } 096 097 @Override 098 public Future<Void> pushAsync(CharSequence message) 099 { 100 return pushAsync(message, -1); 101 } 102 103 @Override 104 public void push(byte[] message, int offset, int length) 105 { 106 if (connection.isOpen()) 107 { 108 Args.notNull(message, "message"); 109 try 110 { 111 connection.sendMessage(message, offset, length); 112 } catch (IOException iox) 113 { 114 LOG.error("An error occurred while pushing binary message.", iox); 115 } 116 } 117 else 118 { 119 LOG.warn("The websocket connection is already closed. Cannot push the binary message '{}'", message); 120 } 121 } 122 123 @Override 124 public Future<Void> pushAsync(byte[] message, int offset, int length) 125 { 126 return pushAsync(message, offset, length, -1); 127 } 128 129 @Override 130 public Future<Void> pushAsync(byte[] message, int offset, int length, long timeout) 131 { 132 if (connection.isOpen()) 133 { 134 Args.notNull(message, "message"); 135 return connection.sendMessageAsync(message, offset, length, timeout); 136 } 137 else 138 { 139 LOG.warn("The websocket connection is already closed. Cannot push the binary message '{}'", message); 140 } 141 return CompletableFuture.completedFuture(null); 142 } 143 144 /** 145 * @return if <code>true</code> then EMPTY partial updates will se send. If <code>false</code> then EMPTY 146 * partial updates will be skipped. A possible use case is: a page receives and a push event but no one is 147 * listening to it, and nothing is added to {@link org.apache.wicket.protocol.ws.api.WebSocketRequestHandler} 148 * thus no real push to client is needed. For compatibilities this is set to true. Thus EMPTY updates are sent 149 * by default. 150 */ 151 protected boolean shouldPushWhenEmpty() 152 { 153 return true; 154 } 155 156 protected PartialPageUpdate getUpdate() { 157 if (update == null) { 158 update = new XmlPartialPageUpdate(getPage()); 159 } 160 return update; 161 } 162 163 164 @Override 165 public Collection<? extends Component> getComponents() 166 { 167 if (update == null) { 168 return Collections.emptyList(); 169 } else { 170 return update.getComponents(); 171 } 172 } 173 174 @Override 175 public ILogData getLogData() 176 { 177 return logData; 178 } 179 180 181 @Override 182 public void respond(IRequestCycle requestCycle) 183 { 184 if (update != null) 185 { 186 if (shouldPushWhenEmpty() || !update.isEmpty()) 187 { 188 update.writeTo(requestCycle.getResponse(), "UTF-8"); 189 } 190 } 191 } 192 193 @Override 194 public void detach(IRequestCycle requestCycle) 195 { 196 if (logData == null) 197 { 198 logData = new PageLogData(getPage()); 199 } 200 201 if (update != null) { 202 update.detach(requestCycle); 203 update = null; 204 } 205 } 206}