001    /*
002     * Copyright 2006 Stephen J. 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.metro.builder;
020    
021    import java.io.IOException;
022    import java.net.URL;
023    import java.net.URI;
024    import java.net.URISyntaxException;
025    import java.util.Properties;
026    import java.beans.IntrospectionException;
027    
028    import net.dpml.lang.Version;
029    
030    import net.dpml.metro.info.Type;
031    import net.dpml.metro.info.InfoDescriptor;
032    import net.dpml.metro.info.ContextDescriptor;
033    import net.dpml.metro.info.CategoryDescriptor;
034    import net.dpml.metro.info.ServiceDescriptor;
035    import net.dpml.metro.info.EntryDescriptor;
036    import net.dpml.metro.info.CollectionPolicy;
037    import net.dpml.metro.info.LifestylePolicy;
038    import net.dpml.metro.info.ThreadSafePolicy;
039    import net.dpml.metro.info.Priority;
040    
041    import net.dpml.state.State;
042    import net.dpml.state.StateDecoder;
043    import net.dpml.state.DefaultState;
044    
045    import net.dpml.util.DOM3DocumentBuilder;
046    import net.dpml.util.DecodingException;
047    import net.dpml.util.ElementHelper;
048    import net.dpml.util.Resolver;
049    
050    import org.w3c.dom.Element;
051    import org.w3c.dom.Document;
052    
053    
054    /**
055     * Component type decoder.
056     *
057     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
058     * @version 1.2.0
059     */
060    public final class ComponentTypeDecoder
061    {
062        private static final StateDecoder STATE_DECODER = new StateDecoder();
063        
064        private static final DOM3DocumentBuilder DOCUMENT_BUILDER = new DOM3DocumentBuilder();
065        
066       /**
067        * Load a type.
068        * @param subject the component implementation class
069        * @param resolver build-time value resolver
070        * @return the component type descriptor
071        * @exception IOException if an error occurs reading the type definition
072        * @exception IntrospectionException if an introspection error occurs
073        */
074        public Type loadType( Class subject, Resolver resolver ) throws IOException, IntrospectionException
075        {
076            String classname = subject.getName();
077            String path = classname.replace( '.', '/' );
078            String target = path + ".type";
079            ClassLoader classloader = subject.getClassLoader();
080            URL url = classloader.getResource( target );
081            if( null == url )
082            {
083                return Type.createType( subject );
084            }
085            else
086            {
087                try
088                {
089                    URI uri = new URI( url.toString() );
090                    return loadType( uri, resolver );
091                }
092                catch( URISyntaxException e )
093                {
094                    final String error =
095                      "Bad uri value."
096                      + "\nURI: " + url;
097                    IOException exception = new IOException( error );
098                    exception.initCause( e );
099                    throw exception;
100                }
101            }
102        }
103    
104       /**
105        * Load a type.
106        * @param uri the component type source uri
107        * @param resolver build-time uri resolver
108        * @return the component type descriptor
109        * @exception IOException if an error occurs reading the type definition
110        */
111        public Type loadType( URI uri, Resolver resolver ) throws IOException
112        {
113            try
114            {
115                final Document document = DOCUMENT_BUILDER.parse( uri );
116                final Element root = document.getDocumentElement();
117                return buildType( root, resolver );
118            }
119            catch( Throwable e )
120            {
121                final String error =
122                  "An error while attempting to load a type."
123                  + "\nType URI: " + uri;
124                IOException exception = new IOException( error );
125                exception.initCause( e );
126                throw exception;
127            }
128        }
129        
130        private Type buildType( Element root, Resolver resolver ) throws Exception
131        {
132            InfoDescriptor info = buildNestedInfoDescriptor( root );
133            ServiceDescriptor[] services = buildNestedServices( root );
134            ContextDescriptor context = buildNestedContext( root );
135            CategoryDescriptor[] categories = buildNestedCategories( root );
136            State state = buildNestedState( root );
137            return new Type( info, categories, context, services, state );
138        }
139        
140        private InfoDescriptor buildNestedInfoDescriptor( Element root ) throws DecodingException
141        {
142            Element info = ElementHelper.getChild( root, "info" );
143            if( null == info )
144            {
145                final String error = 
146                  "Definition of <type> is missing the required <info> element.";
147                throw new DecodingException( root, error );
148            }
149            
150            String name = ElementHelper.getAttribute( info, "name" );
151            String classname = ElementHelper.getAttribute( info, "class" );
152            String version = ElementHelper.getAttribute( info, "version" );
153            String collection = ElementHelper.getAttribute( info, "collection", "system" );
154            String lifestyle = ElementHelper.getAttribute( info, "lifestyle", null );
155            String threadsafe = ElementHelper.getAttribute( info, "threadsafe", "unknown" );
156            Properties properties = buildNestedProperties( info );
157            
158            return new InfoDescriptor( 
159              name, 
160              classname, 
161              Version.parse( version ),
162              buildLifestylePolicy( lifestyle ),
163              CollectionPolicy.parse( collection ),
164              ThreadSafePolicy.parse( threadsafe ),
165              properties );
166        }
167        
168        private LifestylePolicy buildLifestylePolicy( String lifestyle )
169        {
170            if( null == lifestyle )
171            {
172                return null;
173            }
174            else
175            {
176                return LifestylePolicy.parse( lifestyle );
177            }
178        }
179        
180        private ServiceDescriptor[] buildNestedServices( Element root )
181        {
182            Element element = ElementHelper.getChild( root, "services" );
183            if( null == element )
184            {
185                return new ServiceDescriptor[0];
186            }
187            else
188            {
189                Element[] children = ElementHelper.getChildren( element, "service" );
190                ServiceDescriptor[] services = new ServiceDescriptor[ children.length ];
191                for( int i=0; i<children.length; i++ )
192                {
193                    Element child = children[i];
194                    String classname = ElementHelper.getAttribute( child, "class" );
195                    String version = ElementHelper.getAttribute( child, "version", "1.0.0" );
196                    services[i] = new ServiceDescriptor( classname, Version.parse( version ) );
197                }
198                return services;
199            }
200        }
201        
202        private ContextDescriptor buildNestedContext( Element root )
203        {
204            Element context = ElementHelper.getChild( root, "context" );
205            if( null == context )
206            {
207                return new ContextDescriptor( new EntryDescriptor[0] );
208            }
209            else
210            {
211                Element[] children = ElementHelper.getChildren( context, "entry" );
212                EntryDescriptor[] entries = new EntryDescriptor[ children.length ];
213                for( int i=0; i<children.length; i++ )
214                {
215                    Element child = children[i];
216                    String key = ElementHelper.getAttribute( child, "key" );
217                    String classname = ElementHelper.getAttribute( child, "class" );
218                    boolean optional = ElementHelper.getBooleanAttribute( child, "optional", false );
219                    boolean isVolatile = ElementHelper.getBooleanAttribute( child, "volatile", false );
220                    entries[i] = new EntryDescriptor( key, classname, optional, isVolatile );
221                }
222                return new ContextDescriptor( entries );
223            }
224        }
225        
226        private CategoryDescriptor[] buildNestedCategories( Element root )
227        {
228            Element element = ElementHelper.getChild( root, "categories" );
229            if( null == element )
230            {
231                return new CategoryDescriptor[0];
232            }
233            else
234            {
235                Element[] children = ElementHelper.getChildren( element, "category" );
236                CategoryDescriptor[] categories = new CategoryDescriptor[ children.length ];
237                for( int i=0; i<children.length; i++ )
238                {
239                    Element elem = children[i];
240                    String name = ElementHelper.getAttribute( elem, "name" );
241                    String priority = ElementHelper.getAttribute( elem, "priority" );
242                    Properties properties = buildNestedProperties( elem );
243                    categories[i] = new CategoryDescriptor( name, Priority.parse( priority ), properties );
244                }
245                return categories;
246            }
247        }
248        
249        private State buildNestedState( Element root )
250        {
251            Element element = ElementHelper.getChild( root, "state" );
252            if( null == element )
253            {
254                return new DefaultState();
255            }
256            else
257            {
258                return STATE_DECODER.buildStateGraph( element );
259            }
260        }
261    
262        private Properties buildNestedProperties( Element element )
263        {
264            Element[] entries = ElementHelper.getChildren( element, "property" );
265            if( entries.length == 0 )
266            {
267                return null;
268            }
269            else
270            {
271                Properties properties = new Properties();
272                for( int i=0; i<entries.length; i++ )
273                {
274                    Element elem = entries[i];
275                    String name = ElementHelper.getAttribute( elem, "name" );
276                    String value = ElementHelper.getAttribute( elem, "value" );
277                    properties.setProperty( name, value );
278                }
279                return properties;
280            }
281        }
282        
283    }
284