From 0acfebe7b16eee76efc516b9c7e629080b5f1da5 Mon Sep 17 00:00:00 2001 From: Robert Sauer Date: Mon, 24 Oct 2016 08:59:17 +0200 Subject: [PATCH 1/2] Caching Utilities' XMLOutputFactory to improve performance Intruducing a new XmlFactories class as a suggested place to conveniently retrieve properly configured XMLFactories. Provides methods to access shared instances or to retrieve a freshly created instance (if really needed). --- .../com/marklogic/client/impl/Utilities.java | 3 +- .../marklogic/client/impl/XmlFactories.java | 152 ++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/marklogic/client/impl/XmlFactories.java diff --git a/src/main/java/com/marklogic/client/impl/Utilities.java b/src/main/java/com/marklogic/client/impl/Utilities.java index 327d15e04..6f73d4c42 100644 --- a/src/main/java/com/marklogic/client/impl/Utilities.java +++ b/src/main/java/com/marklogic/client/impl/Utilities.java @@ -334,8 +334,7 @@ public static boolean writeEvents(List events, OutputStream out) { } try { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty("javax.xml.stream.isRepairingNamespaces", true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); XMLEventWriter eventWriter = factory.createXMLEventWriter(out, "UTF-8"); diff --git a/src/main/java/com/marklogic/client/impl/XmlFactories.java b/src/main/java/com/marklogic/client/impl/XmlFactories.java new file mode 100644 index 000000000..9f83fcaa5 --- /dev/null +++ b/src/main/java/com/marklogic/client/impl/XmlFactories.java @@ -0,0 +1,152 @@ +/* + * Copyright 2012-2016 MarkLogic Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.marklogic.client.impl; + +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLOutputFactory; +import java.lang.ref.SoftReference; + +public final class XmlFactories { + + private static final CachedInstancePerThreadSupplier cachedOutputFactory = + new CachedInstancePerThreadSupplier(new Supplier() { + @Override + public XMLOutputFactory get() { + return makeNewOutputFactory(); + } + }); + + private XmlFactories() { + // preventing instances of utility class + } + + /** + * Returns a new {@link XMLOutputFactory}. This factory will have its + * {@link XMLOutputFactory#IS_REPAIRING_NAMESPACES} property set to {@code true}. + *

+ * CAUTION: Creating XML factories is potentially a pretty expensive operation. If possible, consider using a shared + * instance ({@link #getOutputFactory()}) to amortize this initialization cost via reuse. + * + * @return a namespace-repairing {@link XMLOutputFactory} + * + * @throws FactoryConfigurationError see {@link XMLOutputFactory#newInstance()} + * + * @see #getOutputFactory() + */ + public static XMLOutputFactory makeNewOutputFactory() { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + return factory; + } + + /** + * Returns a shared {@link XMLOutputFactory}. This factory will have its + * {@link XMLOutputFactory#IS_REPAIRING_NAMESPACES} property set to {@code true}. + *

+ * Creating XML factories is potentially a pretty expensive operation. Using a shared instance helps to amortize + * this initialization cost via reuse. + * + * @return a namespace-repairing {@link XMLOutputFactory} + * + * @throws FactoryConfigurationError see {@link XMLOutputFactory#newInstance()} + * + * @see #makeNewOutputFactory() if you really (really?) need an non-shared instance + */ + public static XMLOutputFactory getOutputFactory() { + return cachedOutputFactory.get(); + } + + /** + * Represents a supplier of results. + * + *

There is no requirement that a new or distinct result be returned each + * time the supplier is invoked. + * + * @param the type of results supplied by this supplier + */ + // TODO replace with java.util.function.Supplier after Java 8 migration + interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); + } + + /** + * A supplier that caches results per thread. + *

+ * The supplier is thread safe. + *

+ * Upon first invocation from a certain thread it is guaranteed to invoke the {@code supplier}'s {@code get()} + * method to obtain a thread-specific result. + *

+ * Cached values are wrapped in a {@link java.lang.ref.SoftReference} to allow them to be garbage collected upon low + * memory. This may lead to multiple calls to the {@code delegate}'s {@code get()} method over the lifetime of a + * certain thread if a previous result was cleared due to low memory. + * + * @param the supplier's value type + */ + private static class CachedInstancePerThreadSupplier implements Supplier { + + private final ThreadLocal> cachedInstances = new ThreadLocal>(); + + /** + * The underlying supplier, invoked to originally retrieve the per-thread result + */ + private final Supplier delegate; + + CachedInstancePerThreadSupplier(Supplier delegate) { + this.delegate = delegate; + + if (null == delegate) { + throw new NullPointerException("Delegate must not be null"); + } + } + + /** + * Returns the thread-specific instance, possibly creating a new one if there is none exists. + * + * @return a thread specific instance of {@code }. Never {@literal null}. + */ + @Override + public T get() { + + SoftReference cachedInstanceReference = cachedInstances.get(); + + // careful, either the reference itself may be null (upon first access from a thread), or the referred-to + // instance may be null (after a GC run that cleared it out) + T cachedInstance = (null != cachedInstanceReference) ? cachedInstanceReference.get() : null; + + if (null == cachedInstance) { + // no instance for the current thread, create a new one ... + cachedInstance = delegate.get(); + if (null == cachedInstance) { + throw new NullPointerException("Must not return null from " + delegate.getClass().getName() + + "::get() (" + delegate + ")"); + } + + // ... and retain it for later re-use + cachedInstances.set(new SoftReference(cachedInstance)); + } + + return cachedInstance; + } + + } +} From 7b3b1b21ecadcdb5f13a1d139847e104b42ad412 Mon Sep 17 00:00:00 2001 From: Robert Sauer Date: Mon, 12 Sep 2016 11:08:47 +0200 Subject: [PATCH 2/2] Replace remaining invocations of XMLOutputFactory::newInsatnce with XmlFactories::getOutputFactory --- .../com/marklogic/client/impl/CombinedQueryBuilderImpl.java | 3 +-- .../client/impl/DocumentMetadataPatchBuilderImpl.java | 3 +-- .../java/com/marklogic/client/io/DocumentMetadataHandle.java | 4 ++-- .../com/marklogic/client/query/StructuredQueryBuilder.java | 4 ++-- .../com/marklogic/client/test/CombinedQueryBuilderTest.java | 4 ++-- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/marklogic/client/impl/CombinedQueryBuilderImpl.java b/src/main/java/com/marklogic/client/impl/CombinedQueryBuilderImpl.java index 9e0c80d7f..0860febd5 100644 --- a/src/main/java/com/marklogic/client/impl/CombinedQueryBuilderImpl.java +++ b/src/main/java/com/marklogic/client/impl/CombinedQueryBuilderImpl.java @@ -239,8 +239,7 @@ private String makeJSONCombinedQuery(CombinedQueryDefinitionImpl qdef) { } private XMLStreamWriter makeXMLSerializer(OutputStream out) { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); try { XMLStreamWriter serializer = factory.createXMLStreamWriter(out, "UTF-8"); diff --git a/src/main/java/com/marklogic/client/impl/DocumentMetadataPatchBuilderImpl.java b/src/main/java/com/marklogic/client/impl/DocumentMetadataPatchBuilderImpl.java index 23486ceb7..b9c5c25bf 100644 --- a/src/main/java/com/marklogic/client/impl/DocumentMetadataPatchBuilderImpl.java +++ b/src/main/java/com/marklogic/client/impl/DocumentMetadataPatchBuilderImpl.java @@ -1053,8 +1053,7 @@ public PatchHandle build() throws MarkLogicIOException { } else { handle.setFormat(Format.XML); try { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty("javax.xml.stream.isRepairingNamespaces", true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); StringWriter writer = new StringWriter(); XMLStreamWriter serializer = factory.createXMLStreamWriter(writer); diff --git a/src/main/java/com/marklogic/client/io/DocumentMetadataHandle.java b/src/main/java/com/marklogic/client/io/DocumentMetadataHandle.java index d271660ce..4740fafd0 100644 --- a/src/main/java/com/marklogic/client/io/DocumentMetadataHandle.java +++ b/src/main/java/com/marklogic/client/io/DocumentMetadataHandle.java @@ -56,6 +56,7 @@ import com.marklogic.client.impl.ClientPropertiesImpl; import com.marklogic.client.impl.DOMWriter; import com.marklogic.client.impl.ValueConverter; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.marker.BufferableHandle; import com.marklogic.client.io.marker.DocumentMetadataReadHandle; import com.marklogic.client.io.marker.DocumentMetadataWriteHandle; @@ -656,8 +657,7 @@ private void receiveQualityImpl(Document document) { // TODO: select the metadata sent private void sendMetadataImpl(OutputStream out) { try { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty("javax.xml.stream.isRepairingNamespaces", true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); valueSerializer = null; diff --git a/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java b/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java index 21cfc3109..d3a6ed682 100644 --- a/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java +++ b/src/main/java/com/marklogic/client/query/StructuredQueryBuilder.java @@ -41,6 +41,7 @@ import com.marklogic.client.MarkLogicIOException; import com.marklogic.client.impl.AbstractQueryDefinition; import com.marklogic.client.impl.RawQueryDefinitionImpl; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.BaseHandle; import com.marklogic.client.io.Format; import com.marklogic.client.io.OutputStreamSender; @@ -2443,8 +2444,7 @@ private void checkRegion(Region region) { } static private XMLStreamWriter makeSerializer(OutputStream out) { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); try { XMLStreamWriter serializer = factory.createXMLStreamWriter(out, "UTF-8"); diff --git a/src/test/java/com/marklogic/client/test/CombinedQueryBuilderTest.java b/src/test/java/com/marklogic/client/test/CombinedQueryBuilderTest.java index 257d9da70..169965e91 100644 --- a/src/test/java/com/marklogic/client/test/CombinedQueryBuilderTest.java +++ b/src/test/java/com/marklogic/client/test/CombinedQueryBuilderTest.java @@ -33,6 +33,7 @@ import com.marklogic.client.impl.CombinedQueryBuilderImpl; import com.marklogic.client.impl.CombinedQueryDefinition; +import com.marklogic.client.impl.XmlFactories; import com.marklogic.client.io.Format; import com.marklogic.client.io.StringHandle; import com.marklogic.client.io.marker.QueryOptionsWriteHandle; @@ -124,8 +125,7 @@ public void writeOptions(XMLStreamWriter writer) throws XMLStreamException { } public XMLStreamWriter makeXMLStreamWriter(OutputStream out) throws XMLStreamException { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + XMLOutputFactory factory = XmlFactories.getOutputFactory(); XMLStreamWriter writer = factory.createXMLStreamWriter(out, "UTF-8"); writer.setDefaultNamespace("http://marklogic.com/appservices/search");