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.5
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    }