001 /* 002 * Copyright 2005 Stephen 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.exec; 020 021 import java.io.IOException; 022 import java.net.URI; 023 import java.rmi.registry.Registry; 024 import java.rmi.registry.LocateRegistry; 025 import java.util.Properties; 026 import java.util.Iterator; 027 import java.util.Set; 028 029 import net.dpml.component.Component; 030 import net.dpml.component.Model; 031 import net.dpml.component.Composition; 032 import net.dpml.component.ActivationPolicy; 033 034 import net.dpml.station.Station; 035 import net.dpml.station.Callback; 036 import net.dpml.station.ApplicationException; 037 038 import net.dpml.transit.Artifact; 039 import net.dpml.util.PropertyResolver; 040 041 import net.dpml.lang.PID; 042 import net.dpml.lang.Part; 043 044 import net.dpml.util.Logger; 045 046 import net.dpml.cli.Option; 047 import net.dpml.cli.Group; 048 import net.dpml.cli.CommandLine; 049 import net.dpml.cli.commandline.Parser; 050 import net.dpml.cli.util.HelpFormatter; 051 import net.dpml.cli.DisplaySetting; 052 import net.dpml.cli.builder.ArgumentBuilder; 053 import net.dpml.cli.builder.GroupBuilder; 054 import net.dpml.cli.builder.DefaultOptionBuilder; 055 import net.dpml.cli.builder.CommandBuilder; 056 import net.dpml.cli.option.PropertyOption; 057 import net.dpml.cli.validation.URIValidator; 058 import net.dpml.cli.validation.NumberValidator; 059 060 /** 061 * Generic application handler that establishes a dedicated handler based 062 * on the type of resource exposed by a codebase uri. 063 * 064 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 065 * @version 1.0.3 066 */ 067 public class ApplicationHandler 068 { 069 private static final PID PROCESS_ID = new PID(); 070 071 private final Logger m_logger; 072 private final Callback m_callback; 073 074 /** 075 * The application handler class is a generic component handler that delegates 076 * application deployment processes to dedicated handlers based on the codebase 077 * type of the target application. Normally this plugin is activated as a 078 * consequence of invoking the <tt>metro</tt> commandline handler (which is 079 * the default haviour of the <tt>station</tt> when deployment local processes). 080 * The following command list help about the <tt>metro</tt> commandline handler: 081 * <pre>$ metro help 082 Usage: 083 metro [-uri <uri> | -help] 084 options 085 -uri <uri> Execute deployment of an application codebase. 086 -key -port 087 -key (-k) <key> Station callback application key. 088 -port (-p) <port> Override default RMI registry port selection. 089 -help Print command help. 090 * </pre> 091 * A typical example of a commandline and resulting log of an application launch 092 * using <tt>metro</tt> is show below: 093 * <pre> 094 $ metro -uri link:part:dpml/planet/http/dpml-http-demo 095 [2236 ] [INFO ] (demo): Starting 096 [2236 ] [INFO ] (org.mortbay.http.HttpServer): Version Jetty/5.1.x 097 [2236 ] [INFO ] (org.mortbay.util.Container): Started net.dpml.http.impl.HttpServerImpl@6355dc 098 [2236 ] [INFO ] (org.mortbay.util.Credential): Checking Resource aliases 099 [2236 ] [INFO ] (org.mortbay.util.Container): Started HttpContext[/,/] 100 [2236 ] [INFO ] (org.mortbay.http.SocketListener): Started SocketListener on 0.0.0.0:8080 101 * </pre> 102 * 103 * When invoked in conjuction with the <tt>station</tt> two optional parameters may be 104 * supplied by the station. These include <tt>-port</tt> option which directs the implementation 105 * to locate the system wide <tt>station</tt> from an RMI registry on the selected port. 106 * The second option is the <tt>-key</tt> argument that the handler uses to to resolve a 107 * station callback through which the implementation notifies the station of the process 108 * identity and startup status. The callback process effectivly hands control over 109 * management of the JVM process lietime and root component to to the station. 110 * 111 * @param logger the assigned logging channel 112 * @param args command line arguments 113 * @exception Exception if an error occurs 114 */ 115 public ApplicationHandler( 116 Logger logger, String[] args ) throws Exception 117 { 118 super(); 119 m_logger = logger; 120 121 Thread.currentThread().setContextClassLoader( Composition.class.getClassLoader() ); 122 123 if( logger.isDebugEnabled() ) 124 { 125 for( int i=0; i<args.length; i++ ) 126 { 127 logger.debug( "arg: " + ( i + 1 ) + ": " + args[i] ); 128 } 129 } 130 131 Parser parser = new Parser(); 132 parser.setGroup( COMMAND_GROUP ); 133 CommandLine line = parser.parse( args ); 134 135 if( line.hasOption( HELP_COMMAND ) || !line.hasOption( EXECUTE_COMMAND ) ) 136 { 137 processHelp(); 138 System.exit( 0 ); 139 } 140 141 URI uri = (URI) line.getValue( EXECUTE_COMMAND, null ); 142 if( null == uri ) 143 { 144 throw new NullPointerException( "uri" ); // will not happen 145 } 146 147 String key = (String) line.getValue( KEY_OPTION, null ); 148 if( null != key ) 149 { 150 int port = Registry.REGISTRY_PORT; 151 if( line.hasOption( PORT_OPTION ) ) 152 { 153 Number number = 154 (Number) line.getValue( PORT_OPTION, null ); 155 port = number.intValue(); 156 } 157 158 Station station = getStation( port ); 159 m_callback = station.getCallback( key ); 160 } 161 else 162 { 163 m_callback = new LocalCallback(); 164 } 165 166 167 URI categories = getCategoriesURI( line ); 168 Properties properties = getCommandLineProperties( line ); 169 String raw = System.getProperty( "dpml.station.partition", "" ); 170 String partition = PropertyResolver.resolve( raw ); 171 Component component = 172 resolveTargetComponent( 173 logger, partition, uri, categories, properties ); 174 m_callback.started( PROCESS_ID, component ); 175 } 176 177 /** 178 * Return a properties instance composed of the <tt>-C<key>=<value></tt> 179 * commandline arguments. 180 * @param line the commandline 181 * @return the resolved properties 182 */ 183 private Properties getCommandLineProperties( CommandLine line ) 184 { 185 Properties properties = new Properties(); 186 Set propertyValue = line.getProperties(); 187 Iterator iterator = propertyValue.iterator(); 188 while( iterator.hasNext() ) 189 { 190 String name = (String) iterator.next(); 191 String value = line.getProperty( name ); 192 properties.setProperty( name, value ); 193 } 194 return properties; 195 } 196 197 //------------------------------------------------------------------------------ 198 // internals 199 //------------------------------------------------------------------------------ 200 201 private URI getCategoriesURI( CommandLine line ) 202 { 203 return (URI) line.getValue( LOGGING_OPTION, null ); 204 } 205 206 private Component resolveTargetComponent( 207 Logger logger, String partition, URI uri, URI categories, Properties properties ) throws Exception 208 { 209 if( Artifact.isRecognized( uri ) ) 210 { 211 Artifact artifact = Artifact.createArtifact( uri ); 212 String type = artifact.getType(); 213 if( type.equals( "part" ) ) 214 { 215 Part part = Part.load( uri ); 216 if( part instanceof Composition ) 217 { 218 Composition composition = (Composition) part; 219 Model model = composition.getModel(); 220 model.setActivationPolicy( ActivationPolicy.STARTUP ); 221 return composition.newComponent(); 222 } 223 } 224 } 225 226 final String error = 227 "URI not supported [" + uri + "]."; 228 throw new ApplicationException( error ); 229 } 230 231 private Station getStation( int port ) throws Exception 232 { 233 Registry registry = LocateRegistry.getRegistry( port ); 234 return (Station) registry.lookup( Station.STATION_KEY ); 235 } 236 237 private Logger getLogger() 238 { 239 return m_logger; 240 } 241 242 /** 243 * List general command help to the console. 244 * @exception IOException if an I/O error occurs 245 */ 246 private void processHelp() throws IOException 247 { 248 HelpFormatter formatter = new HelpFormatter( 249 HelpFormatter.DEFAULT_GUTTER_LEFT, 250 HelpFormatter.DEFAULT_GUTTER_CENTER, 251 HelpFormatter.DEFAULT_GUTTER_RIGHT, 252 100 ); 253 254 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 255 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 256 formatter.getDisplaySettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 257 258 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 259 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_GROUP_OUTER ); 260 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 261 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_OPTIONAL ); 262 formatter.getFullUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 263 formatter.getFullUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 264 265 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_PROPERTY_OPTION ); 266 formatter.getLineUsageSettings().add( DisplaySetting.DISPLAY_ARGUMENT_BRACKETED ); 267 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_PARENT_CHILDREN ); 268 formatter.getLineUsageSettings().remove( DisplaySetting.DISPLAY_GROUP_EXPANDED ); 269 270 formatter.setGroup( COMMAND_GROUP ); 271 formatter.setShellCommand( "metro" ); 272 formatter.print(); 273 } 274 275 //----------------------------------------------------------------------------- 276 // CLI 277 //----------------------------------------------------------------------------- 278 279 private static final DefaultOptionBuilder OPTION_BUILDER = new DefaultOptionBuilder(); 280 private static final ArgumentBuilder ARGUMENT_BUILDER = new ArgumentBuilder(); 281 private static final GroupBuilder GROUP_BUILDER = new GroupBuilder(); 282 private static final CommandBuilder COMMAND_BUILDER = new CommandBuilder(); 283 284 private static final PropertyOption CONTEXT_OPTION = 285 new PropertyOption( "-C", "Set a context entry value.", 'C' ); 286 private static final NumberValidator PORT_VALIDATOR = NumberValidator.getIntegerInstance(); 287 private static final URIValidator URI_VALIDATOR = new URIValidator(); 288 289 private static final Option LOGGING_OPTION = 290 OPTION_BUILDER 291 .withShortName( "categories" ) 292 .withDescription( "Set logging category priorities." ) 293 .withRequired( false ) 294 .withArgument( 295 ARGUMENT_BUILDER 296 .withDescription( "URI." ) 297 .withName( "uri" ) 298 .withMinimum( 1 ) 299 .withMaximum( 1 ) 300 .withValidator( URI_VALIDATOR ) 301 .create() ) 302 .create(); 303 304 private static final Option PORT_OPTION = 305 OPTION_BUILDER 306 .withShortName( "port" ) 307 .withDescription( "Override default RMI registry port selection." ) 308 .withRequired( false ) 309 .withArgument( 310 ARGUMENT_BUILDER 311 .withDescription( "Port." ) 312 .withName( "port" ) 313 .withMinimum( 1 ) 314 .withMaximum( 1 ) 315 .withValidator( PORT_VALIDATOR ) 316 .create() ) 317 .create(); 318 319 private static final Option KEY_OPTION = 320 OPTION_BUILDER 321 .withShortName( "key" ) 322 .withShortName( "k" ) 323 .withDescription( "Station callback application key." ) 324 .withRequired( false ) 325 .withArgument( 326 ARGUMENT_BUILDER 327 .withDescription( "Key." ) 328 .withName( "key" ) 329 .withMinimum( 1 ) 330 .withMaximum( 1 ) 331 .create() ) 332 .create(); 333 334 335 private static final Option HELP_COMMAND = 336 OPTION_BUILDER 337 .withShortName( "help" ) 338 .withShortName( "h" ) 339 .withDescription( "Print command help." ) 340 .create(); 341 342 private static final Group EXECUTE_GROUP = 343 GROUP_BUILDER 344 .withOption( KEY_OPTION ) 345 .withOption( PORT_OPTION ) 346 .withOption( CONTEXT_OPTION ) 347 .withOption( LOGGING_OPTION ) 348 .create(); 349 350 private static final Option EXECUTE_COMMAND = 351 OPTION_BUILDER 352 .withShortName( "uri" ) 353 .withDescription( "Execute deployment of an application codebase." ) 354 .withChildren( EXECUTE_GROUP ) 355 .withArgument( 356 ARGUMENT_BUILDER 357 .withDescription( "Application codebase uri." ) 358 .withName( "uri" ) 359 .withMinimum( 1 ) 360 .withMaximum( 1 ) 361 .withValidator( new URIValidator() ) 362 .create() ) 363 .create(); 364 365 private static final Group COMMAND_GROUP = 366 GROUP_BUILDER 367 .withName( "options" ) 368 .withMinimum( 1 ) 369 .withMaximum( 1 ) 370 .withOption( EXECUTE_COMMAND ) 371 .withOption( HELP_COMMAND ) 372 .create(); 373 374 /** 375 * Internal class supporting callback semantics used in cases where the application 376 * handler has to assume management of a target. 377 */ 378 private class LocalCallback implements Callback 379 { 380 private PID m_pid; 381 private Component m_handler; 382 383 /** 384 * Method invoked by a process to signal that the process has started. 385 * 386 * @param pid the process identifier 387 * @param handler optional handler reference 388 * @exception RemoteException if a remote error occurs 389 */ 390 public void started( PID pid, Component handler ) 391 { 392 m_pid = pid; 393 m_handler = handler; 394 try 395 { 396 handler.commission(); 397 } 398 catch( Exception e ) 399 { 400 final String error = 401 "Initiating process deactivation due to an application error."; 402 getLogger().error( error, e ); 403 try 404 { 405 handler.decommission(); 406 } 407 catch( Exception deactivationError ) 408 { 409 // ignore 410 } 411 } 412 } 413 414 /** 415 * Method invoked by a process to signal that the process has 416 * encounter an error condition. 417 * 418 * @param throwable the error condition 419 * @param fatal if true the process is requesting termination 420 * @exception RemoteException if a remote error occurs 421 */ 422 public void error( Throwable throwable, boolean fatal ) 423 { 424 final String error = "Application error."; 425 if( fatal ) 426 { 427 getLogger().error( error, throwable ); 428 try 429 { 430 m_handler.decommission(); 431 } 432 catch( Throwable e ) 433 { 434 } 435 } 436 else 437 { 438 getLogger().warn( error, throwable ); 439 } 440 } 441 442 /** 443 * Method invoked by a process to send a arbitary message to the 444 * the callback handler. 445 * 446 * @param message the message 447 * @exception RemoteException if a remote error occurs 448 */ 449 public void info( String message ) 450 { 451 getLogger().info( message ); 452 } 453 454 /** 455 * Method invoked by a process to signal its imminent termination. 456 * 457 * @exception RemoteException if a remote error occurs 458 */ 459 public void stopped() 460 { 461 Thread thread = new Thread( 462 new Runnable() 463 { 464 public void run() 465 { 466 System.exit( 0 ); 467 } 468 } 469 ); 470 thread.start(); 471 } 472 } 473 }