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}