| /* |
| * Copyright (c) 2008-2013, 2016 Eike Stepper (Loehne, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Martin Taal - initial api |
| * Eike Stepper - maintenance |
| */ |
| package org.eclipse.emf.cdo.server.internal.hibernate.tuplizer; |
| |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDExternal; |
| import org.eclipse.emf.cdo.common.model.CDOModelUtil; |
| import org.eclipse.emf.cdo.common.model.CDOType; |
| import org.eclipse.emf.cdo.common.revision.CDOListFactory; |
| import org.eclipse.emf.cdo.common.revision.CDORevision; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; |
| import org.eclipse.emf.cdo.server.IStoreChunkReader.Chunk; |
| import org.eclipse.emf.cdo.server.internal.hibernate.HibernateStoreAccessor; |
| import org.eclipse.emf.cdo.server.internal.hibernate.HibernateStoreChunkReader; |
| import org.eclipse.emf.cdo.server.internal.hibernate.HibernateThreadContext; |
| import org.eclipse.emf.cdo.server.internal.hibernate.HibernateUtil; |
| import org.eclipse.emf.cdo.spi.common.revision.CDOReferenceAdjuster; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDOList; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; |
| |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| |
| import org.hibernate.collection.internal.AbstractPersistentCollection; |
| import org.hibernate.engine.spi.CollectionEntry; |
| import org.hibernate.engine.spi.SessionImplementor; |
| import org.hibernate.persister.collection.CollectionPersister; |
| import org.hibernate.proxy.HibernateProxy; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| |
| /** |
| * Wraps a moveable list so that hibernate always sees an object view while cdo always sees a cdoid view. The same for |
| * EEnum: cdo wants to see an int (the ordinal), hibernate the real eenum value. This to support querying with EENum |
| * parameters. |
| * |
| * @author Martin Taal |
| */ |
| public class WrappedHibernateList implements InternalCDOList |
| { |
| private List<Object> delegate; |
| |
| private boolean frozen; |
| |
| private int cachedSize = -1; |
| |
| private final EStructuralFeature eFeature; |
| |
| private final InternalCDORevision owner; |
| |
| private Chunk cachedChunk; |
| |
| private int currentListChunk = -1; |
| |
| private boolean resolveCDOID; |
| |
| public WrappedHibernateList(InternalCDORevision owner, EStructuralFeature eFeature) |
| { |
| this.owner = owner; |
| this.eFeature = eFeature; |
| final HibernateStoreAccessor accessor = HibernateThreadContext.getCurrentStoreAccessor(); |
| if (accessor != null) |
| { |
| currentListChunk = accessor.getCurrentListChunk(); |
| } |
| resolveCDOID = !HibernateUtil.getInstance().isCDOResourceContents(eFeature) && eFeature instanceof EReference; |
| } |
| |
| public void move(int newPosition, Object object) |
| { |
| checkFrozen(); |
| move(newPosition, indexOf(object)); |
| } |
| |
| public Object move(int targetIndex, int sourceIndex) |
| { |
| checkFrozen(); |
| int size = size(); |
| if (sourceIndex >= size) |
| { |
| throw new IndexOutOfBoundsException("sourceIndex=" + sourceIndex + ", size=" + size); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| if (targetIndex >= size) |
| { |
| throw new IndexOutOfBoundsException("targetIndex=" + targetIndex + ", size=" + size); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| Object object = get(sourceIndex); |
| if (targetIndex == sourceIndex) |
| { |
| return object; |
| } |
| |
| if (targetIndex < sourceIndex) |
| { |
| moveUp1(targetIndex, sourceIndex - targetIndex); |
| } |
| else |
| { |
| moveDown1(targetIndex, targetIndex - sourceIndex); |
| } |
| |
| set(targetIndex, object); |
| return object; |
| } |
| |
| private void moveUp1(int index, int count) |
| { |
| for (int i = count; i > 0; i--) |
| { |
| set(index + i, get(index + i - 1)); |
| } |
| } |
| |
| private void moveDown1(int index, int count) |
| { |
| for (int i = count; i > 0; i--) |
| { |
| set(index - i, get(index - i + 1)); |
| } |
| } |
| |
| /** |
| * There's a duplicate of this method in CDOListImpl!!! |
| */ |
| public boolean adjustReferences(CDOReferenceAdjuster adjuster, EStructuralFeature feature) |
| { |
| boolean changed = false; |
| |
| CDOType type = CDOModelUtil.getType(feature); |
| int size = size(); |
| for (int i = 0; i < size; i++) |
| { |
| Object element = get(i); |
| Object newID = type.adjustReferences(adjuster, element, feature, i); |
| if (newID != element) // Just an optimization for NOOP adjusters |
| { |
| set(i, newID); |
| changed = true; |
| } |
| } |
| |
| return changed; |
| } |
| |
| /** |
| * Not loaded and not loadable anymore because the collection is disconnected |
| */ |
| public boolean isUninitializedCollection() |
| { |
| // note the getDelegate checks if the underlying persistentcollection |
| // is loaded or connected |
| final Object theDelegate = getDelegate(); |
| if (theDelegate instanceof UninitializedCollection) |
| { |
| return true; |
| } |
| |
| if (theDelegate instanceof WrappedHibernateList) |
| { |
| return ((WrappedHibernateList)theDelegate).isUninitializedCollection(); |
| } |
| return false; |
| } |
| |
| public InternalCDOList clone(EClassifier classifier) |
| { |
| CDOType type = CDOModelUtil.getType(classifier); |
| int size = size(); |
| InternalCDOList list = (InternalCDOList)CDOListFactory.DEFAULT.createList(size, 0, 0); |
| for (int i = 0; i < size; i++) |
| { |
| list.add(type.copyValue(get(i))); |
| } |
| return list; |
| } |
| |
| /** |
| * @return the delegate |
| */ |
| public List<Object> getDelegate() |
| { |
| // if we got disconnected then internally use a new autoexpanding list |
| if (delegate instanceof AbstractPersistentCollection && !((AbstractPersistentCollection)delegate).wasInitialized() && !isConnectedToSession()) |
| { |
| delegate = new UninitializedCollection<Object>() |
| { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public Object set(int index, Object element) |
| { |
| ensureSize(index); |
| return super.set(index, element); |
| } |
| |
| @Override |
| public Object get(int index) |
| { |
| ensureSize(index); |
| final Object o = super.get(index); |
| if (o == null) |
| { |
| return CDORevisionUtil.UNINITIALIZED; |
| } |
| return o; |
| } |
| |
| private void ensureSize(int index) |
| { |
| if (index >= size()) |
| { |
| for (int i = size() - 1; i <= index; i++) |
| { |
| add(null); |
| } |
| } |
| } |
| |
| }; |
| } |
| |
| return delegate; |
| } |
| |
| protected boolean isConnectedToSession() |
| { |
| final AbstractPersistentCollection persistentCollection = (AbstractPersistentCollection)delegate; |
| final SessionImplementor session = persistentCollection.getSession(); |
| return session != null && session.isOpen() && session.getPersistenceContext().containsCollection(persistentCollection); |
| } |
| |
| /** |
| * @param delegate |
| * the delegate to set |
| */ |
| public void setDelegate(List<Object> delegate) |
| { |
| this.delegate = delegate; |
| } |
| |
| private static Object convertToCDO(Object value) |
| { |
| if (value == null) |
| { |
| return null; |
| } |
| |
| if (value instanceof CDORevision || value instanceof HibernateProxy) |
| { |
| return HibernateUtil.getInstance().getCDOID(value); |
| } |
| |
| if (value instanceof EEnumLiteral) |
| { |
| return ((EEnumLiteral)value).getValue(); |
| } |
| |
| return value; |
| } |
| |
| private static List<Object> convertToCDO(List<?> ids) |
| { |
| List<Object> result = new ArrayList<Object>(); |
| for (Object o : ids) |
| { |
| result.add(convertToCDO(o)); |
| } |
| |
| return result; |
| } |
| |
| protected Object getHibernateValue(Object o) |
| { |
| if (o instanceof CDOIDExternal) |
| { |
| return o; |
| } |
| |
| if (o instanceof CDOID && resolveCDOID) |
| { |
| return HibernateUtil.getInstance().getCDORevision((CDOID)o); |
| } |
| |
| return o; |
| } |
| |
| protected List<Object> getHibernateValues(Collection<?> c) |
| { |
| List<Object> newC = new ArrayList<Object>(); |
| for (Object o : c) |
| { |
| newC.add(getHibernateValue(o)); |
| } |
| |
| return newC; |
| } |
| |
| public void add(int index, Object element) |
| { |
| checkFrozen(); |
| getDelegate().add(index, getHibernateValue(element)); |
| } |
| |
| public boolean add(Object o) |
| { |
| checkFrozen(); |
| return getDelegate().add(getHibernateValue(o)); |
| } |
| |
| public boolean addAll(Collection<? extends Object> c) |
| { |
| checkFrozen(); |
| return getDelegate().addAll(getHibernateValues(c)); |
| } |
| |
| public boolean addAll(int index, Collection<? extends Object> c) |
| { |
| checkFrozen(); |
| return getDelegate().addAll(index, getHibernateValues(c)); |
| } |
| |
| public void clear() |
| { |
| checkFrozen(); |
| getDelegate().clear(); |
| } |
| |
| public boolean contains(Object o) |
| { |
| return getDelegate().contains(getHibernateValue(o)); |
| } |
| |
| public boolean containsAll(Collection<?> c) |
| { |
| return getDelegate().containsAll(getHibernateValues(c)); |
| } |
| |
| public Object get(int index) |
| { |
| Object delegateValue = getDelegate().get(index); |
| |
| // not loaded, force the load |
| if (delegateValue == CDORevisionUtil.UNINITIALIZED) |
| { |
| delegateValue = getChunkedValue(index); |
| } |
| |
| if (delegateValue instanceof CDOID) |
| { |
| return delegateValue; |
| } |
| |
| return convertToCDO(delegateValue); |
| } |
| |
| public Object get(int index, boolean resolve) |
| { |
| Object delegateValue = getDelegate().get(index); |
| |
| // if resolve==false then the caller can handle uninitialized objects. |
| if (!resolve && delegateValue == CDORevisionUtil.UNINITIALIZED) |
| { |
| return CDORevisionUtil.UNINITIALIZED; |
| } |
| |
| // else force the load |
| return get(index); |
| } |
| |
| private Object getChunkedValue(int index) |
| { |
| readChunk(index); |
| if (cachedChunk != null) |
| { |
| // note index must be within the range as the chunk |
| // is read again if index is too large. |
| return cachedChunk.get(index - cachedChunk.getStartIndex()); |
| } |
| return null; |
| } |
| |
| private void readChunk(int index) |
| { |
| if (cachedChunk != null) |
| { |
| if (cachedChunk.getStartIndex() <= index && index < cachedChunk.getStartIndex() + cachedChunk.size()) |
| { |
| // a valid chunk |
| return; |
| } |
| // a not valid chunk reread it |
| // TODO: cache chunks also |
| cachedChunk = null; |
| } |
| final HibernateStoreAccessor accessor = HibernateThreadContext.getCurrentStoreAccessor(); |
| if (accessor == null) |
| { |
| return; |
| } |
| |
| // read in batches always |
| // if the currentListChunk is not set then read a sizeable chunk |
| int chunkSize = Math.max(100, currentListChunk); |
| final HibernateStoreChunkReader chunkReader = accessor.createChunkReader(owner, eFeature); |
| chunkReader.addRangedChunk(index, index + chunkSize); |
| cachedChunk = chunkReader.executeRead().get(0); |
| } |
| |
| public int indexOf(Object o) |
| { |
| return getDelegate().indexOf(getHibernateValue(o)); |
| } |
| |
| public boolean isEmpty() |
| { |
| return getDelegate().isEmpty(); |
| } |
| |
| public Iterator<Object> iterator() |
| { |
| return new CDOHibernateIterator(getDelegate().iterator()); |
| } |
| |
| public int lastIndexOf(Object o) |
| { |
| return getDelegate().lastIndexOf(getHibernateValue(o)); |
| } |
| |
| public ListIterator<Object> listIterator() |
| { |
| return new CDOHibernateListIterator(this, getDelegate().listIterator()); |
| } |
| |
| public ListIterator<Object> listIterator(int index) |
| { |
| return new CDOHibernateListIterator(this, getDelegate().listIterator(index)); |
| } |
| |
| public Object remove(int index) |
| { |
| checkFrozen(); |
| return getDelegate().remove(index); |
| } |
| |
| public boolean remove(Object o) |
| { |
| checkFrozen(); |
| return getDelegate().remove(getHibernateValue(o)); |
| } |
| |
| public boolean removeAll(Collection<?> c) |
| { |
| checkFrozen(); |
| return getDelegate().removeAll(getHibernateValues(c)); |
| } |
| |
| public boolean retainAll(Collection<?> c) |
| { |
| return getDelegate().retainAll(getHibernateValues(c)); |
| } |
| |
| public Object set(int index, Object element) |
| { |
| checkFrozen(); |
| |
| if (element == CDORevisionUtil.UNINITIALIZED) |
| { |
| return null; |
| } |
| |
| return getDelegate().set(index, getHibernateValue(element)); |
| } |
| |
| public int size() |
| { |
| if (cachedSize != -1) |
| { |
| return cachedSize; |
| } |
| if (getDelegate() instanceof AbstractPersistentCollection) |
| { |
| final AbstractPersistentCollection collection = (AbstractPersistentCollection)getDelegate(); |
| if (collection.wasInitialized()) |
| { |
| cachedSize = -1; |
| return getDelegate().size(); |
| } |
| final SessionImplementor session = collection.getSession(); |
| CollectionEntry entry = session.getPersistenceContext().getCollectionEntry(collection); |
| CollectionPersister persister = entry.getLoadedPersister(); |
| if (collection.hasQueuedOperations()) |
| { |
| session.flush(); |
| } |
| cachedSize = persister.getSize(entry.getLoadedKey(), session); |
| return cachedSize; |
| } |
| |
| return getDelegate().size(); |
| } |
| |
| public List<Object> subList(int fromIndex, int toIndex) |
| { |
| return convertToCDO(getDelegate().subList(fromIndex, toIndex)); |
| } |
| |
| public Object[] toArray() |
| { |
| Object[] result = new Object[size()]; |
| int i = 0; |
| for (Object o : this) |
| { |
| result[i++] = o; |
| } |
| |
| return result; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T> T[] toArray(T[] a) |
| { |
| int i = 0; |
| for (Object o : this) |
| { |
| a[i++] = (T)o; |
| } |
| |
| return a; |
| } |
| |
| private static final class CDOHibernateIterator implements Iterator<Object> |
| { |
| private final Iterator<?> delegate; |
| |
| public CDOHibernateIterator(Iterator<?> delegate) |
| { |
| this.delegate = delegate; |
| } |
| |
| public boolean hasNext() |
| { |
| return delegate.hasNext(); |
| } |
| |
| public Object next() |
| { |
| Object value = delegate.next(); |
| return convertToCDO(value); |
| } |
| |
| public void remove() |
| { |
| delegate.remove(); |
| } |
| } |
| |
| private static final class CDOHibernateListIterator implements ListIterator<Object> |
| { |
| private final ListIterator<Object> delegate; |
| |
| private final WrappedHibernateList owner; |
| |
| public CDOHibernateListIterator(WrappedHibernateList owner, ListIterator<Object> delegate) |
| { |
| this.delegate = delegate; |
| this.owner = owner; |
| } |
| |
| public void add(Object o) |
| { |
| owner.checkFrozen(); |
| |
| delegate.add(HibernateUtil.getInstance().getCDOID(o)); |
| } |
| |
| public boolean hasNext() |
| { |
| return delegate.hasNext(); |
| } |
| |
| public boolean hasPrevious() |
| { |
| return delegate.hasPrevious(); |
| } |
| |
| public Object next() |
| { |
| Object value = delegate.next(); |
| return convertToCDO(value); |
| } |
| |
| public int nextIndex() |
| { |
| return delegate.nextIndex(); |
| } |
| |
| public Object previous() |
| { |
| Object value = delegate.previous(); |
| return convertToCDO(value); |
| } |
| |
| public int previousIndex() |
| { |
| return delegate.previousIndex(); |
| } |
| |
| public void remove() |
| { |
| owner.checkFrozen(); |
| delegate.remove(); |
| } |
| |
| public void set(Object o) |
| { |
| owner.checkFrozen(); |
| delegate.set(HibernateUtil.getInstance().getCDOID(o)); |
| } |
| } |
| |
| public void freeze() |
| { |
| frozen = true; |
| } |
| |
| private void checkFrozen() |
| { |
| // a frozen check always implies a modification |
| cachedSize = -1; |
| if (frozen) |
| { |
| throw new IllegalStateException("Cannot modify a frozen list"); |
| } |
| } |
| |
| public void setWithoutFrozenCheck(int i, Object value) |
| { |
| getDelegate().set(i, getHibernateValue(value)); |
| } |
| |
| CDORevision getOwner() |
| { |
| return owner; |
| } |
| |
| // tagging interface |
| private class UninitializedCollection<E> extends ArrayList<E> |
| { |
| private static final long serialVersionUID = 1L; |
| } |
| } |