1 16 package org.apache.cocoon.components.source.impl; 17 18 import java.io.ByteArrayInputStream ; 19 import java.io.ByteArrayOutputStream ; 20 import java.io.IOException ; 21 import java.io.InputStream ; 22 import java.io.OutputStream ; 23 import java.net.MalformedURLException ; 24 25 import javax.xml.transform.TransformerFactory ; 26 import javax.xml.transform.sax.SAXTransformerFactory ; 27 import javax.xml.transform.sax.TransformerHandler ; 28 import javax.xml.transform.stream.StreamResult ; 29 30 import org.apache.avalon.framework.context.Context; 31 import org.apache.avalon.framework.logger.AbstractLogEnabled; 32 import org.apache.avalon.framework.logger.Logger; 33 import org.apache.avalon.framework.service.ServiceManager; 34 import org.apache.avalon.framework.service.ServiceSelector; 35 import org.apache.cocoon.CascadingIOException; 36 import org.apache.cocoon.ProcessingException; 37 import org.apache.cocoon.ResourceNotFoundException; 38 import org.apache.cocoon.components.source.helpers.SourceCredential; 39 import org.apache.cocoon.serialization.Serializer; 40 import org.apache.cocoon.xml.IncludeXMLConsumer; 41 import org.apache.excalibur.source.ModifiableSource; 42 import org.apache.excalibur.source.Source; 43 import org.apache.excalibur.source.SourceException; 44 import org.apache.excalibur.source.SourceValidity; 45 import org.apache.excalibur.xml.sax.XMLizable; 46 import org.xml.sax.ContentHandler ; 47 import org.xml.sax.SAXException ; 48 import org.xml.sax.helpers.AttributesImpl ; 49 import org.xmldb.api.DatabaseManager; 50 import org.xmldb.api.base.Collection; 51 import org.xmldb.api.base.Resource; 52 import org.xmldb.api.base.ResourceIterator; 53 import org.xmldb.api.base.ResourceSet; 54 import org.xmldb.api.base.XMLDBException; 55 import org.xmldb.api.modules.CollectionManagementService; 56 import org.xmldb.api.modules.XMLResource; 57 import org.xmldb.api.modules.XPathQueryService; 58 59 67 public class XMLDBSource extends AbstractLogEnabled 68 implements Source, ModifiableSource, XMLizable { 69 70 74 75 public static final String URI = "http://apache.org/cocoon/xmldb/1.0"; 76 77 78 public static final String PREFIX = "db"; 79 80 81 protected static final String COLLECTIONS = "collections"; 82 83 protected static final String QCOLLECTIONS = PREFIX + ":" + COLLECTIONS; 84 85 protected static final String RESOURCE_COUNT_ATTR = "resources"; 86 87 protected static final String COLLECTION_COUNT_ATTR = "collections"; 88 protected static final String COLLECTION_BASE_ATTR = "base"; 89 90 91 protected static final String COLLECTION = "collection"; 92 93 protected static final String QCOLLECTION = PREFIX + ":" + COLLECTION; 94 95 96 protected static final String RESOURCE = "resource"; 97 98 protected static final String QRESOURCE = PREFIX + ":" + RESOURCE; 99 100 protected static final String NAME_ATTR = "name"; 101 102 103 protected static final String RESULTSET = "results"; 104 105 protected static final String QRESULTSET = PREFIX + ":" + RESULTSET; 106 protected static final String QUERY_ATTR = "query"; 107 protected static final String RESULTS_COUNT_ATTR = "resources"; 108 109 110 protected static final String RESULT = "result"; 111 112 protected static final String QRESULT = PREFIX + ":" + RESULT; 113 protected static final String RESULT_DOCID_ATTR = "docid"; 114 protected static final String RESULT_ID_ATTR = "id"; 115 116 protected static final String CDATA = "CDATA"; 117 118 122 123 protected String url; 124 125 126 protected String user; 127 128 129 protected String password; 130 131 132 protected String query; 133 134 135 protected String systemId; 136 137 138 protected final ServiceManager manager; 139 140 141 protected XMLDBOutputStream os; 142 143 144 protected final Context context; 145 146 154 public XMLDBSource(Logger logger, 155 SourceCredential credential, 156 String url, 157 ServiceManager manager, 158 Context context) { 159 enableLogging(logger); 160 this.context = context; 161 this.manager = manager; 162 163 this.user = credential.getPrincipal(); 164 this.password = credential.getPassword(); 165 166 int start = url.indexOf('#'); 168 if (start != -1) { 169 this.url = url.substring(0, start); 170 this.query = url.substring(start + 1); 171 } else { 172 this.url = url; 173 } 174 this.os = null; 175 } 176 177 181 public void toSAX(ContentHandler handler) throws SAXException { 182 try { 183 if (url.endsWith("/")) { 184 this.collectionToSAX(handler); 185 } else { 186 this.resourceToSAX(handler); 187 } 188 } catch (ProcessingException pe) { 189 throw new SAXException ("ProcessingException", pe); 190 } 191 } 192 193 private void resourceToSAX(ContentHandler handler) 194 throws SAXException , ProcessingException { 195 196 final String col = url.substring(0, url.lastIndexOf('/')); 197 final String res = url.substring(url.lastIndexOf('/') + 1); 198 199 Collection collection = null; 200 try { 201 collection = DatabaseManager.getCollection(col, user, password); 202 if (collection == null) { 203 throw new ResourceNotFoundException("Document " + url + " not found"); 204 } 205 206 XMLResource xmlResource = (XMLResource) collection.getResource(res); 207 if (xmlResource == null) { 208 throw new ResourceNotFoundException("Document " + url + " not found"); 209 } 210 211 if (query != null) { 212 if (getLogger().isDebugEnabled()) { 214 getLogger().debug("Querying resource " + res + " from collection " + url + "; query= " + this.query); 215 } 216 217 queryToSAX(handler, collection, res); 218 } else { 219 if (getLogger().isDebugEnabled()) { 221 getLogger().debug("Obtaining resource " + res + " from collection " + col); 222 } 223 224 xmlResource.getContentAsSAX(handler); 225 } 226 } catch (XMLDBException xde) { 227 String error = "Unable to fetch content. Error " 228 + xde.errorCode + ": " + xde.getMessage(); 229 throw new SAXException (error, xde); 230 } finally { 231 if (collection != null) { 232 try { 233 collection.close(); 234 } catch (XMLDBException ignored) { 235 } 236 } 237 } 238 } 239 240 private void collectionToSAX(ContentHandler handler) 241 throws SAXException , ProcessingException { 242 243 AttributesImpl attributes = new AttributesImpl (); 244 245 Collection collection = null; 246 try { 247 collection = DatabaseManager.getCollection(url, user, password); 248 if (collection == null) { 249 throw new ResourceNotFoundException("Collection " + url + 250 " not found"); 251 } 252 253 if (query != null) { 254 if (getLogger().isDebugEnabled()) { 256 getLogger().debug("Querying collection " + url + "; query= " + this.query); 257 } 258 259 queryToSAX(handler, collection, null); 260 } else { 261 if (getLogger().isDebugEnabled()) { 263 getLogger().debug("Listing collection " + url); 264 } 265 266 final String nresources = Integer.toString(collection.getResourceCount()); 267 attributes.addAttribute("", RESOURCE_COUNT_ATTR, 268 RESOURCE_COUNT_ATTR, "CDATA", nresources); 269 final String ncollections = Integer.toString(collection.getChildCollectionCount()); 270 attributes.addAttribute("", COLLECTION_COUNT_ATTR, 271 COLLECTION_COUNT_ATTR, "CDATA", ncollections); 272 attributes.addAttribute("", COLLECTION_BASE_ATTR, 273 COLLECTION_BASE_ATTR, "CDATA", url); 274 275 handler.startDocument(); 276 handler.startPrefixMapping(PREFIX, URI); 277 handler.startElement(URI, COLLECTIONS, QCOLLECTIONS, attributes); 278 279 String [] collections = collection.listChildCollections(); 281 for (int i = 0; i < collections.length; i++) { 282 attributes.clear(); 283 attributes.addAttribute("", NAME_ATTR, NAME_ATTR, CDATA, collections[i]); 284 handler.startElement(URI, COLLECTION, QCOLLECTION, attributes); 285 handler.endElement(URI, COLLECTION, QCOLLECTION); 286 } 287 288 String [] resources = collection.listResources(); 290 for (int i = 0; i < resources.length; i++) { 291 attributes.clear(); 292 attributes.addAttribute("", NAME_ATTR, NAME_ATTR, CDATA, resources[i]); 293 handler.startElement(URI, RESOURCE, QRESOURCE, attributes); 294 handler.endElement(URI, RESOURCE, QRESOURCE); 295 } 296 297 handler.endElement(URI, COLLECTIONS, QCOLLECTIONS); 298 handler.endPrefixMapping(PREFIX); 299 handler.endDocument(); 300 } 301 } catch (XMLDBException xde) { 302 String error = "Collection listing failed. Error " + xde.errorCode + ": " + xde.getMessage(); 303 throw new SAXException (error, xde); 304 } finally { 305 if (collection != null) { 306 try { 307 collection.close(); 308 } catch (XMLDBException ignored) { 309 } 310 } 311 } 312 } 313 314 private void queryToSAX(ContentHandler handler, Collection collection, String resource) 315 throws SAXException { 316 317 AttributesImpl attributes = new AttributesImpl (); 318 319 try { 320 XPathQueryService service = 321 (XPathQueryService) collection.getService("XPathQueryService", "1.0"); 322 ResourceSet resultSet = (resource == null) ? 323 service.query(query) : service.queryResource(resource, query); 324 325 attributes.addAttribute("", QUERY_ATTR, QUERY_ATTR, "CDATA", query); 326 attributes.addAttribute("", RESULTS_COUNT_ATTR, 327 RESULTS_COUNT_ATTR, "CDATA", Long.toString(resultSet.getSize())); 328 329 handler.startDocument(); 330 handler.startPrefixMapping(PREFIX, URI); 331 handler.startElement(URI, RESULTSET, QRESULTSET, attributes); 332 333 IncludeXMLConsumer includeHandler = new IncludeXMLConsumer(handler); 334 335 ResourceIterator results = resultSet.getIterator(); 337 while (results.hasMoreResources()) { 338 XMLResource result = (XMLResource)results.nextResource(); 339 340 final String id = result.getId(); 341 final String documentId = result.getDocumentId(); 342 343 attributes.clear(); 344 if (id != null) { 345 attributes.addAttribute("", RESULT_ID_ATTR, RESULT_ID_ATTR, 346 CDATA, id); 347 } 348 if (documentId != null) { 349 attributes.addAttribute("", RESULT_DOCID_ATTR, RESULT_DOCID_ATTR, 350 CDATA, documentId); 351 } 352 353 handler.startElement(URI, RESULT, QRESULT, attributes); 354 result.getContentAsSAX(includeHandler); 355 handler.endElement(URI, RESULT, QRESULT); 356 } 357 358 handler.endElement(URI, RESULTSET, QRESULTSET); 359 handler.endPrefixMapping(PREFIX); 360 handler.endDocument(); 361 } catch (XMLDBException xde) { 362 String error = "Query failed. Error " + xde.errorCode + ": " + xde.getMessage(); 363 throw new SAXException (error, xde); 364 } 365 } 366 367 public void recycle() { 368 this.url = null; 369 this.user = null; 370 this.password = null; 371 this.query = null; 372 } 373 374 public String getURI() { 375 return url; 376 } 377 378 public long getContentLength() { 379 return -1; 380 } 381 382 public long getLastModified() { 383 return 0; 384 } 385 386 public boolean exists() { 387 final String col = url.substring(0, url.lastIndexOf('/')); 388 final String res = url.substring(url.lastIndexOf('/') + 1); 389 boolean result = true; 390 391 392 if (getLogger().isDebugEnabled()) { 393 getLogger().debug("Testing existence of resource `" + res + "' from collection `" + url + "'; query (ignored) = `" + this.query + "'"); 394 } 395 396 Collection collection = null; 397 try { 398 collection = DatabaseManager.getCollection(col, user, password); 399 if (collection == null) { 400 result = false; 401 } else { 402 XMLResource xmlResource = (XMLResource) collection.getResource(res); 403 if (xmlResource == null) { 404 result = false; 405 } 406 } 407 } catch (XMLDBException xde) { 408 result = false; 409 } finally { 410 if (collection != null) { 411 try { 412 collection.close(); 413 } catch (XMLDBException ignored) { 414 } 415 } 416 } 417 418 return result; 419 } 420 421 public String getMimeType() { 422 return null; 423 } 424 425 public String getScheme() { 426 return url.substring(url.indexOf('/') - 1); 427 } 428 429 public SourceValidity getValidity() { 430 return null; 431 } 432 433 public void refresh() { 434 } 435 436 439 public InputStream getInputStream() 440 throws IOException { 441 442 ServiceSelector serializerSelector = null; 443 Serializer serializer = null; 444 try { 446 TransformerFactory tf = TransformerFactory.newInstance(); 447 TransformerHandler th = 448 ((SAXTransformerFactory ) tf).newTransformerHandler(); 449 ByteArrayOutputStream bOut = new ByteArrayOutputStream (); 450 StreamResult result = new StreamResult (bOut); 451 th.setResult(result); 452 453 toSAX(th); 454 455 return new ByteArrayInputStream (bOut.toByteArray()); 456 } catch (Exception e) { 457 throw new CascadingIOException("Exception during processing of " + getURI(), e); 458 } finally { 459 if (serializer != null) { 460 serializerSelector.release(serializer); 461 } 462 if (serializerSelector != null) { 463 manager.release(serializerSelector); 464 } 465 } 466 } 467 468 471 public OutputStream getOutputStream() throws IOException , MalformedURLException { 472 if (query != null) { 473 throw new MalformedURLException ("Cannot modify a resource that includes an XPATH expression"); 474 } 475 this.os = new XMLDBOutputStream(); 476 return this.os; 477 } 478 479 private void writeOutputStream(String content) throws SourceException { 480 String name = null; 481 String base = null; 482 483 try { 484 if (this.url.endsWith("/")) { 485 name = ""; 486 base = this.url.substring(0, this.url.length() - 1); 487 } else { 488 base = this.url.substring(0, this.url.lastIndexOf("/")); 489 name = this.url.substring(this.url.lastIndexOf("/")+1); 490 } 491 Collection collection = DatabaseManager.getCollection(base, user, password); 492 493 if (name.equals("")) { 494 name = collection.createId(); 495 this.url += name; 496 } 497 Resource resource = collection.createResource(name, "XMLResource"); 498 499 resource.setContent(content); 500 collection.storeResource(resource); 501 502 getLogger().debug("Written to resource " + name); 503 } catch (XMLDBException e) { 504 String message = "Failed to create resource " + name + ": " + e.errorCode; 505 getLogger().debug(message, e); 506 throw new SourceException(message); 507 } 508 } 509 510 513 public void delete() throws SourceException { 514 String base = null; 515 String name = null; 516 if (this.url.endsWith("/")) { 517 try { 518 String k = this.url.substring(0, this.url.length() - 1); 520 521 base = k.substring(0, k.lastIndexOf("/")); 522 name = k.substring(k.lastIndexOf("/")+1); 523 524 Collection collection = DatabaseManager.getCollection(base, user, password); 525 526 CollectionManagementService service = 527 (CollectionManagementService) collection.getService("CollectionManagementService", "1.0"); 528 service.removeCollection(name); 529 } catch (XMLDBException e) { 530 String message = "Failed to remove collection " + name + ": " + e.errorCode; 531 getLogger().error(message, e); 532 throw new SourceException(message); 533 } 534 } else { 535 try { 536 base = this.url.substring(0, this.url.lastIndexOf("/")); 537 name = this.url.substring(this.url.lastIndexOf("/")+1); 538 539 Collection collection = DatabaseManager.getCollection(base, user, password); 540 541 Resource resource = collection.getResource(name); 542 if (resource == null) { 543 String message = "Resource " + name + " does not exist"; 544 getLogger().debug(message); 545 throw new SourceException(message); 546 } else { 547 collection.removeResource(resource); 548 getLogger().debug("Removed resource: "+ name); 549 } 550 } catch (XMLDBException e) { 551 String message = "Failed to delete resource " + name + ": " + e.errorCode; 552 getLogger().debug(message, e); 553 throw new SourceException(message); 554 } 555 } 556 } 557 558 564 public boolean canCancel(OutputStream stream) { 565 return !this.os.isClosed(); 566 } 567 568 574 public void cancel(OutputStream stream) throws IOException { 575 this.os.cancel(); 576 this.os = null; 577 } 578 579 public class XMLDBOutputStream extends OutputStream { 580 581 private ByteArrayOutputStream baos; 582 private boolean isClosed; 583 public XMLDBOutputStream() { 584 baos = new ByteArrayOutputStream (); 585 isClosed = false; 586 } 587 588 public void write(int b) throws IOException { 589 baos.write(b); 590 } 591 592 public void write(byte b[]) throws IOException { 593 baos.write(b); 594 } 595 596 public void write(byte b[], int off, int len) throws IOException { 597 baos.write(b, off, len); 598 } 599 600 public void close() throws IOException , SourceException { 601 if (!isClosed) { 602 writeOutputStream(baos.toString()); 603 baos.close(); 604 this.isClosed = true; 605 } 606 } 607 608 public void flush() throws IOException { 609 } 610 611 public int size() { 612 return baos.size(); 613 } 614 615 public boolean isClosed() { 616 return this.isClosed; 617 } 618 public void cancel() { 619 this.isClosed = true; 620 } 621 } 622 } 623 | Popular Tags |