View Javadoc

1   /*
2    * AI Soccer Project - network gaming environment for AI warriors.
3    * Copyright (C) 2001-2004  Marcin Werla, Pawel Widera
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, you can find it here:
17   * http://www.gnu.org/licenses/gpl.html
18   */
19  
20  package snifos.server;
21  
22  import org.apache.commons.digester.Digester;
23  
24  import org.apache.log4j.Logger;
25  
26  import org.dom4j.Document;
27  
28  import org.xml.sax.SAXException;
29  
30  import snifos.application.Application;
31  import snifos.application.ApplicationInfo;
32  
33  import snifos.common.UserId;
34  
35  import snifos.communication.CommModule;
36  import snifos.communication.CommModuleInfo;
37  
38  import snifos.exception.ConfigurationException;
39  import snifos.exception.InvalidUserException;
40  
41  import java.io.IOException;
42  import java.io.InputStream;
43  
44  import java.lang.reflect.Constructor;
45  import java.lang.reflect.InvocationTargetException;
46  
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Collections;
50  import java.util.HashMap;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.Properties;
55  import java.util.WeakHashMap;
56  
57  
58  /***
59   * Reference implementation of SNIFOS server interface.
60   * @version $Id: SnifosServer.java,v 1.2 2004/05/08 21:55:29 mwerla Exp $
61   */
62  public class SnifosServer implements Server {
63      private static Logger logger = Logger.getLogger(SnifosServer.class);
64      private Map specificApplications = Collections.synchronizedMap(new HashMap());
65      private Map applications = Collections.synchronizedMap(new WeakHashMap());
66      private Map userIdToAppIdMap = Collections.synchronizedMap(new HashMap());
67      private List commModules = new ArrayList();
68      private Collection commModulesConfigurations;
69      private int appIdCounter = 0;
70      private Constructor appClassConstructor;
71      private Collection uniqueInstancesConfigurations;
72      private Properties typicalInstanceConfiguration = null;
73      private boolean wait = false;
74  
75      /***
76       * Server constructor.
77       * @param configurationStream Stream from which server configurtion
78       * will be loaded.
79       * @throws ConfigurationException Thrown when configuratin is wrong.
80       */
81      public SnifosServer(InputStream configurationStream)
82          throws ConfigurationException {
83          try {
84              loadConfiguration(configurationStream);
85          } catch (Exception e) {
86              logger.error("Error while loading configuration", e);
87              throw new ConfigurationException(e);
88          }
89  
90          createSpecificApplications();
91          createCommModules();
92      }
93  
94      /***
95       * Method for unit test purposes only.
96       * @return Wait flag value.
97       */
98      public boolean isWait() {
99          return wait;
100     }
101 
102     void setWait() {
103         this.wait = true;
104     }
105 
106     private void createCommModules() throws ConfigurationException {
107         synchronized (commModulesConfigurations) {
108             for (Iterator iter = commModulesConfigurations.iterator();
109                     iter.hasNext();) {
110                 CommModuleInfo modConfig = (CommModuleInfo) iter.next();
111                 CommModule commModule = (CommModule) createObject(modConfig.getConstructor());
112                 commModule.setServer(this);
113                 commModule.configure(modConfig.getProperties(),
114                     commModules.size());
115                 commModules.add(commModule);
116             }
117         }
118     }
119 
120     private void createSpecificApplications() throws ConfigurationException {
121         synchronized (uniqueInstancesConfigurations) {
122             for (Iterator iter = uniqueInstancesConfigurations.iterator();
123                     iter.hasNext();) {
124                 Properties properties = (Properties) iter.next();
125 
126                 Application app = createAndConfigureApp(properties, true);
127 
128                 Integer intAppId = new Integer(appIdCounter++);
129                 specificApplications.put(intAppId, app);
130                 applications.put(intAppId, app);
131             }
132         }
133     }
134 
135     private Object createObject(Constructor constructor)
136         throws ConfigurationException {
137         try {
138             return constructor.newInstance(new Object[0]);
139         } catch (IllegalArgumentException e) {
140             throw new ConfigurationException(e);
141         } catch (InstantiationException e) {
142             throw new ConfigurationException(e);
143         } catch (IllegalAccessException e) {
144             throw new ConfigurationException(e);
145         } catch (InvocationTargetException e) {
146             throw new ConfigurationException(e);
147         }
148     }
149 
150     private void loadConfiguration(InputStream configurationStream)
151         throws IOException, SAXException, SecurityException, 
152             NoSuchMethodException, ClassNotFoundException {
153         Digester digester = new Digester();
154 
155         digester.addObjectCreate("server", ArrayList.class);
156 
157         digester.addObjectCreate("server/commModule", CommModuleInfo.class);
158         digester.addSetProperties("server/commModule");
159         addProperties(digester, "server/commModule");
160         digester.addSetNext("server/commModule", "add");
161 
162         digester.addObjectCreate("server/application", ApplicationInfo.class);
163         digester.addSetProperties("server/application");
164 
165         digester.addObjectCreate("server/application/typicalConfiguration",
166             Properties.class);
167         addProperties(digester, "server/application/typicalConfiguration");
168         digester.addSetNext("server/application/typicalConfiguration",
169             "setTypicalConfiguration");
170 
171         digester.addObjectCreate("server/application/uniqueConfiguration",
172             Properties.class);
173         addProperties(digester, "server/application/uniqueConfiguration");
174         digester.addSetNext("server/application/uniqueConfiguration",
175             "addUniqueConfiguration");
176 
177         digester.addSetNext("server/application", "add");
178 
179         ArrayList result = (ArrayList) digester.parse(configurationStream);
180         ApplicationInfo appInfo = (ApplicationInfo) result.remove(result.size() -
181                 1);
182         commModulesConfigurations = result;
183 
184         typicalInstanceConfiguration = appInfo.getTypicalConfiguration();
185         uniqueInstancesConfigurations = appInfo.getUniqueConfigurations();
186 
187         appClassConstructor = Class.forName(appInfo.getClassName())
188                                    .getConstructor(new Class[0]);
189     }
190 
191     private void addProperties(Digester digester, String node) {
192         String localNode = node + "/property";
193         digester.addCallMethod(localNode, "setProperty", 2);
194         digester.addCallParam(localNode, 0, "name");
195         digester.addCallParam(localNode, 1);
196     }
197 
198     /***
199      * @see Server#sendMessage(snifos.common.UserId, org.dom4j.Document)
200      */
201     public void sendMessage(UserId userId, Document message) {
202         CommModule commModule = (CommModule) commModules.get(userId.getCommModuleId());
203 
204         try {
205             commModule.sendMessage(userId, message);
206         } catch (InvalidUserException e) {
207             logger.warn(
208                 "System unstable - trying to send message to wrong user!");
209             unregisterUser(userId);
210         }
211     }
212 
213     /***
214      * @see Server#receiveMessage(UserId, Document)
215      */
216     public void receiveMessage(UserId userId, Document message)
217         throws ConfigurationException, InvalidUserException {
218         Integer appId = (Integer) userIdToAppIdMap.get(userId);
219 
220         if (appId == null) {
221             appId = checkApps(userId, message);
222 
223             if (appId == null) {
224                 if (typicalInstanceConfiguration != null) {
225                     appId = createNewApp(userId, message);
226                 } else {
227                     throw new InvalidUserException();
228                 }
229             }
230 
231             userIdToAppIdMap.put(userId, appId);
232         }
233 
234         Application app = (Application) applications.get(appId);
235         app.receiveMessage(userId, message);
236     }
237 
238     private Integer createNewApp(UserId userId, Document message)
239         throws ConfigurationException, InvalidUserException {
240         Application app = createAndConfigureApp(typicalInstanceConfiguration,
241                 false);
242         Integer appId = new Integer(appIdCounter++);
243         applications.put(appId, app);
244 
245         Thread thread = new Thread(app);
246         thread.start();
247         app.registerUser(userId, message);
248 
249         return appId;
250     }
251 
252     private Application createAndConfigureApp(Properties configuration,
253         boolean unique) throws ConfigurationException {
254         Application app = (Application) createObject(appClassConstructor);
255         app.setServer(this);
256         app.configure(configuration, unique);
257 
258         return app;
259     }
260 
261     private Integer checkApps(UserId userId, Document message) {
262         Integer appKey = null;
263         List appKeysList = new ArrayList(applications.keySet());
264         Collections.sort(appKeysList);
265 
266         for (Iterator appIter = appKeysList.iterator(); appIter.hasNext();) {
267             appKey = (Integer) appIter.next();
268 
269             Application app = (Application) applications.get(appKey);
270 
271             try {
272                 app.registerUser(userId, message);
273 
274                 break;
275             } catch (InvalidUserException e) {
276                 appKey = null;
277             }
278         }
279 
280         return appKey;
281     }
282 
283     /***
284      * @see snifos.server.Server#disconnectUser(UserId)
285      */
286     public void disconnectUser(UserId userId) {
287         CommModule commModule = (CommModule) commModules.get(userId.getCommModuleId());
288         commModule.disconnectUser(userId);
289         userIdToAppIdMap.remove(userId);
290     }
291 
292     /***
293      * @see snifos.server.Server#unregisterUser(UserId)
294      */
295     public void unregisterUser(UserId userId) {
296         Integer appId = (Integer) userIdToAppIdMap.get(userId);
297         Application app = (Application) applications.get(appId);
298 
299         if (app != null) {
300             try {
301                 app.unregisterUser(userId);
302             } catch (InvalidUserException e) {
303                 //It can be safely ignored here.
304             }
305 
306             userIdToAppIdMap.remove(userId);
307         }
308     }
309 
310     /***
311      * @see Runnable#run()
312      */
313     public void run() {
314         synchronized (applications) {
315             for (Iterator iter = applications.values().iterator();
316                     iter.hasNext();) {
317                 Application app = (Application) iter.next();
318 
319                 Thread thread = new Thread(app);
320                 thread.start();
321             }
322         }
323 
324         int commModulesCounter = 0;
325 
326         synchronized (commModules) {
327             for (Iterator iter = commModules.iterator(); iter.hasNext();) {
328                 CommModule commModule = (CommModule) iter.next();
329 
330                 Thread thread = new Thread(commModule);
331                 thread.start();
332                 commModulesCounter++;
333             }
334         }
335 
336         //for unit test purposes only
337         if (wait) {
338             synchronized (this) {
339                 while (commModulesCounter > 0) {
340                     try {
341                         wait();
342                         commModulesCounter--;
343                     } catch (InterruptedException e) {
344                         e.printStackTrace();
345                     }
346                 }
347             }
348         }
349     }
350 }