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