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}