001    /*
002     * Copyright 2004-2005 Niclas Hedhman
003     * Copyright 2005 Stephen 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.transit;
021    
022    import java.io.IOException;
023    import java.rmi.NoSuchObjectException;
024    import java.rmi.Remote;
025    import java.rmi.RemoteException;
026    import java.rmi.server.UnicastRemoteObject;
027    import java.net.PasswordAuthentication;
028    import java.net.URL;
029    import java.util.Properties;
030    
031    import net.dpml.transit.link.ArtifactLinkManager;
032    import net.dpml.transit.link.LinkManager;
033    import net.dpml.transit.model.CacheModel;
034    import net.dpml.transit.model.TransitModel;
035    import net.dpml.transit.model.ProxyModel;
036    import net.dpml.transit.model.ProxyListener;
037    import net.dpml.transit.model.ProxyEvent;
038    import net.dpml.transit.model.RequestIdentifier;
039    import net.dpml.transit.model.DisposalListener;
040    import net.dpml.transit.model.DisposalEvent;
041    import net.dpml.transit.monitor.LoggingAdapter;
042    
043    import net.dpml.lang.UnknownKeyException;
044    import net.dpml.util.Logger;
045    
046    
047    /**
048     * The initial context of the transit system.
049     *
050     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
051     * @version 1.0.3
052     */
053    public final class SecuredTransitContext
054    {
055        //------------------------------------------------------------------
056        // static
057        //------------------------------------------------------------------
058    
059       /**
060        * Creation of the transit context.  If the transit context has already
061        * been established the method returns the singeton context otherwise a new 
062        * context is created relative to the authoritve url and returned.
063        * @param model the active transit model
064        * @return the secured transit context
065        * @exception TransitException if an error occurs during context creation
066        * @exception NullArgumentException if the supplied configration model is null 
067        *    and an instance of this class has not been created already.
068        */
069        public static SecuredTransitContext create( TransitModel model )
070            throws TransitException, NullArgumentException
071        {
072            synchronized( SecuredTransitContext.class )
073            {
074                if( m_CONTEXT != null )
075                {
076                    return m_CONTEXT;
077                }
078                
079                if( null == model )
080                {
081                    throw new NullArgumentException( "model" );
082                }
083                
084                Logger logger = resolveLogger( model );
085                if( logger.isDebugEnabled() )
086                {
087                    logger.debug( "creating transit context" );
088                }
089                
090                try
091                {
092                    m_CONTEXT = new SecuredTransitContext( model, logger );
093                }
094                catch( TransitException e )
095                {
096                    throw e;
097                }
098                catch( Exception e )
099                {
100                    String error = "Unable to establish the transit context.";
101                    throw new TransitException( error, e );
102                }
103                
104                return m_CONTEXT;
105            }
106        }
107        
108        private static Logger resolveLogger( TransitModel model )
109        {
110            if( model instanceof DefaultTransitModel )
111            {
112                DefaultTransitModel m = (DefaultTransitModel) model;
113                return m.getLoggingChannel();
114            }
115            else
116            {
117                return new LoggingAdapter();
118            }
119        }
120    
121       /**
122        * Return the singleton context.
123        * @return the secure context
124        */
125        public static SecuredTransitContext getInstance()
126        {
127            synchronized( SecuredTransitContext.class )
128            {
129                if( null == m_CONTEXT )
130                {
131                    throw new IllegalStateException( "context" );
132                }
133                else
134                {
135                    return m_CONTEXT;
136                }
137            }
138        }
139    
140        //------------------------------------------------------------------
141        // state
142        //------------------------------------------------------------------
143    
144       /**
145        * The configuration model.
146        */
147        private TransitModel m_model;
148    
149       /**
150        * The cache handler.
151        */
152        private CacheHandler m_cacheHandler;
153    
154       /**
155        * The LinkManager instance.
156        */
157        private LinkManager m_linkManager;
158    
159       /**
160        * Logging channel.
161        */
162        private Logger m_logger;
163    
164        private ProxyController m_proxyController;
165        
166        private DisposalController m_disposalController;
167    
168        //------------------------------------------------------------------
169        // constructors
170        //------------------------------------------------------------------
171       /**
172        * Creation of a new secured transit context.
173        * @param model the transit configuration model
174        * @param logger the assigned logging channel
175        * @exception IOException if an I/O error occurs
176        */
177        private SecuredTransitContext( TransitModel model, Logger logger ) throws IOException
178        {
179            m_model = model;
180            m_logger = logger;
181    
182            CacheModel cacheModel = model.getCacheModel();
183            Logger cacheLogger = logger.getChildLogger( "cache" );
184            DefaultCacheHandler cache = new DefaultCacheHandler( cacheModel, cacheLogger );
185            m_cacheHandler = cache;
186            ProxyModel proxy = m_model.getProxyModel();
187            if( null != proxy )
188            {
189                synchronized( proxy )
190                {
191                    setupProxy();
192                    m_proxyController = new ProxyController();
193                    proxy.addProxyListener( m_proxyController );
194                }
195            }
196            m_disposalController = new DisposalController();
197            model.addDisposalListener( m_disposalController );
198        }
199    
200        //------------------------------------------------------------------
201        // SecuredTransitContext 
202        //------------------------------------------------------------------
203    
204       /**
205        * Return a layout object matching the supplied identifier.
206        * @param id the layout identifier
207        * @return the layout object
208        * @exception UnknownKeyException if the supplied layout id is unknown
209        * @exception IOException if an IO error occurs
210        */
211        public Layout getLayout( String id ) throws UnknownKeyException, IOException
212        {
213            LayoutRegistry registry = m_cacheHandler.getLayoutRegistry();
214            Layout layout = registry.getLayout( id );
215            if( null == layout )
216            {
217                throw new UnknownKeyException( id );
218            }
219            else
220            {
221                return layout;
222            }
223        }
224        
225       /**
226        * Return the cache layout.
227        * @return the layout
228        */
229        public Layout getCacheLayout()
230        {
231            return getCacheHandler().getLayout();
232        }
233        
234       /**
235        * Return the cache handler.
236        * @return the cache handler
237        */
238        public CacheHandler getCacheHandler()
239        {
240            return m_cacheHandler;
241        }
242    
243       /**
244        * Return the link manager.
245        * @return the cache handler
246        */
247        public LinkManager getLinkManager()
248        {
249            return m_linkManager;
250        }
251    
252        //------------------------------------------------------------------
253        // internals 
254        //------------------------------------------------------------------
255    
256       /**
257        * General setup.
258        * @exception RemoteException if a remote error occurs
259        */
260        protected synchronized void setupProxy() throws RemoteException
261        {
262            ProxyModel model = m_model.getProxyModel();
263            URL proxy = model.getHost();
264            if( null != proxy )
265            {
266                PasswordAuthentication auth = model.getAuthentication();
267                if( null != auth )
268                {
269                    TransitAuthenticator ta = new TransitAuthenticatorImpl( auth );
270                    RequestIdentifier id = model.getRequestIdentifier();
271                    DelegatingAuthenticator da = DelegatingAuthenticator.getInstance();
272                    da.addTransitAuthenticator( ta, id );
273                }
274    
275                int port = proxy.getPort();
276                Properties system = System.getProperties();
277                system.put( "http.proxyHost", proxy );
278                system.put( "http.proxyPort", "" + port );
279                String[] excludes = model.getExcludes();
280                String path = toExcludesPath( excludes );
281                if( null != path )
282                {
283                    system.put( "http.nonProxyHosts", path );
284                }
285            }
286        }
287    
288       /**
289        * Initialization of any sub-systems following the establishment of the initial
290        * transit system. As a general principal any subsystems that cannot be established
291        * for technical reasons (security or permission restrictions, etc.) should log 
292        * an appropriate message and fallback to the initial setup thereby ensuring that
293        * an operable transit system is available.
294        *
295        * @exception IOException if an io error occurs
296        */
297        protected void initialize() throws IOException
298        {
299            m_linkManager = new ArtifactLinkManager();
300            initializeCache();
301        }
302    
303       /**
304        * Cache initialization.
305        *
306        * @exception IOException if an initialization error occurs
307        */
308        private void initializeCache() throws IOException
309        {
310            getCacheHandler().initialize();
311        }
312    
313       /**
314        * Internal listener to the proxy model.
315        */
316        private class ProxyController extends UnicastRemoteObject implements ProxyListener
317        {
318           /**
319            * Listener creation.
320            * @exception RemoteException if a remote error occurs
321            */
322            public ProxyController() throws RemoteException
323            {
324                super();
325            }
326    
327           /**
328            * Notify a listener of the change to Transit proxy settings.
329            * @param event the proxy change event
330            */
331            public void proxyChanged( ProxyEvent event )
332            {
333                try
334                {
335                    setupProxy();
336                }
337                catch( RemoteException e )
338                {
339                    final String error = 
340                      "Unexpected error while attrempting to set proxy settings.";
341                    getLogger().error( error, e );
342                }
343            }
344        }
345    
346       /**
347        * Internal listener to the proxy model.
348        */
349        private class DisposalController extends UnicastRemoteObject implements DisposalListener
350        {
351           /**
352            * Listener creation.
353            * @exception RemoteException if a remote error occurs
354            */
355            public DisposalController() throws RemoteException
356            {
357                super();
358            }
359            
360           /**
361            * Notify a listener of transit model disposal.
362            * @param event the disposal event
363            */
364            public void notifyDisposal( DisposalEvent event )
365            {
366                Thread thread = new Terminator();
367                thread.start();
368            }
369        }
370        
371       /**
372        * Internal model terminator.
373        */
374        private class Terminator extends Thread
375        {
376            Terminator()
377            {
378            }
379            
380           /**
381            * Initiate model retraction from the RMI.
382            */
383            public void run()
384            {
385                m_logger.debug( "initiating transit runtime disposal" );
386                terminate( m_proxyController );
387                terminate( m_cacheHandler );
388                terminate( m_disposalController );
389                m_logger.debug( "transit runtime disposal complete" );
390            }
391            
392            private void terminate( Object object )
393            {
394                if( object instanceof Disposable )
395                {
396                    Disposable disposable = (Disposable) object;
397                    disposable.dispose();
398                }
399                if( object instanceof Remote )
400                {
401                    try
402                    {
403                        Remote remote = (Remote) object;
404                        UnicastRemoteObject.unexportObject( remote, true );
405                    }
406                    catch( NoSuchObjectException e )
407                    {
408                        // ignore
409                    }
410                    catch( Throwable e )
411                    {
412                        final String error = 
413                          "Unexpected error encountered during transit runtime termination.";
414                        m_logger.warn( error, e );
415                    }
416                }
417            }
418        }
419        
420        private Logger getLogger()
421        {
422            return m_logger;
423        }
424    
425        //------------------------------------------------------------------
426        // static (utils)
427        //------------------------------------------------------------------
428    
429       /**
430        * Resolve the list of host names to be assigned as non-proxied hosts. If proxy
431        * excludes are defined the string returned contains the host name (wilcards allowed)
432        * separated by the "|" character.  If no proxy excludes are defined the value returned
433        * shall be null.  The implementation reads the set of attribute names associated 
434        * with a preferences node named "excludes".  Each attribute name is appended to 
435        * a single string where names are separated by the "|" character.
436        *
437        * @param names an array of named excludes
438        * @return a string containing a sequence of excluded hosts (possibly null)
439        */
440        private static String toExcludesPath( String[] names )
441        {
442            String spec = null;
443            for( int i=0; i < names.length; i++ )
444            {
445                String name = names[i];
446                if( null == spec )
447                {
448                    spec = name;
449                }
450                else
451                {
452                    spec = spec + "|" + name;
453                }
454            }
455            return spec;
456        }
457    
458       /**
459        * The namespace string for transit related properties.
460        */
461        public static final String DOMAIN = "dpml.transit";
462    
463       /**
464        * The singleton transit context.
465        */
466        private static SecuredTransitContext m_CONTEXT;
467    }