001/* 002 * Copyright 2016 Anyware Services 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 implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.cocoon; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.components.ContextHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.excalibur.xml.sax.ContentHandlerProxy; 034import org.xml.sax.Attributes; 035import org.xml.sax.ContentHandler; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.contenttype.ContentConstants; 039import org.ametys.cms.contenttype.MetadataDefinition; 040import org.ametys.cms.contenttype.MetadataManager; 041import org.ametys.cms.contenttype.MetadataType; 042import org.ametys.cms.contenttype.RepeaterDefinition; 043import org.ametys.cms.repository.Content; 044import org.ametys.cms.search.model.MetadataResultField; 045import org.ametys.cms.search.model.ResultField; 046import org.ametys.cms.search.model.SystemProperty; 047import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 048import org.ametys.plugins.repository.AmetysRepositoryException; 049import org.ametys.plugins.repository.UnknownAmetysObjectException; 050import org.ametys.plugins.repository.metadata.CompositeMetadata; 051import org.ametys.runtime.parameter.ParameterHelper; 052 053/** 054 * Helper to SAX result of a search based on the requested result fields. 055 * 056 */ 057public class ContentResultSetHelper extends MetadataManager 058{ 059 /** Avalon Role. */ 060 @SuppressWarnings("hiding") 061 public static final String ROLE = ContentResultSetHelper.class.getName(); 062 063 private static final String __RESULT_FIELD_PATH_ATTRIBUTE = "fieldPath"; 064 065 private SystemPropertyExtensionPoint _systemPropEP; 066 067 @Override 068 public void service(ServiceManager smanager) throws ServiceException 069 { 070 super.service(smanager); 071 _systemPropEP = (SystemPropertyExtensionPoint) smanager.lookup(SystemPropertyExtensionPoint.ROLE); 072 } 073 074 /** 075 * SAXes the result fields of a content. This method does not check rights. 076 * @param contentHandler the content handler where to SAX into. 077 * @param content the content. 078 * @param fields the result fields. 079 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 080 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 081 * @throws AmetysRepositoryException if an error occurs. 082 * @throws SAXException if an error occurs. 083 * @throws IOException if an error occurs. 084 */ 085 public void saxResultFields(ContentHandler contentHandler, Content content, Collection<? extends ResultField> fields, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException 086 { 087 SearchResultFieldSet resultSet = buildResultSet(fields, content); 088 _saxResultSet(contentHandler, content, content.getMetadataHolder(), resultSet, null, defaultLocale, "", false); 089 } 090 091 /** 092 * SAXes the result fields of a content. This method returns only readable fields (user rights are checked) 093 * @param contentHandler the content handler where to SAX into. 094 * @param content the content. 095 * @param fields the result fields. 096 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 097 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 098 * @throws AmetysRepositoryException if an error occurs. 099 * @throws SAXException if an error occurs. 100 * @throws IOException if an error occurs. 101 */ 102 public void saxReadableResultFields(ContentHandler contentHandler, Content content, Collection<? extends ResultField> fields, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException 103 { 104 SearchResultFieldSet resultSet = buildResultSet(fields, content); 105 _saxResultSet(contentHandler, content, content.getMetadataHolder(), resultSet, null, defaultLocale, "", true); 106 } 107 108 /** 109 * SAXes a content result set. This method does not check rights. 110 * @param contentHandler the content handler where to SAX into. 111 * @param content the content. 112 * @param resultSet the set of result fields to SAX 113 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null. 114 * @throws AmetysRepositoryException if an error occurs. 115 * @throws SAXException if an error occurs. 116 * @throws IOException if an error occurs. 117 */ 118 public void saxResultSet(ContentHandler contentHandler, Content content, SearchResultFieldSet resultSet, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException 119 { 120 _saxResultSet(contentHandler, content, content.getMetadataHolder(), resultSet, null, defaultLocale, "", false); 121 } 122 123 /** 124 * SAX a content metadata set that are readable. 125 * @param contentHandler the content handler where to SAX into. 126 * @param content the content. 127 * @param resultSet the set of result fields to SAX 128 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 129 * Only to be valued if initial content's language is null, otherwise set this parameter to null. 130 * @throws AmetysRepositoryException if an error occurs. 131 * @throws SAXException if an error occurs. 132 * @throws IOException if an error occurs. 133 */ 134 public void saxReadableResultSet (ContentHandler contentHandler, Content content, SearchResultFieldSet resultSet, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException 135 { 136 _saxResultSet(contentHandler, content, content.getMetadataHolder(), resultSet, null, defaultLocale, "", true); 137 } 138 139 private void _saxResultSet(ContentHandler contentHandler, Content content, CompositeMetadata metadata, SearchResultFieldSet resultSetElement, MetadataDefinition parentMetadataDef, Locale defaultLocale, String prefix, boolean checkRead) throws AmetysRepositoryException, SAXException, IOException 140 { 141 if (resultSetElement instanceof SearchResultFieldSetElement) 142 { 143 String elmtId = ((SearchResultFieldSetElement) resultSetElement).getName(); 144 145 MetadataDefinition metadataDef = _getMetadataDefinition(elmtId, parentMetadataDef, content); 146 147 if (metadataDef != null) 148 { 149 _saxMetadata(contentHandler, content, metadata, resultSetElement, metadataDef, defaultLocale, prefix, checkRead); 150 } 151 else if (_systemPropEP.hasExtension(elmtId)) 152 { 153 ResultFieldContentHandler wrappedHandler = new ResultFieldContentHandler(contentHandler, ((SearchResultFieldSetElement) resultSetElement).getPath()); 154 saxSystemProperty(wrappedHandler, elmtId, content); 155 } 156 else 157 { 158 // TODO Custom field 159 } 160 } 161 else 162 { 163 for (SearchResultFieldSetElement subElement : resultSetElement.getElements()) 164 { 165 _saxResultSet(contentHandler, content, metadata, subElement, parentMetadataDef, defaultLocale, prefix, checkRead); 166 } 167 } 168 } 169 170 /** 171 * SAX a metadata 172 * @param contentHandler The content handler where to SAX into. 173 * @param content The content 174 * @param metadata The parent composite metadata. 175 * @param resultSet the set of remaining result fields to SAX 176 * @param metadataDefinition The metadata definition 177 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null. 178 * @param prefix the metadata path prefix. 179 * @param checkRead true if need to check read right 180 * @throws AmetysRepositoryException if an error occurred 181 * @throws SAXException if an error occurred while SAXing 182 * @throws IOException if an error occurred 183 */ 184 protected void _saxMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, SearchResultFieldSet resultSet, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead) throws AmetysRepositoryException, SAXException, IOException 185 { 186 String metadataName = metadataDefinition.getName(); 187 188 ContentHandler wrappedHandler = resultSet instanceof SearchResultFieldSetElement ? new ResultFieldContentHandler(contentHandler, ((SearchResultFieldSetElement) resultSet).getPath()) : contentHandler; 189 190 if (metadata.hasMetadata(metadataName)) 191 { 192 MetadataType type = metadataDefinition.getType(); 193 switch (type) 194 { 195 case COMPOSITE: 196 _saxCompositeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, resultSet, defaultLocale, prefix, checkRead); 197 break; 198 case BINARY: 199 _saxBinaryMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 200 break; 201 202 case FILE: 203 _saxFileMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 204 break; 205 206 case RICH_TEXT: 207 _saxRichTextMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 208 break; 209 210 case DATE: 211 case DATETIME: 212 _saxSingleDateMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead); 213 break; 214 215 case USER: 216 _saxUserMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 217 break; 218 219 case CONTENT: 220 _saxContentReferenceMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, resultSet, defaultLocale, prefix, checkRead); 221 break; 222 223 case SUB_CONTENT: 224 // TODO To remove ? 225 break; 226 227 case GEOCODE: 228 _saxGeocodeMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 229 break; 230 231 case REFERENCE: 232 _saxReferenceMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, false); 233 break; 234 235 case MULTILINGUAL_STRING: 236 _saxMultilingualStringMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, defaultLocale, prefix, checkRead, false); 237 break; 238 239 default: 240 _saxStringMetadata(wrappedHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, metadataDefinition.getEnumerator()); 241 break; 242 } 243 } 244 } 245 246 /** 247 * SAX a composite metadata. 248 * @param contentHandler the content handler to SAX into. 249 * @param content the content. 250 * @param metadata the parent metadata holder. 251 * @param metadataDefinition the metadata definition. 252 * @param metadataName The name of the metadata to sax 253 * @param resultSet the set of result fields to SAX for this composite 254 * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null. 255 * @param prefix the metadata path prefix. 256 * @param checkRead true if need to check read right. 257 * @throws SAXException if an error occurs. 258 * @throws IOException if an error occurs. 259 */ 260 protected void _saxCompositeMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDefinition, String metadataName, SearchResultFieldSet resultSet, Locale defaultLocale, String prefix, boolean checkRead) throws SAXException, IOException 261 { 262 CompositeMetadata subMetadata = metadata.getCompositeMetadata(metadataName); 263 264 AttributesImpl attrs = new AttributesImpl(); 265 if (metadataDefinition instanceof RepeaterDefinition) 266 { 267 attrs.addCDATAAttribute("entryCount", Integer.toString(subMetadata.getMetadataNames().length)); 268 } 269 270 XMLUtils.startElement(contentHandler, metadataName, attrs); 271 272 if (metadataDefinition instanceof RepeaterDefinition) 273 { 274 String[] subMetadataNames = subMetadata.getMetadataNames(); 275 Arrays.sort(subMetadataNames, MetadataManager.REPEATER_ENTRY_COMPARATOR); 276 277 for (String entryName : subMetadataNames) 278 { 279 org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType entryType = subMetadata.getType(entryName); 280 281 if (entryType != org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.COMPOSITE) 282 { 283 throw new AmetysRepositoryException("Invalid type: " + entryType + " for metadata: " + metadataName + " and entry of name: " + entryName); 284 } 285 286 CompositeMetadata entry = subMetadata.getCompositeMetadata(entryName); 287 288 AttributesImpl entryAttrs = new AttributesImpl(); 289 entryAttrs.addCDATAAttribute("name", entryName); 290 XMLUtils.startElement(contentHandler, "entry", entryAttrs); 291 292 for (SearchResultFieldSetElement subElement : resultSet.getElements()) 293 { 294 // SAX all result fields contains in the current SearchResultFieldSet 295 _saxResultSet(contentHandler, content, entry, subElement, metadataDefinition, defaultLocale, prefix + metadataName + "/" + entryName + "/", checkRead); 296 } 297 298 XMLUtils.endElement(contentHandler, "entry"); 299 } 300 } 301 else 302 { 303 // SAX all result fields contains in the current SearchResultFieldSet 304 for (SearchResultFieldSetElement subElement : resultSet.getElements()) 305 { 306 _saxResultSet(contentHandler, content, subMetadata, subElement, metadataDefinition, defaultLocale, prefix + metadataName + "/", checkRead); 307 } 308 } 309 310 XMLUtils.endElement(contentHandler, metadataName); 311 } 312 313 /** 314 * SAX a "content" metadata. 315 * @param contentHandler the content handler to SAX into. 316 * @param content The currently saxed content. 317 * @param metadatas the parent composite metadata. 318 * @param metadataDefinition the metadata definition. 319 * @param metadataName the metadata name. 320 * @param resultSet the set of result fields to SAX for the referenced contents 321 * @param defaultLocale The locale to use to resolve localized values of referenced content. Only use if initial content's language is not null. 322 * @param prefix the metadata path prefix. 323 * @param checkRead true if need to check read right. 324 * @throws SAXException if an error occurs. 325 * @throws IOException if an error occurs. 326 */ 327 protected void _saxContentReferenceMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, SearchResultFieldSet resultSet, Locale defaultLocale, String prefix, boolean checkRead) throws SAXException, IOException 328 { 329 if (!checkRead || _canRead(content, metadataDefinition)) 330 { 331 Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale; 332 String[] values = metadatas.getStringArray(metadataName); 333 334 for (String value : values) 335 { 336 Request request = ContextHelper.getRequest(_context); 337 338 Object oldContentAttribute = request.getAttribute(Content.class.getName()); 339 340 try 341 { 342 request.setAttribute(Content.class.getName(), content); 343 344 Content refContent = _resolver.resolveById(value); 345 _saxContent(contentHandler, refContent, locale, metadataName, resultSet, prefix, checkRead); 346 } 347 catch (UnknownAmetysObjectException e) 348 { 349 if (getLogger().isInfoEnabled()) 350 { 351 getLogger().info("The content of ID '" + content.getId() + "' references a non-existing content in the metadata '" + metadataDefinition.getId() + "'", e); 352 } 353 } 354 finally 355 { 356 request.setAttribute(Content.class.getName(), oldContentAttribute); 357 } 358 } 359 } 360 } 361 362 /** 363 * SAX a content (referenced or sub-content) in view mode. 364 * @param contentHandler the content handler to SAX into. 365 * @param content The referenced or sub-content to SAX. 366 * @param defaultLocale The locale to use to sax the referenced content's localized values. Only use if initial content 's language is null. 367 * @param metadataName the metadata name. 368 * @param resultSet the set of result fields to SAX for this content 369 * @param prefix the metadata path prefix. 370 * @param checkRead true if need to check read right. 371 * @throws SAXException if an error occurs. 372 * @throws IOException if an error occurs. 373 */ 374 protected void _saxContent(ContentHandler contentHandler, Content content, Locale defaultLocale, String metadataName, SearchResultFieldSet resultSet, String prefix, boolean checkRead) throws SAXException, IOException 375 { 376 AttributesImpl attrs = new AttributesImpl(); 377 attrs.addCDATAAttribute("id", content.getId()); 378 attrs.addCDATAAttribute("name", content.getName()); 379 attrs.addCDATAAttribute("title", content.getTitle(defaultLocale)); 380 if (content.getLanguage() != null) 381 { 382 attrs.addCDATAAttribute("language", content.getLanguage()); 383 } 384 attrs.addCDATAAttribute("createdAt", ParameterHelper.valueToString(content.getCreationDate())); 385 attrs.addCDATAAttribute("creator", content.getCreator().getLogin()); 386 attrs.addCDATAAttribute("lastModifiedAt", ParameterHelper.valueToString(content.getLastModified())); 387 388 if (resultSet instanceof SearchResultFieldSetElement) 389 { 390 attrs.addCDATAAttribute(__RESULT_FIELD_PATH_ATTRIBUTE, ((SearchResultFieldSetElement) resultSet).getPath()); 391 } 392 XMLUtils.startElement(contentHandler, metadataName, attrs); 393 394 if (resultSet != null && resultSet.getElements().size() > 0) 395 { 396 SearchResultFieldSet refResultSet = new SearchResultFieldSet(); 397 refResultSet.addElements(resultSet.getElements()); 398 399 Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale; 400 _saxResultSet(contentHandler, content, content.getMetadataHolder(), refResultSet, null, locale, prefix, checkRead); 401 } 402 403 XMLUtils.endElement(contentHandler, metadataName); 404 } 405 406 /** 407 * SAX a system property 408 * @param contentHandler the content handler to SAX into. 409 * @param systemPropertyId The id of system property 410 * @param content The content 411 * @throws SAXException if an error occurs. 412 */ 413 public void saxSystemProperty(ContentHandler contentHandler, String systemPropertyId, Content content) throws SAXException 414 { 415 SystemProperty systemProp = _systemPropEP.getExtension(systemPropertyId); 416 if (systemProp != null) 417 { 418 systemProp.saxValue(contentHandler, content); 419 } 420 } 421 422 /** 423 * Build a result set from result fields 424 * @param fields The result fields 425 * @param content The result content 426 * @return The result set 427 */ 428 public SearchResultFieldSet buildResultSet (Collection<? extends ResultField> fields, Content content) 429 { 430 SearchResultFieldSet resultSet = new SearchResultFieldSet(); 431 432 SearchResultFieldSet resultSetElmt = resultSet; 433 for (ResultField field : fields) 434 { 435 String fieldPath = field instanceof MetadataResultField ? ((MetadataResultField) field).getFieldPath() : field.getId(); 436 String[] pathSegments = fieldPath.split(ContentConstants.METADATA_PATH_SEPARATOR); 437 438 MetadataDefinition metadataDef = null; 439 440 String currentFieldPath = null; 441 for (String pathSegment : pathSegments) 442 { 443 currentFieldPath = currentFieldPath == null ? pathSegment : currentFieldPath + ContentConstants.METADATA_PATH_SEPARATOR + pathSegment; 444 metadataDef = _getMetadataDefinition(pathSegment, metadataDef, content); 445 446 if (metadataDef != null) 447 { 448 SearchResultFieldSetElement element = resultSetElmt.getElement(pathSegment); 449 if (element == null) 450 { 451 element = new SearchResultFieldSetElement(pathSegment, currentFieldPath); 452 } 453 454 // This part of the path represents a metadata field, continue 455 resultSetElmt.addElement(element); 456 resultSetElmt = element; 457 } 458 else 459 { 460 resultSetElmt.addElement(new SearchResultFieldSetElement(pathSegment, currentFieldPath)); 461 // This part of the path represents a system property field or a custom field 462 // Stop iteration : a system property can be only at end of the path 463 break; 464 } 465 } 466 467 resultSetElmt = resultSet; 468 } 469 470 return resultSet; 471 } 472 473 private MetadataDefinition _getMetadataDefinition(String metadataName, MetadataDefinition parentMetadataDef, Content content) 474 { 475 if (parentMetadataDef == null) 476 { 477 return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 478 } 479 480 if (parentMetadataDef.getType().equals(MetadataType.CONTENT)) 481 { 482 return _contentTypesHelper.getMetadataDefinition(metadataName, new String[] {parentMetadataDef.getContentType()}, org.apache.commons.lang3.ArrayUtils.EMPTY_STRING_ARRAY); 483 } 484 485 return parentMetadataDef.getMetadataDefinition(metadataName); 486 } 487 488 489 /** 490 * Inner class representing a element of a {@link SearchResultFieldSet} 491 * This can contains sub elements. 492 * 493 */ 494 public class SearchResultFieldSetElement extends SearchResultFieldSet 495 { 496 private String _name; 497 private String _fieldPath; 498 499 /** 500 * Creates a new element representing a result field 501 * @param name The unique name of result field into its parent set 502 * @param fieldPath The path of result field into its parent set 503 */ 504 public SearchResultFieldSetElement(String name, String fieldPath) 505 { 506 super(); 507 _name = name; 508 _fieldPath = fieldPath; 509 } 510 511 /** 512 * Get the name of this element 513 * @return The name 514 */ 515 public String getName() 516 { 517 return _name; 518 } 519 520 /** 521 * Get the path of this field into its parent set 522 * @return the path 523 */ 524 public String getPath() 525 { 526 return _fieldPath; 527 } 528 } 529 530 /** 531 * Inner class representing a set of result fields. 532 * 533 */ 534 public class SearchResultFieldSet 535 { 536 private Map<String, SearchResultFieldSetElement> _elements = new LinkedHashMap<>(); 537 538 /** 539 * Creates a set of a result field 540 */ 541 public SearchResultFieldSet() 542 { 543 // Nothing 544 } 545 546 /** 547 * Add a new element to this set 548 * @param element The element to add 549 * @return true if the element was added, false otherwise 550 */ 551 public boolean addElement(SearchResultFieldSetElement element) 552 { 553 String id = element.getName(); 554 555 if (_elements.containsKey(id)) 556 { 557 // Already existing metadata definition reference 558 return false; 559 } 560 561 _elements.put(id, element); 562 563 return true; 564 } 565 566 /** 567 * Add a list of elements to this set 568 * @param elements The elements to add 569 */ 570 public void addElements(List<SearchResultFieldSetElement> elements) 571 { 572 for (SearchResultFieldSetElement elmt : elements) 573 { 574 addElement(elmt); 575 } 576 } 577 578 /** 579 * Get a element contains into this set by its name 580 * @param name The name of the element 581 * @return the element or <code>null</code> if not found 582 */ 583 public SearchResultFieldSetElement getElement (String name) 584 { 585 return _elements.get(name); 586 } 587 588 /** 589 * Determines if this set contains the given element 590 * @param name The name of the element 591 * @return <code>true</code> if exists 592 */ 593 public boolean hasElement (String name) 594 { 595 return _elements.containsKey(name); 596 } 597 598 /** 599 * Returns the elements of this set 600 * @return the element 601 */ 602 public List<SearchResultFieldSetElement> getElements() 603 { 604 return new ArrayList<>(_elements.values()); 605 } 606 } 607 608 class ResultFieldContentHandler extends ContentHandlerProxy 609 { 610 /** Used to add fieldPath only on top level element */ 611 private int _depth; 612 private String _resultFieldPath; 613 614 ResultFieldContentHandler(ContentHandler wrappedHandler, String fieldPath) 615 { 616 super(wrappedHandler); 617 _resultFieldPath = fieldPath; 618 _depth = 0; 619 } 620 621 @Override 622 public void startElement(String uri, String loc, String raw, Attributes a) throws SAXException 623 { 624 if (_depth == 0) 625 { 626 AttributesImpl atts = new AttributesImpl(a); 627 atts.addCDATAAttribute(__RESULT_FIELD_PATH_ATTRIBUTE, _resultFieldPath); 628 super.startElement(uri, loc, raw, atts); 629 } 630 else 631 { 632 super.startElement(uri, loc, raw, a); 633 } 634 _depth++; 635 } 636 637 @Override 638 public void endElement(String uri, String loc, String raw) throws SAXException 639 { 640 super.endElement(uri, loc, raw); 641 _depth--; 642 } 643 } 644}