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.javax; 018 019import java.io.EOFException; 020import java.io.IOException; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.concurrent.ConcurrentHashMap; 025 026import javax.websocket.CloseReason; 027import javax.websocket.Endpoint; 028import javax.websocket.EndpointConfig; 029import javax.websocket.Session; 030 031import org.apache.wicket.Application; 032import org.apache.wicket.IApplicationListener; 033import org.apache.wicket.ThreadContext; 034import org.apache.wicket.protocol.http.WebApplication; 035import org.apache.wicket.util.lang.Checks; 036import org.apache.wicket.util.string.Strings; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * JSR 356 WebSocket Endpoint that integrates with Wicket Native WebSocket's IWebSocketProcessor 042 */ 043public class WicketEndpoint extends Endpoint 044{ 045 private static final Logger LOG = LoggerFactory.getLogger(WicketEndpoint.class); 046 047 /** 048 * A set of started applications for which this endpoint is registered. 049 */ 050 private static final Set<String> RUNNING_APPLICATIONS = ConcurrentHashMap.newKeySet(); 051 052 /** 053 * The name of the request parameter that holds the application name 054 */ 055 private static final String WICKET_APP_PARAM_NAME = "wicket-app-name"; 056 057 private String applicationName; 058 private JavaxWebSocketProcessor javaxWebSocketProcessor; 059 060 @Override 061 public void onOpen(Session session, EndpointConfig endpointConfig) 062 { 063 applicationName = getApplicationName(session); 064 065 WebApplication app = (WebApplication)WebApplication.get(applicationName); 066 if (RUNNING_APPLICATIONS.add(applicationName)) 067 { 068 app.getApplicationListeners().add(new ApplicationListener()); 069 } 070 071 try 072 { 073 ThreadContext.setApplication(app); 074 javaxWebSocketProcessor = new JavaxWebSocketProcessor(session, app, endpointConfig); 075 javaxWebSocketProcessor.onOpen(new JavaxWebSocketSession(session), app); 076 } 077 finally 078 { 079 ThreadContext.detach(); 080 } 081 } 082 083 @Override 084 public void onClose(Session session, CloseReason closeReason) 085 { 086 super.onClose(session, closeReason); 087 088 final int closeCode = closeReason.getCloseCode().getCode(); 089 final String reasonPhrase = closeReason.getReasonPhrase(); 090 091 LOG.debug("Web Socket connection with id '{}' has been closed with code '{}' and reason: {}", 092 session.getId(), closeCode, reasonPhrase); 093 094 if (javaxWebSocketProcessor != null && isApplicationAlive(applicationName)) 095 { 096 javaxWebSocketProcessor.onClose(closeCode, reasonPhrase); 097 } 098 } 099 100 @Override 101 public void onError(Session session, Throwable t) 102 { 103 if (isIgnorableError(t)) 104 { 105 LOG.debug("An error occurred in web socket connection with id : {}", session.getId(), t); 106 } 107 else 108 { 109 LOG.error("An error occurred in web socket connection with id : {}", session.getId(), t); 110 } 111 112 super.onError(session, t); 113 114 if (javaxWebSocketProcessor != null && isApplicationAlive(applicationName)) 115 { 116 javaxWebSocketProcessor.onError(t); 117 } 118 } 119 120 private boolean isIgnorableError(Throwable t) 121 { 122 return 123 t instanceof EOFException || 124 (t instanceof IOException && "Broken pipe".equals(t.getMessage())); 125 } 126 127 private boolean isApplicationAlive(String appName) 128 { 129 return RUNNING_APPLICATIONS.contains(appName); 130 } 131 132 private String getApplicationName(Session session) 133 { 134 String appName = null; 135 136 Map<String, List<String>> parameters = session.getRequestParameterMap(); 137 if (parameters != null) 138 { 139 appName = parameters.get(WICKET_APP_PARAM_NAME).get(0); 140 } 141 else 142 { 143 // Glassfish 4 has null parameters map and non-null query string ... 144 String queryString = session.getQueryString(); 145 if (!Strings.isEmpty(queryString)) 146 { 147 String[] params = Strings.split(queryString, '&'); 148 for (String paramPair : params) 149 { 150 String[] nameValues = Strings.split(paramPair, '='); 151 if (WICKET_APP_PARAM_NAME.equals(nameValues[0])) 152 { 153 appName = nameValues[1]; 154 } 155 } 156 } 157 } 158 159 Checks.notNull(appName, "The application name cannot be read from the upgrade request's parameters"); 160 161 return appName; 162 } 163 164 private static class ApplicationListener implements IApplicationListener 165 { 166 @Override 167 public void onBeforeDestroyed(Application application) 168 { 169 String appName = application.getName(); 170 RUNNING_APPLICATIONS.remove(appName); 171 application.getApplicationListeners().remove(this); 172 } 173 } 174}