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 }