1 package org.collectionspace.services.nuxeo.client.java;
3 import java.io.UnsupportedEncodingException;
4 import java.net.URLDecoder;
5 import java.net.URLEncoder;
6 import java.security.Principal;
7 import java.time.Instant;
8 import java.time.LocalDateTime;
9 import java.time.ZoneId;
10 import java.time.ZonedDateTime;
11 import java.time.format.DateTimeFormatter;
12 import java.time.format.DateTimeParseException;
13 import java.util.regex.Matcher;
14 import java.util.regex.Pattern;
16 import org.collectionspace.services.common.document.DocumentException;
17 import org.nuxeo.ecm.core.api.ClientException;
18 import org.nuxeo.ecm.core.api.DocumentModel;
19 import org.nuxeo.ecm.core.api.DocumentModelList;
20 import org.nuxeo.ecm.core.api.DocumentRef;
21 import org.nuxeo.ecm.core.api.Filter;
22 import org.nuxeo.ecm.core.api.IterableQueryResult;
23 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
24 import org.nuxeo.ecm.core.api.impl.LifeCycleFilter;
25 import org.nuxeo.ecm.core.api.CoreSession;
26 import org.nuxeo.runtime.transaction.TransactionHelper;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 public class CoreSessionWrapper implements CoreSessionInterface {
33 private CoreSession repoSession;
34 private boolean transactionSetForRollback = false;
37 private static Logger logger = LoggerFactory.getLogger(CoreSessionWrapper.class);
39 private void logQuery(String query) {
40 logger.debug(String.format("NXQL: %s", query));
43 private void logQuery(String query, String queryType) {
44 logger.debug(String.format("Query Type: '%s' NXQL: %s", queryType, query));
47 private void logQuery(String query, Filter filter, long limit,
48 long offset, boolean countTotal) {
49 logger.debug(String.format("Filter: '%s', Limit: '%d', Offset: '%d', Count Total?: %b, NXQL: %s",
50 filter != null ? filter.toString() : "none", limit, offset, countTotal, query));
53 public CoreSessionWrapper(CoreSession repoSession) {
54 this.repoSession = repoSession;
58 * Mark this session's transaction for rollback only
61 public void setTransactionRollbackOnly() {
62 TransactionHelper.setTransactionRollbackOnly();
63 transactionSetForRollback = true;
67 public boolean isTransactionMarkedForRollbackOnly() {
68 if (transactionSetForRollback != TransactionHelper.isTransactionMarkedRollback()) {
69 logger.error(String.format("Transaction status is in an inconsistent state. Internal state is '%b'. TransactionHelper statis is '%b'.",
70 transactionSetForRollback, TransactionHelper.isTransactionMarkedRollback()));
72 return transactionSetForRollback;
76 public CoreSession getCoreSession() {
81 public String getSessionId() {
82 return repoSession.getSessionId();
86 public void close() throws Exception {
89 } catch (Throwable t) {
90 logger.error(String.format("Could not close session for repository '%s'.", this.repoSession.getRepositoryName()),
96 * Gets the root document of this repository.
98 * @return the root document. cannot be null
99 * @throws ClientException
100 * @throws SecurityException
103 public DocumentModel getRootDocument() throws ClientException {
104 return repoSession.getRootDocument();
108 * Returns the repository name against which this core session is bound.
110 * @return the repository name used currently used as an identifier
113 public String getRepositoryName() {
114 return repoSession.getRepositoryName();
118 * Gets the principal that created the client session.
120 * @return the principal
123 public Principal getPrincipal() {
124 return repoSession.getPrincipal();
127 private String toLocalTimestamp(String utcTime, boolean base64Encoded) throws DocumentException {
128 String result = null;
131 if (base64Encoded == true) {
132 utcTime = URLDecoder.decode(utcTime, java.nio.charset.StandardCharsets.UTF_8.name());
134 LocalDateTime localTime;
136 Instant instant = Instant.parse(utcTime);
137 ZonedDateTime localInstant = instant.atZone(ZoneId.systemDefault()); // DateTimeFormatter.ISO_LOCAL_DATE_TIME
138 localTime = localInstant.toLocalDateTime();
139 } catch (DateTimeParseException e) {
140 localTime = LocalDateTime.parse(utcTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
142 result = localTime.toString();
143 if (base64Encoded == true) {
144 result = URLEncoder.encode(result, java.nio.charset.StandardCharsets.UTF_8.name());
146 } catch (UnsupportedEncodingException e) {
147 throw new DocumentException(e);
153 private String localizeTimestamps(String query) throws DocumentException {
154 String result = query;
156 if (query.contains("TIMESTAMP")) {
157 StringBuffer stringBuffer = new StringBuffer();
158 Pattern pattern = Pattern.compile("\\+TIMESTAMP\\+%22(.+?)%22");
159 Matcher matcher = pattern.matcher(query);
160 while (matcher.find()) {
161 String time = matcher.group(1);
162 String localizedTime = toLocalTimestamp(time, true);
163 matcher.appendReplacement(stringBuffer, String.format("+TIMESTAMP+%%22%s%%22", localizedTime));
165 matcher.appendTail(stringBuffer);
166 result = stringBuffer.toString();
173 public IterableQueryResult queryAndFetch(String query, String queryType,
174 Object... params) throws ClientException, DocumentException {
175 query = localizeTimestamps(query);
176 logQuery(query, queryType);
177 return repoSession.queryAndFetch(query, queryType, params);
181 public DocumentModelList query(String query, Filter filter, long limit,
182 long offset, boolean countTotal) throws ClientException, DocumentException {
183 query = localizeTimestamps(query);
184 logQuery(query, filter, limit, offset, countTotal);
185 return repoSession.query(query, filter, limit, offset, countTotal);
189 public DocumentModelList query(String query, int max) throws ClientException, DocumentException {
190 query = localizeTimestamps(query);
192 return repoSession.query(query, max);
196 public DocumentModelList query(String query) throws ClientException, DocumentException {
197 query = localizeTimestamps(query);
199 return repoSession.query(query);
203 public DocumentModelList query(String query, LifeCycleFilter workflowStateFilter) throws DocumentException {
204 query = localizeTimestamps(query);
205 return repoSession.query(query, workflowStateFilter);
209 * Gets a document model given its reference.
211 * The default schemas are used to populate the returned document model.
212 * Default schemas are configured via the document type manager.
214 * Any other data model not part of the default schemas will be lazily
217 * @param docRef the document reference
218 * @return the document
219 * @throws ClientException
220 * @throws SecurityException
223 public DocumentModel getDocument(DocumentRef docRef) throws ClientException {
224 return repoSession.getDocument(docRef);
228 public DocumentModel saveDocument(DocumentModel docModel) throws ClientException {
229 DocumentModel result = null;
232 if (isTransactionMarkedForRollbackOnly() == false) {
233 result = repoSession.saveDocument(docModel);
235 logger.trace(String.format("The repository session on thread '%d' has a transaction that is marked for rollback.",
236 Thread.currentThread().getId()));
238 } catch (Throwable t) {
239 setTransactionRollbackOnly();
247 public void save() throws ClientException {
249 if (isTransactionMarkedForRollbackOnly() == false) {
252 logger.trace(String.format("The repository session on thread '%d' has a transaction that is marked for rollback.",
253 Thread.currentThread().getId()));
255 } catch (Throwable t) {
256 setTransactionRollbackOnly();
262 * Bulk document saving.
264 * @param docModels the document models that needs to be saved
265 * @throws ClientException
268 public void saveDocuments(DocumentModel[] docModels) throws ClientException {
270 if (isTransactionMarkedForRollbackOnly() == false) {
271 repoSession.saveDocuments(docModels);
273 logger.trace(String.format("The repository session on thread '%d' has a transaction that is marked for rollback.",
274 Thread.currentThread().getId()));
276 } catch (Throwable t) {
277 setTransactionRollbackOnly();
283 * Removes this document and all its children, if any.
285 * @param docRef the reference to the document to remove
286 * @throws ClientException
289 public void removeDocument(DocumentRef docRef) throws ClientException {
290 repoSession.removeDocument(docRef);
294 * Creates a document model using required information.
296 * Used to fetch initial datamodels from the type definition.
298 * DocumentModel creation notifies a
299 * {@link DocumentEventTypes.EMPTY_DOCUMENTMODEL_CREATED} so that core event
300 * listener can initialize its content with computed properties.
305 * @return the initial document model
306 * @throws ClientException
309 public DocumentModel createDocumentModel(String parentPath, String id,
310 String typeName) throws ClientException {
311 return repoSession.createDocumentModel(parentPath, id, typeName);
315 * Creates a document using given document model for initialization.
317 * The model contains path of the new document, its type and optionally the
318 * initial data models of the document.
321 * @param model the document model to use for initialization
322 * @return the created document
323 * @throws ClientException
326 public DocumentModel createDocument(DocumentModel model) throws ClientException {
327 return repoSession.createDocument(model);
331 * Gets the children of the given parent.
333 * @param parent the parent reference
334 * @return the children if any, an empty list if no children or null if the
335 * specified parent document is not a folder
336 * @throws ClientException
339 public DocumentModelList getChildren(DocumentRef parent) throws ClientException {
340 return repoSession.getChildren(parent);