001 /* 002 * Copyright 2005 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.station.server; 020 021 import java.io.IOException; 022 import java.rmi.ConnectException; 023 import java.rmi.RemoteException; 024 import java.util.Enumeration; 025 import java.util.ArrayList; 026 import java.util.Properties; 027 import java.util.EventObject; 028 import java.util.EventListener; 029 030 import net.dpml.station.info.StartupPolicy; 031 import net.dpml.station.info.ApplicationDescriptor; 032 033 import net.dpml.component.Component; 034 import net.dpml.component.Provider; 035 036 import net.dpml.station.Callback; 037 import net.dpml.station.ProcessState; 038 import net.dpml.station.Application; 039 import net.dpml.station.ApplicationException; 040 import net.dpml.station.ApplicationListener; 041 import net.dpml.station.ApplicationEvent; 042 043 import net.dpml.util.Logger; 044 import net.dpml.lang.PID; 045 046 /** 047 * The RemoteApplication is the default implementation of a remotely 048 * accessible Aplication. 049 * 050 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 051 * @version 1.0.3 052 */ 053 public class RemoteApplication extends UnicastEventSource implements Callback, Application 054 { 055 private final Logger m_logger; 056 private final ApplicationDescriptor m_descriptor; 057 private final String m_id; 058 private final int m_port; 059 060 private ProcessState m_state = ProcessState.IDLE; 061 private PID m_pid = null; 062 private Component m_handler = null; 063 private Provider m_instance = null; 064 private Process m_process = null; 065 private Exception m_error = null; 066 067 /** 068 * Creation of an application instance. 069 * 070 * @param logger the assigned logging channel 071 * @param descriptor the application descriptor 072 * @param id the application key 073 * @param port the rmi registry port on which the station is registered 074 * @exception RemoteException if a remote exception occurs 075 */ 076 public RemoteApplication( 077 Logger logger, ApplicationDescriptor descriptor, String id, int port ) 078 throws RemoteException 079 { 080 super( logger ); 081 082 m_logger = logger; 083 m_descriptor = descriptor; 084 m_id = id; 085 m_port = port; 086 } 087 088 //------------------------------------------------------------------------------- 089 // Callback 090 //------------------------------------------------------------------------------- 091 092 /** 093 * Method invoked by a process to signal that the process has 094 * commenced startup. 095 * 096 * @param pid the process identifier 097 * @param handler the component handler 098 * @exception ApplicationException if an application exception occurs 099 */ 100 public void started( PID pid, Component handler ) throws ApplicationException 101 { 102 if( null != m_pid ) 103 { 104 final String error = 105 "PID already assigned."; 106 throw new ApplicationException( error ); 107 } 108 synchronized( m_state ) 109 { 110 m_pid = pid; 111 m_handler = handler; 112 try 113 { 114 m_instance = handler.getProvider(); 115 setProcessState( ProcessState.STARTED ); 116 } 117 catch( Exception e ) 118 { 119 try 120 { 121 handler.decommission(); 122 } 123 catch( Exception ee ) 124 { 125 } 126 } 127 } 128 } 129 130 /** 131 * Method invoked by a process to signal that the process has 132 * encounter an error condition. 133 * 134 * @param throwable the error condition 135 * @param fatal if true the process is requesting termination 136 */ 137 public void error( Throwable throwable, boolean fatal ) 138 { 139 if( fatal ) 140 { 141 getLogger().error( "Process raised an fatal error.", throwable ); 142 } 143 else 144 { 145 getLogger().warn( "Process raised an non-fatal error.", throwable ); 146 } 147 } 148 149 /** 150 * Method invoked by a process to send a arbitary message to the 151 * the callback handler. 152 * 153 * @param message the message 154 */ 155 public void info( String message ) 156 { 157 getLogger().info( "[" + m_pid + "]: " + message ); 158 } 159 160 /** 161 * Method invoked by a process to signal its imminent termination. 162 */ 163 public void stopped() 164 { 165 synchronized( m_state ) 166 { 167 setProcessState( ProcessState.STOPPED ); 168 m_pid = null; 169 } 170 } 171 172 //------------------------------------------------------------------------------- 173 // Application 174 //------------------------------------------------------------------------------- 175 176 /** 177 * Return the process identifier of the process within which the 178 * application is running. If the application is not running a null 179 * value is returned. 180 * 181 * @return the pid 182 */ 183 public PID getPID() 184 { 185 return m_pid; 186 } 187 188 /** 189 * Return the application id. 190 * 191 * @return the id 192 */ 193 public String getID() 194 { 195 return m_id; 196 } 197 198 /** 199 * Return the profile associated with this application 200 * @return the application profile 201 */ 202 public ApplicationDescriptor getApplicationDescriptor() 203 { 204 return m_descriptor; 205 } 206 207 /** 208 * Return the current deployment state of the process. 209 * @return the current process state 210 */ 211 public ProcessState getProcessState() 212 { 213 synchronized( m_state ) 214 { 215 return m_state; 216 } 217 } 218 219 /** 220 * Start the application. 221 * @exception ApplicationException if an application error occurs 222 */ 223 public void start() throws ApplicationException 224 { 225 if( m_descriptor.getStartupPolicy() == StartupPolicy.DISABLED ) 226 { 227 final String error = 228 "Cannot start the application [" 229 + m_id 230 + "] due to the DISABLED startup status."; 231 throw new ApplicationException( error ); 232 } 233 234 synchronized( m_state ) 235 { 236 if( m_state.equals( ProcessState.IDLE ) || m_state.equals( ProcessState.STOPPED ) ) 237 { 238 startProcess(); 239 } 240 else 241 { 242 final String error = 243 "Cannot start a process in the [" 244 + m_state 245 + "] state."; 246 throw new ApplicationException( error ); 247 } 248 } 249 } 250 251 private void startProcess() throws ApplicationException 252 { 253 synchronized( m_state ) 254 { 255 setProcessState( ProcessState.STARTING ); 256 try 257 { 258 String[] command = getProcessCommand(); 259 m_process = Runtime.getRuntime().exec( command, null ); 260 } 261 catch( Exception e ) 262 { 263 setProcessState( ProcessState.IDLE ); 264 final String error = 265 "Process establishment failure."; 266 throw new ApplicationException( error, e ); 267 } 268 } 269 270 Logger logger = getLogger(); 271 OutputStreamReader output = new OutputStreamReader( logger, m_process.getInputStream() ); 272 ErrorStreamReader err = new ErrorStreamReader( logger, m_process.getErrorStream() ); 273 output.setDaemon( true ); 274 err.setDaemon( true ); 275 output.start(); 276 err.start(); 277 278 /* 279 long timestamp = System.currentTimeMillis(); 280 long timeout = timestamp + getStartupTimeout(); 281 getLogger().info( "waiting " + getStartupTimeout() ); 282 283 while( ( getProcessState() == ProcessState.STARTING ) 284 && ( System.currentTimeMillis() < timeout ) 285 && ( null == m_error ) ) 286 { 287 try 288 { 289 Thread.currentThread().sleep( 600 ); 290 } 291 catch( InterruptedException e ) 292 { 293 } 294 } 295 296 getLogger().info( "review" ); 297 if( getProcessState().equals( ProcessState.STARTING ) ) 298 { 299 final String error = 300 "Process failed to start within the timeout period."; 301 getLogger().error( error ); 302 handleStop( false ); 303 } 304 else if( null != m_error ) 305 { 306 final String error = 307 "Application deployment failure."; 308 handleStop( false ); 309 throw new ApplicationException( error, m_error ); 310 } 311 */ 312 } 313 314 /** 315 * Construct the process command parameters sequence. 316 * @exception IOException if an IO error occurs 317 */ 318 private String[] getProcessCommand() throws IOException 319 { 320 ArrayList list = new ArrayList(); 321 322 String path = m_descriptor.getCodeBaseURISpec(); 323 list.add( "metro" ); 324 325 // 326 // add system properties 327 // 328 329 Properties properties = m_descriptor.getSystemProperties(); 330 properties.setProperty( "dpml.station.key", m_id ); 331 properties.setProperty( "dpml.subprocess", "true" ); 332 properties.setProperty( "dpml.station.partition", "depot.station." + m_id ); 333 334 if( "true".equals( System.getProperty( "dpml.debug" ) ) ) 335 { 336 properties.setProperty( "dpml.debug", "true" ); 337 } 338 339 if( null == properties.getProperty( "java.util.logging.config.class" ) ) 340 { 341 properties.setProperty( 342 "java.util.logging.config.class", 343 "net.dpml.depot.DepotLoggingConfiguration" ); 344 } 345 346 if( null == properties.getProperty( "dpml.logging.config" ) ) 347 { 348 properties.setProperty( 349 "dpml.logging.config", 350 "local:properties:dpml/station/application" ); 351 } 352 353 Enumeration names = properties.propertyNames(); 354 while( names.hasMoreElements() ) 355 { 356 String name = (String) names.nextElement(); 357 String value = properties.getProperty( name ); 358 list.add( "-D" + name + "=" + value ); 359 } 360 361 // 362 // add the -uri option and codebase parameter 363 // 364 365 list.add( "-uri" ); 366 list.add( path ); 367 368 // 369 // add options necessary for the handler to establish a callback 370 // 371 372 list.add( "-port" ); 373 list.add( "" + m_port ); 374 list.add( "-key" ); 375 list.add( "" + m_id ); 376 377 return (String[]) list.toArray( new String[0] ); 378 } 379 380 /** 381 * Stop the application. 382 * @exception RemoteException if a rmote error occurs 383 */ 384 public void stop() throws RemoteException 385 { 386 handleStop( true ); 387 } 388 389 void shutdown() throws IOException 390 { 391 try 392 { 393 handleStop( false ); 394 } 395 catch( Throwable e ) 396 { 397 final String error = 398 "Application shutdown error."; 399 getLogger().warn( error, e ); 400 } 401 } 402 403 private void handleStop( boolean check ) throws ApplicationException 404 { 405 synchronized( m_state ) 406 { 407 if( m_state.equals( ProcessState.IDLE ) ) 408 { 409 return; 410 } 411 else if( m_state.equals( ProcessState.STOPPED ) ) 412 { 413 return; 414 } 415 else 416 { 417 getLogger().info( "stopping application" ); 418 setProcessState( ProcessState.STOPPING ); 419 if( m_handler != null ) 420 { 421 try 422 { 423 m_handler.decommission(); 424 } 425 catch( Throwable e ) 426 { 427 final String error = 428 "Component deactivation error reported."; 429 getLogger().warn( error, e ); 430 } 431 } 432 setProcessState( ProcessState.STOPPED ); 433 if( null != m_process ) 434 { 435 m_process.destroy(); 436 m_process = null; 437 m_instance = null; 438 } 439 m_pid = null; 440 } 441 } 442 } 443 444 /** 445 * Restart the application. 446 * @exception RemoteException if a rmote error occurs 447 */ 448 public void restart() throws RemoteException 449 { 450 stop(); 451 start(); 452 } 453 454 /** 455 * Return the component instance handler. 456 * @return the instance handler (possibly null) 457 */ 458 public Provider getProvider() 459 { 460 return m_instance; 461 } 462 463 /** 464 * Add an application listener. 465 * @param listener the listener to add 466 */ 467 public void addApplicationListener( ApplicationListener listener ) 468 { 469 super.addListener( listener ); 470 } 471 472 /** 473 * Remove an application listener. 474 * @param listener the listener to remove 475 */ 476 public void removeApplicationListener( ApplicationListener listener ) 477 { 478 super.removeListener( listener ); 479 } 480 481 //------------------------------------------------------------------------------- 482 // private utilities 483 //------------------------------------------------------------------------------- 484 485 private void setProcessState( ProcessState state ) 486 { 487 synchronized( m_state ) 488 { 489 if( m_state != state ) 490 { 491 m_state = state; 492 ApplicationEvent event = new ApplicationEvent ( this, state ); 493 super.enqueueEvent( event ); 494 getLogger().info( "state set to [" + state.getName() + "]" ); 495 } 496 } 497 } 498 499 //------------------------------------------------------------------------------- 500 // EventChannel 501 //------------------------------------------------------------------------------- 502 503 /** 504 * Internal event handler. 505 * @param eventObject the event 506 */ 507 protected void processEvent( EventObject eventObject ) 508 { 509 if( eventObject instanceof ApplicationEvent ) 510 { 511 ApplicationEvent event = (ApplicationEvent) eventObject; 512 processApplicationEvent( event ); 513 } 514 else 515 { 516 final String error = 517 "Event class not recognized: " + eventObject.getClass().getName(); 518 throw new IllegalArgumentException( error ); 519 } 520 } 521 522 private void processApplicationEvent( ApplicationEvent event ) 523 { 524 EventListener[] listeners = super.listeners(); 525 for( int i=0; i < listeners.length; i++ ) 526 { 527 EventListener listener = listeners[i]; 528 if( listener instanceof ApplicationListener ) 529 { 530 try 531 { 532 ApplicationListener applicationListener = (ApplicationListener) listener; 533 applicationListener.stateChanged( event ); 534 } 535 catch( ConnectException e ) 536 { 537 super.removeListener( listener ); 538 } 539 catch( Throwable e ) 540 { 541 final String error = 542 "ApplicationListener notification error."; 543 getLogger().error( error, e ); 544 } 545 } 546 } 547 } 548 549 private long getStartupTimeout() 550 { 551 return m_descriptor.getStartupTimeout() * 1000000; 552 } 553 554 private long getShutdownTimeout() 555 { 556 return m_descriptor.getShutdownTimeout() * 1000000; 557 } 558 }