JSR-356 - making ClientContainer a use jetty LifeCycle properly
This commit is contained in:
parent
8a2ccdf8ae
commit
0fd0ecc887
|
@ -36,8 +36,7 @@ import javax.websocket.Extension;
|
||||||
import javax.websocket.Session;
|
import javax.websocket.Session;
|
||||||
import javax.websocket.WebSocketContainer;
|
import javax.websocket.WebSocketContainer;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
|
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
|
||||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||||
|
@ -58,10 +57,8 @@ import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
|
||||||
* <p>
|
* <p>
|
||||||
* This should be specific to a JVM if run in a standalone mode. or specific to a WebAppContext if running on the Jetty server.
|
* This should be specific to a JVM if run in a standalone mode. or specific to a WebAppContext if running on the Jetty server.
|
||||||
*/
|
*/
|
||||||
public class ClientContainer implements WebSocketContainer
|
public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientContainer.class);
|
|
||||||
|
|
||||||
/** Tracking all primitive decoders for the container */
|
/** Tracking all primitive decoders for the container */
|
||||||
private final DecoderFactory decoderFactory;
|
private final DecoderFactory decoderFactory;
|
||||||
/** Tracking all primitive encoders for the container */
|
/** Tracking all primitive encoders for the container */
|
||||||
|
@ -161,6 +158,16 @@ public class ClientContainer implements WebSocketContainer
|
||||||
return connect(instance,path);
|
return connect(instance,path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStart() throws Exception
|
||||||
|
{
|
||||||
|
client = new WebSocketClient();
|
||||||
|
client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
|
||||||
|
client.setSessionFactory(new JsrSessionFactory(this));
|
||||||
|
addBean(client);
|
||||||
|
super.doStart();
|
||||||
|
}
|
||||||
|
|
||||||
public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
|
public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
|
||||||
{
|
{
|
||||||
EndpointMetadata metadata = null;
|
EndpointMetadata metadata = null;
|
||||||
|
@ -320,38 +327,4 @@ public class ClientContainer implements WebSocketContainer
|
||||||
client.getPolicy().setMaxTextMessageSize(max);
|
client.getPolicy().setMaxTextMessageSize(max);
|
||||||
client.setMaxTextMessageBufferSize(max);
|
client.setMaxTextMessageBufferSize(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the container
|
|
||||||
*/
|
|
||||||
public void start()
|
|
||||||
{
|
|
||||||
client = new WebSocketClient();
|
|
||||||
client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
|
|
||||||
client.setSessionFactory(new JsrSessionFactory(this));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.start();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOG.warn("Unable to start Jetty WebSocketClient instance",e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the container
|
|
||||||
*/
|
|
||||||
public void stop()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOG.warn("Unable to start Jetty WebSocketClient instance",e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,14 @@ public class JettyClientContainerProvider extends ContainerProvider
|
||||||
protected WebSocketContainer getContainer()
|
protected WebSocketContainer getContainer()
|
||||||
{
|
{
|
||||||
ClientContainer container = new ClientContainer();
|
ClientContainer container = new ClientContainer();
|
||||||
|
try
|
||||||
|
{
|
||||||
container.start();
|
container.start();
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Unable to start Client Container",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,20 +38,24 @@ import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
|
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
|
||||||
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
|
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
|
||||||
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
|
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
|
||||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
|
||||||
|
|
||||||
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer, WebSocketServerFactory.Listener
|
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ServerContainer.class);
|
private static final Logger LOG = Log.getLogger(ServerContainer.class);
|
||||||
|
|
||||||
private final MappedWebSocketCreator mappedCreator;
|
private final MappedWebSocketCreator mappedCreator;
|
||||||
private WebSocketServerFactory webSocketServletFactory;
|
private final WebSocketServerFactory webSocketServerFactory;
|
||||||
private Map<Class<?>, ServerEndpointMetadata> endpointServerMetadataCache = new ConcurrentHashMap<>();
|
private Map<Class<?>, ServerEndpointMetadata> endpointServerMetadataCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ServerContainer(MappedWebSocketCreator creator)
|
public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.mappedCreator = creator;
|
this.mappedCreator = creator;
|
||||||
|
this.webSocketServerFactory = factory;
|
||||||
|
EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory();
|
||||||
|
eventDriverFactory.addImplementation(new JsrServerEndpointImpl());
|
||||||
|
eventDriverFactory.addImplementation(new JsrEndpointImpl());
|
||||||
|
this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
|
public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
|
||||||
|
@ -134,25 +138,4 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketServletFactory getWebSocketServletFactory()
|
|
||||||
{
|
|
||||||
return webSocketServletFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketServerFactoryStarted(WebSocketServerFactory factory)
|
|
||||||
{
|
|
||||||
this.webSocketServletFactory = factory;
|
|
||||||
EventDriverFactory eventDriverFactory = this.webSocketServletFactory.getEventDriverFactory();
|
|
||||||
eventDriverFactory.addImplementation(new JsrServerEndpointImpl());
|
|
||||||
eventDriverFactory.addImplementation(new JsrEndpointImpl());
|
|
||||||
this.webSocketServletFactory.addSessionFactory(new JsrSessionFactory(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWebSocketServerFactoryStopped(WebSocketServerFactory factory)
|
|
||||||
{
|
|
||||||
/* do nothing */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,17 +54,16 @@ public class WebSocketConfiguration extends AbstractConfiguration
|
||||||
// Store reference to the WebSocketUpgradeFilter
|
// Store reference to the WebSocketUpgradeFilter
|
||||||
context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter);
|
context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter);
|
||||||
|
|
||||||
// Store reference to DiscoveredEndpoints
|
|
||||||
context.setAttribute(DiscoveredEndpoints.class.getName(),new DiscoveredEndpoints());
|
|
||||||
|
|
||||||
// Create the Jetty ServerContainer implementation
|
// Create the Jetty ServerContainer implementation
|
||||||
ServerContainer jettyContainer = new ServerContainer(filter);
|
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory());
|
||||||
filter.setWebSocketServerFactoryListener(jettyContainer);
|
context.addBean(jettyContainer);
|
||||||
context.addBean(jettyContainer,true);
|
|
||||||
|
|
||||||
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
|
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
|
||||||
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
|
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);
|
||||||
|
|
||||||
|
// Store reference to DiscoveredEndpoints
|
||||||
|
context.setAttribute(DiscoveredEndpoints.class.getName(),new DiscoveredEndpoints());
|
||||||
|
|
||||||
return jettyContainer;
|
return jettyContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortObjectT
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.primitives.ShortTextSocket;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.ReaderParamSocket;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.streaming.StringReturnReaderParamSocket;
|
||||||
|
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -103,7 +104,7 @@ public class ServerAnnotatedEndpointScanner_GoodSignaturesTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ServerContainer container = new ServerContainer(new DummyCreator());
|
private static ServerContainer container = new ServerContainer(new DummyCreator(), new WebSocketServerFactory());
|
||||||
|
|
||||||
@Parameters
|
@Parameters
|
||||||
public static Collection<Case[]> data() throws Exception
|
public static Collection<Case[]> data() throws Exception
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidErrorIntSocket;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenCloseReasonSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenCloseReasonSocket;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenIntSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenIntSocket;
|
||||||
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenSessionIntSocket;
|
import org.eclipse.jetty.websocket.jsr356.server.samples.InvalidOpenSessionIntSocket;
|
||||||
|
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -57,7 +58,7 @@ public class ServerAnnotatedEndpointScanner_InvalidSignaturesTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ServerAnnotatedEndpointScanner_InvalidSignaturesTest.class);
|
private static final Logger LOG = Log.getLogger(ServerAnnotatedEndpointScanner_InvalidSignaturesTest.class);
|
||||||
|
|
||||||
private static ServerContainer container = new ServerContainer(new DummyCreator());
|
private static ServerContainer container = new ServerContainer(new DummyCreator(), new WebSocketServerFactory());
|
||||||
|
|
||||||
@Parameters
|
@Parameters
|
||||||
public static Collection<Class<?>[]> data()
|
public static Collection<Class<?>[]> data()
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -66,13 +65,6 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||||
*/
|
*/
|
||||||
public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory
|
public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory
|
||||||
{
|
{
|
||||||
public static interface Listener
|
|
||||||
{
|
|
||||||
void onWebSocketServerFactoryStarted(WebSocketServerFactory factory);
|
|
||||||
|
|
||||||
void onWebSocketServerFactoryStopped(WebSocketServerFactory factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
|
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
|
||||||
private static final ThreadLocal<UpgradeContext> ACTIVE_CONTEXT = new ThreadLocal<>();
|
private static final ThreadLocal<UpgradeContext> ACTIVE_CONTEXT = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@ -100,7 +92,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
private final WebSocketPolicy basePolicy;
|
private final WebSocketPolicy basePolicy;
|
||||||
private final EventDriverFactory eventDriverFactory;
|
private final EventDriverFactory eventDriverFactory;
|
||||||
private final WebSocketExtensionFactory extensionFactory;
|
private final WebSocketExtensionFactory extensionFactory;
|
||||||
private List<WebSocketServerFactory.Listener> listeners = new ArrayList<>();
|
|
||||||
private List<SessionFactory> sessionFactories;
|
private List<SessionFactory> sessionFactories;
|
||||||
private WebSocketCreator creator;
|
private WebSocketCreator creator;
|
||||||
private List<Class<?>> registeredSocketClasses;
|
private List<Class<?>> registeredSocketClasses;
|
||||||
|
@ -197,11 +188,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(WebSocketServerFactory.Listener listener)
|
|
||||||
{
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSessionFactory(SessionFactory sessionFactory)
|
public void addSessionFactory(SessionFactory sessionFactory)
|
||||||
{
|
{
|
||||||
if (sessionFactories.contains(sessionFactory))
|
if (sessionFactories.contains(sessionFactory))
|
||||||
|
@ -291,24 +277,10 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws Exception
|
|
||||||
{
|
|
||||||
for (WebSocketServerFactory.Listener listener : listeners)
|
|
||||||
{
|
|
||||||
listener.onWebSocketServerFactoryStarted(this);
|
|
||||||
}
|
|
||||||
super.doStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doStop() throws Exception
|
protected void doStop() throws Exception
|
||||||
{
|
{
|
||||||
closeAllConnections();
|
closeAllConnections();
|
||||||
for (WebSocketServerFactory.Listener listener : listeners)
|
|
||||||
{
|
|
||||||
listener.onWebSocketServerFactoryStopped(this);
|
|
||||||
}
|
|
||||||
super.doStop();
|
super.doStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,11 +301,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
return extensionFactory;
|
return extensionFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<WebSocketServerFactory.Listener> getListeners()
|
|
||||||
{
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebSocketPolicy getPolicy()
|
public WebSocketPolicy getPolicy()
|
||||||
{
|
{
|
||||||
|
@ -343,7 +310,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception
|
public void init() throws Exception
|
||||||
{
|
{
|
||||||
start();
|
start(); // start lifecycle
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -415,11 +382,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
|
||||||
registeredSocketClasses.add(websocketPojo);
|
registeredSocketClasses.add(websocketPojo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(WebSocketServerFactory.Listener listener)
|
|
||||||
{
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sessionClosed(WebSocketSession session)
|
public boolean sessionClosed(WebSocketSession session)
|
||||||
{
|
{
|
||||||
return isRunning() && sessions.remove(session);
|
return isRunning() && sessions.remove(session);
|
||||||
|
|
|
@ -46,12 +46,18 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||||
* Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
|
* Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
|
||||||
*/
|
*/
|
||||||
@ManagedObject("WebSocket Upgrade Filter")
|
@ManagedObject("WebSocket Upgrade Filter")
|
||||||
public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, Dumpable
|
public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
|
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
|
||||||
|
private final WebSocketServerFactory factory;
|
||||||
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
|
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
|
||||||
private WebSocketServerFactory factory;
|
|
||||||
private WebSocketServerFactory.Listener listener;
|
public WebSocketUpgradeFilter()
|
||||||
|
{
|
||||||
|
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
||||||
|
factory = new WebSocketServerFactory(policy);
|
||||||
|
addBean(factory,true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMapping(PathSpec spec, WebSocketCreator creator)
|
public void addMapping(PathSpec spec, WebSocketCreator creator)
|
||||||
|
@ -153,7 +159,7 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
|
WebSocketPolicy policy = factory.getPolicy();
|
||||||
|
|
||||||
String max = config.getInitParameter("maxIdleTime");
|
String max = config.getInitParameter("maxIdleTime");
|
||||||
if (max != null)
|
if (max != null)
|
||||||
|
@ -179,9 +185,7 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
|
||||||
policy.setInputBufferSize(Integer.parseInt(max));
|
policy.setInputBufferSize(Integer.parseInt(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
factory = new WebSocketServerFactory(policy);
|
factory.start();
|
||||||
factory.addListener(this.listener);
|
|
||||||
factory.init();
|
|
||||||
}
|
}
|
||||||
catch (Exception x)
|
catch (Exception x)
|
||||||
{
|
{
|
||||||
|
@ -189,11 +193,6 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWebSocketServerFactoryListener(WebSocketServerFactory.Listener listener)
|
|
||||||
{
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue