com.marklogic.xcc.impl.SessionImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of marklogic-xcc Show documentation
Show all versions of marklogic-xcc Show documentation
MarkLogic XML Contentbase Connector for Java (XCC/J)
/*
* Copyright (c) 2020 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.xcc.impl;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.HttpCookie;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.io.IOException;
import javax.transaction.xa.XAResource;
import com.marklogic.xcc.AdhocQuery;
import com.marklogic.xcc.Content;
import com.marklogic.xcc.ContentSource;
import com.marklogic.xcc.ContentbaseMetaData;
import com.marklogic.xcc.ModuleInvoke;
import com.marklogic.xcc.ModuleSpawn;
import com.marklogic.xcc.Request;
import com.marklogic.xcc.RequestOptions;
import com.marklogic.xcc.ResultItem;
import com.marklogic.xcc.ResultSequence;
import com.marklogic.xcc.Session;
import com.marklogic.xcc.UserCredentials;
import com.marklogic.xcc.Version;
import com.marklogic.xcc.exceptions.RequestException;
import com.marklogic.xcc.exceptions.StreamingResultException;
import com.marklogic.xcc.exceptions.XQueryException;
import com.marklogic.xcc.impl.handlers.ContentInsertController;
import com.marklogic.xcc.impl.handlers.EvalRequestController;
import com.marklogic.xcc.spi.ConnectionProvider;
import com.marklogic.xcc.spi.SingleHostAddress;
import com.marklogic.xcc.types.XSDecimal;
import com.marklogic.xcc.types.XSInteger;
import com.marklogic.http.HttpChannel;
@SuppressWarnings("deprecation")
public class SessionImpl implements Session {
private final Set activeResultSeqs;
private final ContentSource contentSource;
private final ConnectionProvider provider;
private final UserCredentials credentials;
private final String contentBase;
private XAResourceImpl xaResource = null;
private Logger logger = null;
private RequestOptions defaultOptions = new RequestOptions();
String sessionID = null;
String txnID = null;
private TransactionMode txnMode = null;
private boolean commitMode = false;
private TransactionMode userSpecifiedTxnMode = null;
private int timeout = 0;
private boolean closed = false;
boolean inXATxn = false;
private Object userObject = null;
private String serverVersion = null;
private Throwable created = new Throwable();
private boolean compatibleTxnMode =
System.getProperty("xcc.txn.compatible", "false").equals("true");
private Boolean txnIncompatible = null;
private boolean txnModeChanged = false;
private final static boolean envCompactSequencesEnabled =
System.getProperty("xcc.compact.sequences", "true").equals("true");
private boolean compactSequencesEnabled = envCompactSequencesEnabled;
private static final String agentString = "Java/" + System.getProperty("java.version") + " MarkLogicXCC/"
+ Version.getVersionMajor() + "." + Version.getVersionMinor() + "-" + Version.getVersionPatch();
private Map cookieJar = null;
private StringBuilder requestCookies = null;
public boolean isCompactSequencesEnabled() {
return compactSequencesEnabled;
}
public void setCompactSequencesEnabled(boolean compactSequencesEnabled) {
this.compactSequencesEnabled = compactSequencesEnabled;
}
public SessionImpl(ContentSource contentSource, ConnectionProvider connectionProvider, UserCredentials credentials,
String contentBase) {
this.contentSource = contentSource;
this.provider = connectionProvider;
this.credentials = credentials;
this.contentBase = contentBase;
activeResultSeqs = Collections.synchronizedSet(new HashSet());
}
public SessionImpl clone()
{
return new SessionImpl(contentSource,provider,credentials,contentBase);
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getServerVersion() {
return serverVersion;
}
// -------------------------------------------------------------
// Session interface
public UserCredentials getUserCredentials() {
return credentials;
}
public String getContentBaseName() {
return contentBase;
}
public ContentSource getContentSource() {
return (contentSource);
}
public XAResource getXAResource() {
if(xaResource == null)
xaResource = new XAResourceImpl(this);
return xaResource;
}
public void setTransactionMode(TransactionMode mode) {
if (getTxnID() != null) {
throwIllegalState("Cannot call setTransactionMode() when there is an active transaction");
}
if (commitMode) {
throwIllegalState("Cannot call setTransactionMode() in combination with setAutoCommit() or setUpdate().");
}
txnMode = mode;
txnModeChanged = true;
}
public TransactionMode getTransactionMode() {
return txnMode;
}
public boolean isAutoCommit() {
return txnMode == null ? true : txnMode.isAutoCommit();
}
public void setAutoCommit(boolean autoCommit) {
commitMode = true;
if (txnMode == null) {
txnMode = autoCommit ?
TransactionMode.AUTO : TransactionMode.MULTI_AUTO;
return;
}
if (autoCommit == txnMode.isAutoCommit()) {
return;
}
if (getTxnID() != null) {
throwIllegalState(
"Cannot change transaction's autoCommit when there is an active transaction");
}
txnMode = txnMode.setAutoCommit(autoCommit);
}
public void setTransactionTimeout(int seconds) throws RequestException {
if(getTxnID() != null) {
submitRequestInternal(new AdhocImpl(this,
"xquery version '1.0-ml';\n" +
"xdmp:set-transaction-time-limit(" + seconds + ")", null)).close();
}
timeout = seconds;
}
public int getTransactionTimeout() throws RequestException {
if(getTxnID() != null) {
ResultSequence rs = submitRequestInternal(new AdhocImpl(this,
"xquery version '1.0-ml';\n" +
"xdmp:host-status(xdmp:host())//*:transaction[*:transaction-id eq xdmp:transaction()]" +
"/*:time-limit/string()", null));
try {
timeout = Integer.parseInt(rs.next().asString());
} finally {
rs.close();
}
}
return timeout;
}
public int getCachedTransactionTimeout() {
return timeout;
}
public boolean commit() throws RequestException {
assertSessionOpen();
if (getTxnID() != null) {
submitRequestInternal(new AdhocImpl(this,
"xquery version '1.0-ml'; xdmp:commit()", null)).close();
return true;
} else {
return false;
}
}
public void rollback() throws RequestException {
assertSessionOpen();
if(getTxnID() != null)
submitRequestInternal(new AdhocImpl(this, "xquery version '1.0-ml'; xdmp:rollback()", null)).close();
}
public void close() {
if (closed) return;
boolean doRollback = false;
synchronized(this) {
doRollback = !inXATxn && txnID != null;
}
if (doRollback) {
try {
rollback();
} catch(XQueryException e) {
// Ignore XDMP-NOTXN
if(!e.equals("XDMP-NOTXN"))
getLogger().log(Level.INFO, "Exception rolling back during Session.close()", e);
} catch(Exception e) {
getLogger().log(Level.INFO, "Exception rolling back during Session.close()", e);
}
}
closed = true;
synchronized (activeResultSeqs) {
for (Iterator it = activeResultSeqs.iterator(); it.hasNext();) {
StreamingResultSequence rs = it.next();
it.remove();
try {
rs.close();
} catch (StreamingResultException e) {
getLogger().log(Level.WARNING, "Exception closing streaming result sequence.", e);
// carry on
}
}
}
}
public boolean isClosed() {
return closed;
}
@Override
public void finalize() {
try {
if(!closed) {
if(getTxnID() != null)
getLogger().log(Level.SEVERE,
"Destructing Session object with open transaction " +
getTxnID() + ": ", created);
close();
}
} catch(Throwable t) {
getLogger().log(Level.SEVERE, "Exception during SessionImpl.finalize()", t);
}
}
public ModuleInvoke newModuleInvoke(String moduleUri, RequestOptions options) {
assertSessionOpen();
return new ModuleImpl(this, moduleUri, options, false);
}
public ModuleInvoke newModuleInvoke(String moduleUri) {
return (newModuleInvoke(moduleUri, null));
}
public ModuleSpawn newModuleSpawn(String moduleUri, RequestOptions options) {
assertSessionOpen();
return new ModuleImpl(this, moduleUri, options, true);
}
public ModuleSpawn newModuleSpawn(String moduleUri) {
return (newModuleSpawn(moduleUri, null));
}
public AdhocQuery newAdhocQuery(String queryText, RequestOptions options) {
assertSessionOpen();
return new AdhocImpl(this, queryText, options);
}
public AdhocQuery newAdhocQuery(String queryText) {
assertSessionOpen();
return (newAdhocQuery(queryText, null));
}
public void insertContent(Content[] contents) throws RequestException {
boolean startTxn = contents != null && contents.length > 1 &&
HttpChannel.isUseHTTP() && isAutoCommit() && txnID == null;
TransactionMode origMode = txnMode;
if (startTxn) {
setAutoCommit(false);
}
try {
insertContent(contents, false);
} finally {
if (startTxn && getTxnID() != null) {
commit();
txnMode = origMode;
}
}
}
public List insertContentCollectErrors(Content[] contents)
throws RequestException {
return insertContent(contents, true);
}
public List insertContent(Content[] contents,
boolean collectErrors) throws RequestException {
assertSessionOpen();
Request request = newAdhocQuery("()");
RequestOptions sessionOptions = getDefaultRequestOptions();
// These numbers correspond to the server values, about two minutes overall
if ((sessionOptions.getMaxAutoRetry() == -1) || (sessionOptions.getAutoRetryDelayMillis() == -1)) {
RequestOptions options = new RequestOptions();
if (sessionOptions.getMaxAutoRetry() == -1)
options.setMaxAutoRetry(64);
if (sessionOptions.getAutoRetryDelayMillis() == -1)
options.setAutoRetryDelayMillis(125);
if (getTxnID() != null)
options.setMaxAutoRetry(0);
request.setOptions(options);
}
assertNoTimeStamp(request);
assertNonEmptyUris(request, contents);
createTransaction(request);
ContentInsertController controller =
new ContentInsertController(contents, txnMode, collectErrors);
controller.runRequest(provider, request, getLogger());
return controller.getErrors();
}
public boolean isInCompatibleMode() {
return compatibleTxnMode;
}
public void setCompatibleMode(boolean mode) {
compatibleTxnMode = mode;
}
private void createTransaction(Request request) throws RequestException {
if ((getTxnID() == null && txnMode != null) || txnModeChanged) {
if (commitMode) {
userSpecifiedTxnMode = txnMode;
}
if (compatibleTxnMode && txnMode != TransactionMode.AUTO) {
// Set the new transaction mode on the server, creating a new
// current transaction if one does not already exist
RequestOptions options = new RequestOptions();
RequestOptions reqOpt = request.getOptions();
options.setAutoRetryDelayMillis(reqOpt.getAutoRetryDelayMillis());
options.setMaxAutoRetry(reqOpt.getMaxAutoRetry());
options.setRequestName(reqOpt.getRequestName());
options.setRequestTimeLimit(reqOpt.getRequestTimeLimit());
options.setTimeoutMillis(reqOpt.getTimeoutMillis());
options.setTimeZone(reqOpt.getTimeZone());
submitRequestInternal(
new AdhocImpl(this, "xquery version '1.0-ml';\n"
+ "declare option xdmp:transaction-mode '"
+ serializeTransactionMode(txnMode)
+ "'; "
+ (timeout == 0 || getTxnID() != null ? "()"
: "xdmp:set-transaction-time-limit("
+ timeout + ")"), options)).close();
}
}
}
private String serializeTransactionMode(TransactionMode mode) {
switch(mode) {
case AUTO: return "auto";
case QUERY: return "query";
case UPDATE: return "update";
case UPDATE_AUTO_COMMIT:
throwIllegalArg(
"Transaction mode UPDATE_AUTO_COMMIT is not supported when " +
"xcc.txn.compatible is set to true",
getLogger());
break;
default:
break;
}
throwIllegalArg(
"Unknown transaction mode: should be TransactionMode.AUTO, " +
"TransactionMode.QUERY, TransactionMode.UPDATE", getLogger());
return null;
}
private void assertNonEmptyUris(Request request, Content[] contents) throws RequestException {
for (int i = 0; i < contents.length; i++) {
String uri = contents[i].getUri();
if ((uri == null) || (uri.length() == 0)) {
throw new RequestException("Content insertion with empty URI is not allowed", request);
}
}
}
private void assertNoTimeStamp(Request request) throws RequestException {
RequestOptions options = getEffectiveRequestOptions();
if (options.getEffectivePointInTime() == null) {
return;
}
if (options.getEffectivePointInTime().equals(BigInteger.ZERO)) {
return;
}
throw new RequestException("Content insertion not allowed with non-zero Point-In-Time", request);
}
public void insertContent(Content content) throws RequestException {
insertContent(new Content[] { content });
}
public ContentbaseMetaData getContentbaseMetaData() {
return new CBMetaDataImpl(this);
}
public void setDefaultRequestOptions(RequestOptions options) {
if (options == null) {
this.defaultOptions = new RequestOptions();
} else {
this.defaultOptions = options;
}
}
public RequestOptions getDefaultRequestOptions() {
return defaultOptions;
}
public Logger getLogger() {
return (logger == null) ? contentSource.getDefaultLogger() : logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public ResultSequence submitRequest(Request request) throws RequestException {
assertSessionOpen();
if ((request.getSession() != this) || (!(request instanceof RequestImpl))) {
throwIllegalArg("Request object was not created by this session", getLogger());
}
createTransaction(request);
return submitRequestInternal((RequestImpl)request);
}
ResultSequence submitRequestInternal(RequestImpl req) throws RequestException {
EvalRequestController controller = new EvalRequestController(req.serverPath(), req.encodedQueryString(getLogger()));
return controller.runRequest(provider, req, getLogger());
}
public BigInteger getCurrentServerPointInTime() throws RequestException {
Request pitReq = newAdhocQuery("xdmp:request-timestamp()");
ResultSequence rs = submitRequest(pitReq);
ResultItem item = rs.next();
BigInteger stamp = null;
if (item.getItem() instanceof XSDecimal) {
stamp = ((XSDecimal)item.getItem()).asBigDecimal().toBigInteger();
} else {
stamp = ((XSInteger)item.getItem()).asBigInteger();
}
rs.close();
return stamp;
}
public Object getUserObject() {
return userObject;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public URI getConnectionUri() {
if (!(provider instanceof SingleHostAddress)) {
return null;
}
InetSocketAddress addr = ((SingleHostAddress)provider).getAddress();
try {
return new URI("xcc", getUserCredentials().getUserName() + ":xxxx", addr.getHostName(), addr.getPort(),
(getContentBaseName() == null) ? null : ("/" + getContentBaseName()), null, null);
} catch (URISyntaxException e) {
return null;
}
}
// ---------------------------------------------------------------
// Implementation-specific accessors
public void registerResultSequence(StreamingResultSequence resultSequence) {
activeResultSeqs.add(resultSequence);
}
public void deRegisterResultSequence(StreamingResultSequence resultSequence) {
synchronized (activeResultSeqs) {
if (activeResultSeqs.contains(resultSequence)) {
activeResultSeqs.remove(resultSequence);
}
}
}
public RequestOptions getEffectiveRequestOptions() {
RequestOptions eff = new RequestOptions();
RequestOptions ses = getDefaultRequestOptions();
eff.applyEffectiveValues(new RequestOptions[] { ses });
return eff;
}
public ConnectionProvider getProvider() {
return provider;
}
public String userAgentString() {
return (agentString);
}
public synchronized String getSessionID() {
return sessionID;
}
public synchronized String getTxnID() {
return txnID;
}
public String getAcceptedContentTypes() {
if (compactSequencesEnabled) {
return "application/vnd.marklogic.sequence, */*";
} else {
return "*/*";
}
}
public boolean readCookieValues(HttpChannel http) throws IOException {
List cookies = http.getResponseHeaders("set-cookie");
String mode = null;
if (cookies != null) {
if (cookieJar == null) {
// DLEE: only set-cookie, not set-cookie2
cookieJar = new HashMap<>();
}
// DLEE: Don't do any RFC-6265 or sorting
for (String cookieString: cookies) {
for (HttpCookie cookie:
HttpCookie.parse("Set-Cookie: " + cookieString)) {
// CookieJar only keeps cookies not managed by marklogic
switch (cookie.getName()) {
case "SessionID":
sessionID = cookie.getValue();
break;
case "TxnID":
String txn = cookie.getValue();
txnID = txn.equals("null")?null:txn;
break;
case "TxnMode":
mode = cookie.getValue();
break;
default:
cookieJar.put(cookie.getName(), cookie);
}
}
}
}
TransactionMode newMode = null;
if (mode != null) {
newMode = parseTransactionMode(mode);
}
if (getLogger().isLoggable(Level.FINE)) {
getLogger().fine("Receiving SessionID: " + sessionID + ", TxnID: "
+ txnID + ", TxnMode: " + newMode + ", userSpecifiedTxnMode: "
+ userSpecifiedTxnMode);
}
if (txnIncompatible == null) {
txnIncompatible = isTxnIncompatible();
}
if (txnMode != null && txnMode != TransactionMode.AUTO &&
txnIncompatible) {
compatibleTxnMode = true;
return false;
} else if (mode != null) {
if (commitMode) {
if (txnID != null) { // update txnMode
txnMode = newMode;
} else if (userSpecifiedTxnMode != null) {
txnMode = userSpecifiedTxnMode; // reset it
}
} else {
txnMode = newMode;
}
txnModeChanged = false;
} else if (commitMode && userSpecifiedTxnMode != null) {
// reset if no new mode is received
txnMode = userSpecifiedTxnMode;
}
return true;
}
public void addRequestCookie(HttpCookie cookie) {
if (requestCookies == null) {
requestCookies = new StringBuilder();
}
if (cookie != null) {
requestCookies.append(cookie.toString());
requestCookies.append("; ");
}
}
public void writeCookies(HttpChannel http) {
requestCookies = null;
// DLEE: only V1 cookie format
if (cookieJar != null) {
// Expire cookies
for (Iterator> it =
cookieJar.entrySet().iterator(); it.hasNext();) {
HttpCookie cookie = it.next().getValue();
if (cookie.hasExpired()) {
it.remove();
} else {
addRequestCookie(cookie);
}
}
// SessionID cookie
if(sessionID != null) {
addRequestCookie(new HttpCookie("SessionID", sessionID));
}
if (requestCookies != null) {
http.setRequestHeader("Cookie",
requestCookies.toString().replaceAll("\"", ""));
}
}
}
private boolean isTxnIncompatible() {
if (compatibleTxnMode == true) {
return false;
}
if (serverVersion == null) {
return false;
}
// parse server version
String[] vers = serverVersion.split("\\.");
if (vers.length < 2) { // unexpected
return true;
}
int majorVer = Integer.parseInt(vers[0]);
if (majorVer >= 8) {
return false;
}
return true;
}
private TransactionMode parseTransactionMode(String val) {
if(val.equals("auto")) return TransactionMode.AUTO;
else if(val.equals("query")) return TransactionMode.QUERY;
else if(val.equals("update")) return TransactionMode.UPDATE;
else if (val.equals("update-auto-commit"))
return TransactionMode.UPDATE_AUTO_COMMIT;
else if (val.equals("multi-auto"))
return TransactionMode.MULTI_AUTO;
else if (val.equals("query-single-statement"))
return TransactionMode.QUERY_SINGLE_STATEMENT;
return TransactionMode.AUTO;
}
// ---------------------------------------------------------------
private void assertSessionOpen() {
if (isClosed()) {
throw new IllegalStateException("Session has been closed");
}
}
private void throwIllegalArg(String msg, Logger logger) {
logger.severe(msg);
throw new IllegalArgumentException(msg);
}
void throwIllegalState(String msg) {
getLogger().severe(msg);
throw new IllegalStateException(msg);
}
// ---------------------------------------------------------------
@Override
public String toString() {
return credentials.toString() + ", cb=" + ((contentBase == null) ? "{default}" : contentBase)
+ " [ContentSource: " + contentSource.toString() + "]";
}
@Override
public int getCachedTxnTimeout() {
return timeout;
}
@Override
public Update getUpdate() {
return txnMode.getUpdate();
}
@Override
public void setUpdate(Update update) {
commitMode = true;
if (txnMode == null) {
switch (update) {
case AUTO: txnMode = TransactionMode.AUTO; return;
case TRUE:
txnMode = TransactionMode.UPDATE_AUTO_COMMIT;
return;
case FALSE:
txnMode = TransactionMode.QUERY_SINGLE_STATEMENT;
return;
}
}
if (update == txnMode.getUpdate()) {
return;
}
if (getTxnID() != null) {
throwIllegalState(
"Cannot change transaction's update mode when there is an active transaction");
}
txnMode = txnMode.setUpdate(update);
}
}