blob: 6e7bd1316ea8074e4e7f64cd9020fc05eed832c6 [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.olingo.odata2.annotation.processor.core.datasource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.olingo.odata2.annotation.processor.core.util.AnnotationHelper;
import org.apache.olingo.odata2.annotation.processor.core.util.AnnotationRuntimeException;
import org.apache.olingo.odata2.annotation.processor.core.util.ClassHelper;
import org.apache.olingo.odata2.api.annotation.edm.EdmKey;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
/**
*
*/
public class DataStore<T> {
private static final AnnotationHelper ANNOTATION_HELPER = new AnnotationHelper();
private final Map<KeyElement, T> dataStore;
private final Class<T> dataTypeClass;
private final KeyAccess keyAccess;
private static class InMemoryDataStore {
private static final Map<Class<?>, DataStore<?>> c2ds = new HashMap<Class<?>, DataStore<?>>();
@SuppressWarnings("unchecked")
static synchronized DataStore<?> getInstance(final Class<?> clz, final boolean createNewInstance)
throws DataStoreException {
DataStore<?> ds = c2ds.get(clz);
if (createNewInstance || ds == null) {
ds = new DataStore<Object>((Class<Object>) clz);
c2ds.put(clz, ds);
}
return ds;
}
}
@SuppressWarnings("unchecked")
public static <T> DataStore<T> createInMemory(final Class<T> clazz) throws DataStoreException {
return (DataStore<T>) InMemoryDataStore.getInstance(clazz, true);
}
@SuppressWarnings("unchecked")
public static <T> DataStore<T> createInMemory(final Class<T> clazz, final boolean keepExisting)
throws DataStoreException {
return (DataStore<T>) InMemoryDataStore.getInstance(clazz, !keepExisting);
}
private DataStore(final Map<KeyElement, T> wrapStore, final Class<T> clz) throws DataStoreException {
dataStore = Collections.synchronizedMap(wrapStore);
dataTypeClass = clz;
keyAccess = new KeyAccess(clz);
}
private DataStore(final Class<T> clz) throws DataStoreException {
this(new HashMap<KeyElement, T>(), clz);
}
public Class<T> getDataTypeClass() {
return dataTypeClass;
}
public String getEntityTypeName() {
return ANNOTATION_HELPER.extractEntityTypeName(dataTypeClass);
}
public T createInstance() {
try {
return dataTypeClass.newInstance();
} catch (InstantiationException e) {
throw new AnnotationRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
} catch (IllegalAccessException e) {
throw new AnnotationRuntimeException("Unable to create instance of class '" + dataTypeClass + "'.", e);
}
}
public T read(final T obj) {
KeyElement objKeys = getKeys(obj);
return dataStore.get(objKeys);
}
public Collection<T> read() {
return Collections.unmodifiableCollection(dataStore.values());
}
public T create(final T object) throws DataStoreException {
KeyElement keyElement = getKeys(object);
return create(object, keyElement);
}
/**
* Store an entity, preserving any existing keys if possible. If the combination of
* existing and generated keys would produce a duplicate entry, replace all keys.
*/
private T create(final T object, final KeyElement keyElement) throws DataStoreException {
synchronized (dataStore) {
final boolean replaceKeys = dataStore.containsKey(keyElement);
if (keyElement.keyValuesMissing() || replaceKeys) {
KeyElement newKey = createSetAndGetKeys(object, replaceKeys);
return this.create(object, newKey);
}
dataStore.put(keyElement, object);
}
return object;
}
public T update(final T object) {
KeyElement keyElement = getKeys(object);
synchronized (dataStore) {
dataStore.remove(keyElement);
dataStore.put(keyElement, object);
}
return object;
}
public T delete(final T object) {
KeyElement keyElement = getKeys(object);
synchronized (dataStore) {
return dataStore.remove(keyElement);
}
}
/**
* Are the key values equal for both instances.
* If all compared key values are <code>null</code> this also means equal.
*
* @param first first instance to check for key equal
* @param second second instance to check for key equal
* @return <code>true</code> if object instance have equal keys set.
*/
public boolean isKeyEqual(final T first, final T second) {
KeyElement firstKeys = getKeys(first);
KeyElement secondKeys = getKeys(second);
return firstKeys.equals(secondKeys);
}
/**
* Are the key values equal for both instances.
* If all compared key values are <code>null</code> this also means equal.
* Before object (keys) are compared it is validated that both object instance are NOT null
* and that both are from the same class as this {@link DataStore} (see {@link #dataTypeClass}).
* For the equal check on {@link #dataTypeClass} instances without validation see {@link #isKeyEqual(Object, Object)}.
*
* @param first first instance to check for key equal
* @param second second instance to check for key equal
* @return <code>true</code> if object instance have equal keys set.
*/
@SuppressWarnings("unchecked")
public boolean isKeyEqualChecked(final Object first, final Object second) throws DataStoreException {
if (first == null || second == null) {
throw new DataStoreException("Tried to compare null values which is not allowed.");
} else if (first.getClass() != dataTypeClass) {
throw new DataStoreException("First value is no instance from required class '" + dataTypeClass + "'.");
} else if (second.getClass() != dataTypeClass) {
throw new DataStoreException("Second value is no instance from required class '" + dataTypeClass + "'.");
}
return isKeyEqual((T) first, (T) second);
}
private class KeyElement {
private int cachedHashCode = 42;
private final List<Object> keyValues;
public KeyElement(final int size) {
keyValues = new ArrayList<Object>(size);
}
private void addValue(final Object keyValue) {
keyValues.add(keyValue);
cachedHashCode = 89 * cachedHashCode + (keyValue != null ? keyValue.hashCode() : 0);
}
boolean keyValuesMissing() {
return keyValues.contains(null);
}
@Override
public int hashCode() {
return cachedHashCode;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("unchecked")
final KeyElement other = (KeyElement) obj;
if (this.keyValues != other.keyValues && (this.keyValues == null || !this.keyValues.equals(other.keyValues))) {
return false;
}
return true;
}
@Override
public String toString() {
return "KeyElement{" + "cachedHashCode=" + cachedHashCode + ", keyValues=" + keyValues + '}';
}
}
private class KeyAccess {
final List<Field> keyFields;
final AtomicInteger idCounter = new AtomicInteger(1);
KeyAccess(final Class<?> clazz) throws DataStoreException {
keyFields = ANNOTATION_HELPER.getAnnotatedFields(clazz, EdmKey.class);
if (keyFields.isEmpty()) {
throw new DataStoreException("No EdmKey annotated fields found for class " + clazz);
}
}
KeyElement getKeyValues(final T object) {
KeyElement keyElement = new KeyElement(keyFields.size());
for (Field field : keyFields) {
Object keyValue = ClassHelper.getFieldValue(object, field);
keyElement.addValue(keyValue);
}
return keyElement;
}
KeyElement createSetAndGetKeys(final T object, boolean replaceKeys) throws DataStoreException {
KeyElement keyElement = new KeyElement(keyFields.size());
for (Field field : keyFields) {
Object key = ClassHelper.getFieldValue(object, field);
if (key == null || replaceKeys) {
key = createKey(field);
ClassHelper.setFieldValue(object, field, key);
}
keyElement.addValue(key);
}
return keyElement;
}
private Object createKey(final Field field) {
Class<?> type = field.getType();
if (type == String.class) {
return String.valueOf(idCounter.getAndIncrement());
} else if (type == Integer.class || type == int.class) {
return Integer.valueOf(idCounter.getAndIncrement());
} else if (type == Long.class || type == long.class) {
return Long.valueOf(idCounter.getAndIncrement());
} else if (type == UUID.class) {
return UUID.randomUUID();
}
throw new UnsupportedOperationException("Automated key generation for type '" + type
+ "' is not supported (caused on field '" + field + "').");
}
}
private KeyElement getKeys(final T object) {
return keyAccess.getKeyValues(object);
}
private KeyElement createSetAndGetKeys(final T object, boolean replaceKeys) throws DataStoreException {
return keyAccess.createSetAndGetKeys(object, replaceKeys);
}
public static class DataStoreException extends ODataApplicationException {
private static final long serialVersionUID = 42L;
public DataStoreException(final String message) {
this(message, null);
}
public DataStoreException(final String message, final Throwable cause) {
super(message, Locale.ENGLISH, cause);
}
}
}