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 }