001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.transformation;
018:
019: import java.io.IOException;
020: import java.io.Serializable;
021: import java.io.StringWriter;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.Properties;
026:
027: import javax.xml.transform.OutputKeys;
028: import javax.xml.transform.TransformerConfigurationException;
029: import javax.xml.transform.TransformerFactory;
030: import javax.xml.transform.sax.SAXTransformerFactory;
031: import javax.xml.transform.sax.TransformerHandler;
032: import javax.xml.transform.stream.StreamResult;
033:
034: import org.apache.avalon.framework.activity.Initializable;
035: import org.apache.avalon.framework.configuration.Configurable;
036: import org.apache.avalon.framework.configuration.Configuration;
037: import org.apache.avalon.framework.configuration.ConfigurationException;
038: import org.apache.avalon.framework.parameters.Parameters;
039: import org.apache.cocoon.ProcessingException;
040: import org.apache.cocoon.ResourceNotFoundException;
041: import org.apache.cocoon.caching.CacheableProcessingComponent;
042: import org.apache.cocoon.environment.SourceResolver;
043: import org.apache.cocoon.util.TraxErrorHandler;
044: import org.apache.excalibur.source.SourceValidity;
045: import org.xml.sax.Attributes;
046: import org.xml.sax.SAXException;
047: import org.xml.sax.helpers.AttributesImpl;
048: import org.xmldb.api.DatabaseManager;
049: import org.xmldb.api.base.Collection;
050: import org.xmldb.api.base.Database;
051: import org.xmldb.api.base.Resource;
052: import org.xmldb.api.base.XMLDBException;
053: import org.xmldb.api.modules.CollectionManagementService;
054: import org.xmldb.api.modules.XUpdateQueryService;
055:
056: /**
057: * This transformer allows to perform resource creation, deletion, and
058: * XUpdate command execution in XML:DB. All operations are performed either
059: * in <code>base</code> collection, or context collection, which
060: * is specified as <code>collection</code> attribute on the <code>query</code>
061: * element. Context collection must be specified relative to the base collection.
062: *
063: * <p>Definition:</p>
064: * <pre>
065: * <map:transformer name="xmldb" src="org.apache.cocoon.transformation.XMLDBTransformer">
066: * <!-- Optional driver parameter. Uncomment if you want transformer to register a database.
067: * <driver>org.apache.xindice.client.xmldb.DatabaseImpl</driver>
068: * -->
069: * <base>xmldb:xindice:///db/collection</base>
070: * <user>myDatabaseLogin</user>
071: * <password>myDatabasePassword</password>
072: * </map:transformer>
073: * </pre>
074: *
075: * <p>The component configuration defined in <map:transformer> can be
076: * overriden with sitemap parameters on the <map:transform>:</p>
077: * <pre>
078: * <map:transform type="xmldb">
079: * <map:parameter name="base" value="xmldb:xindice:///db/collection"/>
080: * <map:parameter name="user" value="myDatabaseLogin"/>
081: * <map:parameter name="password" value="myDatabasePassword"/>
082: * </map:transform>
083: * </pre>
084: *
085: * <p>Input XML document example:</p>
086: * <pre>
087: * <page xmlns:db="http://apache.org/cocoon/xmldb/1.0">
088: * ...
089: * <p>Create XML resource in base collection with specified object ID</p>
090: * <db:query type="create" oid="xmldb-object-id">
091: * <page>
092: * XML Object body
093: * </page>
094: * </db:query>
095: *
096: * <p>Delete XML resource from the base collection with specified object ID</p>
097: * <db:query type="delete" oid="xmldb-object-id"/>
098: *
099: * <p>Update XML resource with specified object ID</p>
100: * <db:query type="update" oid="xmldb-object-id">
101: * <xu:modifications version="1.0" xmlns:xu="http://www.xmldb.org/xupdate">
102: * <xu:remove select="/person/phone[@type = 'home']"/>
103: * <xu:update select="/person/phone[@type = 'work']">
104: * 480-300-3003
105: * </xu:update>
106: * </xu:modifications>
107: * </db:query>
108: *
109: * <p>Create collection nested into the base collection</p>
110: * <db:query type="create" oid="inner/"/>
111: *
112: * <p>Create XML resource in context collection with specified object ID</p>
113: * <db:query type="create" collection="inner" oid="xmldb-object-id">
114: * <page>
115: * XML Object body
116: * </page>
117: * </db:query>
118: * ...
119: * </page>
120: * </pre>
121: *
122: * <p>Output XML document example:</p>
123: * <pre>
124: * <page xmlns:db="http://apache.org/cocoon/xmldb/1.0">
125: * ...
126: * <db:query type="create" oid="xmldb-object-id" result="success"/>
127: *
128: * <db:query type="delete" oid="xmldb-object-id" result="success"/>
129: *
130: * <db:query type="update" oid="xmldb-object-id" result="failure">
131: * Resource xmldb-object-id is not found
132: * </db:query>
133: * ...
134: * </page>
135: * </pre>
136: *
137: * <p>Known bugs and limitations:</p>
138: * <ul>
139: * <li>No namespaces with Xalan (see AbstractTextSerializer)</li>
140: * </ul>
141: *
142: * @version $Id: XMLDBTransformer.java 433543 2006-08-22 06:22:54Z crossley $
143: */
144: public class XMLDBTransformer extends AbstractTransformer implements
145: CacheableProcessingComponent, Configurable, Initializable {
146:
147: private static String XMLDB_URI = "http://apache.org/cocoon/xmldb/1.0";
148: private static String XMLDB_QUERY_ELEMENT = "query";
149: private static String XMLDB_QUERY_TYPE_ATTRIBUTE = "type";
150: private static String XMLDB_QUERY_CONTEXT_ATTRIBUTE = "collection";
151: private static String XMLDB_QUERY_OID_ATTRIBUTE = "oid";
152: private static String XMLDB_QUERY_RESULT_ATTRIBUTE = "result";
153:
154: /** The trax <code>TransformerFactory</code> used by this transformer. */
155: private SAXTransformerFactory tfactory = null;
156: private Properties format = new Properties();
157:
158: /** The map of namespace prefixes. */
159: private Map prefixMap = new HashMap();
160:
161: /** XML:DB driver class name (optional) */
162: private String driver = null;
163:
164: /** Default collection name. */
165: private String default_base;
166:
167: /** Default user name. */
168: private String default_user;
169:
170: /** Default password. */
171: private String default_password;
172:
173: /** Current collection name. */
174: private String local_base;
175:
176: /** Current collection name. */
177: private String xbase;
178:
179: /** Current collection. */
180: private Collection collection;
181:
182: /** database login */
183: private String local_user = null;
184:
185: /** database password */
186: private String local_password = null;
187:
188: /** Operation. One of: create, delete, update. */
189: private String operation;
190:
191: /** Document ID. Can be null if update or insert is performed on collection. */
192: private String key;
193:
194: /** Result of current operation. Success or failure. */
195: private String result;
196:
197: /** Message in case current operation failed. */
198: private String message;
199:
200: private StringWriter queryWriter;
201: private TransformerHandler queryHandler;
202:
203: /** True when inside <query> element. */
204: private boolean processing;
205:
206: public XMLDBTransformer() {
207: format.put(OutputKeys.ENCODING, "utf-8");
208: format.put(OutputKeys.INDENT, "no");
209: format.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
210: }
211:
212: public void configure(Configuration configuration)
213: throws ConfigurationException {
214: this .driver = configuration.getChild("driver").getValue(null);
215: if (driver == null) {
216: getLogger()
217: .debug(
218: "Driver parameter is missing. Transformer will not initialize database.");
219: }
220:
221: this .default_base = configuration.getChild("base").getValue(
222: null);
223: this .default_user = configuration.getChild("user").getValue(
224: null);
225: this .default_password = configuration.getChild("password")
226: .getValue(null);
227: }
228:
229: /**
230: * Initializes XML:DB database instance if driver class was configured.
231: */
232: public void initialize() throws Exception {
233: if (driver != null) {
234: Class c = Class.forName(driver);
235: Database database = (Database) c.newInstance();
236: DatabaseManager.registerDatabase(database);
237: }
238: }
239:
240: /** Setup the transformer. */
241: public void setup(SourceResolver resolver, Map objectModel,
242: String src, Parameters par) throws ProcessingException,
243: SAXException, IOException {
244:
245: this .local_base = par.getParameter("base", this .default_base);
246: if (this .local_base == null) {
247: throw new ProcessingException(
248: "Required base parameter is missing. Syntax is: xmldb:xindice:///db/collection");
249: }
250:
251: /** Get user password from parameter for the database. Usefull for update action */
252: this .local_user = par.getParameter("user", this .default_user);
253: this .local_password = par.getParameter("password",
254: this .default_password);
255:
256: try {
257: this .collection = DatabaseManager.getCollection(
258: this .local_base, this .local_user,
259: this .local_password);
260: } catch (XMLDBException e) {
261: throw new ProcessingException("Could not get collection "
262: + this .local_base + ": " + e.errorCode, e);
263: }
264:
265: if (this .collection == null) {
266: throw new ResourceNotFoundException("Collection "
267: + this .local_base + " does not exist");
268: }
269: }
270:
271: /**
272: * Helper for TransformerFactory.
273: */
274: protected SAXTransformerFactory getTransformerFactory() {
275: if (tfactory == null) {
276: tfactory = (SAXTransformerFactory) TransformerFactory
277: .newInstance();
278: tfactory
279: .setErrorListener(new TraxErrorHandler(getLogger()));
280: }
281: return tfactory;
282: }
283:
284: /**
285: * Generate the unique key.
286: * This key must be unique inside the space of this component.
287: * This method must be invoked before the generateValidity() method.
288: *
289: * @return The generated key or <code>null</code> if the component
290: * is currently not cacheable.
291: */
292: public Serializable getKey() {
293: return null;
294: }
295:
296: /**
297: * Generate the validity object.
298: * Before this method can be invoked the generateKey() method
299: * must be invoked.
300: *
301: * @return The generated validity object or <code>null</code> if the
302: * component is currently not cacheable.
303: */
304: public SourceValidity getValidity() {
305: return null;
306: }
307:
308: /**
309: * Receive notification of the beginning of a document.
310: */
311: public void startDocument() throws SAXException {
312: super .startDocument();
313: }
314:
315: /**
316: * Receive notification of the end of a document.
317: */
318: public void endDocument() throws SAXException {
319: super .endDocument();
320: }
321:
322: /**
323: * Begin the scope of a prefix-URI Namespace mapping.
324: *
325: * @param prefix The Namespace prefix being declared.
326: * @param uri The Namespace URI the prefix is mapped to.
327: */
328: public void startPrefixMapping(String prefix, String uri)
329: throws SAXException {
330: if (!processing) {
331: super .startPrefixMapping(prefix, uri);
332: prefixMap.put(prefix, uri);
333: } else if (this .queryHandler != null) {
334: this .queryHandler.startPrefixMapping(prefix, uri);
335: }
336: }
337:
338: /**
339: * End the scope of a prefix-URI mapping.
340: *
341: * @param prefix The prefix that was being mapping.
342: */
343: public void endPrefixMapping(String prefix) throws SAXException {
344: if (!processing) {
345: super .endPrefixMapping(prefix);
346: prefixMap.remove(prefix);
347: } else if (this .queryHandler != null) {
348: this .queryHandler.endPrefixMapping(prefix);
349: }
350: }
351:
352: /**
353: * Receive notification of the beginning of an element.
354: *
355: * @param uri The Namespace URI, or the empty string if the element has no
356: * Namespace URI or if Namespace
357: * processing is not being performed.
358: * @param loc The local name (without prefix), or the empty string if
359: * Namespace processing is not being performed.
360: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
361: * raw names are not available.
362: * @param a The attributes attached to the element. If there are no
363: * attributes, it shall be an empty Attributes object.
364: */
365: public void startElement(String uri, String loc, String raw,
366: Attributes a) throws SAXException {
367: if (!processing) {
368: if (XMLDB_URI.equals(uri)
369: && XMLDB_QUERY_ELEMENT.equals(loc)) {
370:
371: this .operation = a.getValue(XMLDB_QUERY_TYPE_ATTRIBUTE);
372: if (!"create".equals(operation)
373: && !"delete".equals(operation)
374: && !"update".equals(operation)) {
375: throw new SAXException(
376: "Supported operation types are: create, delete, update");
377: }
378:
379: this .key = a.getValue(XMLDB_QUERY_OID_ATTRIBUTE);
380: if ("delete".equals(operation) && this .key == null) {
381: throw new SAXException(
382: "Object ID attribute is missing on query element");
383: }
384:
385: this .xbase = a.getValue(XMLDB_QUERY_CONTEXT_ATTRIBUTE);
386:
387: // Start processing
388: result = "failure";
389: message = null;
390: processing = true;
391:
392: if ("create".equals(operation) && this .key != null
393: && this .key.endsWith("/")) {
394: } else if (!"delete".equals(operation)) {
395: // Prepare SAX query writer
396: queryWriter = new StringWriter(256);
397: try {
398: this .queryHandler = getTransformerFactory()
399: .newTransformerHandler();
400: this .queryHandler.setResult(new StreamResult(
401: queryWriter));
402: this .queryHandler.getTransformer()
403: .setOutputProperties(format);
404: } catch (TransformerConfigurationException e) {
405: throw new SAXException(
406: "Failed to get transformer handler", e);
407: }
408:
409: // Start query document
410: this .queryHandler.startDocument();
411: Iterator i = prefixMap.entrySet().iterator();
412: while (i.hasNext()) {
413: Map.Entry entry = (Map.Entry) i.next();
414: this .queryHandler.startPrefixMapping(
415: (String) entry.getKey(), (String) entry
416: .getValue());
417: }
418: }
419: } else {
420: super .startElement(uri, loc, raw, a);
421: }
422: } else if (this .queryHandler != null) {
423: this .queryHandler.startElement(uri, loc, raw, a);
424: }
425: }
426:
427: /**
428: * Receive notification of the end of an element.
429: *
430: * @param uri The Namespace URI, or the empty string if the element has no
431: * Namespace URI or if Namespace
432: * processing is not being performed.
433: * @param loc The local name (without prefix), or the empty string if
434: * Namespace processing is not being performed.
435: * @param raw The raw XML 1.0 name (with prefix), or the empty string if
436: * raw names are not available.
437: */
438: public void endElement(String uri, String loc, String raw)
439: throws SAXException {
440: if (!processing) {
441: super .endElement(uri, loc, raw);
442: } else {
443: if (XMLDB_URI.equals(uri)
444: && XMLDB_QUERY_ELEMENT.equals(loc)) {
445: processing = false;
446:
447: String document = null;
448: if (this .queryHandler != null) {
449: // Finish building query. Remove existing prefix mappings.
450: Iterator i = prefixMap.entrySet().iterator();
451: while (i.hasNext()) {
452: Map.Entry entry = (Map.Entry) i.next();
453: this .queryHandler
454: .endPrefixMapping((String) entry
455: .getKey());
456: }
457: this .queryHandler.endDocument();
458: document = this .queryWriter.toString();
459: }
460:
461: // Perform operation
462: Collection collection = null;
463: try {
464: // Obtain collection for the current operation
465: collection = (xbase != null) ? DatabaseManager
466: .getCollection(local_base + "/" + xbase,
467: this .local_user,
468: this .local_password)
469: : this .collection;
470:
471: if (collection == null) {
472: message = "Failed to " + operation
473: + " resource " + this .key
474: + ": Collection " + local_base + "/"
475: + xbase + " not found.";
476: getLogger().debug(message);
477: } else if ("create".equals(operation)) {
478: if (key != null && key.endsWith("/")) {
479: try {
480: // Cut trailing '/'
481: String k = this .key.substring(0,
482: this .key.length() - 1);
483: CollectionManagementService service = (CollectionManagementService) collection
484: .getService(
485: "CollectionManagementService",
486: "1.0");
487: service.createCollection(k);
488: result = "success";
489: } catch (XMLDBException e) {
490: message = "Failed to create collection "
491: + this .key + ": " + e.errorCode;
492: getLogger().error(message, e);
493: }
494: } else {
495: try {
496: if (key == null) {
497: key = collection.createId();
498: }
499: // Support of binary objects can be added. Content can be obtained using Source.
500: Resource resource = collection
501: .createResource(key,
502: "XMLResource");
503: resource.setContent(document);
504: collection.storeResource(resource);
505: result = "success";
506: key = resource.getId();
507: } catch (XMLDBException e) {
508: message = "Failed to create resource "
509: + key + ": " + e.errorCode;
510: getLogger().debug(message, e);
511: }
512: }
513: } else if ("delete".equals(operation)) {
514: if (key != null && key.endsWith("/")) {
515: try {
516: // Cut trailing '/'
517: String k = this .key.substring(0,
518: this .key.length() - 1);
519: CollectionManagementService service = (CollectionManagementService) collection
520: .getService(
521: "CollectionManagementService",
522: "1.0");
523: service.removeCollection(k);
524: result = "success";
525: } catch (XMLDBException e) {
526: message = "Failed to delete collection "
527: + this .key + ": " + e.errorCode;
528: getLogger().error(message, e);
529: }
530: } else {
531: try {
532: Resource resource = collection
533: .getResource(this .key);
534: if (resource == null) {
535: message = "Resource " + this .key
536: + " does not exist";
537: getLogger().debug(message);
538: } else {
539: collection.removeResource(resource);
540: result = "success";
541: }
542: } catch (XMLDBException e) {
543: message = "Failed to delete resource "
544: + key + ": " + e.errorCode;
545: getLogger().debug(message, e);
546: }
547: }
548: } else if ("update".equals(operation)) {
549: try {
550: XUpdateQueryService service = (XUpdateQueryService) collection
551: .getService("XUpdateQueryService",
552: "1.0");
553: long count = (this .key == null) ? service
554: .update(document) : service
555: .updateResource(this .key, document);
556: message = count + " entries updated.";
557: result = "success";
558: } catch (XMLDBException e) {
559: message = "Failed to update resource "
560: + key + ": " + e.errorCode;
561: getLogger().debug(message, e);
562: }
563: }
564: } catch (XMLDBException e) {
565: message = "Failed to get context collection for the query (base: "
566: + local_base
567: + ", context: "
568: + xbase
569: + "): " + e.errorCode;
570: getLogger().debug(message, e);
571: } finally {
572: if (xbase != null && collection != null) {
573: try {
574: collection.close();
575: } catch (XMLDBException ignored) {
576: }
577: }
578: }
579:
580: // Report result
581: AttributesImpl attrs = new AttributesImpl();
582: attrs.addAttribute("", XMLDB_QUERY_OID_ATTRIBUTE,
583: XMLDB_QUERY_OID_ATTRIBUTE, "CDATA", this .key);
584: attrs.addAttribute("", XMLDB_QUERY_TYPE_ATTRIBUTE,
585: XMLDB_QUERY_TYPE_ATTRIBUTE, "CDATA",
586: this .operation);
587: attrs.addAttribute("", XMLDB_QUERY_RESULT_ATTRIBUTE,
588: XMLDB_QUERY_RESULT_ATTRIBUTE, "CDATA", result);
589: super .startElement(uri, loc, raw, attrs);
590: if (message != null) {
591: super .characters(message.toCharArray(), 0, message
592: .length());
593: }
594: super .endElement(uri, loc, raw);
595: } else if (this .queryHandler != null) {
596: this .queryHandler.endElement(uri, loc, raw);
597: }
598: }
599: }
600:
601: /**
602: * Receive notification of character data.
603: *
604: * @param c The characters from the XML document.
605: * @param start The start position in the array.
606: * @param len The number of characters to read from the array.
607: */
608: public void characters(char c[], int start, int len)
609: throws SAXException {
610: if (!processing) {
611: super .characters(c, start, len);
612: } else if (this .queryHandler != null) {
613: this .queryHandler.characters(c, start, len);
614: }
615: }
616:
617: /**
618: * Receive notification of ignorable whitespace in element content.
619: *
620: * @param c The characters from the XML document.
621: * @param start The start position in the array.
622: * @param len The number of characters to read from the array.
623: */
624: public void ignorableWhitespace(char c[], int start, int len)
625: throws SAXException {
626: if (!processing) {
627: super .ignorableWhitespace(c, start, len);
628: } else if (this .queryHandler != null) {
629: this .queryHandler.ignorableWhitespace(c, start, len);
630: }
631: }
632:
633: /**
634: * Receive notification of a processing instruction.
635: *
636: * @param target The processing instruction target.
637: * @param data The processing instruction data, or null if none was
638: * supplied.
639: */
640: public void processingInstruction(String target, String data)
641: throws SAXException {
642: if (!processing) {
643: super .processingInstruction(target, data);
644: } else if (this .queryHandler != null) {
645: this .queryHandler.processingInstruction(target, data);
646: }
647: }
648:
649: /**
650: * Receive notification of a skipped entity.
651: *
652: * @param name The name of the skipped entity. If it is a parameter
653: * entity, the name will begin with '%'.
654: */
655: public void skippedEntity(String name) throws SAXException {
656: if (!processing) {
657: super .skippedEntity(name);
658: } else if (this .queryHandler != null) {
659: this .queryHandler.skippedEntity(name);
660: }
661: }
662:
663: /**
664: * Report the start of DTD declarations, if any.
665: *
666: * @param name The document type name.
667: * @param publicId The declared public identifier for the external DTD
668: * subset, or null if none was declared.
669: * @param systemId The declared system identifier for the external DTD
670: * subset, or null if none was declared.
671: */
672: public void startDTD(String name, String publicId, String systemId)
673: throws SAXException {
674: if (!processing) {
675: super .startDTD(name, publicId, systemId);
676: } else {
677: throw new SAXException(
678: "Recieved startDTD after beginning SVG extraction process.");
679: }
680: }
681:
682: /**
683: * Report the end of DTD declarations.
684: */
685: public void endDTD() throws SAXException {
686: if (!processing) {
687: super .endDTD();
688: } else {
689: throw new SAXException(
690: "Recieved endDTD after xmldb element.");
691: }
692: }
693:
694: /**
695: * Report the beginning of an entity.
696: *
697: * @param name The name of the entity. If it is a parameter entity, the
698: * name will begin with '%'.
699: */
700: public void startEntity(String name) throws SAXException {
701: if (!processing) {
702: super .startEntity(name);
703: } else if (this .queryHandler != null) {
704: this .queryHandler.startEntity(name);
705: }
706: }
707:
708: /**
709: * Report the end of an entity.
710: *
711: * @param name The name of the entity that is ending.
712: */
713: public void endEntity(String name) throws SAXException {
714: if (!processing) {
715: super .endEntity(name);
716: } else if (this .queryHandler != null) {
717: this .queryHandler.endEntity(name);
718: }
719: }
720:
721: /**
722: * Report the start of a CDATA section.
723: */
724: public void startCDATA() throws SAXException {
725: if (!processing) {
726: super .startCDATA();
727: } else if (this .queryHandler != null) {
728: this .queryHandler.startCDATA();
729: }
730: }
731:
732: /**
733: * Report the end of a CDATA section.
734: */
735: public void endCDATA() throws SAXException {
736: if (!processing) {
737: super .endCDATA();
738: } else if (this .queryHandler != null) {
739: this .queryHandler.endCDATA();
740: }
741: }
742:
743: /**
744: * Report an XML comment anywhere in the document.
745: *
746: * @param ch An array holding the characters in the comment.
747: * @param start The starting position in the array.
748: * @param len The number of characters to use from the array.
749: */
750: public void comment(char ch[], int start, int len)
751: throws SAXException {
752: if (!processing) {
753: super .comment(ch, start, len);
754: } else if (this .queryHandler != null) {
755: this .queryHandler.comment(ch, start, len);
756: }
757: }
758:
759: public void recycle() {
760: this .prefixMap.clear();
761: this .queryHandler = null;
762: this .queryWriter = null;
763:
764: try {
765: if (collection != null) {
766: collection.close();
767: }
768: } catch (XMLDBException e) {
769: getLogger().error(
770: "Failed to close collection " + this .local_base
771: + ". Error " + e.errorCode, e);
772: }
773: collection = null;
774: super.recycle();
775: }
776: }
|