Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright 2012-2018 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 java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
* A {@link Function} that caches results per thread.
* <p>
* The Function is thread safe.
* <p>
* Upon first invocation from a certain thread it is guaranteed to invoke the {@link Function}'s {@link Function#apply(Object) apply}
* method to obtain a thread-specific result.
* <p>
* Uses a Map internally to cache values created by the Function. The hashCode of the Function's arguments is used as a key for the map.
* <p>
* Cached values are wrapped in a {@link SoftReference} to allow them to be garbage collected upon low
* memory. This may lead to multiple calls to the {@code delegate}'s {@link Function#apply(Object)} method over the lifetime of a
* certain thread if a previous result was cleared due to low memory.
*
* @param <E> the delegate function's parameter type.
* @param <T> the delegate function's value type.
*/
class CachedInstancePerThreadFunction<E, T> implements Function<E, T> {

private final ThreadLocal<SoftReference<Map<Integer, T>>> cachedInstances = new ThreadLocal<>();

/** The underlying function, invoked to originally retrieve the per-thread result. */
private final Function<E, T> delegate;

CachedInstancePerThreadFunction(final Function<E, T> delegate) {
this.delegate = delegate;

if (null == delegate) {
throw new IllegalArgumentException("Delegate must not be null.");
}
}

/**
* Returns the thread-specific instance, possibly creating a new one if there is none exists.
*
* @param key value which is passed as an argument to the delegating function. Required to be not {@literal null}.
* @return a thread specific instance of {@code <T>}. Never {@literal null}.
*/
@Override
public T apply(final E key) {
Objects.requireNonNull(key);

T result;
final SoftReference<Map<Integer, T>> 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)
Map<Integer, T> cacheMap = (null != cachedInstanceReference) ? cachedInstanceReference.get() : null;

if (null == cacheMap) {
// no map cache instance for the current thread, create a new one ...
cacheMap = new HashMap<>();
result = get(key);
cacheMap.put(key.hashCode(), result);

// ... and retain it for later re-use
cachedInstances.set(new SoftReference<>(cacheMap));
} else {
// cache map already existing
result = cacheMap.get(key.hashCode());

if (null == result) {
// no cached instance found in the threads cache map
// use delegate to create a new one and cache it
result = get(key);
cacheMap.put(key.hashCode(), result);
}
}

return result;
}

/**
* Creates a instance using the provided {@link #delegate}.
*
* @param key key for the delegating function. Expected to be not {@literal null}.
* @return Result of the delegating function. Never {@literal null}.
*/
private T get(final E key) {
final T result = delegate.apply(key);
if (null == result) {
throw new IllegalStateException(String.format("Must not return null from %s::apply() (%s)", delegate.getClass().getName(), delegate));
}

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2012-2018 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 java.lang.ref.SoftReference;
import java.util.function.Supplier;

/**
* A supplier that caches results per thread.
* <p>
* The supplier is thread safe.
* <p>
* 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.
* <p>
* Cached values are wrapped in a {@link 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 <T> the supplier's value type
*/
class CachedInstancePerThreadSupplier<T> implements Supplier<T> {

private final ThreadLocal<SoftReference<T>> cachedInstances = new ThreadLocal<>();

/**
* The underlying supplier, invoked to originally retrieve the per-thread result
*/
private final Supplier<T> delegate;

CachedInstancePerThreadSupplier(Supplier<T> delegate) {
this.delegate = delegate;

if (null == delegate) {
throw new IllegalArgumentException("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 <T>}. Never {@literal null}.
*/
@Override
public T get() {

SoftReference<T> 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 IllegalStateException("Must not return null from " + delegate.getClass().getName()
+ "::get() (" + delegate + ")");
}

// ... and retain it for later re-use
cachedInstances.set(new SoftReference<>(cachedInstance));
}

return cachedInstance;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,30 @@
*/
package com.marklogic.client.impl;

import com.marklogic.client.MarkLogicInternalException;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSParser;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import java.lang.ref.SoftReference;

public final class XmlFactories {

private static final CachedInstancePerThreadSupplier<XMLOutputFactory> cachedOutputFactory =
new CachedInstancePerThreadSupplier<XMLOutputFactory>(new Supplier<XMLOutputFactory>() {
@Override
public XMLOutputFactory get() {
return makeNewOutputFactory();
}
});
new CachedInstancePerThreadSupplier<>(XmlFactories::makeNewOutputFactory);

private static final DocumentBuilderFactory documentBuilderFactory = makeDocumentBuilderFactory();
private static final CachedInstancePerThreadSupplier<DocumentBuilder> cachedDocumentBuilder =
new CachedInstancePerThreadSupplier<>(XmlFactories::makeDocumentBuilder);

private XmlFactories() {} // preventing instances of utility class
private static final CachedInstancePerThreadFunction<DocumentBuilder, LSParser> cachedLsParser =
new CachedInstancePerThreadFunction<>(XmlFactories::makeLSParser);

private XmlFactories() {
} // preventing instances of utility class

/**
* Returns a new {@link XMLOutputFactory}. This factory will have its
Expand All @@ -38,10 +47,8 @@ private XmlFactories() {} // preventing instances of utility class
* 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()}
*
* @return a namespace-repairing {@link XMLOutputFactory}
* @throws FactoryConfigurationError see {@link XMLOutputFactory#newInstance()}
* @see #getOutputFactory()
*/
public static XMLOutputFactory makeNewOutputFactory() {
Expand All @@ -57,94 +64,58 @@ public static XMLOutputFactory makeNewOutputFactory() {
* 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()}
*
* @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();
}

private static DocumentBuilderFactory makeDocumentBuilderFactory() {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);

return factory;
}

private static DocumentBuilder makeDocumentBuilder() {
try {
return documentBuilderFactory.newDocumentBuilder();
} catch (final ParserConfigurationException e) {
throw new MarkLogicInternalException("Failed to create a document builder.", e);
}
}

/**
* Represents a supplier of results.
*
* <p>There is no requirement that a new or distinct result be returned each
* time the supplier is invoked.
* Returns a shared {@link DocumentBuilder}. This DocumentBuilder is created <b>namespace-aware</b> and <b>not validating</b>.
* <p>
* Creating a DocumentBuilder is potentially a pretty expensive operation. Using a shared instance helps to amortize
* this initialization cost via reuse.
*
* @param <T> the type of results supplied by this supplier
* @return a namespace-aware, non-validating {@link DocumentBuilder}
*/
// TODO replace with java.util.function.Supplier<T> after Java 8 migration
interface Supplier<T> {

/**
* Gets a result.
*
* @return a result
*/
T get();
public static DocumentBuilder getNsAwareNotValidatingDocBuilder() {
return cachedDocumentBuilder.get();
}

/**
* A supplier that caches results per thread.
* Returns a shared {@link LSParser}. This LSParser will have {@link DOMImplementationLS#MODE_SYNCHRONOUS} and no {@code schemaType} set.
* <p>
* The supplier is thread safe.
* <p>
* 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.
* <p>
* 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.
* Every first usage per {@code documentBuilder} instance will create a new {@link LSParser}.
* Further usages might return a cached LSParser instance.
*
* @param <T> the supplier's value type
* @param documentBuilder DocumentBuilder from which the LSParser is created.
* @return a synchronous LSParser
*/
private static class CachedInstancePerThreadSupplier<T> implements Supplier<T> {

private final ThreadLocal<SoftReference<T>> cachedInstances = new ThreadLocal<SoftReference<T>>();

/**
* The underlying supplier, invoked to originally retrieve the per-thread result
*/
private final Supplier<T> delegate;

CachedInstancePerThreadSupplier(Supplier<T> delegate) {
this.delegate = delegate;

if (null == delegate) {
throw new IllegalArgumentException("Delegate must not be null");
}
}
public static LSParser getSynchronousLSParser(final DocumentBuilder documentBuilder) {
return cachedLsParser.apply(documentBuilder);
}

/**
* Returns the thread-specific instance, possibly creating a new one if there is none exists.
*
* @return a thread specific instance of {@code <T>}. Never {@literal null}.
*/
@Override
public T get() {

SoftReference<T> 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 IllegalStateException("Must not return null from " + delegate.getClass().getName()
+ "::get() (" + delegate + ")");
}

// ... and retain it for later re-use
cachedInstances.set(new SoftReference<T>(cachedInstance));
}

return cachedInstance;
}
private static LSParser makeLSParser(final DocumentBuilder documentBuilder) {
final DOMImplementationLS domImpl = (DOMImplementationLS) documentBuilder.getDOMImplementation();

return domImpl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
}
}
Loading