| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |