diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java b/marklogic-client-api/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java index 25960978d..475ad6fdb 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java @@ -1,10 +1,11 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.query; import com.marklogic.client.MarkLogicIOException; import com.marklogic.client.impl.AbstractQueryDefinition; +import com.marklogic.client.impl.DOMWriter; import com.marklogic.client.impl.RawQueryDefinitionImpl; import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.BaseHandle; @@ -20,13 +21,18 @@ import javax.xml.XMLConstants; import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Templates; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.Calendar; import java.util.Date; import java.util.HashMap; @@ -289,6 +295,35 @@ public RawStructuredQueryDefinition build(StructuredQueryDefinition... queries) ); } + /** + * Defines one or more annotation elements to include in the query. An annotation is a marker + * element that is ignored when the query is evaluated; it can hold arbitrary XML (or plain text) + * and is typically used to document a query or to mark parts of a query so they can be located and + * manipulated later. When the structured query is turned into a + * cts:query by the + * server, each annotation becomes a {@code cts:annotation} element. + *
+ * Each argument becomes the content of a separate {@code annotation} element and may be a well-formed + * XML fragment (including elements in their own namespaces) or plain text. Annotations are valid at + * the top level of a query, so the resulting query definition should be passed to + * {@link #build(StructuredQueryDefinition...)} alongside the other query definitions, for example: + *
{@code
+ * qb.build(
+ * qb.and(qb.term("hello"), qb.term("world")),
+ * qb.annotation("roles-and-rights "));
+ * }
+ * @param annotations the content of one or more annotation elements
+ * @return the StructuredQueryDefinition for the annotations
+ * @see
+ * Composing cts:query Expressions: cts:annotation
+ */
+ public StructuredQueryDefinition annotation(String... annotations) {
+ if (annotations == null) {
+ throw new IllegalArgumentException("annotations cannot be null");
+ }
+ return new AnnotationQuery(annotations);
+ }
+
/**
* Defines an AND query over the list of query definitions.
* @param queries the query definitions
@@ -1272,6 +1307,65 @@ public void innerSerialize(XMLStreamWriter serializer) throws XMLStreamException
}
}
+ protected class AnnotationQuery
+ extends AbstractStructuredQuery {
+ private String[] annotations;
+
+ public AnnotationQuery(String... annotations) {
+ super();
+ this.annotations = annotations;
+ }
+
+ @Override
+ public void innerSerialize(XMLStreamWriter serializer) throws XMLStreamException {
+ if (annotations == null) {
+ return;
+ }
+ for (String annotation: annotations) {
+ writeSearchElement(serializer, "annotation");
+ if (annotation != null) {
+ writeAnnotationContent(serializer, annotation);
+ }
+ serializer.writeEndElement();
+ }
+ }
+
+ // The content of an annotation is arbitrary XML (or plain text). Attempt to parse it as an XML
+ // fragment and copy the resulting nodes into the serializer; if it is not well-formed XML, fall
+ // back to writing it as text so plain-text annotations are still supported.
+ private void writeAnnotationContent(XMLStreamWriter serializer, String annotation)
+ throws XMLStreamException
+ {
+ Document document = parseAnnotationContent(annotation);
+ if (document == null) {
+ serializer.writeCharacters(annotation);
+ return;
+ }
+ new DOMWriter(serializer).serializeNodeList(document.getDocumentElement().getChildNodes());
+ }
+
+ private Document parseAnnotationContent(String annotation) {
+ String wrapped = "