| /* |
| * 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 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 rows = new Vector(1); |
| convertRecordBytesToString((OracleNoSQLRecord)value); |
| rows.add(interaction.buildRow((OracleNoSQLRecord)value, accessor)); |
| return rows; |
| } else if (value instanceof Collection) { |
| Vector 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 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> iterator = record.entrySet().iterator(); iterator.hasNext(); ) { |
| Map.Entry 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 "<dataTypeName>/<id>"}. |
| */ |
| @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 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 = (String)accessor.getDatasourcePlatform().getConversionManager().convertObject(idValue, String.class); |
| key.add(idString); |
| } |
| } else { |
| String idString = (String)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(); |
| } |
| } |