blob: 4fe94cbab6ac6ccffb94a2fb12d30d0a8913abe1 [file] [log] [blame]
/*
* Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
package org.eclipse.persistence.nosql.adapters.nosql;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import jakarta.resource.cci.InteractionSpec;
import jakarta.resource.cci.MappedRecord;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
import org.eclipse.persistence.descriptors.SelectedFieldsLockingPolicy;
import org.eclipse.persistence.eis.EISAccessor;
import org.eclipse.persistence.eis.EISDOMRecord;
import org.eclipse.persistence.eis.EISDescriptor;
import org.eclipse.persistence.eis.EISException;
import org.eclipse.persistence.eis.EISMappedRecord;
import org.eclipse.persistence.eis.EISPlatform;
import org.eclipse.persistence.eis.interactions.EISInteraction;
import org.eclipse.persistence.eis.interactions.MappedInteraction;
import org.eclipse.persistence.eis.interactions.XMLInteraction;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.nosql.adapters.nosql.OracleNoSQLInteractionSpec;
import org.eclipse.persistence.internal.nosql.adapters.nosql.OracleNoSQLOperation;
import org.eclipse.persistence.internal.nosql.adapters.nosql.OracleNoSQLRecord;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sequencing.UUIDSequence;
import org.w3c.dom.Element;
import oracle.kv.Consistency;
import oracle.kv.Durability;
import oracle.kv.Version;
/**
* Platform for Oracle NoSQL database.
*
* @author James
* @since EclipseLink 2.4
*/
public class OracleNoSQLPlatform extends EISPlatform {
/** OracleNoSQL interaction spec properties. */
public static String OPERATION = "nosql.operation";
public static String CONSISTENCY = "nosql.consistency";
public static String DURABILITY = "nosql.durability";
public static String TIMEOUT = "nosql.timeout";
public static String VERSION = "nosql.version";
/**
* Default constructor.
*/
public OracleNoSQLPlatform() {
super();
setShouldConvertDataToStrings(true);
setIsMappedRecordSupported(true);
setIsIndexedRecordSupported(false);
setIsDOMRecordSupported(true);
setSupportsLocalTransactions(true);
}
/**
* Allow the platform to build the interaction spec based on properties defined in the interaction.
*/
@Override
public InteractionSpec buildInteractionSpec(EISInteraction interaction) {
InteractionSpec spec = interaction.getInteractionSpec();
if (spec == null) {
OracleNoSQLInteractionSpec noSqlSpec = new OracleNoSQLInteractionSpec();
Object operation = interaction.getProperty(OPERATION);
if (operation == null) {
throw new EISException("'" + OPERATION + "' property must be set on the query's interation.");
}
if (operation instanceof String) {
operation = OracleNoSQLOperation.valueOf((String)operation);
}
noSqlSpec.setOperation((OracleNoSQLOperation)operation);
// Allows setting of consistency as a property.
Object consistency = interaction.getProperty(CONSISTENCY);
if (consistency == null) {
// Default to session property.
consistency = interaction.getQuery().getSession().getProperty(CONSISTENCY);
}
if (consistency instanceof Consistency) {
noSqlSpec.setConsistency((Consistency)consistency);
} else if (consistency instanceof String) {
String constant = (String)consistency;
if (constant.equals("ABSOLUTE")) {
noSqlSpec.setConsistency(Consistency.ABSOLUTE);
} else if (constant.equals("NONE_REQUIRED")) {
noSqlSpec.setConsistency(Consistency.NONE_REQUIRED );
} else {
throw new EISException("Invalid consistency property value: " + constant);
}
}
// Allows setting of durability as a property.
Object durability = interaction.getProperty(DURABILITY);
if (durability == null) {
// Default to session property.
durability = interaction.getQuery().getSession().getProperty(DURABILITY);
}
if (durability instanceof Durability) {
noSqlSpec.setDurability((Durability)durability);
} else if (durability instanceof String) {
String constant = (String)durability;
if (constant.equals("COMMIT_NO_SYNC")) {
noSqlSpec.setDurability(Durability.COMMIT_NO_SYNC);
} else if (constant.equals("COMMIT_SYNC")) {
noSqlSpec.setDurability(Durability.COMMIT_SYNC );
} else if (constant.equals("COMMIT_WRITE_NO_SYNC")) {
noSqlSpec.setDurability(Durability.COMMIT_WRITE_NO_SYNC );
} else {
throw new EISException("Invalid durability property value: " + constant);
}
}
// Allows setting of timeout as a property.
Object timeout = interaction.getProperty(TIMEOUT);
if (timeout == null) {
// Default to session property.
timeout = interaction.getQuery().getSession().getProperty(TIMEOUT);
}
if (timeout instanceof Number) {
noSqlSpec.setTimeout(((Number)timeout).longValue());
} else if (timeout instanceof String) {
noSqlSpec.setTimeout(Long.parseLong(((String)timeout)));
} else if (interaction.getQuery().getQueryTimeout() > 0) {
noSqlSpec.setTimeout(interaction.getQuery().getQueryTimeout());
}
// Allows setting of version as a property.
Object version = interaction.getProperty(VERSION);
if (version == null) {
// Default to session property.
version = interaction.getQuery().getSession().getProperty(VERSION);
}
if (version == null) {
if (interaction.getQuery().getDescriptor() != null) {
ClassDescriptor descriptor = interaction.getQuery().getDescriptor();
if (descriptor.usesOptimisticLocking() && descriptor.getOptimisticLockingPolicy() instanceof SelectedFieldsLockingPolicy) {
DatabaseField field = ((SelectedFieldsLockingPolicy)descriptor.getOptimisticLockingPolicy()).getLockFields().get(0);
if (interaction.getInputRow() != null) {
version = interaction.getInputRow().get(field);
}
}
}
}
if (version instanceof Version) {
noSqlSpec.setVersion((Version)version);
} else if (version instanceof byte[]) {
noSqlSpec.setVersion(Version.fromByteArray((byte[])version));
}
spec = noSqlSpec;
}
return spec;
}
/**
* INTERNAL:
* Allow the platform to handle record to row conversion.
*/
@Override
public AbstractRecord buildRow(jakarta.resource.cci.Record record, EISInteraction interaction, EISAccessor accessor) {
if (record == null) {
return null;
}
OracleNoSQLRecord output = (OracleNoSQLRecord)record;
if ((output.size() == 1) && (interaction.getQuery().getDescriptor() != null)) {
// Check for a nested mapped record.
Object value = output.values().iterator().next();
if (value instanceof OracleNoSQLRecord) {
convertRecordBytesToString((OracleNoSQLRecord)value);
output = (OracleNoSQLRecord)value;
}
}
jakarta.resource.cci.Record result = output;
if (getRecordConverter() != null) {
result = getRecordConverter().converterFromAdapterRecord(output);
}
return interaction.buildRow(result, accessor);
}
/**
* Allow the platform to handle record to row conversion.
*/
@Override
public Vector<AbstractRecord> buildRows(jakarta.resource.cci.Record record, EISInteraction interaction, EISAccessor accessor) {
if (record == null) {
return new Vector<>(0);
}
OracleNoSQLRecord output = (OracleNoSQLRecord)record;
if ((output.size() == 1) && (interaction.getQuery().getDescriptor() != null)) {
// Check for a nested mapped record.
Object value = output.values().iterator().next();
if (value instanceof OracleNoSQLRecord) {
Vector<AbstractRecord> rows = new Vector<>(1);
convertRecordBytesToString((OracleNoSQLRecord)value);
rows.add(interaction.buildRow((OracleNoSQLRecord)value, accessor));
return rows;
} else if (value instanceof Collection) {
Vector<AbstractRecord> rows = new Vector<>(((Collection<?>)value).size());
for (Object nestedValue : (Collection<?>)value) {
if (nestedValue instanceof OracleNoSQLRecord) {
rows.add(interaction.buildRow((OracleNoSQLRecord)nestedValue, accessor));
}
}
return rows;
}
}
if (interaction.getQuery().getDescriptor() != null) {
// Check for a map of values.
Vector<AbstractRecord> rows = new Vector<>();
for (Object value : output.values()) {
if (value instanceof OracleNoSQLRecord) {
convertRecordBytesToString((OracleNoSQLRecord)value);
rows.add(interaction.buildRow((OracleNoSQLRecord)value, accessor));
} else if (value instanceof byte[]) {
EISDOMRecord domRecord = new EISDOMRecord();
domRecord.transformFromXML(new String((byte[])value));
rows.add(domRecord);
}
}
return rows;
}
return interaction.buildRows(record, accessor);
}
/**
* INTERNAL:
* Convert the record and nested records bytes to strings.
*/
protected void convertRecordBytesToString(OracleNoSQLRecord record) {
// Convert byte[] to String.
for (Iterator<Map.Entry<?, Object>> iterator = record.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<?, Object> entry = iterator.next();
if (entry.getValue() instanceof byte[]) {
entry.setValue(new String((byte[])entry.getValue()));
} else if (entry.getValue() instanceof OracleNoSQLRecord) {
convertRecordBytesToString((OracleNoSQLRecord)entry.getValue());
}
}
}
/**
* Allow the platform to create the appropriate type of record for the interaction.
* Convert the nested local mapped record to a flat global keyed record.
*/
@Override
public jakarta.resource.cci.Record createInputRecord(EISInteraction interaction, EISAccessor accessor) {
if (interaction instanceof XMLInteraction) {
return super.createInputRecord(interaction, accessor);
} if (interaction instanceof MappedInteraction) {
MappedRecord input = (MappedRecord)interaction.createInputRecord(accessor);
// Create the key from the objects id.
ClassDescriptor descriptor = interaction.getQuery().getDescriptor();
if (descriptor == null) {
if (getRecordConverter() != null) {
return getRecordConverter().converterToAdapterRecord(input);
}
return input;
}
Object key = createMajorKey(descriptor, new EISMappedRecord(input, accessor), interaction, accessor);
OracleNoSQLRecord record = new OracleNoSQLRecord();
record.put(key, input);
if (getRecordConverter() != null) {
return getRecordConverter().converterToAdapterRecord(record);
}
return record;
} else {
return super.createInputRecord(interaction, accessor);
}
}
/**
* Stores the XML DOM value into the record.
* XML is stored in Oracle NoSQL but storing the XML text, keyed on the object's {@literal "&lt;dataTypeName&gt;/&lt;id&gt;"}.
*/
@Override
public void setDOMInRecord(Element dom, jakarta.resource.cci.Record record, EISInteraction interaction, EISAccessor accessor) {
OracleNoSQLRecord noSqlRecord = (OracleNoSQLRecord)record;
org.eclipse.persistence.oxm.record.DOMRecord domRecord = new org.eclipse.persistence.oxm.record.DOMRecord(dom);
domRecord.setSession(interaction.getQuery().getSession());
// Create the key from the objects id.
ClassDescriptor descriptor = interaction.getQuery().getDescriptor();
if (descriptor == null) {
throw new EISException("XMLInteraction is only valid for object queries, use MappedIneraction for native queries: " + interaction);
}
Object key = createMajorKey(descriptor, domRecord, interaction, accessor);
noSqlRecord.put(key, domRecord.transformToXML().getBytes());
}
/**
* NoSQL stores the data in a single big map.
* The keys are called major keys (minor keys are used to store the attributes).
* Major keys can have multiple values (and normally do).
* The first value is normally the type, then the id.
*/
protected Object createMajorKey(ClassDescriptor descriptor, AbstractRecord record, EISInteraction interaction, EISAccessor accessor) {
Object id = descriptor.getObjectBuilder().extractPrimaryKeyFromRow(record, interaction.getQuery().getSession());
List<String> key = new ArrayList<>(descriptor.getPrimaryKeyFields().size() + 1);
if (((EISDescriptor)descriptor).getDataTypeName().length() > 0) {
key.add(((EISDescriptor)descriptor).getDataTypeName());
}
if (id != null) {
if (id instanceof CacheId) {
Object[] idValues = ((CacheId)id).getPrimaryKey();
for (Object idValue : idValues) {
String idString = accessor.getDatasourcePlatform().getConversionManager().convertObject(idValue, String.class);
key.add(idString);
}
} else {
String idString = accessor.getDatasourcePlatform().getConversionManager().convertObject(id, String.class);
key.add(idString);
}
}
return key;
}
/**
* Allow the platform to handle the creation of the Record for the DOM record.
* Creates a DOM record from the XML data stored as a value in the OracleNoSQLRecord.
*/
@Override
public AbstractRecord createDatabaseRowFromDOMRecord(jakarta.resource.cci.Record record, EISInteraction call, EISAccessor accessor) {
if (record == null) {
return null;
}
EISDOMRecord domRecord = null;
OracleNoSQLRecord noSqlRecord = (OracleNoSQLRecord)record;
if (noSqlRecord.size() == 0) {
return null;
} else if (noSqlRecord.size() == 1) {
domRecord = new EISDOMRecord();
Object value = noSqlRecord.values().iterator().next();
String xml = null;
if (value instanceof byte[]) {
xml = new String ((byte[])value);
} else {
xml = (String) value;
}
if (xml != null) {
domRecord.transformFromXML(xml);
}
} else {
domRecord = new EISDOMRecord();
for (Map.Entry<?, ?> entry : (Set<Map.Entry<?, ?>>)noSqlRecord.entrySet()) {
Object value = entry.getValue();
String xml = null;
if (value instanceof byte[]) {
xml = new String ((byte[])value);
} else {
xml = (String)value;
}
if (xml != null) {
EISDOMRecord dom = new EISDOMRecord();
dom.transformFromXML(xml);
domRecord.put(entry.getKey(), dom);
}
}
}
return domRecord;
}
/**
* INTERNAL:
* Allow the platform to initialize the CRUD queries to defaults.
* Configure the CRUD operations using GET/PUT and DELETE.
*/
@Override
public void initializeDefaultQueries(DescriptorQueryManager queryManager, AbstractSession session) {
boolean isXML = ((EISDescriptor)queryManager.getDescriptor()).isXMLFormat();
// Insert
if (!queryManager.hasInsertQuery()) {
EISInteraction call = isXML ? new XMLInteraction() : new MappedInteraction();
call.setProperty(OracleNoSQLPlatform.OPERATION, OracleNoSQLOperation.PUT_IF_ABSENT);
queryManager.setInsertCall(call);
}
// Update
if (!queryManager.hasUpdateQuery()) {
EISInteraction call = isXML ? new XMLInteraction() : new MappedInteraction();
call.setProperty(OracleNoSQLPlatform.OPERATION, OracleNoSQLOperation.PUT_IF_PRESENT);
queryManager.setUpdateCall(call);
}
// Read
if (!queryManager.hasReadObjectQuery()) {
MappedInteraction call = isXML ? new XMLInteraction() : new MappedInteraction();
call.setProperty(OracleNoSQLPlatform.OPERATION, OracleNoSQLOperation.GET);
for (DatabaseField field : queryManager.getDescriptor().getPrimaryKeyFields()) {
call.addArgument(field.getName());
}
queryManager.setReadObjectCall(call);
}
// Read-all
if (!queryManager.hasReadAllQuery()) {
MappedInteraction call = isXML ? new XMLInteraction() : new MappedInteraction();
call.setProperty(OracleNoSQLPlatform.OPERATION, OracleNoSQLOperation.ITERATOR);
queryManager.setReadAllCall(call);
}
// Delete
if (!queryManager.hasDeleteQuery()) {
MappedInteraction call = isXML ? new XMLInteraction() : new MappedInteraction();
call.setProperty(OracleNoSQLPlatform.OPERATION, OracleNoSQLOperation.DELETE);
for (DatabaseField field : queryManager.getDescriptor().getPrimaryKeyFields()) {
call.addArgument(field.getName());
}
queryManager.setDeleteCall(call);
}
}
/**
* Do not prepare to avoid errors being triggered for id and all queries.
*/
@Override
public boolean shouldPrepare(DatabaseQuery query) {
return (query.getDatasourceCall() instanceof EISInteraction);
}
/**
* INTERNAL:
* NoSQL does not support id generation, so use UUID as the default.
*/
@Override
protected Sequence createPlatformDefaultSequence() {
return new UUIDSequence();
}
}