package be.demmel.jgws;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.JAXBIntrospector;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import be.demmel.jgws.cfg.jaxb.Binding;
import be.demmel.jgws.cfg.jaxb.Configuration;
import be.demmel.jgws.cfg.jaxb.Jpa;
import be.demmel.jgws.cfg.jaxb.LogBlackList;
import be.demmel.jgws.network.InboundPacketHandler;
import be.demmel.jgws.network.NettyServer;
import be.demmel.jgws.packets.Packet;
import be.demmel.jgws.packets.handlers.PacketHandler;
import be.demmel.jgws.packets.handlers.tools.EntityManagerFactoryTool;
import be.demmel.jgws.packets.serialization.PacketDeserializer;
import be.demmel.jgws.packets.serialization.PacketSerializer;
import be.demmel.jgws.rmi.GameServer;
import be.demmel.jgws.rmi.LoginServerPortal;
import be.demmel.jgws.rmi.LoginServerPortalImpl;
import be.demmel.jgws.rmi.RmiRegistry;
import be.demmel.jgws.utils.GeneralUtils;
// TODO; test coverage plugin
public class LoginServer {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginServer.class);
private static final List<GameServer> availableGameServers = new ArrayList<>();
public static void main(String... parameters) {
Thread.currentThread().setName("Startup");
LOGGER.info("The \"Java Guild Wars (Login)Server\" (version {}) is starting", Version.getVersion());
try {
Configuration loginServerConfiguration = getLoginServerConfiguration();
// Retrieve the PacketDeserializers for every outgoing (login) packet
Map<Integer, PacketDeserializer> packetDeserializers = GeneralUtils.getPacketDeserializers("be.demmel.jgws.packets.loginserver.inbound");
// Retrieve the PacketSerializer for every incoming (login) packet
Map<Class<? extends Packet>, PacketSerializer> packetSerializers = GeneralUtils.getPacketSerializers("be.demmel.jgws.packets.loginserver.outbound");
// Retrieve the PacketHandlers for every incoming (login) packet
Map<Class<? extends Packet>, PacketHandler> packetHandlers = GeneralUtils.getPacketHandlers("be.demmel.jgws.packets.handlers.login");
Jpa jpaConfiguration = loginServerConfiguration.getJpa();
Map<String, String> properties = new HashMap<>();
properties.put("javax.persistence.jdbc.driver", jpaConfiguration.getDriver());
properties.put("javax.persistence.jdbc.url", jpaConfiguration.getUrl());
properties.put("javax.persistence.jdbc.user", jpaConfiguration.getUser());
properties.put("javax.persistence.jdbc.password", jpaConfiguration.getPassword());
final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jgws", properties);
EntityManagerFactoryTool.setEntityManagerFactory(entityManagerFactory);
Binding bindingConfiguration = loginServerConfiguration.getBinding();
String bindingIp = bindingConfiguration.getIp();
int bindingPort = bindingConfiguration.getPort();
ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
final InboundPacketHandler<LoginServerSession> inboundPacketHandler = new InboundPacketHandler<LoginServerSession>(channels, packetHandlers, SessionKey.SESSSION_KEY);
LogBlackList logBlackList = loginServerConfiguration.getLogBlackList();
if(loginServerConfiguration.getLogBlackList() != null) { // don't log some inbound and outbound packets
Set<Class<? extends Packet>> inboundPacketsLogBlackList = new HashSet<Class<? extends Packet>>(), outboundPacketsLogBlackList = new HashSet<Class<? extends Packet>>();
for(String packet : logBlackList.getInbound().getPacket()) {
inboundPacketsLogBlackList.add( (Class<? extends Packet>)Class.forName(packet));
}
for(String packet : logBlackList.getOutbound().getPacket()) {
outboundPacketsLogBlackList.add( (Class<? extends Packet>)Class.forName(packet));
}
LoginServerChannelInitializer defaultChannelInitializer = new LoginServerChannelInitializer(packetDeserializers, inboundPacketHandler, packetSerializers, SessionKey.SESSSION_KEY, inboundPacketsLogBlackList, outboundPacketsLogBlackList);
Thread serverThread = new Thread(new NettyServer<LoginServerSession>(new InetSocketAddress(bindingIp, bindingPort), defaultChannelInitializer));
serverThread.start();
} else {
LoginServerChannelInitializer defaultChannelInitializer = new LoginServerChannelInitializer(packetDeserializers, inboundPacketHandler, packetSerializers, SessionKey.SESSSION_KEY);
Thread serverThread = new Thread(new NettyServer<LoginServerSession>(new InetSocketAddress(bindingIp, bindingPort), defaultChannelInitializer));
serverThread.start();
}
// Start the RMI server
String portalUri = loginServerConfiguration.getPortalUri();
URI serviceRmiUri = new URI(portalUri);
RmiRegistry.INSTANCE.start(serviceRmiUri);
LoginServerPortal gameServerPortal = new LoginServerPortalImpl();
RmiRegistry.INSTANCE.bind(gameServerPortal);
LOGGER.info("Login Server Commands RMI service ready");
// The startup process went well. Log the event and keep the main thread alive
LOGGER.info("The \"Java Guild Wars (Login)Server\" started");
for (;;) {
Thread.sleep(Long.MAX_VALUE);
}
} catch (Throwable throwable) {
LOGGER.error("Initializing the \"Java Guild Wars (Login)Server\" failed because: ", throwable);
}
}
// TODO: provide what's needed to remove gameservers
public static void addGameServer(GameServer gameServer) {
LOGGER.info("Adding Game Server: {}", gameServer);
availableGameServers.add(gameServer);
}
public static GameServer getBestGameServer() {
// FIXME: selection should be based on utilization of all the GameServer instances
// How do I define that at the GS side? and how do I transfer that here? Ping each GS for stats every XXX ms ? to avoid
// over-utilization, do I add a max additions thresholds "before new check" ?
synchronized (availableGameServers) {
if (availableGameServers.isEmpty()) {
return null;
} else {
return availableGameServers.get(0);
}
}
}
// TODO: generic code in the core!
private static <T> T validateAndUnmarshal(Class<T> docClass, File xmlFile, String schemaLocation) throws JAXBException, SAXException {
String packageName = docClass.getPackage().getName();
JAXBContext jc = JAXBContext.newInstance(packageName);
Unmarshaller u = jc.createUnmarshaller();
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
URL url = LoginServer.class.getResource(schemaLocation);
Schema mySchema = sf.newSchema(url);
u.setSchema(mySchema);
return (T) JAXBIntrospector.getValue(u.unmarshal(xmlFile));
}
private static Configuration getLoginServerConfiguration() throws JAXBException, SAXException {
try {
String configurationFileLocation = System.getProperty("configurationFile");
if (configurationFileLocation == null) {
LOGGER.error("The \"configurationFile\" system property must be set");
throw new RuntimeException("The \"configurationFile\" system property must be set");
}
File configurationFile = new File(configurationFileLocation);
if (!configurationFile.exists()) {
LOGGER.error("The given \"configurationFile\" system property point to a non-existing file");
throw new RuntimeException("The given \"configurationFile\" system property points to a non-existing file");
}
return validateAndUnmarshal(Configuration.class, configurationFile, "/loginserverConfiguration.xsd");
} catch (Throwable t) {
throw t;
}
}
}