001    /*
002     * Copyright 2006 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;
020    
021    import dpml.lang.DOM3DocumentBuilder;
022    import dpml.util.SimpleResolver;
023    import dpml.util.DefaultLogger;
024    
025    import java.io.IOException;
026    import java.lang.management.ManagementFactory;
027    import java.net.URI;
028    import java.net.URL;
029    import java.net.URLConnection;
030    import java.util.Hashtable;
031    import java.util.ArrayList;
032    import java.util.List;
033    
034    import javax.management.MBeanServer;
035    import javax.management.ObjectName;
036    
037    import net.dpml.transit.Artifact;
038    import net.dpml.transit.ContentHandler;
039    import net.dpml.lang.DecodingException;
040    import net.dpml.util.Logger;
041    import net.dpml.util.Resolver;
042    
043    import net.dpml.appliance.Appliance;
044    import net.dpml.appliance.ApplianceManager;
045    import net.dpml.appliance.ApplianceContentManager;
046    import net.dpml.appliance.ApplianceFactory;
047    import net.dpml.appliance.ApplianceException;
048    
049    import dpml.station.info.ApplianceDescriptor;
050    
051    import org.w3c.dom.Element;
052    import org.w3c.dom.Document;
053    import org.w3c.dom.TypeInfo;
054    
055    /**
056     * Content handler for the <tt>appliance</tt> artifact type.
057     *
058     * @author <a href="http://www.dpml.net">Digital Product Management Library</a>
059     * @version 2.1.1
060     */
061    public class ApplianceContentHandler extends ContentHandler implements ApplianceContentManager, ApplianceFactory
062    {
063        static final String NAMESPACE = "dpml:station";
064        static final String TYPE = "appliance";
065        
066        private static final Logger LOGGER = new DefaultLogger( "dpml.metro.appliance" );
067        private static final DOM3DocumentBuilder DOCUMENT_BUILDER = 
068          new DOM3DocumentBuilder();
069          
070        private static final List<ApplianceManager> MANAGERS = 
071          new ArrayList<ApplianceManager>();
072          
073        private static final ApplianceContentHandler SINGLETON = newApplianceContentHandler();
074        
075        private static ApplianceContentHandler newApplianceContentHandler()
076        {
077            ApplianceContentHandler handler = new ApplianceContentHandler();
078            String flag = System.getProperty( "dpml.jmx.enabled", "false" );
079            if( "true".equals( flag ) )
080            {
081                try
082                {
083                    MBeanServer server = ManagementFactory.getPlatformMBeanServer();
084                    Hashtable<String,String> table = new Hashtable<String,String>();
085                    table.put( "type", "Appliances" );
086                    ObjectName name =
087                      ObjectName.getInstance( "net.dpml.transit", table );
088                    server.registerMBean( handler, name );
089                }
090                catch( Exception e )
091                {
092                    e.printStackTrace();
093                }
094            }
095            return handler;
096        }
097        
098       /**
099        * Creation of a new applicance content handler.
100        */
101        public ApplianceContentHandler()
102        {
103            this( LOGGER );
104        }
105        
106       /**
107        * Creation of a new applicance content handler.
108        * @param logger the assigned logging channel
109        */
110        protected ApplianceContentHandler( Logger logger )
111        {
112            //logger.debug( "instantiating" );
113        }
114        
115        /*
116        protected MBeanServerConnection getMBeanServerConnection() throws MalformedURLException, IOException
117        {
118            String spec = System.getProperty( Station.STATION_JMX_URI_KEY );
119            if( null != spec )
120            {
121                JMXServiceURL url = new JMXServiceURL( spec );
122                JMXConnector connector = JMXConnectorFactory.connect( url, null ); 
123                return connector.getMBeanServerConnection();
124            }
125            else
126            {
127                return ManagementFactory.getPlatformMBeanServer();
128            }
129        }
130        */
131        
132       /**
133        * Return the set of applicance managers established by the handler.
134        * @return the appliance managers
135        */
136        public ApplianceManager[] getApplianceManagers()
137        {
138            return MANAGERS.toArray( new ApplianceManager[0] );
139        }
140        
141       /**
142        * Returns the type thar the content handler supports.
143        * @return the content type name
144        */
145        public String getType()
146        {
147            return TYPE;
148        }
149        
150       /**
151        * Returns the content in the form of a {@link net.dpml.appliance.Appliance}.
152        * @param connection the url connection
153        * @return the application handler
154        * @exception IOException if an IO error occurs
155        */
156        public Object getContent( URLConnection connection ) throws IOException
157        {
158            return newAppliance( connection, null );
159        }
160        
161       /**
162        * Returns the content assignable to the first recognized class in the list
163        * of suppied classes.  If the class array is empty the application handler is returned.
164        * If none of the classes are recognized, null is returned.
165        * @param connection the url connection
166        * @param classes the selection class array
167        * @return the resolved instance
168        * @exception IOException if an IO error occurs
169        */
170        public Object getContent( URLConnection connection, Class[] classes ) throws IOException
171        {
172            ApplianceDescriptor descriptor = getApplianceDescriptor( connection );
173            return getContentForClasses( descriptor, classes );
174        }
175        
176       /**
177        * Create a new appliance using the supplied connection object.
178        * @param connection the URL connection
179        * @param partition an optional partition name
180        * @return the appliance
181        * @exception IOException if an IO error occurs
182        */
183        public Appliance newAppliance( URLConnection connection, String partition ) throws IOException
184        {
185            ApplianceDescriptor descriptor = getApplianceDescriptor( connection );
186            return newAppliance( partition, descriptor );
187        }
188    
189        static <T>T getContentForClass( URLConnection connection, Class<T> type ) throws IOException
190        {
191            ApplianceDescriptor descriptor = getApplianceDescriptor( connection );
192            return getContentForClass( descriptor, type );
193        }
194        
195        private static Object getContentForClasses( ApplianceDescriptor descriptor, Class<?>[] classes ) throws IOException
196        {
197            for( Class<?> c : classes )
198            {
199                Object value = getContentForClass( descriptor, c );
200                if( null != value )
201                {
202                    return value;
203                }
204            }
205            return null;
206        }
207        
208        private static <T>T getContentForClass( ApplianceDescriptor descriptor, Class<T> type ) throws IOException
209        {
210            if( ApplianceDescriptor.class == type )
211            {
212                return type.cast( descriptor );
213            }
214            else if( Appliance.class == type )
215            {
216                String key = "" + System.identityHashCode( descriptor );
217                Appliance appliance = newAppliance( key, descriptor );
218                return type.cast( appliance );
219            }
220            else
221            {
222                return null;
223            }
224        }
225        
226        static final Appliance newAppliance( String key, ApplianceDescriptor descriptor ) throws IOException
227        {
228            if( null == key )
229            {
230                throw new NullPointerException( "key" );
231            }
232            Logger logger = getLogger();
233            Application application = new Application( logger, key, descriptor );
234            Appliance appliance = application.getAppliance();
235            register( key, appliance );
236            return appliance;
237        }
238        
239        static void register( String id, Appliance appliance )
240        {
241            if( appliance instanceof ApplianceManager )
242            {
243                if( null == id )
244                {
245                    throw new NullPointerException( "id" );
246                }
247                ApplianceManager manager = (ApplianceManager) appliance;
248                if( !MANAGERS.contains( manager ) )
249                {
250                    try
251                    {
252                        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
253                        Hashtable<String, String> table = new Hashtable<String, String>();
254                        table.put( "type", "Appliances" );
255                        table.put( "name", id );
256                        ObjectName name =
257                          ObjectName.getInstance( "net.dpml.transit", table );
258                        server.registerMBean( manager, name );
259                        MANAGERS.add( manager );
260                    }
261                    catch( Exception e )
262                    {
263                        e.printStackTrace();
264                    }
265                }
266            }
267        }
268        
269        static void deregister( Appliance appliance )
270        {
271            if( appliance instanceof ApplianceManager )
272            {
273                ApplianceManager manager = (ApplianceManager) appliance;
274                if( MANAGERS.contains( manager ) )
275                {
276                    MANAGERS.remove( manager );
277                }
278            }
279        }
280        
281        static Appliance newAppliance( String key, URI uri ) throws IOException
282        {
283            URL url = Artifact.toURL( uri );
284            URLConnection connection = url.openConnection();
285            ApplianceDescriptor descriptor = getApplianceDescriptor( connection );
286            return newAppliance( key, descriptor );
287        }
288        
289        private static ApplianceDescriptor getApplianceDescriptor( URLConnection connection ) throws IOException
290        {
291            URL url = connection.getURL();
292            try
293            {
294                Document document = DOCUMENT_BUILDER.parse( url );
295                final Element element = document.getDocumentElement();
296                TypeInfo type = element.getSchemaTypeInfo();
297                String namespace = type.getTypeNamespace();
298                if( NAMESPACE.equals( namespace ) )
299                {
300                    URI uri = URI.create( url.toExternalForm() );
301                    Resolver resolver = new SimpleResolver();
302                    return new ApplianceDescriptor( element, resolver, uri );
303                }
304                else
305                {
306                    final String error = 
307                      "ApplianceDescriptor namespace not recognized."
308                      + "\nFound: " + namespace
309                      + "\nExpecting: " + NAMESPACE;
310                    throw new DecodingException( error, element );
311                }
312            }
313            catch( DecodingException e )
314            {
315                throw e;
316            }
317            catch( Exception e )
318            {
319                final String error = "Unexpected error while constructing application profile: " + url;
320                throw new ApplianceException( error, e, null );  
321            }
322        }
323        
324        private static Logger getLogger()
325        {
326            return LOGGER;
327        }
328    }