diff --git a/Mage.Client/src/main/java/mage/client/dialog/RegisterUserDialog.java b/Mage.Client/src/main/java/mage/client/dialog/RegisterUserDialog.java
new file mode 100644
index 0000000000..38d0756820
--- /dev/null
+++ b/Mage.Client/src/main/java/mage/client/dialog/RegisterUserDialog.java
@@ -0,0 +1,231 @@
+package mage.client.dialog;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.swing.SwingWorker;
+import mage.client.MageFrame;
+import mage.client.util.Config;
+import mage.remote.Connection;
+import mage.remote.Session;
+import mage.remote.SessionImpl;
+import org.apache.log4j.Logger;
+
+public class RegisterUserDialog extends MageDialog {
+
+ private static final Logger logger = Logger.getLogger(ConnectDialog.class);
+ private Connection connection;
+ private ConnectTask task;
+ private Session session;
+
+ /**
+ * Creates new form RegisterUserDialog
+ */
+ public RegisterUserDialog() {
+ initComponents();
+ }
+
+ public void showDialog() {
+ this.txtServer.setText(MageFrame.getPreferences().get("serverAddress", Config.serverName));
+ this.txtPort.setText(MageFrame.getPreferences().get("serverPort", Integer.toString(Config.port)));
+ this.lblStatus.setText("");
+
+ this.setModal(true);
+ this.setLocation(50, 50);
+ this.setVisible(true);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ lblServer = new javax.swing.JLabel();
+ lblPort = new javax.swing.JLabel();
+ lblUserName = new javax.swing.JLabel();
+ lblPassword = new javax.swing.JLabel();
+ txtUserName = new javax.swing.JTextField();
+ txtPassword = new javax.swing.JPasswordField();
+ btnRegister = new javax.swing.JButton();
+ btnCancel = new javax.swing.JButton();
+ lblStatus = new javax.swing.JLabel();
+ txtServer = new javax.swing.JTextField();
+ txtPort = new javax.swing.JTextField();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle("Register");
+
+ lblServer.setLabelFor(txtServer);
+ lblServer.setText("Server:");
+
+ lblPort.setLabelFor(txtPort);
+ lblPort.setText("Port:");
+
+ lblUserName.setLabelFor(txtUserName);
+ lblUserName.setText("User name:");
+
+ lblPassword.setLabelFor(txtPassword);
+ lblPassword.setText("Password:");
+
+ txtUserName.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ txtUserNameActionPerformed(evt);
+ }
+ });
+
+ btnRegister.setText("Register");
+ btnRegister.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ btnRegisterActionPerformed(evt);
+ }
+ });
+
+ btnCancel.setText("Cancel");
+ btnCancel.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ btnCancelActionPerformed(evt);
+ }
+ });
+
+ lblStatus.setToolTipText("");
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(14, 14, 14)
+ .addComponent(lblUserName))
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(lblPassword, javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(lblPort, javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(lblServer, javax.swing.GroupLayout.Alignment.TRAILING))))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(lblStatus, javax.swing.GroupLayout.PREFERRED_SIZE, 292, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(btnRegister)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(btnCancel))
+ .addComponent(txtUserName, javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(txtPassword, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 292, Short.MAX_VALUE)
+ .addComponent(txtServer, javax.swing.GroupLayout.Alignment.LEADING))
+ .addComponent(txtPort, javax.swing.GroupLayout.PREFERRED_SIZE, 292, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addContainerGap(16, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(9, 9, 9)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(lblServer)
+ .addComponent(txtServer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(lblPort)
+ .addComponent(txtPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(lblUserName)
+ .addComponent(txtUserName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(lblPassword)
+ .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGap(18, 18, 18)
+ .addComponent(lblStatus, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(btnRegister)
+ .addComponent(btnCancel))
+ .addContainerGap(14, Short.MAX_VALUE))
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void txtUserNameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtUserNameActionPerformed
+ // TODO add your handling code here:
+ }//GEN-LAST:event_txtUserNameActionPerformed
+
+ private void btnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCancelActionPerformed
+ this.hideDialog();
+ }//GEN-LAST:event_btnCancelActionPerformed
+
+ private void btnRegisterActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRegisterActionPerformed
+ connection = new Connection();
+ connection.setHost(this.txtServer.getText().trim());
+ connection.setPort(Integer.valueOf(this.txtPort.getText().trim()));
+ connection.setUsername(this.txtUserName.getText().trim());
+ connection.setPassword(this.txtPassword.getText().trim());
+ PreferencesDialog.setProxyInformation(connection);
+ task = new ConnectTask();
+ task.execute();
+ }//GEN-LAST:event_btnRegisterActionPerformed
+
+ private class ConnectTask extends SwingWorker {
+
+ private boolean result = false;
+
+ private static final int CONNECTION_TIMEOUT_MS = 2100;
+
+ @Override
+ protected Boolean doInBackground() throws Exception {
+ lblStatus.setText("Connecting...");
+ btnRegister.setEnabled(false);
+ session = new SessionImpl(MageFrame.getInstance());
+ result = session.register(connection);
+ return result;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ get(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (result) {
+ lblStatus.setText("Registration succeeded");
+ MageFrame.getInstance().showMessage("Registration succeeded");
+ hideDialog();
+ } else {
+ lblStatus.setText("Could not register");
+ }
+ } catch (InterruptedException ex) {
+ logger.fatal("Update Players Task error", ex);
+ } catch (ExecutionException ex) {
+ logger.fatal("Update Players Task error", ex);
+ } catch (CancellationException ex) {
+ logger.info("Registration was canceled");
+ lblStatus.setText("Registration was canceled (but an account might have been actually created)");
+ } catch (TimeoutException ex) {
+ logger.fatal("Registration timeout: ", ex);
+ } finally {
+ MageFrame.stopConnecting();
+ btnRegister.setEnabled(true);
+ }
+ }
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton btnCancel;
+ private javax.swing.JButton btnRegister;
+ private javax.swing.JLabel lblPassword;
+ private javax.swing.JLabel lblPort;
+ private javax.swing.JLabel lblServer;
+ private javax.swing.JLabel lblStatus;
+ private javax.swing.JLabel lblUserName;
+ private javax.swing.JPasswordField txtPassword;
+ private javax.swing.JTextField txtPort;
+ private javax.swing.JTextField txtServer;
+ private javax.swing.JTextField txtUserName;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Mage.Common/src/mage/interfaces/MageServer.java b/Mage.Common/src/mage/interfaces/MageServer.java
index 85bae2817e..ac4dbf84ab 100644
--- a/Mage.Common/src/mage/interfaces/MageServer.java
+++ b/Mage.Common/src/mage/interfaces/MageServer.java
@@ -55,14 +55,17 @@ import mage.view.UserView;
*/
public interface MageServer {
+ // registers a user to the user DB.
+ boolean registerUser(String sessionId, String userName, String password, String email) throws MageException;
+
// connection methods
- // DEPRECATED - Use registerClientWithPassword instead. This is kept for older clients.
+ // DEPRECATED - Use connectUser instead. This is only kept for older clients.
// This can be deleted once users transitioned to newer clients (1.4.6v1 and later).
boolean registerClient(String userName, String sessionId, MageVersion version) throws MageException;
- boolean registerClientWithPassword(String userName, String password, String sessionId, MageVersion version) throws MageException;
+ boolean connectUser(String userName, String password, String sessionId, MageVersion version) throws MageException;
- boolean registerAdmin(String password, String sessionId, MageVersion version) throws MageException;
+ boolean connectAdmin(String password, String sessionId, MageVersion version) throws MageException;
// Not used
// void deregisterClient(String sessionId) throws MageException;
diff --git a/Mage.Common/src/mage/remote/SessionImpl.java b/Mage.Common/src/mage/remote/SessionImpl.java
index eab3306e5b..ddc6b99d87 100644
--- a/Mage.Common/src/mage/remote/SessionImpl.java
+++ b/Mage.Common/src/mage/remote/SessionImpl.java
@@ -122,183 +122,18 @@ public class SessionImpl implements Session {
return sessionId;
}
- @Override
- public synchronized boolean connect(Connection connection) {
- if (isConnected()) {
- disconnect(true);
- }
- this.connection = connection;
- this.canceled = false;
- return connect();
+ // RemotingTask encapsulates a task which is involved with some JBoss Remoting. This is
+ // intended to be used with handleRemotingTaskExceptions for sharing the common exception
+ // handling.
+ public interface RemotingTask {
+ public boolean run() throws Throwable;
}
- @Override
- public boolean stopConnecting() {
- canceled = true;
- return true;
- }
-
- @Override
- public boolean connect() {
- sessionState = SessionState.CONNECTING;
+ // handleRemotingTaskExceptions runs the given task and handles exceptions appropriately. This
+ // way we can share the common exception handling.
+ private boolean handleRemotingTaskExceptions(RemotingTask remoting) {
try {
- System.setProperty("http.nonProxyHosts", "code.google.com");
- System.setProperty("socksNonProxyHosts", "code.google.com");
-
- // clear previous values
- System.clearProperty("socksProxyHost");
- System.clearProperty("socksProxyPort");
- System.clearProperty("http.proxyHost");
- System.clearProperty("http.proxyPort");
-
- switch (connection.getProxyType()) {
- case SOCKS:
- System.setProperty("socksProxyHost", connection.getProxyHost());
- System.setProperty("socksProxyPort", Integer.toString(connection.getProxyPort()));
- break;
- case HTTP:
- System.setProperty("http.proxyHost", connection.getProxyHost());
- System.setProperty("http.proxyPort", Integer.toString(connection.getProxyPort()));
- Authenticator.setDefault(new MageAuthenticator(connection.getProxyUsername(), connection.getProxyPassword()));
- break;
- }
- InvokerLocator clientLocator = new InvokerLocator(connection.getURI());
-
- Map metadata = new HashMap<>();
- /*
- 5.8.3.1.1. Write timeouts
- The socket timeout facility offered by the JDK applies only to read operations on the socket. As of release 2.5.2,
- the socket and bisocket (and also sslsocket and sslbisocket) transports offer a write timeout facility. When a client
- or server is configured, in any of the usual ways, with the parameter org.jboss.remoting.transport.socket.SocketWrapper.WRITE_TIMEOUT
- (actual value "writeTimeout") set to a positive value (in milliseconds), all write operations will time out if they do
- not complete within the configured period. When a write operation times out, the socket upon which the write was invoked
- will be closed, which is likely to result in a java.net.SocketException.
- Note. A SocketException is considered to be a "retriable" exception, so, if the parameter "numberOfCallRetries" is set
- to a value greater than 1, an invocation interrupted by a write timeout can be retried.
- Note. The write timeout facility applies to writing of both invocations and responses. It applies to push callbacks as well.
- */
- metadata.put(SocketWrapper.WRITE_TIMEOUT, "2000");
- metadata.put("generalizeSocketException", "true");
- server = (MageServer) TransporterClient.createTransporterClient(clientLocator.getLocatorURI(), MageServer.class, metadata);
-
- // http://docs.jboss.org/jbossremoting/docs/guide/2.5/html_single/#d0e1057
- Map clientMetadata = new HashMap<>();
-
- clientMetadata.put(SocketWrapper.WRITE_TIMEOUT, "2000");
- /* generalizeSocketException
- * If set to false, a failed invocation will be retried in the case of
- * SocketExceptions. If set to true, a failed invocation will be retried in the case of
- * SocketExceptions and also any IOException
- * whose message matches the regular expression
- * ^.*(?:connection.*reset|connection.*closed|broken.*pipe).*$.
- * See also the "numberOfCallRetries" parameter, above. The default value is false.*/
- clientMetadata.put("generalizeSocketException", "true");
-
- /* A remoting server also has the capability to detect when a client is no longer available.
- * This is done by estabilishing a lease with the remoting clients that connect to a server.
- * On the client side, an org.jboss.remoting.LeasePinger periodically sends PING messages to
- * the server, and on the server side an org.jboss.remoting.Lease informs registered listeners
- * if the PING doesn't arrive withing the specified timeout period. */
- clientMetadata.put(Client.ENABLE_LEASE, "true");
- /*
- When the socket client invoker makes its first invocation, it will check to see if there is an available
- socket connection in its pool. Since is the first invocation, there will not be and will create a new socket
- connection and use it for making the invocation. Then when finished making invocation, will return the still
- active socket connection to the pool. As more client invocations are made, is possible for the number of
- socket connections to reach the maximum allowed (which is controlled by 'clientMaxPoolSize' property). At this
- point, when the next client invocation is made, it will wait up to some configured number of milliseconds, at
- which point it will throw an org.jboss.remoting.CannotConnectException. The number of milliseconds is given by
- the parameter MicroSocketClientInvoker.CONNECTION_WAIT (actual value "connectionWait"), with a default of
- 30000 milliseconds. Note that if more than one call retry is configured (see next paragraph),
- the CannotConnectException will be swallowed.
- Once the socket client invoker get an available socket connection from the pool, are not out of the woods yet.
- For example, a network problem could cause a java.net.SocketException. There is also a possibility that the socket
- connection, while still appearing to be valid, has "gone stale" while sitting in the pool. For example, a ServerThread
- on the other side of the connection could time out and close its socket. If the attempt to complete an invocation
- fails, then MicroSocketClientInvoker will make a number of attempts, according to the parameter "numberOfCallRetries",
- with a default value of 3. Once the configured number of retries has been exhausted,
- an org.jboss.remoting.InvocationFailureException will be thrown.
- */
- clientMetadata.put("numberOfCallRetries", "1");
-
- /**
- * I'll explain the meaning of "secondaryBindPort" and
- * "secondaryConnectPort", and maybe that will help. The Remoting
- * bisocket transport creates two ServerSockets on the server. The
- * "primary" ServerSocket is used to create connections used for
- * ordinary invocations, e.g., a request to create a JMS consumer,
- * and the "secondary" ServerSocket is used to create "control"
- * connections for internal Remoting messages. The port for the
- * primary ServerSocket is configured by the "serverBindPort"
- * parameter, and the port for the secondary ServerSocket is, by
- * default, chosen randomly. The "secondaryBindPort" parameter can
- * be used to assign a specific port to the secondary ServerSocket.
- * Now, if there is a translating firewall between the client and
- * server, the client should be given the value of the port that is
- * translated to the actual binding port of the secondary
- * ServerSocket. For example, your configuration will tell the
- * secondary ServerSocket to bind to port 14000, and it will tell
- * the client to connect to port 14001. It assumes that there is a
- * firewall which will translate 14001 to 14000. Apparently, that's
- * not happening.
- */
- // secondaryBindPort - the port to which the secondary server socket is to be bound. By default, an arbitrary port is selected.
- // secondaryConnectPort - the port clients are to use to connect to the secondary server socket.
- // By default, the value of secondaryBindPort is used. secondaryConnectPort is useful if the server is behind a translating firewall.
- // Indicated the max number of threads used within oneway thread pool.
- clientMetadata.put(Client.MAX_NUM_ONEWAY_THREADS, "10");
- clientMetadata.put(Remoting.USE_CLIENT_CONNECTION_IDENTITY, "true");
- callbackClient = new Client(clientLocator, "callback", clientMetadata);
-
- Map listenerMetadata = new HashMap<>();
- if (debugMode) {
- // prevent client from disconnecting while debugging
- listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "1000000");
- listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "900000");
- } else {
- listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "15000");
- listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "13000");
- }
- callbackClient.connect(new ClientConnectionListener(), listenerMetadata);
-
- Map callbackMetadata = new HashMap<>();
- callbackMetadata.put(Bisocket.IS_CALLBACK_SERVER, "true");
- if (callbackHandler == null) {
- callbackHandler = new CallbackHandler();
- }
- callbackClient.addListener(callbackHandler, callbackMetadata);
-
- Set callbackConnectors = callbackClient.getCallbackConnectors(callbackHandler);
- if (callbackConnectors.size() != 1) {
- logger.warn("There should be one callback Connector (number existing = " + callbackConnectors.size() + ")");
- }
-
- logger.info("Trying to connect as " + (this.getUserName() == null ? "" : this.getUserName()) + " to XMAGE server at " + connection.getHost() + ":" + connection.getPort());
- callbackClient.invoke(null);
-
- this.sessionId = callbackClient.getSessionId();
- boolean registerResult;
- if (connection.getAdminPassword() == null) {
- // for backward compatibility. don't remove twice call - first one does nothing but for version checking
- registerResult = server.registerClientWithPassword(connection.getUsername(), connection.getPassword(), sessionId, client.getVersion());
- if (registerResult) {
- server.setUserData(connection.getUsername(), sessionId, connection.getUserData());
- }
- } else {
- registerResult = server.registerAdmin(connection.getAdminPassword(), sessionId, client.getVersion());
- }
- if (registerResult) {
- sessionState = SessionState.CONNECTED;
- serverState = server.getServerState();
- if (!connection.getUsername().equals("Admin")) {
- updateDatabase(connection.isForceDBComparison(), serverState);
- }
- logger.info("Connected as " + (this.getUserName() == null ? "" : this.getUserName()) + " to MAGE server at " + connection.getHost() + ":" + connection.getPort());
- client.connected(this.getUserName() == null ? "" : this.getUserName() + "@" + connection.getHost() + ":" + connection.getPort() + " ");
- return true;
- }
- disconnect(false);
- // client.showMessage("Unable to connect to server.");
+ return remoting.run();
} catch (MalformedURLException ex) {
logger.fatal("", ex);
client.showMessage("Unable to connect to server. " + ex.getMessage());
@@ -353,6 +188,217 @@ public class SessionImpl implements Session {
return false;
}
+ @Override
+ public synchronized boolean register(final Connection connection) {
+ return establishJBossRemotingConnection(connection) && handleRemotingTaskExceptions(new RemotingTask() {
+ @Override
+ public boolean run() throws Throwable {
+ logger.info("Trying to register as " + getUserName() + " to XMAGE server at " + connection.getHost() + ":" + connection.getPort());
+ boolean registerResult = server.registerUser(sessionId, connection.getUsername(),
+ connection.getPassword(), "");
+ if (registerResult) {
+ logger.info("Registered as " + getUserName() + " to MAGE server at " + connection.getHost() + ":" + connection.getPort());
+ }
+ return registerResult;
+ }
+ });
+ }
+
+ @Override
+ public synchronized boolean connect(final Connection connection) {
+ return establishJBossRemotingConnection(connection) && handleRemotingTaskExceptions(new RemotingTask() {
+ @Override
+ public boolean run() throws Throwable {
+ logger.info("Trying to log-in as " + getUserName() + " to XMAGE server at " + connection.getHost() + ":" + connection.getPort());
+ boolean registerResult;
+ if (connection.getAdminPassword() == null) {
+ // for backward compatibility. don't remove twice call - first one does nothing but for version checking
+ registerResult = server.connectUser(connection.getUsername(), connection.getPassword(), sessionId, client.getVersion());
+ if (registerResult) {
+ server.setUserData(connection.getUsername(), sessionId, connection.getUserData());
+ }
+ } else {
+ registerResult = server.connectAdmin(connection.getAdminPassword(), sessionId, client.getVersion());
+ }
+ if (registerResult) {
+ serverState = server.getServerState();
+ if (!connection.getUsername().equals("Admin")) {
+ updateDatabase(connection.isForceDBComparison(), serverState);
+ }
+ logger.info("Logged-in as " + getUserName() + " to MAGE server at " + connection.getHost() + ":" + connection.getPort());
+ client.connected(getUserName() + "@" + connection.getHost() + ":" + connection.getPort() + " ");
+ return true;
+ }
+ disconnect(false);
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public boolean stopConnecting() {
+ canceled = true;
+ return true;
+ }
+
+ private boolean establishJBossRemotingConnection(final Connection connection) {
+ if (isConnected()) {
+ disconnect(true);
+ }
+ this.connection = connection;
+ this.canceled = false;
+ sessionState = SessionState.CONNECTING;
+ boolean result = handleRemotingTaskExceptions(new RemotingTask() {
+ @Override
+ public boolean run() throws Throwable {
+ logger.info("Trying to connect to XMAGE server at " + connection.getHost() + ":" + connection.getPort());
+
+ System.setProperty("http.nonProxyHosts", "code.google.com");
+ System.setProperty("socksNonProxyHosts", "code.google.com");
+
+ // clear previous values
+ System.clearProperty("socksProxyHost");
+ System.clearProperty("socksProxyPort");
+ System.clearProperty("http.proxyHost");
+ System.clearProperty("http.proxyPort");
+
+ switch (connection.getProxyType()) {
+ case SOCKS:
+ System.setProperty("socksProxyHost", connection.getProxyHost());
+ System.setProperty("socksProxyPort", Integer.toString(connection.getProxyPort()));
+ break;
+ case HTTP:
+ System.setProperty("http.proxyHost", connection.getProxyHost());
+ System.setProperty("http.proxyPort", Integer.toString(connection.getProxyPort()));
+ Authenticator.setDefault(new MageAuthenticator(connection.getProxyUsername(), connection.getProxyPassword()));
+ break;
+ }
+ InvokerLocator clientLocator = new InvokerLocator(connection.getURI());
+
+ Map metadata = new HashMap<>();
+ /*
+ 5.8.3.1.1. Write timeouts
+ The socket timeout facility offered by the JDK applies only to read operations on the socket. As of release 2.5.2,
+ the socket and bisocket (and also sslsocket and sslbisocket) transports offer a write timeout facility. When a client
+ or server is configured, in any of the usual ways, with the parameter org.jboss.remoting.transport.socket.SocketWrapper.WRITE_TIMEOUT
+ (actual value "writeTimeout") set to a positive value (in milliseconds), all write operations will time out if they do
+ not complete within the configured period. When a write operation times out, the socket upon which the write was invoked
+ will be closed, which is likely to result in a java.net.SocketException.
+ Note. A SocketException is considered to be a "retriable" exception, so, if the parameter "numberOfCallRetries" is set
+ to a value greater than 1, an invocation interrupted by a write timeout can be retried.
+ Note. The write timeout facility applies to writing of both invocations and responses. It applies to push callbacks as well.
+ */
+ metadata.put(SocketWrapper.WRITE_TIMEOUT, "2000");
+ metadata.put("generalizeSocketException", "true");
+ server = (MageServer) TransporterClient.createTransporterClient(clientLocator.getLocatorURI(), MageServer.class, metadata);
+
+ // http://docs.jboss.org/jbossremoting/docs/guide/2.5/html_single/#d0e1057
+ Map clientMetadata = new HashMap<>();
+
+ clientMetadata.put(SocketWrapper.WRITE_TIMEOUT, "2000");
+ /* generalizeSocketException
+ * If set to false, a failed invocation will be retried in the case of
+ * SocketExceptions. If set to true, a failed invocation will be retried in the case of
+ * SocketExceptions and also any IOException
+ * whose message matches the regular expression
+ * ^.*(?:connection.*reset|connection.*closed|broken.*pipe).*$.
+ * See also the "numberOfCallRetries" parameter, above. The default value is false.*/
+ clientMetadata.put("generalizeSocketException", "true");
+
+ /* A remoting server also has the capability to detect when a client is no longer available.
+ * This is done by estabilishing a lease with the remoting clients that connect to a server.
+ * On the client side, an org.jboss.remoting.LeasePinger periodically sends PING messages to
+ * the server, and on the server side an org.jboss.remoting.Lease informs registered listeners
+ * if the PING doesn't arrive withing the specified timeout period. */
+ clientMetadata.put(Client.ENABLE_LEASE, "true");
+ /*
+ When the socket client invoker makes its first invocation, it will check to see if there is an available
+ socket connection in its pool. Since is the first invocation, there will not be and will create a new socket
+ connection and use it for making the invocation. Then when finished making invocation, will return the still
+ active socket connection to the pool. As more client invocations are made, is possible for the number of
+ socket connections to reach the maximum allowed (which is controlled by 'clientMaxPoolSize' property). At this
+ point, when the next client invocation is made, it will wait up to some configured number of milliseconds, at
+ which point it will throw an org.jboss.remoting.CannotConnectException. The number of milliseconds is given by
+ the parameter MicroSocketClientInvoker.CONNECTION_WAIT (actual value "connectionWait"), with a default of
+ 30000 milliseconds. Note that if more than one call retry is configured (see next paragraph),
+ the CannotConnectException will be swallowed.
+ Once the socket client invoker get an available socket connection from the pool, are not out of the woods yet.
+ For example, a network problem could cause a java.net.SocketException. There is also a possibility that the socket
+ connection, while still appearing to be valid, has "gone stale" while sitting in the pool. For example, a ServerThread
+ on the other side of the connection could time out and close its socket. If the attempt to complete an invocation
+ fails, then MicroSocketClientInvoker will make a number of attempts, according to the parameter "numberOfCallRetries",
+ with a default value of 3. Once the configured number of retries has been exhausted,
+ an org.jboss.remoting.InvocationFailureException will be thrown.
+ */
+ clientMetadata.put("numberOfCallRetries", "1");
+
+ /**
+ * I'll explain the meaning of "secondaryBindPort" and
+ * "secondaryConnectPort", and maybe that will help. The Remoting
+ * bisocket transport creates two ServerSockets on the server. The
+ * "primary" ServerSocket is used to create connections used for
+ * ordinary invocations, e.g., a request to create a JMS consumer,
+ * and the "secondary" ServerSocket is used to create "control"
+ * connections for internal Remoting messages. The port for the
+ * primary ServerSocket is configured by the "serverBindPort"
+ * parameter, and the port for the secondary ServerSocket is, by
+ * default, chosen randomly. The "secondaryBindPort" parameter can
+ * be used to assign a specific port to the secondary ServerSocket.
+ * Now, if there is a translating firewall between the client and
+ * server, the client should be given the value of the port that is
+ * translated to the actual binding port of the secondary
+ * ServerSocket. For example, your configuration will tell the
+ * secondary ServerSocket to bind to port 14000, and it will tell
+ * the client to connect to port 14001. It assumes that there is a
+ * firewall which will translate 14001 to 14000. Apparently, that's
+ * not happening.
+ */
+ // secondaryBindPort - the port to which the secondary server socket is to be bound. By default, an arbitrary port is selected.
+ // secondaryConnectPort - the port clients are to use to connect to the secondary server socket.
+ // By default, the value of secondaryBindPort is used. secondaryConnectPort is useful if the server is behind a translating firewall.
+ // Indicated the max number of threads used within oneway thread pool.
+ clientMetadata.put(Client.MAX_NUM_ONEWAY_THREADS, "10");
+ clientMetadata.put(Remoting.USE_CLIENT_CONNECTION_IDENTITY, "true");
+ callbackClient = new Client(clientLocator, "callback", clientMetadata);
+
+ Map listenerMetadata = new HashMap<>();
+ if (debugMode) {
+ // prevent client from disconnecting while debugging
+ listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "1000000");
+ listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "900000");
+ } else {
+ listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_PERIOD, "15000");
+ listenerMetadata.put(ConnectionValidator.VALIDATOR_PING_TIMEOUT, "13000");
+ }
+ callbackClient.connect(new ClientConnectionListener(), listenerMetadata);
+
+ Map callbackMetadata = new HashMap<>();
+ callbackMetadata.put(Bisocket.IS_CALLBACK_SERVER, "true");
+ if (callbackHandler == null) {
+ callbackHandler = new CallbackHandler();
+ }
+ callbackClient.addListener(callbackHandler, callbackMetadata);
+
+ Set callbackConnectors = callbackClient.getCallbackConnectors(callbackHandler);
+ if (callbackConnectors.size() != 1) {
+ logger.warn("There should be one callback Connector (number existing = " + callbackConnectors.size() + ")");
+ }
+
+ callbackClient.invoke(null);
+
+ sessionId = callbackClient.getSessionId();
+ sessionState = SessionState.CONNECTED;
+ logger.info("Connected to MAGE server at " + connection.getHost() + ":" + connection.getPort());
+ return true;
+ }
+ });
+ if (result) {
+ return true;
+ }
+ disconnect(false);
+ return false;
+ }
+
private void updateDatabase(boolean forceDBComparison, ServerState serverState) {
long cardDBVersion = CardRepository.instance.getContentVersionFromDB();
if (forceDBComparison || serverState.getCardsContentVersion() > cardDBVersion) {
@@ -1395,7 +1441,8 @@ public class SessionImpl implements Session {
@Override
public String getUserName() {
- return connection.getUsername();
+ String username = connection.getUsername();
+ return username == null ? "" : username;
}
@Override
diff --git a/Mage.Common/src/mage/remote/interfaces/Connect.java b/Mage.Common/src/mage/remote/interfaces/Connect.java
index 948fc228d1..2b5921e2e7 100644
--- a/Mage.Common/src/mage/remote/interfaces/Connect.java
+++ b/Mage.Common/src/mage/remote/interfaces/Connect.java
@@ -34,12 +34,12 @@ import mage.remote.Connection;
*/
public interface Connect {
+ boolean register(Connection connection);
+
boolean connect(Connection connection);
boolean stopConnecting();
- boolean connect();
-
void disconnect(boolean showMessage);
void reconnect(Throwable throwable);
diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUser.java b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java
index d3a678ccd1..b9b754a698 100644
--- a/Mage.Server/src/main/java/mage/server/AuthorizedUser.java
+++ b/Mage.Server/src/main/java/mage/server/AuthorizedUser.java
@@ -29,15 +29,19 @@ public class AuthorizedUser {
@DatabaseField
protected int hashIterations;
+ @DatabaseField
+ protected String email;
+
public AuthorizedUser() {
}
- public AuthorizedUser(String name, Hash hash) {
+ public AuthorizedUser(String name, Hash hash, String email) {
this.name = name;
this.password = hash.toBase64();
this.salt = hash.getSalt().toBase64();
this.hashAlgorithm = hash.getAlgorithmName();
this.hashIterations = hash.getIterations();
+ this.email = email;
}
public boolean doCredentialsMatch(String name, String password) {
diff --git a/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java
index 551c8cb6fc..3a56ec15a1 100644
--- a/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java
+++ b/Mage.Server/src/main/java/mage/server/AuthorizedUserRepository.java
@@ -27,7 +27,7 @@ public enum AuthorizedUserRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/authorized_user.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "authorized_user";
// raise this if db structure was changed
- private static final long DB_VERSION = 0;
+ private static final long DB_VERSION = 1;
private static final RandomNumberGenerator rng = new SecureRandomNumberGenerator();
private Dao dao;
@@ -52,14 +52,14 @@ public enum AuthorizedUserRepository {
}
}
- public void add(final String userName, final String password) {
+ public void add(final String userName, final String password, final String email) {
try {
dao.callBatchTasks(new Callable