001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.client; 018 019import java.net.URI; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import org.springframework.context.SmartLifecycle; 025import org.springframework.web.util.UriComponentsBuilder; 026 027/** 028 * A base class for WebSocket connection managers. Provides a declarative style of 029 * connecting to a WebSocket server given a URI to connect to. The connection occurs when 030 * the Spring ApplicationContext is refreshed, if the {@link #autoStartup} property is set 031 * to {@code true}, or if set to {@code false}, the {@link #start()} and #stop methods can 032 * be invoked manually. 033 * 034 * @author Rossen Stoyanchev 035 * @since 4.0 036 */ 037public abstract class ConnectionManagerSupport implements SmartLifecycle { 038 039 protected final Log logger = LogFactory.getLog(getClass()); 040 041 private final URI uri; 042 043 private boolean autoStartup = false; 044 045 private int phase = Integer.MAX_VALUE; 046 047 private volatile boolean running = false; 048 049 private final Object lifecycleMonitor = new Object(); 050 051 052 public ConnectionManagerSupport(String uriTemplate, Object... uriVariables) { 053 this.uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand( 054 uriVariables).encode().toUri(); 055 } 056 057 058 protected URI getUri() { 059 return this.uri; 060 } 061 062 /** 063 * Set whether to auto-connect to the remote endpoint after this connection manager 064 * has been initialized and the Spring context has been refreshed. 065 * <p>Default is "false". 066 */ 067 public void setAutoStartup(boolean autoStartup) { 068 this.autoStartup = autoStartup; 069 } 070 071 /** 072 * Return the value for the 'autoStartup' property. If "true", this endpoint 073 * connection manager will connect to the remote endpoint upon a 074 * ContextRefreshedEvent. 075 */ 076 @Override 077 public boolean isAutoStartup() { 078 return this.autoStartup; 079 } 080 081 /** 082 * Specify the phase in which a connection should be established to the remote 083 * endpoint and subsequently closed. The startup order proceeds from lowest to 084 * highest, and the shutdown order is the reverse of that. By default this value is 085 * Integer.MAX_VALUE meaning that this endpoint connection factory connects as late as 086 * possible and is closed as soon as possible. 087 */ 088 public void setPhase(int phase) { 089 this.phase = phase; 090 } 091 092 /** 093 * Return the phase in which this endpoint connection factory will be auto-connected 094 * and stopped. 095 */ 096 @Override 097 public int getPhase() { 098 return this.phase; 099 } 100 101 102 /** 103 * Start the WebSocket connection. If already connected, the method has no impact. 104 */ 105 @Override 106 public final void start() { 107 synchronized (this.lifecycleMonitor) { 108 if (!isRunning()) { 109 startInternal(); 110 } 111 } 112 } 113 114 protected void startInternal() { 115 synchronized (this.lifecycleMonitor) { 116 if (logger.isInfoEnabled()) { 117 logger.info("Starting " + getClass().getSimpleName()); 118 } 119 this.running = true; 120 openConnection(); 121 } 122 } 123 124 @Override 125 public final void stop() { 126 synchronized (this.lifecycleMonitor) { 127 if (isRunning()) { 128 if (logger.isInfoEnabled()) { 129 logger.info("Stopping " + getClass().getSimpleName()); 130 } 131 try { 132 stopInternal(); 133 } 134 catch (Throwable ex) { 135 logger.error("Failed to stop WebSocket connection", ex); 136 } 137 finally { 138 this.running = false; 139 } 140 } 141 } 142 } 143 144 @Override 145 public final void stop(Runnable callback) { 146 synchronized (this.lifecycleMonitor) { 147 stop(); 148 callback.run(); 149 } 150 } 151 152 protected void stopInternal() throws Exception { 153 if (isConnected()) { 154 closeConnection(); 155 } 156 } 157 158 /** 159 * Return whether this ConnectionManager has been started. 160 */ 161 @Override 162 public boolean isRunning() { 163 return this.running; 164 } 165 166 167 protected abstract void openConnection(); 168 169 protected abstract void closeConnection() throws Exception; 170 171 protected abstract boolean isConnected(); 172 173}