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.server;
020    
021    import java.net.URI;
022    import java.net.URL;
023    import java.util.Map;
024    import java.util.Hashtable;
025    import java.util.EventObject;
026    import java.util.EventListener;
027    import java.util.ArrayList;
028    import java.io.File;
029    import java.io.IOException;
030    import java.io.FileInputStream;
031    import java.io.OutputStream;
032    import java.io.FileOutputStream;
033    import java.io.FileNotFoundException;
034    
035    import net.dpml.util.Logger;
036    import net.dpml.lang.UnknownKeyException;
037    import net.dpml.lang.DuplicateKeyException;
038    
039    import net.dpml.station.ApplicationRegistry;
040    import net.dpml.station.RegistryEvent;
041    import net.dpml.station.RegistryListener;
042    import net.dpml.station.builder.RegistryBuilder;
043    import net.dpml.station.builder.RegistryWriter;
044    import net.dpml.station.info.ApplicationDescriptor;
045    import net.dpml.station.info.RegistryDescriptor;
046    import net.dpml.station.info.RegistryDescriptor.Entry;
047    import net.dpml.station.info.ApplicationRegistryRuntimeException;
048    
049    import net.dpml.util.StreamUtils;
050    
051    /**
052     * Implements of the application registry within which a set of application profiles 
053     * are maintained.
054     *
055     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
056     * @version 1.2.0
057     */
058    public class RemoteApplicationRegistry extends DefaultModel implements ApplicationRegistry
059    {
060        private final URL m_url;
061        private final Map m_map = new Hashtable();
062        
063       /**
064        * Creation of a new application registry model.
065        * @param logger the assigned logging channel
066        * @param url storage location
067        * @exception Exception if an error occurs
068        */
069        public RemoteApplicationRegistry( Logger logger, URL url ) throws Exception
070        {
071            super( logger );
072    
073            try
074            {
075                RegistryDescriptor registry = loadRegistryDescriptor( url );
076                RegistryDescriptor.Entry[] entries = registry.getEntries();
077                for( int i=0; i<entries.length; i++ )
078                {
079                    RegistryDescriptor.Entry entry = entries[i];
080                    if( null == entry )
081                    {
082                        final String error = 
083                          "Internal error causes by a null registry array entry.  Probable "
084                          + "cause is an incompatible data source.";
085                        throw new ApplicationRegistryRuntimeException( error );
086                    }
087                    String key = entry.getKey();
088                    ApplicationDescriptor descriptor = entry.getApplicationDescriptor();
089                    m_map.put( key, descriptor );
090                }
091                m_url = url;
092            }
093            catch( Exception e )
094            {
095                final String error = 
096                  "Unexpected exception raised while loading: " + url;
097                throw new ApplicationRegistryRuntimeException( error, e );
098            } 
099        }
100    
101       /**
102        * Return the array of application keys.
103        * @return the application key array
104        */
105        public String[] getKeys()
106        {
107            synchronized( getLock() )
108            {
109                return (String[]) m_map.keySet().toArray( new String[0] );
110            }
111        }
112        
113       /**
114        * Return the array of application descriptors.
115        * @return the application descriptor array
116        */
117        Entry[] getEntries()
118        {
119            synchronized( getLock() )
120            {
121                String[] keys = getKeys();
122                ArrayList entries = new ArrayList();
123                //Entry[] entries = new Entry[ keys.length ];
124                for( int i=0; i<keys.length; i++ )
125                {
126                    String key = keys[i];
127                    ApplicationDescriptor descriptor = 
128                      (ApplicationDescriptor) m_map.get( key );
129                    if( null != descriptor )
130                    {
131                        Entry entry = new Entry( key, descriptor );
132                        entries.add( entry );
133                    }
134                }
135                return (Entry[]) entries.toArray( new Entry[0] );
136            }
137        }
138        
139       /**
140        * Return the number of application descriptors in the registry.
141        * @return the application descriptor count
142        */
143        public int getApplicationDescriptorCount()
144        {
145            return m_map.size();
146        }
147    
148       /**
149        * Add an application descriptor to the registry.
150        * @param key the application key
151        * @param descriptor the application descriptor
152        * @exception DuplicateKeyException if the key is already assigned
153        */
154        public void addApplicationDescriptor( String key, ApplicationDescriptor descriptor ) 
155          throws DuplicateKeyException
156        {
157            synchronized( getLock() )
158            {
159                if( m_map.containsKey( key ) )
160                {
161                    throw new DuplicateKeyException( key );
162                }
163                m_map.put( key, descriptor );
164                getLogger().debug( "added application: " + key );
165                ApplicationDescriptorAddedEvent event = 
166                  new ApplicationDescriptorAddedEvent( this, descriptor );
167                enqueueEvent( event );
168            }
169        }
170    
171       /**
172        * Remove an application descriptor from the registry.
173        * @param key the application key
174        * @exception UnknownKeyException if the key is not recognized
175        */
176        public void removeApplicationDescriptor( String key ) 
177          throws UnknownKeyException
178        {
179            synchronized( getLock() )
180            {
181                if( !m_map.containsKey( key ) )
182                {
183                    throw new UnknownKeyException( key );
184                }
185                ApplicationDescriptor descriptor = (ApplicationDescriptor) m_map.get( key );
186                ApplicationDescriptorRemovedEvent event = 
187                  new ApplicationDescriptorRemovedEvent( this, descriptor );
188                m_map.remove( key );
189                getLogger().debug( "removed application: " + key );
190                enqueueEvent( event );
191            }
192        }
193    
194       /**
195        * Replace an application descriptor within the registry with a supplied descriptor.
196        * @param key the application key
197        * @param descriptor the updated application descriptor
198        * @exception UnknownKeyException if the key is not recognized
199        */
200        public void updateApplicationDescriptor( String key, ApplicationDescriptor descriptor ) 
201          throws UnknownKeyException
202        {
203            synchronized( getLock() )
204            {
205                m_map.put( key, descriptor );
206                getLogger().debug( "updated application: " + descriptor );
207                ApplicationDescriptorAddedEvent event = 
208                  new ApplicationDescriptorAddedEvent( this, descriptor );
209                enqueueEvent( event );
210            }
211        }
212    
213       /**
214        * Return an array of all profiles in the registry.
215        * @return the application profiles
216        */
217        public ApplicationDescriptor[] getApplicationDescriptors()
218        {
219            synchronized( getLock() )
220            {
221                return (ApplicationDescriptor[]) m_map.values().toArray( new ApplicationDescriptor[0] );
222            }
223        }
224        
225       /**
226        * Retrieve an application profile.
227        * @param key the application profile key
228        * @return the application profile
229        * @exception UnknownKeyException if the key is unknown
230        */
231        public ApplicationDescriptor getApplicationDescriptor( String key ) 
232          throws UnknownKeyException
233        {
234            synchronized( getLock() )
235            {
236                if( !m_map.containsKey( key ) )
237                {
238                    throw new UnknownKeyException( key );
239                }
240                return (ApplicationDescriptor) m_map.get( key );
241            }
242        }
243    
244       /**
245        * Flush the state of the server to external storage.
246        * @exception IOException if an I/O error occurs
247        */
248        public void flush() throws IOException
249        {
250            synchronized( getLock() )
251            {
252                if( null == m_url )
253                {
254                    return;
255                }
256                
257                File file = File.createTempFile( "dpml-station", ".xml" );
258                getLogger().debug( "writing to temp file: " + file );
259                file.createNewFile();
260                FileOutputStream output = new FileOutputStream( file );
261                Entry[] entries = getEntries();
262                RegistryDescriptor descriptor = new RegistryDescriptor( entries );
263                RegistryWriter writer = new RegistryWriter();
264                writer.writeRegistryDescriptor( descriptor, output, "" );
265                FileInputStream input = new FileInputStream( file );
266                OutputStream dest = m_url.openConnection().getOutputStream();
267                StreamUtils.copyStream( input, dest, true );
268                getLogger().debug( "updated registry: " + m_url );
269            }
270        }
271        
272       /**
273        * Add a depot content change listener.
274        * @param listener the registry change listener to add
275        */
276        public void addRegistryListener( RegistryListener listener )
277        {
278            super.addListener( listener );
279        }
280    
281       /**
282        * Add a registry change listener.
283        * @param listener the registry change listener to add
284        */
285        public void removeRegistryListener( RegistryListener listener )
286        {
287            super.removeListener( listener );
288        }
289    
290       /**
291        * Proces a registry event.
292        * @param event the event top process
293        */
294        protected void processEvent( EventObject event )
295        {
296            if( event instanceof RegistryEvent )
297            {
298                processRegistryEvent( (RegistryEvent) event );
299            }
300            else
301            {
302                final String error = 
303                  "Event class not recognized: " 
304                  + event.getClass().getName();
305                throw new IllegalArgumentException( error );
306            }
307        }
308    
309       /**
310        * Return a string representation of the registy model.
311        * @return the string value
312        */
313        public String toString()
314        {
315            return "[registry]";
316        }
317    
318        private RegistryDescriptor loadRegistryDescriptor( URL url ) throws Exception
319        {
320            if( null == url )
321            {
322                return new RegistryDescriptor( new Entry[0] );
323            }
324            else 
325            {
326                RegistryBuilder builder = new RegistryBuilder();
327                try
328                {
329                    Object object = builder.build( new URI( url.toString() ) );
330                    if( object instanceof RegistryDescriptor )
331                    {
332                        return (RegistryDescriptor) object;
333                    }
334                    else
335                    {
336                        final String error = 
337                          "The object returned from the uri ["
338                          + url
339                          + "] of the class ["
340                          + object.getClass().getName()
341                          + "] is not an instance of "
342                          + RegistryDescriptor.class.getName()
343                          + ".";
344                        throw new IllegalArgumentException( error );
345                    }
346                }
347                catch( FileNotFoundException e )
348                {
349                    return new RegistryDescriptor( new Entry[0] );
350                }
351            }
352        }
353        
354        private void processRegistryEvent( RegistryEvent event )
355        {
356            EventListener[] listeners = super.listeners();
357            for( int i=0; i<listeners.length; i++ )
358            {
359                EventListener listener = listeners[i];
360                if( listener instanceof RegistryListener )
361                {
362                    RegistryListener rl = (RegistryListener) listener;
363                    if( event instanceof ApplicationDescriptorAddedEvent )
364                    {
365                        try
366                        {
367                            rl.profileAdded( event );
368                        }
369                        catch( Throwable e )
370                        {
371                            final String error =
372                              "RegistryListener profile addition notification error.";
373                            getLogger().error( error, e );
374                        }
375                    }
376                    else if( event instanceof ApplicationDescriptorRemovedEvent )
377                    {
378                        try
379                        {
380                            rl.profileRemoved( event );
381                        }
382                        catch( Throwable e )
383                        {
384                            final String error =
385                              "RegistryListener profile removed notification error.";
386                            getLogger().error( error, e );
387                        }
388                    }
389                }
390            }
391        }
392    
393       /**
394        * ApplicationDescriptorAddedEvent.
395        */
396        static class ApplicationDescriptorAddedEvent extends RegistryEvent
397        {
398           /**
399            * Creation of a new ProfileAddedEvent.
400            * @param source the source registry
401            * @param descriptor the application descriptor that was added
402            */
403            public ApplicationDescriptorAddedEvent( 
404              ApplicationRegistry source, ApplicationDescriptor descriptor )
405            {
406                super( source, descriptor );
407            }
408        }
409    
410       /**
411        * ApplicationDescriptorRemovedEvent.
412        */
413        static class ApplicationDescriptorRemovedEvent extends RegistryEvent
414        {
415           /**
416            * Creation of a new ProfileRemovedEvent.
417            * @param source the source registry
418            * @param descriptor the application descriptor that was removed
419            */
420            public ApplicationDescriptorRemovedEvent( ApplicationRegistry source, ApplicationDescriptor descriptor )
421            {
422                super( source, descriptor );
423            }
424        }
425    }