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.util.Map; 020import java.util.Set; 021 022import jakarta.servlet.SessionTrackingMode; 023import jakarta.servlet.http.Cookie; 024import jakarta.servlet.http.HttpServletRequest; 025import org.apache.wicket.Component; 026import org.apache.wicket.behavior.Behavior; 027import org.apache.wicket.markup.head.IHeaderResponse; 028import org.apache.wicket.markup.head.JavaScriptHeaderItem; 029import org.apache.wicket.markup.head.OnDomReadyHeaderItem; 030import org.apache.wicket.protocol.http.WebApplication; 031import org.apache.wicket.protocol.ws.WebSocketSettings; 032import org.apache.wicket.util.cookies.CookieUtils; 033import org.apache.wicket.util.lang.Args; 034import org.apache.wicket.util.lang.Generics; 035import org.apache.wicket.util.string.Strings; 036import org.apache.wicket.util.template.PackageTextTemplate; 037 038/** 039 * A behavior that contributes {@link WicketWebSocketJQueryResourceReference} 040 */ 041public class BaseWebSocketBehavior extends Behavior 042{ 043 private final String resourceName; 044 private final String connectionToken; 045 046 /** 047 * Constructor. 048 * 049 * Contributes WebSocket initialization code that will 050 * work with {@link org.apache.wicket.protocol.ws.api.WebSocketBehavior} 051 */ 052 protected BaseWebSocketBehavior() 053 { 054 this.resourceName = null; 055 this.connectionToken = null; 056 } 057 058 /** 059 * Constructor. 060 * 061 * Contributes WebSocket initialization code that will 062 * work with {@link org.apache.wicket.protocol.ws.api.WebSocketResource} 063 * 064 * To use WebSocketResource the application have to setup the 065 * resource as a shared one in its {@link org.apache.wicket.Application#init()} 066 * method: 067 * <code><pre> 068 * getSharedResources().add(resourceName, new MyWebSocketResource()) 069 * </pre></code> 070 * 071 * @param resourceName 072 * the name of the shared {@link org.apache.wicket.protocol.ws.api.WebSocketResource} 073 */ 074 public BaseWebSocketBehavior(String resourceName) 075 { 076 this(resourceName, null); 077 } 078 079 /** 080 * Constructor. 081 * 082 * Contributes WebSocket initialization code that will 083 * work with {@link org.apache.wicket.protocol.ws.api.WebSocketResource} 084 * 085 * To use WebSocketResource the application have to setup the 086 * resource as a shared one in its {@link org.apache.wicket.Application#init()} 087 * method: 088 * <code><pre> 089 * getSharedResources().add(resourceName, new MyWebSocketResource()) 090 * </pre></code> 091 * 092 * @param resourceName 093 * the name of the shared {@link org.apache.wicket.protocol.ws.api.WebSocketResource} 094 * @param connectionToken 095 * an optional token to support connections to the same resource from multiple browser tabs 096 */ 097 public BaseWebSocketBehavior(String resourceName, String connectionToken) 098 { 099 this.resourceName = Args.notEmpty(resourceName, "resourceName"); 100 this.connectionToken = connectionToken; 101 } 102 103 @Override 104 public void renderHead(Component component, IHeaderResponse response) 105 { 106 super.renderHead(component, response); 107 108 response.render(JavaScriptHeaderItem.forReference(WicketWebSocketJQueryResourceReference.get())); 109 110 Map<String, Object> parameters = getParameters(component); 111 String webSocketSetupScript = getWebSocketSetupScript(parameters); 112 113 response.render(OnDomReadyHeaderItem.forScript(webSocketSetupScript)); 114 } 115 116 protected String getWebSocketSetupScript(Map<String, Object> parameters) { 117 PackageTextTemplate webSocketSetupTemplate = 118 new PackageTextTemplate(WicketWebSocketJQueryResourceReference.class, 119 "res/js/wicket-websocket-setup.js.tmpl"); 120 121 return webSocketSetupTemplate.asString(parameters); 122 } 123 124 /** 125 * Override to return a context. By default, this is the page class name. 126 * 127 * @param component the {@link org.apache.wicket.Component} 128 * @return the context for this websocket behavior. 129 */ 130 protected String getContext(Component component) { 131 return component.getPage().getClass().getName(); 132 } 133 134 private Map<String, Object> getParameters(Component component) { 135 Map<String, Object> variables = Generics.newHashMap(); 136 137 variables.put("context", getContext(component)); 138 139 // set falsy JS values for the non-used parameters 140 if (Strings.isEmpty(resourceName)) 141 { 142 int pageId = component.getPage().getPageId(); 143 variables.put("pageId", pageId); 144 variables.put("resourceName", ""); 145 variables.put("connectionToken", ""); 146 } 147 else 148 { 149 variables.put("resourceName", resourceName); 150 variables.put("connectionToken", connectionToken); 151 variables.put("pageId", false); 152 } 153 154 WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(component.getApplication()); 155 156 CharSequence baseUrl = getBaseUrl(webSocketSettings); 157 Args.notNull(baseUrl, "baseUrl"); 158 variables.put("baseUrl", baseUrl); 159 160 Integer port = getPort(webSocketSettings); 161 variables.put("port", port); 162 Integer securePort = getSecurePort(webSocketSettings); 163 variables.put("securePort", securePort); 164 165 CharSequence contextPath = getContextPath(webSocketSettings); 166 Args.notNull(contextPath, "contextPath"); 167 variables.put("contextPath", contextPath); 168 169 // preserve the application name for JSR356 based impl 170 variables.put("applicationName", component.getApplication().getName()); 171 172 CharSequence filterPrefix = getFilterPrefix(webSocketSettings); 173 Args.notNull(filterPrefix, "filterPrefix"); 174 variables.put("filterPrefix", filterPrefix); 175 176 final CharSequence sessionId = getSessionId(component); 177 variables.put("sessionId", sessionId); 178 return variables; 179 } 180 181 protected Integer getPort(WebSocketSettings webSocketSettings) 182 { 183 return webSocketSettings.getPort(); 184 } 185 186 protected Integer getSecurePort(WebSocketSettings webSocketSettings) 187 { 188 return webSocketSettings.getSecurePort(); 189 } 190 191 protected CharSequence getFilterPrefix(final WebSocketSettings webSocketSettings) { 192 return webSocketSettings.getFilterPrefix(); 193 } 194 195 protected CharSequence getContextPath(final WebSocketSettings webSocketSettings) { 196 return webSocketSettings.getContextPath(); 197 } 198 199 protected CharSequence getBaseUrl(final WebSocketSettings webSocketSettings) { 200 return webSocketSettings.getBaseUrl(); 201 } 202 203 /** 204 * @param component 205 * The component this behavior is bound to 206 * @return The http session id if it is tracked in the url, otherwise empty string 207 */ 208 protected CharSequence getSessionId(final Component component) 209 { 210 String sessionId = ""; 211 final WebApplication application = (WebApplication) component.getApplication(); 212 final Set<SessionTrackingMode> effectiveSessionTrackingModes = application.getServletContext().getEffectiveSessionTrackingModes(); 213 Object containerRequest = component.getRequest().getContainerRequest(); 214 if (effectiveSessionTrackingModes.size() == 1 && SessionTrackingMode.URL.equals(effectiveSessionTrackingModes.iterator().next())) 215 { 216 sessionId = component.getSession().getId(); 217 } 218 else if (containerRequest instanceof HttpServletRequest) 219 { 220 CookieUtils cookieUtils = new CookieUtils(); 221 final String jsessionCookieName = cookieUtils.getSessionIdCookieName(application); 222 final Cookie jsessionid = cookieUtils.getCookie(jsessionCookieName); 223 HttpServletRequest httpServletRequest = (HttpServletRequest) containerRequest; 224 if (jsessionid == null || httpServletRequest.isRequestedSessionIdValid() == false) 225 { 226 sessionId = component.getSession().getId(); 227 } 228 } 229 return sessionId; 230 } 231 232 @Override 233 public boolean getStatelessHint(Component component) 234 { 235 return false; 236 } 237}