Set SocketWriteTimeout to 10 seconds.

This commit is contained in:
LevelX2 2017-04-17 00:01:39 +02:00
parent ed341528d9
commit 35791ac6b7
9 changed files with 164 additions and 154 deletions

View file

@ -27,6 +27,21 @@
*/ */
package mage.client; package mage.client;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import mage.cards.decks.Deck; import mage.cards.decks.Deck;
import mage.cards.repository.CardCriteria; import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo; import mage.cards.repository.CardInfo;
@ -77,22 +92,6 @@ import org.mage.plugins.card.images.DownloadPictures;
import org.mage.plugins.card.info.CardInfoPaneImpl; import org.mage.plugins.card.info.CardInfoPaneImpl;
import org.mage.plugins.card.utils.impl.ImageManagerImpl; import org.mage.plugins.card.utils.impl.ImageManagerImpl;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.prefs.Preferences;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -1056,7 +1055,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient {
// use already open editor // use already open editor
Component[] windows = desktopPane.getComponentsInLayer(JLayeredPane.DEFAULT_LAYER); Component[] windows = desktopPane.getComponentsInLayer(JLayeredPane.DEFAULT_LAYER);
for (Component window : windows) { for (Component window : windows) {
if (window instanceof DeckEditorPane && ((MagePane)window).getTitle().equals(name)) { if (window instanceof DeckEditorPane && ((MagePane) window).getTitle().equals(name)) {
setActive((MagePane) window); setActive((MagePane) window);
return; return;
} }

View file

@ -56,6 +56,7 @@ public class Connection {
private int clientCardDatabaseVersion; private int clientCardDatabaseVersion;
private boolean forceDBComparison; private boolean forceDBComparison;
private String userIdStr; private String userIdStr;
private int socketWriteTimeout;
private UserData userData; private UserData userData;
@ -76,6 +77,7 @@ public class Connection {
public Connection(String parameter) { public Connection(String parameter) {
this.parameter = parameter; this.parameter = parameter;
socketWriteTimeout = 10000;
} }
@Override @Override
@ -276,7 +278,6 @@ public class Connection {
return allMACs.toString(); return allMACs.toString();
} }
public void setUserData(UserData userData) { public void setUserData(UserData userData) {
this.userData = userData; this.userData = userData;
} }
@ -292,4 +293,8 @@ public class Connection {
public void setForceDBComparison(boolean forceDBComparison) { public void setForceDBComparison(boolean forceDBComparison) {
this.forceDBComparison = forceDBComparison; this.forceDBComparison = forceDBComparison;
} }
public int getSocketWriteTimeout() {
return socketWriteTimeout;
}
} }

View file

@ -27,6 +27,12 @@
*/ */
package mage.remote; package mage.remote;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import mage.MageException; import mage.MageException;
import mage.cards.decks.DeckCardLists; import mage.cards.decks.DeckCardLists;
import mage.cards.decks.InvalidDeckException; import mage.cards.decks.InvalidDeckException;
@ -56,13 +62,6 @@ import org.jboss.remoting.transport.bisocket.Bisocket;
import org.jboss.remoting.transport.socket.SocketWrapper; import org.jboss.remoting.transport.socket.SocketWrapper;
import org.jboss.remoting.transporter.TransporterClient; import org.jboss.remoting.transporter.TransporterClient;
import javax.swing.*;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -220,34 +219,35 @@ public class SessionImpl implements Session {
public synchronized boolean connect(final Connection connection) { public synchronized boolean connect(final Connection connection) {
return establishJBossRemotingConnection(connection) return establishJBossRemotingConnection(connection)
&& handleRemotingTaskExceptions(new RemotingTask() { && handleRemotingTaskExceptions(new RemotingTask() {
@Override @Override
public boolean run() throws Throwable { public boolean run() throws Throwable {
logger.info("Trying to log-in as " + getUserName() + " to XMAGE server at " + connection.getHost() + ':' + connection.getPort()); logger.info("Trying to log-in as " + getUserName() + " to XMAGE server at " + connection.getHost() + ':' + connection.getPort());
boolean registerResult; boolean registerResult;
if (connection.getAdminPassword() == null) { if (connection.getAdminPassword() == null) {
// for backward compatibility. don't remove twice call - first one does nothing but for version checking // 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(), connection.getUserIdStr()); registerResult = server.connectUser(connection.getUsername(), connection.getPassword(), sessionId, client.getVersion(), connection.getUserIdStr());
if (registerResult) { if (registerResult) {
server.setUserData(connection.getUsername(), sessionId, connection.getUserData(), client.getVersion().toString(), connection.getUserIdStr()); server.setUserData(connection.getUsername(), sessionId, connection.getUserData(), client.getVersion().toString(), connection.getUserIdStr());
}
} 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;
} }
} 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 Optional<String> getServerHostname() { public Optional<String> getServerHostname() {
return isConnected() ? Optional.of(connection.getHost()) : Optional.<String>empty(); return isConnected() ? Optional.of(connection.getHost()) : Optional.<String>empty();
} }
@ -305,14 +305,14 @@ public class SessionImpl implements Session {
to a value greater than 1, an invocation interrupted by a write timeout can be retried. 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. 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(SocketWrapper.WRITE_TIMEOUT, String.valueOf(connection.getSocketWriteTimeout()));
metadata.put("generalizeSocketException", "true"); metadata.put("generalizeSocketException", "true");
server = (MageServer) TransporterClient.createTransporterClient(clientLocator.getLocatorURI(), MageServer.class, metadata); server = (MageServer) TransporterClient.createTransporterClient(clientLocator.getLocatorURI(), MageServer.class, metadata);
// http://docs.jboss.org/jbossremoting/docs/guide/2.5/html_single/#d0e1057 // http://docs.jboss.org/jbossremoting/docs/guide/2.5/html_single/#d0e1057
Map<String, String> clientMetadata = new HashMap<>(); Map<String, String> clientMetadata = new HashMap<>();
clientMetadata.put(SocketWrapper.WRITE_TIMEOUT, "2000"); clientMetadata.put(SocketWrapper.WRITE_TIMEOUT, String.valueOf(connection.getSocketWriteTimeout()));
/* generalizeSocketException /* generalizeSocketException
* If set to false, a failed invocation will be retried in the case of * 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. If set to true, a failed invocation will be retried in the case of
@ -473,7 +473,7 @@ public class SessionImpl implements Session {
/** /**
* @param askForReconnect - true = connection was lost because of error and * @param askForReconnect - true = connection was lost because of error and
* ask the user if he want to try to reconnect * ask the user if he want to try to reconnect
*/ */
@Override @Override
public synchronized void disconnect(boolean askForReconnect) { public synchronized void disconnect(boolean askForReconnect) {
@ -967,7 +967,6 @@ public class SessionImpl implements Session {
return false; return false;
} }
@Override @Override
public boolean joinGame(UUID gameId) { public boolean joinGame(UUID gameId) {
try { try {

View file

@ -12,6 +12,7 @@
leasePeriod - To turn on server side connection failure detection of remoting clients, it is necessary to satisfy two criteria. leasePeriod - To turn on server side connection failure detection of remoting clients, it is necessary to satisfy two criteria.
The first is that the client lease period is set and is a value greater than 0. The value is represented in milliseconds. The first is that the client lease period is set and is a value greater than 0. The value is represented in milliseconds.
The client lease period can be set by either the 'clientLeasePeriod' attribute within the Connector configuration or by calling the Connector method The client lease period can be set by either the 'clientLeasePeriod' attribute within the Connector configuration or by calling the Connector method
socketWriteTimeout - All write operations will time out if they do not complete within the configured period.
maxGameThreads - Number of games that can be started simultanously on the server maxGameThreads - Number of games that can be started simultanously on the server
maxSecondsIdle - Number of seconds after that a game is auto conceded by the player that was idle for such a time maxSecondsIdle - Number of seconds after that a game is auto conceded by the player that was idle for such a time
minUserNameLength - minmal allowed length of a user name to connect to the server minUserNameLength - minmal allowed length of a user name to connect to the server
@ -40,6 +41,7 @@
numAcceptThreads="2" numAcceptThreads="2"
maxPoolSize="300" maxPoolSize="300"
leasePeriod="5000" leasePeriod="5000"
socketWriteTimeout="10000"
maxGameThreads="10" maxGameThreads="10"
maxSecondsIdle="600" maxSecondsIdle="600"
minUserNameLength="3" minUserNameLength="3"

View file

@ -12,6 +12,7 @@
leasePeriod - To turn on server side connection failure detection of remoting clients, it is necessary to satisfy two criteria. leasePeriod - To turn on server side connection failure detection of remoting clients, it is necessary to satisfy two criteria.
The first is that the client lease period is set and is a value greater than 0. The value is represented in milliseconds. The first is that the client lease period is set and is a value greater than 0. The value is represented in milliseconds.
The client lease period can be set by either the 'clientLeasePeriod' attribute within the Connector configuration or by calling the Connector method The client lease period can be set by either the 'clientLeasePeriod' attribute within the Connector configuration or by calling the Connector method
socketWriteTimeout - All write operations will time out if they do not complete within the configured period.
maxGameThreads - Number of games that can be started simultanously on the server maxGameThreads - Number of games that can be started simultanously on the server
maxSecondsIdle - Number of seconds after that a game is auto conceded by the player that was idle for such a time maxSecondsIdle - Number of seconds after that a game is auto conceded by the player that was idle for such a time
minUserNameLength - minmal allowed length of a user name to connect to the server minUserNameLength - minmal allowed length of a user name to connect to the server
@ -39,6 +40,7 @@
numAcceptThreads="2" numAcceptThreads="2"
maxPoolSize="300" maxPoolSize="300"
leasePeriod="5000" leasePeriod="5000"
socketWriteTimeout="10000"
maxGameThreads="10" maxGameThreads="10"
maxSecondsIdle="600" maxSecondsIdle="600"
minUserNameLength="3" minUserNameLength="3"

View file

@ -27,6 +27,12 @@
*/ */
package mage.server; package mage.server;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.*;
import javax.management.MBeanServer;
import mage.cards.ExpansionSet; import mage.cards.ExpansionSet;
import mage.cards.Sets; import mage.cards.Sets;
import mage.cards.repository.CardScanner; import mage.cards.repository.CardScanner;
@ -59,13 +65,6 @@ import org.jboss.remoting.transporter.TransporterClient;
import org.jboss.remoting.transporter.TransporterServer; import org.jboss.remoting.transporter.TransporterServer;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import javax.management.MBeanServer;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.*;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -202,6 +201,7 @@ public final class Main {
logger.info("Config - save game active: " + (config.isSaveGameActivated() ? "true" : "false")); logger.info("Config - save game active: " + (config.isSaveGameActivated() ? "true" : "false"));
logger.info("Config - backlog size : " + config.getBacklogSize()); logger.info("Config - backlog size : " + config.getBacklogSize());
logger.info("Config - lease period : " + config.getLeasePeriod()); logger.info("Config - lease period : " + config.getLeasePeriod());
logger.info("Config - sock wrt timeout: " + config.getSocketWriteTimeout());
logger.info("Config - max pool size : " + config.getMaxPoolSize()); logger.info("Config - max pool size : " + config.getMaxPoolSize());
logger.info("Config - num accp.threads: " + config.getNumAcceptThreads()); logger.info("Config - num accp.threads: " + config.getNumAcceptThreads());
logger.info("Config - second.bind port: " + config.getSecondaryBindPort()); logger.info("Config - second.bind port: " + config.getSecondaryBindPort());
@ -244,7 +244,7 @@ public final class Main {
static boolean isAlreadyRunning(InvokerLocator serverLocator) { static boolean isAlreadyRunning(InvokerLocator serverLocator) {
Map<String, String> metadata = new HashMap<>(); Map<String, String> metadata = new HashMap<>();
metadata.put(SocketWrapper.WRITE_TIMEOUT, "2000"); metadata.put(SocketWrapper.WRITE_TIMEOUT, String.valueOf(ConfigSettings.instance.getSocketWriteTimeout()));
metadata.put("generalizeSocketException", "true"); metadata.put("generalizeSocketException", "true");
try { try {
MageServer testServer = (MageServer) TransporterClient.createTransporterClient(serverLocator.getLocatorURI(), MageServer.class, metadata); MageServer testServer = (MageServer) TransporterClient.createTransporterClient(serverLocator.getLocatorURI(), MageServer.class, metadata);

View file

@ -24,8 +24,7 @@
* The views and conclusions contained in the software and documentation are those of the * The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed * authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com. * or implied, of BetaSteward_at_googlemail.com.
*/ */
package mage.server.util; package mage.server.util;
import java.io.File; import java.io.File;
@ -33,7 +32,6 @@ import java.util.List;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import mage.server.util.config.Config; import mage.server.util.config.Config;
import mage.server.util.config.GamePlugin; import mage.server.util.config.GamePlugin;
import mage.server.util.config.Plugin; import mage.server.util.config.Plugin;
@ -48,7 +46,6 @@ public enum ConfigSettings {
private Config config; private Config config;
ConfigSettings() { ConfigSettings() {
try { try {
JAXBContext jaxbContext = JAXBContext.newInstance("mage.server.util.config"); JAXBContext jaxbContext = JAXBContext.newInstance("mage.server.util.config");
@ -79,6 +76,10 @@ public enum ConfigSettings {
return config.getServer().getLeasePeriod().intValue(); return config.getServer().getLeasePeriod().intValue();
} }
public int getSocketWriteTimeout() {
return config.getServer().getSocketWriteTimeout().intValue();
}
public int getMaxPoolSize() { public int getMaxPoolSize() {
return config.getServer().getMaxPoolSize().intValue(); return config.getServer().getMaxPoolSize().intValue();
} }

View file

@ -9,6 +9,7 @@
numAcceptThreads="2" numAcceptThreads="2"
maxPoolSize="300" maxPoolSize="300"
leasePeriod="5000" leasePeriod="5000"
socketWriteTimeout="10000"
maxGameThreads="10" maxGameThreads="10"
maxSecondsIdle="600" maxSecondsIdle="600"
minUserNameLength="3" minUserNameLength="3"

View file

@ -2,102 +2,103 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="config"> <xs:element name="config">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element ref="server"/> <xs:element ref="server"/>
<xs:element ref="playerTypes"/> <xs:element ref="playerTypes"/>
<xs:element ref="gameTypes"/> <xs:element ref="gameTypes"/>
<xs:element ref="tournamentTypes"/> <xs:element ref="tournamentTypes"/>
<xs:element ref="draftCubes"/> <xs:element ref="draftCubes"/>
<xs:element ref="deckTypes"/> <xs:element ref="deckTypes"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="server"> <xs:element name="server">
<xs:complexType> <xs:complexType>
<xs:attribute name="serverAddress" type="xs:string" use="required"/> <xs:attribute name="serverAddress" type="xs:string" use="required"/>
<xs:attribute name="serverName" type="xs:string" use="required"/> <xs:attribute name="serverName" type="xs:string" use="required"/>
<xs:attribute name="port" type="xs:positiveInteger" use="required"/> <xs:attribute name="port" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxGameThreads" type="xs:positiveInteger" use="required"/> <xs:attribute name="maxGameThreads" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxSecondsIdle" type="xs:positiveInteger" use="required"/> <xs:attribute name="maxSecondsIdle" type="xs:positiveInteger" use="required"/>
<xs:attribute name="secondaryBindPort" type="xs:integer" use="required"/> <xs:attribute name="secondaryBindPort" type="xs:integer" use="required"/>
<xs:attribute name="backlogSize" type="xs:positiveInteger" use="required"/> <xs:attribute name="backlogSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="numAcceptThreads" type="xs:positiveInteger" use="required"/> <xs:attribute name="numAcceptThreads" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxPoolSize" type="xs:positiveInteger" use="required"/> <xs:attribute name="maxPoolSize" type="xs:positiveInteger" use="required"/>
<xs:attribute name="leasePeriod" type="xs:positiveInteger" use="required"/> <xs:attribute name="leasePeriod" type="xs:positiveInteger" use="required"/>
<xs:attribute name="minUserNameLength" type="xs:positiveInteger" use="required"/> <xs:attribute name="socketWriteTimeout" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxUserNameLength" type="xs:positiveInteger" use="required"/> <xs:attribute name="minUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="invalidUserNamePattern" type="xs:string" use="required"/> <xs:attribute name="maxUserNameLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="minPasswordLength" type="xs:positiveInteger" use="required"/> <xs:attribute name="invalidUserNamePattern" type="xs:string" use="required"/>
<xs:attribute name="maxPasswordLength" type="xs:positiveInteger" use="required"/> <xs:attribute name="minPasswordLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="maxAiOpponents" type="xs:string" use="optional"/> <xs:attribute name="maxPasswordLength" type="xs:positiveInteger" use="required"/>
<xs:attribute name="saveGameActivated" type="xs:boolean" use="optional"/> <xs:attribute name="maxAiOpponents" type="xs:string" use="optional"/>
<xs:attribute name="authenticationActivated" type="xs:boolean" use="optional"/> <xs:attribute name="saveGameActivated" type="xs:boolean" use="optional"/>
<xs:attribute name="googleAccount" type="xs:string" use="optional"/> <xs:attribute name="authenticationActivated" type="xs:boolean" use="optional"/>
<xs:attribute name="mailgunApiKey" type="xs:string" use="optional"/> <xs:attribute name="googleAccount" type="xs:string" use="optional"/>
<xs:attribute name="mailgunDomain" type="xs:string" use="optional"/> <xs:attribute name="mailgunApiKey" type="xs:string" use="optional"/>
<xs:attribute name="mailSmtpHost" type="xs:string" use="optional"/> <xs:attribute name="mailgunDomain" type="xs:string" use="optional"/>
<xs:attribute name="mailSmtpPort" type="xs:string" use="optional"/> <xs:attribute name="mailSmtpHost" type="xs:string" use="optional"/>
<xs:attribute name="mailUser" type="xs:string" use="optional"/> <xs:attribute name="mailSmtpPort" type="xs:string" use="optional"/>
<xs:attribute name="mailPassword" type="xs:string" use="optional"/> <xs:attribute name="mailUser" type="xs:string" use="optional"/>
<xs:attribute name="mailFromAddress" type="xs:string" use="optional"/> <xs:attribute name="mailPassword" type="xs:string" use="optional"/>
</xs:complexType> <xs:attribute name="mailFromAddress" type="xs:string" use="optional"/>
</xs:element> </xs:complexType>
</xs:element>
<xs:complexType name="plugin"> <xs:complexType name="plugin">
<xs:attribute name="name" type="xs:string"/> <xs:attribute name="name" type="xs:string"/>
<xs:attribute name="jar" type="xs:string"/> <xs:attribute name="jar" type="xs:string"/>
<xs:attribute name="className" type="xs:string"/> <xs:attribute name="className" type="xs:string"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="gamePlugin"> <xs:complexType name="gamePlugin">
<xs:complexContent> <xs:complexContent>
<xs:extension base="plugin"> <xs:extension base="plugin">
<xs:attribute name="typeName" type="xs:string"/> <xs:attribute name="typeName" type="xs:string"/>
</xs:extension> </xs:extension>
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
<xs:element name="playerTypes"> <xs:element name="playerTypes">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="playerType" type="plugin" maxOccurs="unbounded"/> <xs:element name="playerType" type="plugin" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="gameTypes"> <xs:element name="gameTypes">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="gameType" type="gamePlugin" maxOccurs="unbounded"/> <xs:element name="gameType" type="gamePlugin" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="tournamentTypes"> <xs:element name="tournamentTypes">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="tournamentType" type="gamePlugin" maxOccurs="unbounded"/> <xs:element name="tournamentType" type="gamePlugin" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="draftCubes"> <xs:element name="draftCubes">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="draftCube" type="plugin" maxOccurs="unbounded"/> <xs:element name="draftCube" type="plugin" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="deckTypes"> <xs:element name="deckTypes">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>
<xs:element name="deckType" type="plugin" maxOccurs="unbounded"/> <xs:element name="deckType" type="plugin" maxOccurs="unbounded"/>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
</xs:schema> </xs:schema>