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.transit;
020    
021    import java.net.URI;
022    import java.net.URL;
023    import java.net.URLConnection;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    import java.io.IOException;
027    import java.io.Writer;
028    import java.io.OutputStreamWriter;
029    
030    import javax.xml.parsers.DocumentBuilder;
031    import javax.xml.parsers.DocumentBuilderFactory;
032    
033    import net.dpml.util.ElementHelper;
034    import net.dpml.transit.info.TransitDirective;
035    import net.dpml.transit.info.CacheDirective;
036    import net.dpml.transit.info.HostDirective;
037    import net.dpml.transit.info.ProxyDirective;
038    import net.dpml.transit.info.LayoutDirective;
039    
040    import net.dpml.lang.ValueDirective;
041    import net.dpml.util.DecodingException;
042    import net.dpml.util.Logger;
043    
044    import org.xml.sax.ErrorHandler;
045    
046    import org.w3c.dom.Element;
047    import org.w3c.dom.Document;
048    
049    /**
050     * Utility class supporting the reading of Transit XML configurations.
051     *
052     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
053     * @version 1.0.3
054     */
055    public class TransitBuilder
056    {
057        private static final String XML_HEADER = 
058          "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
059    
060        private static final String NAME = "transit";
061    
062        private static final String PUBLIC_ID = 
063          "-//DPML//DTD Transit Configuration Version 1.0//EN";
064          
065        private static final String SYSTEM_ID = 
066          "http://download.dpml.net/dtds/transit_1_0.dtd";
067    
068        private static final String RESOURCE = 
069          "net/dpml/transit/transit_1_0.dtd";
070    
071        private static final String DOCTYPE = 
072          "\n<!DOCTYPE "
073          + NAME
074          + " PUBLIC \"" 
075          + PUBLIC_ID
076          + "\" \""
077          + SYSTEM_ID 
078          + "\" >";
079        
080        private static final DTD[] DTDS = new DTD[]
081        {
082            new DTD( 
083              PUBLIC_ID, 
084              SYSTEM_ID, 
085              RESOURCE, null )
086        };
087    
088        //private static final DTDResolver DTD_RESOLVER =
089        //    new DTDResolver( DTDS, TransitBuilder.class.getClassLoader() );
090    
091        private Logger m_logger;
092        
093       /**
094        * Creation of a new transit configuration builder.
095        * @param logger the assigned logging channel
096        */
097        public TransitBuilder( Logger logger )
098        {
099            m_logger = logger;
100        }
101    
102       /**
103        * Construct a transit configuration from a supplied uri.
104        * @param url the configuration url
105        * @return the transit configuration
106        * @exception Exception if an error occurs during configuration loading
107        */
108        public TransitDirective load( final URL url ) throws Exception
109        {
110            URLConnection connection = url.openConnection();
111            InputStream input = connection.getInputStream();
112    
113            final DocumentBuilderFactory factory =
114              DocumentBuilderFactory.newInstance();
115            factory.setValidating( true );
116            factory.setNamespaceAware( true );
117            factory.setExpandEntityReferences( true );
118            DocumentBuilder builder = factory.newDocumentBuilder();
119            DTDResolver resolver =
120              new DTDResolver( DTDS, getClass().getClassLoader() );
121            builder.setEntityResolver( resolver );
122            ErrorHandler errors = new SaxMonitor( m_logger );
123            builder.setErrorHandler( errors );
124            
125            final Document document = builder.parse( input );
126            final Element root = document.getDocumentElement();
127            return build( root );
128        }
129    
130       /**
131        * Write a transit directive to an output stream as XML.
132        * @param directive the directive to externalize
133        * @param output the output stream to write to
134        * @exception IOException if an I/O error occurs
135        */
136        public void write( TransitDirective directive, OutputStream output ) throws IOException 
137        {
138            final Writer writer = new OutputStreamWriter( output );
139            try
140            {
141                writer.write( XML_HEADER );
142                writer.write( DOCTYPE );
143                
144                CacheDirective cache = directive.getCacheDirective();
145                String cachePath = cache.getCache();
146                String cacheLayout = cache.getCacheLayout();
147                writeHeader( writer, cachePath, cacheLayout );
148                
149                ProxyDirective proxy = directive.getProxyDirective();
150                writeProxy( writer, proxy );
151                
152                String localPath = cache.getLocal();
153                String localLayout = cache.getLocalLayout();
154                writeLocal( writer, localPath, localLayout );
155                
156                HostDirective[] hosts = cache.getHostDirectives();
157                writeHosts( writer, hosts );
158                
159                writeFooter( writer );
160                writer.write( "\n" );
161            }
162            finally
163            {
164                writer.flush();
165                writer.close();
166            }
167        }
168        
169        //-------------------------------------------------------------
170        // internals supporting XML to directive transformation
171        //-------------------------------------------------------------
172        
173        private TransitDirective build( Element root ) throws Exception
174        {
175            String name = root.getTagName();
176            if( !NAME.equals( name ) )
177            {
178                final String error = 
179                  "Invalid root element name ["
180                  + name
181                  + "].";
182                throw new IOException( error );
183            }
184            
185            String cachePath = ElementHelper.getAttribute( root, "cache" );
186            String cacheLayout = ElementHelper.getAttribute( root, "layout" );
187            
188            Element localElement = ElementHelper.getChild( root, "local" );
189            String localPath = ElementHelper.getAttribute( localElement, "path" );
190            String localLayout = ElementHelper.getAttribute( localElement, "layout" );
191            
192            Element proxyElement = ElementHelper.getChild( root, "proxy" );
193            ProxyDirective proxy = buildProxyDirective( proxyElement );
194            
195            Element hostsElement = ElementHelper.getChild( root, "hosts" );
196            HostDirective[] hosts = buildHosts( hostsElement );
197            
198            Element layoutsElement = ElementHelper.getChild( root, "layouts" );
199            LayoutDirective[] layouts = buildLayouts( layoutsElement );
200            
201            // handlers TBD
202            
203            CacheDirective cache = 
204              new CacheDirective( 
205                cachePath, cacheLayout, localPath, localLayout,
206                CacheDirective.EMPTY_LAYOUTS, hosts );
207            return new TransitDirective( proxy, cache );
208        }
209        
210        private LayoutDirective[] buildLayouts( Element element ) throws Exception
211        {
212            if( null == element )
213            {
214                return null;
215            }
216            else
217            {
218                Element[] layoutElements = ElementHelper.getChildren( element, "layout" );
219                LayoutDirective[] layouts = new LayoutDirective[ layoutElements.length ];
220                for( int i=0; i<layoutElements.length; i++ )
221                {
222                    Element elem = layoutElements[i];
223                    layouts[i] = buildLayout( elem );
224                }
225                return layouts;
226            }
227        }
228                
229        private LayoutDirective buildLayout( Element element ) throws Exception
230        {
231            String id = ElementHelper.getAttribute( element, "id" );
232            String title = ElementHelper.getAttribute( element, "title" );
233            Element codebase = ElementHelper.getChild( element, "codebase" );
234            URI uri = decodeURI( codebase );
235            ValueDirective[] values = getValueDirectives( codebase );
236            return new LayoutDirective( id, title, uri, values );
237        }
238        
239        private URI decodeURI( Element element ) throws DecodingException
240        {
241            String uri = ElementHelper.getAttribute( element, "uri" );
242            if( null == uri )
243            {
244                final String error = "Missing uri attribute.";
245                throw new DecodingException( element, error );
246            }
247            else
248            {
249                try
250                {
251                    return new URI( uri );
252                }
253                catch( Exception e )
254                {
255                    final String error = "Bad uri argument [" + uri + "].";
256                    throw new DecodingException( element, error );
257                    
258                }
259            }
260        }
261        
262        private ValueDirective[] getValueDirectives( Element element )
263        {
264            if( null == element )
265            {
266                return null;
267            }
268            else
269            {
270                Element[] valueElements = ElementHelper.getChildren( element, "value" );
271                ValueDirective[] values = new ValueDirective[ valueElements.length ];
272                for( int i=0; i<valueElements.length; i++ )
273                {
274                    Element elem = valueElements[i];
275                    values[i] = buildValue( elem );
276                }
277                return values;
278            }
279        }
280        
281        private ValueDirective buildValue( Element element )
282        {
283            String target = ElementHelper.getAttribute( element, "target" );
284            String method = ElementHelper.getAttribute( element, "method" );
285            String value = ElementHelper.getAttribute( element, "value" );
286            if( value != null )
287            {
288                return new ValueDirective( target, method, value );
289            }
290            else
291            {
292                ValueDirective[] values = getValueDirectives( element );
293                return new ValueDirective( target, method, values );
294            }
295        }
296        
297        private ProxyDirective buildProxyDirective( Element element )
298        {
299            if( null == element )
300            {
301                return null;
302            }
303            else
304            {
305                String host = ElementHelper.getAttribute( element, "host" );
306                Element credentialsElement = ElementHelper.getChild( element, "credentials" );
307                String username = getUsername( credentialsElement );
308                char[] password = getPassword( credentialsElement );
309                String[] excludes = buildProxyExcludes( element );
310                return new ProxyDirective( host, excludes, username, password );
311            }
312        }
313        
314        private String[] buildProxyExcludes( Element element )
315        {
316            if( null == element )
317            {
318                return null;
319            }
320            else
321            {
322                Element[] elements = ElementHelper.getChildren( element, "exclude" );
323                String[] excludes = new String[ elements.length ];
324                for( int i=0; i<excludes.length; i++ )
325                {
326                    Element elem = elements[i];
327                    excludes[i] = ElementHelper.getValue( elem );
328                }
329                return excludes;
330            }
331        }
332        
333        private HostDirective[] buildHosts( Element element )
334        {
335            Element[] elements = ElementHelper.getChildren( element, "host" );
336            HostDirective[] hosts = new HostDirective[ elements.length ];
337            for( int i=0; i<hosts.length; i++ )
338            {
339                Element elem = elements[i];
340                String id = ElementHelper.getAttribute( elem, "id" );
341                int priority = Integer.parseInt( ElementHelper.getAttribute( elem, "priority" ) );
342                String url = ElementHelper.getAttribute( elem, "url" );
343                String layout = ElementHelper.getAttribute( elem, "layout" );
344                boolean enabled = ElementHelper.getBooleanAttribute( elem, "enabled" );
345                boolean trusted = ElementHelper.getBooleanAttribute( elem, "trusted" );
346                String index = ElementHelper.getAttribute( elem, "index" );
347                String scheme = ElementHelper.getAttribute( elem, "scheme" );
348                String prompt = ElementHelper.getAttribute( elem, "prompt" );
349                Element credentialsElement = ElementHelper.getChild( elem, "credentials" );
350                String username = getUsername( credentialsElement );
351                char[] password = getPassword( credentialsElement );
352                hosts[i] = 
353                  new HostDirective( 
354                    id, priority, url, index, username, password, enabled, trusted,
355                    layout, scheme, prompt );
356            }
357            return hosts;
358        }
359        
360        private String getUsername( Element element )
361        {
362            if( null == element )
363            {
364                return null;
365            }
366            else
367            {
368                return ElementHelper.getAttribute( element, "username" );
369            }
370        }
371        
372        private char[] getPassword( Element element )
373        {
374            if( null == element )
375            {
376                return null;
377            }
378            else
379            {
380                String password = ElementHelper.getAttribute( element, "password" );
381                if( null == password )
382                {
383                    return null;
384                }
385                else
386                {
387                    return password.toCharArray();
388                }
389            }
390        }
391        
392        //-------------------------------------------------------------
393        // internals supporting directive to XML transformation
394        //-------------------------------------------------------------
395        
396        private void writeHeader( Writer writer, String cache, String layout ) throws IOException
397        {
398            writer.write( "\n\n<" + NAME + " cache=\"" + cache + "\" layout=\"" + layout + "\">" );
399        }
400        
401        private void writeFooter( Writer writer ) throws IOException
402        {
403            writer.write( "\n</" + NAME + ">" );
404        }
405    
406        private void writeProxy( Writer writer, ProxyDirective proxy ) throws IOException 
407        {
408            if( null != proxy )
409            {
410                String host = proxy.getHost();
411                String username = proxy.getUsername();
412                String password = getPassword( proxy.getPassword() );
413                String[] excludes = proxy.getExcludes();
414                
415                boolean credentials = ( ( null != username ) || ( null != password ) );
416                
417                if( excludes.length == 0 && ( !credentials ) )
418                {
419                    writer.write( 
420                      "\n  <proxy host=\"" + host + "\"/>" );
421                }
422                else
423                {
424                    writer.write( "\n  <proxy host=\"" + host + "\">" );
425                    if( credentials )
426                    {
427                        writer.write( "\n    <credentials" );
428                        if( null != username )
429                        {
430                            writer.write( " username=\"" + username + "\"" );
431                        }
432                        if( null != password )
433                        {
434                            writer.write( " password=\"" + password + "\"" );
435                        }
436                        writer.write( "/>" );
437                    }
438                    if( excludes.length > 0 )
439                    {
440                        writer.write( "\n    <excludes>" );
441                        for( int i=0; i<excludes.length; i++ )
442                        {
443                            String exclude = excludes[i];
444                            writer.write( "\n      <exclude>" + exclude + "</exclude>" );
445                        }
446                        writer.write( "\n    </excludes>" );
447                    }
448                    
449                    writer.write( "\n  </proxy>" );
450                }
451            }
452        }
453        
454        private void writeLocal( Writer writer, String path, String layout ) throws IOException 
455        {
456            writer.write( "\n  <local path=\"" + path + "\" layout=\"" + layout + "\"/>" );
457        }
458        
459        private void writeHosts( Writer writer, HostDirective[] hosts ) throws IOException 
460        {
461            writer.write( "\n  <hosts>" );
462            for( int i=0; i<hosts.length; i++ )
463            {
464                HostDirective host = hosts[i];
465                writeHost( writer, host );
466            }
467            writer.write( "\n  </hosts>" );
468        }
469        
470        private void writeHost( Writer writer, HostDirective host ) throws IOException 
471        {
472            String id = host.getID();
473            int priority = host.getPriority();
474            String url = host.getHost();
475            boolean enabled = host.getEnabled();
476            boolean trusted = host.getTrusted();
477            String layout = host.getLayout();
478            String index = host.getIndex();
479            String scheme = host.getScheme();
480            String prompt = host.getPrompt();
481            String username = host.getUsername();
482            String password = getPassword( host.getPassword() );
483            boolean credentials = ( ( null != username ) || ( null != password ) );
484            
485            writer.write( "\n    <host id=\"" + id + "\" priority=\"" + priority + "\" url=\"" + url + "\"" );
486            if( !enabled )
487            {
488                writer.write( " enabled=\"false\"" );
489            }
490            if( trusted )
491            {
492                writer.write( " trusted=\"true\"" );
493            }
494            if( null != layout )
495            {
496                writer.write( " layout=\"" + layout + "\"" );
497            }
498            if( null != index )
499            {
500                writer.write( " index=\"" + index + "\"" );
501            }
502            if( ( null != scheme ) && !scheme.equals( "" ) )
503            {
504                writer.write( " scheme=\"" + scheme + "\"" );
505            }
506            if( ( null != prompt ) && !prompt.equals( "" ) )
507            {
508                writer.write( " prompt=\"" + prompt + "\"" );
509            }
510            if( credentials )
511            {
512                writer.write( "\n    <credentials" );
513                if( null != username )
514                {
515                    writer.write( " username=\"" + username + "\"" );
516                }
517                if( null != password )
518                {
519                    writer.write( " password=\"" + password + "\"" );
520                }
521                writer.write( "/>" );
522                writer.write( "\n    </host>" );
523            }
524            else
525            {
526                writer.write( "/>" );
527            }
528        }
529        
530        private String getPassword( char[] password )
531        {
532            if( null == password )
533            {
534                return null;
535            }
536            else
537            {
538                return new String( password );
539            }
540        }
541    }