Source code: org/vrspace/server/ProxyDispatcher.java
1 package org.vrspace.server;
2
3 import java.util.*;
4 import org.vrspace.util.*;
5 import org.vrspace.attributes.*;
6 import org.vrspace.server.db.*;
7
8 /**
9 This dispatcher only forwards events to/from client/other host. <br>
10 But it should maintain a scene for all foreign objects, in order to reduce
11 network trafic and speed up event distribution, also keep local-remote
12 user mappings etc.
13 */
14 public class ProxyDispatcher extends Dispatcher {
15 protected static Hashtable connections = new Hashtable();
16 public boolean remove = false;
17 public boolean autocommit = true;
18 public ProxyDispatcher( Client gate, Client client ) throws Exception {
19 Logger.logDebug( "New ProxyDispatcher for "+client.getName()+" to "+gate.getName() );
20 server = client.getDispatcher().getServer(client);
21
22 checkConnections( client );
23 startDB( client, gate.getName() );
24
25 AuthInfo a = getAuthInfo( client, gate.getName() );
26
27 Proxy proxy = new Proxy( client, client.dispatcher, gate, a.login, a.password);
28
29 connected( client, a );
30
31 connections.put( client, proxy );
32 // let client clean up the mess
33 //Util.sleep(3000);
34 // start transfer
35 proxy.connection.addObserver(proxy);
36 Logger.logDebug( "Proxy for "+client.getName()+" initialized" );
37
38 }
39 public ProxyDispatcher( String host, int port, Client client ) throws Exception {
40 Logger.logDebug( "New ProxyDispatcher for "+client.getName()+" to "+host+":"+port );
41
42 checkConnections( client );
43 startDB( client, host + "_" + port );
44 AuthInfo a = getAuthInfo( client, host+":"+port );
45
46 Proxy proxy = new Proxy( client, client.dispatcher, host, port, a.login, a.password);
47
48 connected( client, a );
49
50 connections.put( client, proxy );
51 // let client clean up the mess
52 //Util.sleep(3000);
53 // start transfer
54 proxy.connection.addObserver(proxy); //?????
55 Logger.logDebug( "Proxy for "+client.getName()+" initialized" );
56 }
57 private void checkConnections( Client client ) {
58 Proxy proxy = (Proxy) connections.get( client );
59 server = client.getDispatcher().getServer(client);
60 Connection conn = null;
61 if ( proxy != null ) {
62 Logger.logError( client.getName()+" - another connection!!!" );
63 remove( proxy );
64 }
65 }
66 private void connected( Client client, AuthInfo a ) throws Exception {
67 if ( space != null ) {
68 space.put( a );
69 space.commit();
70 }
71 Logger.logDebug( "New Proxy for "+client.getName() );
72 // tell other users we're offline
73 client.online = false;
74 // clear the scene
75 client.scene.removeAll();
76 client.scene.clear();
77 // more setup
78 client.dispatcher = this;
79 // ok we can change to on-line now - they should know we're on another host
80 client.online = true;
81 }
82 private void startDB( Client client, String name ) {
83 try {
84 String dbUrl = client.getDispatcher().space.create( name );
85 DB newDB = (DB) Class.forName(server.getProperty("vrspace.db.class")).newInstance();
86 space = new DBCache( newDB );
87 space.connect( dbUrl );
88 Logger.logInfo( "Connected to proxy DB "+dbUrl );
89 } catch ( Throwable t ) {
90 Logger.logError( "Failed create/connect proxy DB for "+name, t );
91 }
92 }
93 private AuthInfo getAuthInfo( Client client, String name ) {
94 AuthInfo a = null;
95 try {
96 a = getAuthInfo( client );
97 } catch ( Throwable t ) {
98 // first login to remote host
99 Logger.logDebug( "No remote AuthInfo - "+t );
100 }
101 if ( a == null ) {
102 a = new AuthInfo( client.getName() + "@" + server.getProperty( "vrspace.server.name" ), ""+System.currentTimeMillis() );
103 a.className = client.getClassName();
104 a.id = client.db_id;
105 Logger.logDebug( "First visit to "+name+" by "+client.getName() );
106 }
107 return a;
108 }
109 /**
110 Return client to previous state
111 */
112 void remove( Proxy proxy ) {
113 proxy.connection.deleteObserver( proxy );
114 connections.remove( proxy.client );
115 try {
116 proxy.client.dispatcher = proxy.dispatcher;
117 //proxy.connection.close();
118 proxy.connection.quit();
119 // let client clean up the mess
120 //Util.sleep(3000);
121 proxy.client.scene = new Scene( proxy.client );
122 proxy.client.scene.update( proxy.client.pos );
123 } catch ( NullPointerException e ) {
124 // client disconnected
125 } catch ( Exception e ) {
126 // scene exception
127 Logger.logError("Error removing proxy "+proxy, e);
128 }
129 Logger.logInfo( "Remote session closed" );
130 }
131 /** start */
132 public void login( Client c ) throws ConnectionException {
133 Proxy proxy = (Proxy) connections.get( c );
134 proxy.login();
135 }
136 public class Proxy implements Observer {
137 Dispatcher dispatcher;
138 Client client;
139 Connection connection;
140 double x,y,z,rotx,roty,rotz,angle;
141 long startTime, lastReceived;
142 boolean identified = false;
143 long myId;
144 String myClass;
145 Queue queue = new Queue();
146 public Proxy( Client client,
147 Dispatcher dispatcher,
148 Client gate,
149 //Connection connection,
150 String login,
151 String password )
152 throws ConnectionException
153 {
154 // remember user's coordinates
155 x = client.pos.x;
156 y = client.pos.y;
157 z = client.pos.z;
158 rotx = client.pos.rotx;
159 roty = client.pos.roty;
160 rotz = client.pos.rotz;
161 angle = client.pos.angle;
162 //this.connection = connection;
163 this.client = client;
164 this.dispatcher = dispatcher;
165 // connect
166 //connection = new PipedConnection(gate.session, login, password); // TEMP DISABLED
167 throw new RuntimeException( "Temporary disabled!" );
168 }
169 public Proxy( Client client,
170 Dispatcher dispatcher,
171 String host,
172 int port,
173 String login,
174 String password )
175 throws ConnectionException
176 {
177 // remember user's coordinates
178 x = client.pos.x;
179 y = client.pos.y;
180 z = client.pos.z;
181 rotx = client.pos.rotx;
182 roty = client.pos.roty;
183 rotz = client.pos.rotz;
184 angle = client.pos.angle;
185 // connect
186 connection = new Connection(host, port, login, password);
187 this.client = client;
188 this.dispatcher = dispatcher;
189 }
190 public void login() throws ConnectionException {
191 ((PipedConnection) connection).login();
192 }
193 /**
194 Listens connection to remote server and forwards to client's session
195 */
196 public synchronized void update( Observable conn, Object msg ) {
197 //Logger.logDebug( "Remote message: "+msg );
198 if ( msg instanceof String ) {
199 if ( ! identified ) {
200 Message message = new Message( (String) msg );
201 identified = message.getClassName().equals("YouAre");
202 if ( identified ) {
203 myClass = message.getEventValue();
204 myId = message.getId();
205 try {
206 VRObject myInstance = get( client, myClass, myId );
207 if ( myInstance == null ) {
208 myInstance = VRObject.newInstance( myClass );
209 myInstance.db_id = myId;
210 put( client, myInstance );
211 }
212 } catch ( Exception e ) {
213 Logger.logError( "Can't store my instance", e );
214 }
215 // set users avatar
216 try {
217 Object url = client.getClass().getField("url").get( client );
218 connection.send( message.getEventValue()+" "+message.getId()+" url "+url );
219 } catch ( Throwable t ) {
220 Logger.logDebug( "Cannot set avatar url: "+t );
221 }
222 startTime = System.currentTimeMillis();
223 Logger.logInfo( client.getID()+" "+client.getName()+" - remote ID "+ myClass + " " + myId );
224 while ( queue.size() > 0 ) {
225 Request next = new Request( client, (String) queue.remove() );
226 try {
227 remoteRequest( next );
228 } catch ( Throwable t ) {
229 Logger.logError("Error processing "+next, t);
230 }
231 }
232 if ( client.session != null ) {
233 //client.session.write( (String) msg );
234 client.session.write( "", message );
235 }
236 } else {
237 // cache requests
238 queue.add( msg );
239 }
240 } else {
241 if ( msg instanceof String && ((String)msg).indexOf("+quit") == 0) {
242 // quit request - return to previous world
243 // don't send this back to client - let remote host clears
244 // the scene first, and than break the connection
245 // maybe send 'busy'?
246 // send something to the gate?
247 // user position like entering portal
248 client.pos.x = x;
249 client.pos.y = y;
250 client.pos.z = z;
251 // turn the user on the other side, like exiting the portal :)
252 // USE PORTAL ORIENTATION AND POSITION!!!
253 client.pos.rotx = rotx;
254 client.pos.roty = roty;
255 client.pos.rotz = rotz;
256 client.pos.angle = angle;
257 } else if ( msg instanceof Exception ) {
258 Logger.logError("Received exception",(Exception)msg);
259 client.pos.x = x;
260 client.pos.y = y;
261 client.pos.z = z;
262 // turn the user on the other side, like exiting the portal :)
263 // USE PORTAL ORIENTATION AND POSITION!?
264 client.pos.rotx = rotx;
265 client.pos.roty = roty;
266 client.pos.rotz = rotz;
267 client.pos.angle = angle;
268 } else {
269 try {
270 //Logger.logDebug( "Forwarding message: "+msg );
271 remoteRequest( new Request( client, (String) msg ) );
272 } catch ( RequestException reqE ) {
273 Logger.logError( "Error processing remote request: "+msg, reqE );
274 }
275 lastReceived=System.currentTimeMillis();
276 }
277 }
278 } else if ( msg instanceof Throwable ) {
279 Logger.logError("Received exception",(Throwable)msg);
280 } else {
281 Logger.logError( "Invalid message: "+msg );
282 }
283 if ( ! connection.isActive() ) {
284 remove( this );
285 space.disconnect();
286 Logger.logDebug( "Connection closed, db disconnected" );
287 }
288 }
289 /**
290 Forwards client's request to remote host - update local db here?
291 */
292 public void send( Request r ) {
293 //Logger.logDebug( "Forwarding client request ("+r.getClassName()+" "+r.getId()+" "+r.getEventName()+" "+r.getEventValue()+"): "+r );
294 // ignore all requests for some time
295 //if ( identified && System.currentTimeMillis() > 10000 + startTime ) {
296 if ( identified ) {
297 if ( r.getClassName().equals( client.getClassName() ) && r.getId() == client.db_id ) {
298 r = new Request( client, myClass + " " + myId + " " + r.getEventName() + " " + r.getEventValue() );
299 }
300 connection.send( r.toString() );
301 } else {
302 Logger.logWarning( "Ignored request "+r );
303 }
304 }
305 }
306
307 /**
308 Forward Request to the Client
309 */
310 protected void forward( Request r ) {
311 if ( r.getClient().session != null ) {
312 //client.session.sendRequest( r.toString() );
313 r.getClient().session.sendRequest( r );
314 //Logger.logDebug( "Forwarded message: "+r );
315 }
316 }
317 /**
318 Process remote request and forward it to the client
319 */
320 synchronized void remoteRequest( Request r ) throws RequestException {
321 //Logger.logDebug( "Forwarding message: "+r );
322 //super.request( r );
323 Client client = r.getClient();
324 try {
325 if ( r.getId() > -1 ) {
326 VRObject obj = r.object;
327 if ( obj == null ) {
328 try {
329 obj = (VRObject)space.get( r.getClassName(), r.getId() );
330 // cached
331 } catch ( Exception dbE ) {
332 Logger.logError( "Error fetching "+r, dbE );
333 }
334 }
335 if (obj == null) {
336 // object not in database - maybe Add/Remove
337 //obj = (VRObject) r.getClient().getClassLoader().loadClass( r.getClassName() ).newInstance();
338 //obj.db_id = r.getId();
339 obj = VRObject.fromText( r.toString() )[0];
340 //Logger.logDebug( client.getID()+" got event "+r );
341 }
342 // simple privilege check:
343 if ( client instanceof Admin ) {
344 obj = VRObject.fromText( r.toString() )[0];
345 if ( obj instanceof Remove ) {
346 if ( remove ) {
347 obj.sendEvent( r );
348 space.commit();
349 } else {
350 //Logger.logDebug( "Won't "+obj );
351 }
352 } else if ( obj instanceof Add ) {
353 VRObject child = get( client, ((Add)obj).className, ((Add)obj).db_id );
354 if ( child == null ) {
355 // new object
356 obj.sendEvent( r );
357 space.commit();
358 }
359 // else ignore - we already have that in local db
360 }
361 // else done later
362 }
363 // Admin & others need foreign objects in local scene:
364 if ( obj instanceof Remove ) {
365 VRObject child = get( client, ((Remove)obj).className, ((Remove)obj).db_id );
366 if ( child instanceof Remove ) {
367 throw new RequestException( r, client.getID()+" got invalid remote request: "+child );
368 }
369 if ( child != null ) {
370 //Logger.logDebug( client.getID()+": removing "+child );
371 client.removeObject( child );
372 } else {
373 Logger.logWarning( client.getID()+" can't "+obj+" - not found. Forwarding message to the client." );
374 forward( r );
375 }
376 } else if ( obj instanceof Add ) {
377 VRObject child = get( client, ((Add)obj).className, ((Add)obj).db_id );
378 if ( child instanceof Add ) {
379 throw new RequestException( r, client.getID()+" got invalid remote request: "+child );
380 }
381 try {
382 //Logger.logDebug( client.getID()+": adding "+child );
383 if ( child == null ) {
384 child = VRObject.newInstance( ((Add)obj).className );
385 child.db_id = ((Add)obj).db_id;
386 space.put( child );
387 }
388 // Note: at this point, object may have no properties at all
389 // we can check this with VRObject.toText() ( length() == 0 )
390 // but it is not necessary since Session ignores empty requests - any property change forwards anyway as Client.addObject() adds the client as Observer
391 client.addObject( child );
392 } catch ( Throwable t ) {
393 Logger.logError( client.getID()+" can't "+obj+" - not found. Forwarding message to the client.", t );
394 forward( r );
395 }
396 } else { //if ( ! (obj instanceof Add) && ! (obj instanceof Remove) )
397 // only Admin can set local properties
398 r.object = obj;
399 Proxy proxy = ((Proxy) connections.get( client ));
400 if ( obj != null && obj.getId() == proxy.myId && obj.getClassName().equals( proxy.myClass ) ) {
401 // my request - map to my properties:
402 // just in case:
403 r.setClass( proxy.myClass );
404 r.setId( proxy.myId );
405 if ( obj instanceof Client ) {
406 Logger.logDebug( obj.getID()+": my property - "+r );
407 forward( r );
408 // we have to trick OwnedDBObjects and stuff
409 r.client = (Client) obj;
410 obj.sendEvent( r );
411 }
412 /*
413 // copy foreign properties to this client - RISK! CKECKME
414 r.setClass( r.getClient().getClassName() );
415 r.setId( r.getClient().getId() );
416 r.object = r.getClient();
417 r.getClient().sendEvent( r );
418 */
419 } else {
420 if ( client instanceof Admin ) {
421 //Logger.logDebug( client.getID()+" dispatching "+r );
422 obj.sendEvent( r ); // this doesn't work!!! Seems that Admin does not observe this object... so workaround:
423 forward( r );
424 } else {
425 // other clients can't set any object properties - forward only
426 // (otherwise Client.addObject/removeObject calls forward to the client)
427 forward( r );
428 //Logger.logDebug( client.getID()+" got unwanted message from remote object: "+obj );
429 }
430 }
431 //space.put( obj ); // admin only
432 }
433 //space.commit(); //admin only - removeme
434 // this if has to come after sendRequest()! Request.toString() depends on object existence.
435 r.object = obj;
436 }
437 } catch ( Throwable e ) {
438 Logger.logError("Can't forward ("+r.getClassName()+" "+r.getId()+")"+r, e);
439 }
440 }
441 /**
442 Process request from the client. This forwards request to remote host, changes local client ID to remote ID if necessary
443 */
444 public void request( Request r ) throws RequestException {
445 // log remote requests?
446 //RequestLog.logRequest(r); // forget it till it works well
447 //Logger.logDebug( "Dispatching: "+r.getClassName()+"["+r.getId()+"]."+r.getEventName()+"="+r.getEventValue()+" by "+r.getClient().getID() );
448 Proxy proxy = ((Proxy) connections.get( r.getClient() ));
449 if ( r.getId() == proxy.myId && r.getClassName().equals( proxy.myClass )) {
450 //Logger.logDebug( r.getClient().getID()+" - my remote property changed: "+r );
451 } else {
452 // fixit
453 if ( proxy.client == r.getClient() ) {
454 r = (Request) r.clone();
455 //Logger.logDebug( proxy.client.getID()+" Dispatching: "+r );
456 if ( r.getId() > 0 ) {
457 r.setClass( proxy.myClass );
458 r.setId( proxy.myId );
459 //Logger.logDebug( r.getClient().getID()+": Fixed request - "+r );
460 } else {
461 // command - forward only
462 // execute locally - TODO
463 }
464 }
465 }
466 proxy.send(r);
467 }
468
469 /**
470 This should not be called
471 */
472 synchronized public Client login( Session session,
473 String login,
474 String password,
475 boolean daemon
476 )
477 throws Exception
478 {
479 throw new UnsupportedOperationException ( "Don't call this method!" );
480 }
481
482 /**
483 Logout Client <b>c</b> from remote host, session is not used
484 */
485 protected void logout( Client c, Session s ) {
486 Proxy proxy = (Proxy) connections.get( c );
487 try {
488 proxy.connection.close();
489 } catch ( Throwable t ) {
490 Logger.logError("Error in logout", t);
491 }
492 proxy.dispatcher.logout( c, s );
493 remove( proxy );
494 }
495
496 /**
497 Called from server upon shutdown
498 */
499 protected void shutdown() {
500 throw new UnsupportedOperationException ( "Should I support this?" );
501 }
502 }