001 /* 002 /* 003 * Copyright (c) 2005 Stephen J. McConnell 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. 015 * 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 package net.dpml.component; 021 022 import java.io.File; 023 import java.lang.reflect.Constructor; 024 import java.lang.reflect.InvocationHandler; 025 import java.lang.reflect.Method; 026 import java.lang.reflect.Proxy; 027 import java.net.URI; 028 import java.util.EventObject; 029 import java.util.EventListener; 030 031 import net.dpml.lang.Part; 032 import net.dpml.lang.Plugin; 033 034 import net.dpml.util.DefaultLogger; 035 036 /** 037 * The CompositionControllerContext class wraps a ContentModel and supplies convinience 038 * operations that translate ContentModel properties and events to type-safe values used 039 * in the conposition controller. 040 * 041 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 042 * @version 1.2.0 043 */ 044 public final class InitialContext extends LocalEventProducer 045 implements ControllerContext, Disposable 046 { 047 //---------------------------------------------------------------------------- 048 // static 049 //---------------------------------------------------------------------------- 050 051 /** 052 * Create the default controller using the default initial context. 053 * The default context and associated controller disposal will be triggered 054 * on JVM shutdown. 055 * 056 * @return the default controller 057 */ 058 public static Controller createController() 059 { 060 return createController( null ); 061 } 062 063 /** 064 * Create the default controller. Controller disposal is the responsiblity 065 * of the client application. 066 * 067 * @param context the controller context 068 * @return the controller 069 */ 070 public static Controller createController( final InitialContext context ) 071 { 072 ControllerInvocationHandler handler = 073 new ControllerInvocationHandler( context ); 074 return (Controller) Proxy.newProxyInstance( 075 Controller.class.getClassLoader(), new Class[]{Controller.class}, handler ); 076 } 077 078 /** 079 * Internal invocation handler that delays controller instantiation 080 * until a request against the controller is made by a client. 081 */ 082 private static final class ControllerInvocationHandler implements InvocationHandler 083 { 084 private InitialContext m_context; 085 private Controller m_controller; 086 087 private ControllerInvocationHandler( InitialContext context ) 088 { 089 m_context = context; 090 } 091 092 /** 093 * Invoke the specified method on underlying object. 094 * This is called by the proxy object. 095 * 096 * @param proxy the proxy object 097 * @param method the method invoked on proxy object 098 * @param args the arguments supplied to method 099 * @return the return value of method 100 * @throws Throwable if an error occurs 101 */ 102 public Object invoke( 103 final Object proxy, final Method method, final Object[] args ) throws Throwable 104 { 105 Controller controller = getController(); 106 return method.invoke( controller, args ); 107 } 108 109 private synchronized Controller getController() 110 { 111 if( null != m_controller ) 112 { 113 return m_controller; 114 } 115 else 116 { 117 m_controller = InitialContext.newController( m_context ); 118 return m_controller; 119 } 120 } 121 } 122 123 private static URI getControllerURI() throws Exception 124 { 125 String spec = 126 System.getProperty( 127 "dpml.part.controller.uri", 128 "artifact:part:dpml/metro/dpml-metro-runtime#1.2.0" ); 129 return new URI( spec ); 130 } 131 132 /** 133 * Construct a controller. 134 * @param context the controller context 135 * @return the controller 136 */ 137 private static Controller newController( final InitialContext context ) 138 { 139 InitialContext control = context; 140 if( null == control ) 141 { 142 control = new InitialContext(); 143 Runtime.getRuntime().addShutdownHook( new ContextShutdownHook( control ) ); 144 } 145 146 ClassLoader classloader = Thread.currentThread().getContextClassLoader(); 147 try 148 { 149 Thread.currentThread().setContextClassLoader( InitialContext.class.getClassLoader() ); 150 URI uri = getControllerURI(); 151 Part part = Part.load( uri ); 152 if( part instanceof Plugin ) 153 { 154 Plugin plugin = (Plugin) part; 155 Class c = plugin.getPluginClass(); 156 Constructor constructor = c.getConstructor( new Class[]{ControllerContext.class} ); 157 return (Controller) constructor.newInstance( new Object[]{control} ); 158 } 159 else 160 { 161 return (Controller) part.instantiate( new Object[]{control} ); 162 } 163 } 164 catch( Throwable e ) 165 { 166 final String error = 167 "Internal error while attempting to establish the standard controller."; 168 throw new RuntimeException( error, e ); 169 } 170 finally 171 { 172 Thread.currentThread().setContextClassLoader( classloader ); 173 } 174 } 175 176 //---------------------------------------------------------------------------- 177 // state 178 //---------------------------------------------------------------------------- 179 180 /** 181 * Working directory. 182 */ 183 private File m_work; 184 185 /** 186 * Temp directory. 187 */ 188 private File m_temp; 189 190 /** 191 * The assigned partition name. 192 */ 193 private String m_partition; 194 195 //---------------------------------------------------------------------------- 196 // constructor 197 //---------------------------------------------------------------------------- 198 199 /** 200 * Creation of a new <tt>InitialContext</tt>. 201 */ 202 public InitialContext() 203 { 204 this( "", null, null ); 205 } 206 207 /** 208 * Creation of a new <tt>InitialContext</tt>. 209 * @param partition the assigned partition name 210 */ 211 public InitialContext( String partition ) 212 { 213 this( partition, null, null ); 214 } 215 216 /** 217 * Creation of a new <tt>InitialContext</tt>. 218 * @param partition the partition name 219 * @param work the working directory 220 * @param temp the temporary directory 221 */ 222 public InitialContext( String partition, File work, File temp ) 223 { 224 super( new DefaultLogger( partition ) ); 225 226 m_partition = partition; 227 228 if( null == work ) 229 { 230 String path = System.getProperty( "user.dir" ); 231 m_work = new File( path ); 232 } 233 else 234 { 235 m_work = work; 236 } 237 238 if( null == temp ) 239 { 240 String path = System.getProperty( "java.io.tmpdir" ); 241 m_temp = new File( path ); 242 } 243 else 244 { 245 m_temp = temp; 246 } 247 } 248 249 //---------------------------------------------------------------------------- 250 // Disposable 251 //---------------------------------------------------------------------------- 252 253 /** 254 * Initiate disposal. 255 */ 256 public void dispose() 257 { 258 ControllerDisposalEvent event = new ControllerDisposalEvent( this ); 259 enqueueEvent( event, false ); 260 super.dispose(); 261 } 262 263 //---------------------------------------------------------------------------- 264 // ControllerContext 265 //---------------------------------------------------------------------------- 266 267 /** 268 * Return the partition name 269 * @return the partion name 270 */ 271 public String getPartition() 272 { 273 return m_partition; 274 } 275 276 /** 277 * Return the root working directory. 278 * 279 * @return directory representing the root of the working directory hierachy 280 */ 281 public File getWorkingDirectory() 282 { 283 synchronized( getLock() ) 284 { 285 return m_work; 286 } 287 } 288 289 /** 290 * Set the root working directory. 291 * 292 * @param dir the root of the working directory hierachy 293 */ 294 public void setWorkingDirectory( File dir ) 295 { 296 synchronized( getLock() ) 297 { 298 m_work = dir; 299 } 300 } 301 302 /** 303 * Return the root temporary directory. 304 * 305 * @return directory representing the root of the temporary directory hierachy. 306 */ 307 public File getTempDirectory() 308 { 309 synchronized( getLock() ) 310 { 311 return m_temp; 312 } 313 } 314 315 /** 316 * Add the supplied controller context listener to the controller context. A 317 * controller implementation should not maintain strong references to supplied 318 * listeners. 319 * 320 * @param listener the controller context listener to add 321 */ 322 public void addControllerContextListener( ControllerContextListener listener ) 323 { 324 addListener( listener ); 325 } 326 327 /** 328 * Remove the supplied controller context listener from the controller context. 329 * 330 * @param listener the controller context listener to remove 331 */ 332 public void removeControllerContextListener( ControllerContextListener listener ) 333 { 334 removeListener( listener ); 335 } 336 337 //---------------------------------------------------------------------------- 338 // impl 339 //---------------------------------------------------------------------------- 340 341 /** 342 * Process a context related event. 343 * @param event the event to process 344 */ 345 protected void processEvent( EventObject event ) 346 { 347 if( event instanceof WorkingDirectoryChangeEvent ) 348 { 349 processWorkingDirectoryChangeEvent( (WorkingDirectoryChangeEvent) event ); 350 } 351 else if( event instanceof TempDirectoryChangeEvent ) 352 { 353 processTempDirectoryChangeEvent( (TempDirectoryChangeEvent) event ); 354 } 355 else if( event instanceof ControllerDisposalEvent ) 356 { 357 processControllerDisposalEvent( (ControllerDisposalEvent) event ); 358 } 359 } 360 361 private void processWorkingDirectoryChangeEvent( WorkingDirectoryChangeEvent event ) 362 { 363 EventListener[] listeners = super.listeners(); 364 for( int i=0; i<listeners.length; i++ ) 365 { 366 EventListener eventListener = listeners[i]; 367 if( eventListener instanceof ControllerContextListener ) 368 { 369 ControllerContextListener listener = (ControllerContextListener) eventListener; 370 try 371 { 372 listener.workingDirectoryChanged( event ); 373 } 374 catch( Throwable e ) 375 { 376 final String error = 377 "ControllerContextListener working dir change notification error."; 378 getInternalLogger().error( error, e ); 379 } 380 } 381 } 382 } 383 384 private void processTempDirectoryChangeEvent( TempDirectoryChangeEvent event ) 385 { 386 EventListener[] listeners = super.listeners(); 387 for( int i=0; i<listeners.length; i++ ) 388 { 389 EventListener eventListener = listeners[i]; 390 if( eventListener instanceof ControllerContextListener ) 391 { 392 ControllerContextListener listener = (ControllerContextListener) eventListener; 393 try 394 { 395 listener.tempDirectoryChanged( event ); 396 } 397 catch( Throwable e ) 398 { 399 final String error = 400 "ControllerContextListener temp dir change notification error."; 401 getInternalLogger().error( error, e ); 402 } 403 } 404 } 405 } 406 407 private void processControllerDisposalEvent( ControllerDisposalEvent event ) 408 { 409 EventListener[] listeners = super.listeners(); 410 for( int i=0; i<listeners.length; i++ ) 411 { 412 EventListener eventListener = listeners[i]; 413 if( eventListener instanceof ControllerContextListener ) 414 { 415 ControllerContextListener listener = (ControllerContextListener) eventListener; 416 try 417 { 418 listener.controllerDisposal( event ); 419 } 420 catch( Throwable e ) 421 { 422 final String error = 423 "ControllerContextListener disposal notification error."; 424 getInternalLogger().error( error, e ); 425 } 426 } 427 } 428 } 429 430 //---------------------------------------------------------------------------- 431 // static (private) 432 //---------------------------------------------------------------------------- 433 434 /** 435 * Working directory change event impl. 436 */ 437 private static class WorkingDirectoryChangeEvent extends ControllerContextEvent 438 { 439 public WorkingDirectoryChangeEvent( 440 ControllerContext source, File oldDir, File newDir ) 441 { 442 super( source, oldDir, newDir ); 443 } 444 } 445 446 /** 447 * Temp directory change event impl. 448 */ 449 private static class TempDirectoryChangeEvent extends ControllerContextEvent 450 { 451 public TempDirectoryChangeEvent( 452 ControllerContext source, File oldDir, File newDir ) 453 { 454 super( source, oldDir, newDir ); 455 } 456 } 457 458 /** 459 * Disposal event. 460 */ 461 private static class ControllerDisposalEvent extends ControllerContextEvent 462 { 463 public ControllerDisposalEvent( ControllerContext source ) 464 { 465 super( source, null, null ); 466 } 467 } 468 469 /** 470 * Internal utility class to handle context disposal on JVM shutdown. 471 */ 472 private static class ContextShutdownHook extends Thread 473 { 474 private InitialContext m_context; 475 476 /** 477 * Creation of a new initial context shutdown hook. 478 * @param context the initional context to be shutdown on JVM shutdown 479 */ 480 ContextShutdownHook( InitialContext context ) 481 { 482 m_context = context; 483 } 484 485 /** 486 * Execute context disposal. 487 */ 488 public void run() 489 { 490 try 491 { 492 m_context.dispose(); 493 } 494 catch( Throwable e ) 495 { 496 boolean ignorable = true; 497 } 498 finally 499 { 500 System.runFinalization(); 501 } 502 } 503 } 504 }