/* * Copyright 2004-2009 the original author or authors. * * Licensed 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.compass.core.impl; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.compass.core.CompassAnalyzerHelper; import org.compass.core.CompassException; import org.compass.core.CompassHits; import org.compass.core.CompassQuery; import org.compass.core.CompassQueryBuilder; import org.compass.core.CompassQueryFilterBuilder; import org.compass.core.CompassSession; import org.compass.core.CompassTermFreqsBuilder; import org.compass.core.CompassTransaction; import org.compass.core.Resource; import org.compass.core.ResourceFactory; import org.compass.core.cache.first.FirstLevelCache; import org.compass.core.cache.first.NullFirstLevelCache; import org.compass.core.cascade.CascadingManager; import org.compass.core.config.CompassSettings; import org.compass.core.config.RuntimeCompassSettings; import org.compass.core.engine.SearchEngine; import org.compass.core.engine.SearchEngineAnalyzerHelper; import org.compass.core.engine.SearchEngineQueryBuilder; import org.compass.core.events.FilterOperation; import org.compass.core.lucene.LuceneEnvironment; import org.compass.core.mapping.Cascade; import org.compass.core.mapping.CompassMapping; import org.compass.core.marshall.DefaultMarshallingStrategy; import org.compass.core.marshall.MarshallingContext; import org.compass.core.marshall.MarshallingException; import org.compass.core.marshall.MarshallingStrategy; import org.compass.core.metadata.CompassMetaData; import org.compass.core.spi.DirtyOperationContext; import org.compass.core.spi.InternalCompass; import org.compass.core.spi.InternalCompassSession; import org.compass.core.spi.InternalResource; import org.compass.core.spi.InternalSessionDelegateClose; import org.compass.core.spi.ResourceKey; import org.compass.core.transaction.LocalTransaction; import org.compass.core.transaction.LocalTransactionFactory; import org.compass.core.transaction.TransactionFactory; /** * @author kimchy */ // TODO we need to support multiple resource with ResourceKey and first cache public class DefaultCompassSession implements InternalCompassSession { private static final Log logger = LogFactory.getLog(DefaultCompassSession.class); private final InternalCompass compass; private final CompassMapping mapping; private final CompassMetaData compassMetaData; private final SearchEngine searchEngine; private final TransactionFactory transactionFactory; private final LocalTransactionFactory localTransactionFactory; private final MarshallingStrategy marshallingStrategy; private FirstLevelCache firstLevelCache; private volatile boolean closed = false; private final RuntimeCompassSettings runtimeSettings; private final CascadingManager cascadingManager; private boolean localTransaction; private volatile CompassTransaction transaction; private final List<InternalSessionDelegateClose> delegateClose = new ArrayList<InternalSessionDelegateClose>(); public DefaultCompassSession(RuntimeCompassSettings runtimeSettings, InternalCompass compass, SearchEngine searchEngine, FirstLevelCache firstLevelCache) { this.compass = compass; this.mapping = compass.getMapping(); this.compassMetaData = compass.getMetaData(); this.transactionFactory = compass.getTransactionFactory(); this.localTransactionFactory = compass.getLocalTransactionFactory(); this.runtimeSettings = runtimeSettings; this.searchEngine = searchEngine; this.firstLevelCache = firstLevelCache; this.marshallingStrategy = new DefaultMarshallingStrategy(mapping, searchEngine, compass.getConverterLookup(), this); this.cascadingManager = new CascadingManager(this); transaction = transactionFactory.tryJoinExistingTransaction(this); } public ResourceFactory resourceFactory() { return compass.getResourceFactory(); } public CompassSettings getSettings() { return runtimeSettings; } public void setReadOnly() { searchEngine.setReadOnly(); } public boolean isReadOnly() { return searchEngine.isReadOnly(); } public CompassQueryBuilder queryBuilder() throws CompassException { checkClosed(); SearchEngineQueryBuilder searchEngineQueryBuilder = searchEngine.queryBuilder(); return new DefaultCompassQueryBuilder(searchEngineQueryBuilder, compass, this); } public CompassQueryFilterBuilder queryFilterBuilder() throws CompassException { checkClosed(); return compass.queryFilterBuilder(); } public CompassTermFreqsBuilder termFreqsBuilder(String... names) throws CompassException { checkClosed(); return new DefaultCompassTermFreqsBuilder(this, names); } public CompassAnalyzerHelper analyzerHelper() throws CompassException { checkClosed(); SearchEngineAnalyzerHelper analyzerHelper = searchEngine.analyzerHelper(); return new DefaultCompassAnalyzerHelper(analyzerHelper, this); } public CompassSession useLocalTransaction() { if (transaction != null && !(transaction instanceof LocalTransaction)) { throw new IllegalStateException("There is already a transaction bounded to this session, and it is not a local one"); } this.localTransaction = true; return this; } public CompassTransaction beginTransaction() throws CompassException { checkClosed(); if (LuceneEnvironment.Transaction.Processor.Lucene.NAME.equalsIgnoreCase(getSettings().getSetting(LuceneEnvironment.Transaction.Processor.TYPE))) { firstLevelCache = NullFirstLevelCache.INSTANCE; } transaction = transactionFactory.beginTransaction(this); return transaction; } public CompassTransaction beginLocalTransaction() throws CompassException { checkClosed(); if (LuceneEnvironment.Transaction.Processor.Lucene.NAME.equalsIgnoreCase(getSettings().getSetting(LuceneEnvironment.Transaction.Processor.TYPE))) { firstLevelCache = new NullFirstLevelCache(); } transaction = localTransactionFactory.beginTransaction(this); return transaction; } public void flush() throws CompassException { checkClosed(); searchEngine.flush(); } public void flushCommit(String... aliases) throws CompassException { checkClosed(); searchEngine.flushCommit(aliases); } public Resource getResource(Class clazz, Object... ids) throws CompassException { return getResource(clazz, (Object) ids); } public Resource getResource(Class clazz, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(clazz, id); if (idResource == null) { return null; } return getResourceByIdResource(idResource); } public Resource getResource(String alias, Object... ids) throws CompassException { return getResource(alias, (Object) ids); } public Resource getResource(String alias, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(alias, id); if (idResource == null) { return null; } return getResourceByIdResource(idResource); } public Resource getResourceByIdResource(Resource idResource) { checkClosed(); startTransactionIfNeeded(); ResourceKey key = ((InternalResource) idResource).getResourceKey(); Resource cachedValue = firstLevelCache.getResource(key); if (cachedValue != null) { return cachedValue; } Resource value = searchEngine.get(idResource); if (value != null) { firstLevelCache.setResource(key, value); } return value; } public Resource getResourceByIdResourceNoCache(Resource idResource) { checkClosed(); startTransactionIfNeeded(); return searchEngine.get(idResource); } public <T> T get(Class<T> clazz, Object... ids) throws CompassException { return get(clazz, (Object) ids); } public <T> T get(Class<T> clazz, Object id) throws CompassException { Resource resource = getResource(clazz, id); if (resource == null) { return null; } //noinspection unchecked return (T) getByResource(resource); } public Object get(String alias, Object... ids) throws CompassException { return get(alias, (Object) ids); } public Object get(String alias, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource resource = getResource(alias, id); if (resource == null) { return null; } return getByResource(resource); } public Object get(String alias, Object id, MarshallingContext context) throws CompassException { checkClosed(); Resource resource = getResource(alias, id); if (resource == null) { return null; } return getByResource(resource, context); } public Object getByResource(Resource resource) { checkClosed(); return getByResource(resource, null); } public Object getByResource(Resource resource, MarshallingContext context) { checkClosed(); startTransactionIfNeeded(); ResourceKey key = ((InternalResource) resource).getResourceKey(); Object cachedValue = firstLevelCache.get(key); if (cachedValue != null) { return cachedValue; } Object value; if (context == null) { value = marshallingStrategy.unmarshall(resource); } else { value = marshallingStrategy.unmarshall(resource, context); } firstLevelCache.set(key, value); return value; } public Resource loadResource(Class clazz, Object... ids) throws CompassException { return loadResource(clazz, (Object) ids); } public Resource loadResource(Class clazz, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(clazz, id); return loadResourceByIdResource(idResource); } public Resource loadResource(String alias, Object... ids) throws CompassException { return loadResource(alias, (Object) ids); } public Resource loadResource(String alias, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(alias, id); return loadResourceByIdResource(idResource); } public Resource loadResourceByIdResource(Resource idResource) { checkClosed(); startTransactionIfNeeded(); ResourceKey key = ((InternalResource) idResource).getResourceKey(); Resource cachedValue = firstLevelCache.getResource(key); if (cachedValue != null) { return cachedValue; } Resource value = searchEngine.load(idResource); firstLevelCache.setResource(key, value); return value; } public <T> T load(Class<T> clazz, Object... ids) throws CompassException { return load(clazz, (Object) ids); } public <T> T load(Class<T> clazz, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource resource = loadResource(clazz, id); //noinspection unchecked return (T) getByResource(resource); } public Object load(String alias, Object... ids) throws CompassException { return load(alias, (Object) ids); } public Object load(String alias, Object id) throws CompassException { checkClosed(); startTransactionIfNeeded(); Resource resource = loadResource(alias, id); return getByResource(resource); } public CompassHits find(String query) throws CompassException { checkClosed(); startTransactionIfNeeded(); return queryBuilder().queryString(query).toQuery().hits(); } public void create(String alias, Object object) throws CompassException { checkClosed(); startTransactionIfNeeded(); create(alias, object, new DirtyOperationContext()); } public void create(String alias, Object object, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(object)) { return; } if (compass.getEventManager().onPreCreate(alias, object) == FilterOperation.YES) { return; } Resource resource = marshallingStrategy.marshall(alias, object); if (resource != null) { if (compass.getEventManager().onPreCreate(resource) == FilterOperation.YES) { return; } searchEngine.create(resource); ResourceKey key = ((InternalResource) resource).getResourceKey(); firstLevelCache.set(key, object); firstLevelCache.setResource(key, resource); } context.addOperatedObjects(object); boolean performedCascading = cascadingManager.cascade(alias, object, Cascade.CREATE, context); if (resource == null && !performedCascading) { throw new MarshallingException("Alias [" + alias + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (resource != null) { compass.getEventManager().onPostCreate(resource); } compass.getEventManager().onPostCreate(alias, object); } public void create(Object object) throws CompassException { checkClosed(); startTransactionIfNeeded(); create(object, new DirtyOperationContext()); } public void create(Object object, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(object)) { return; } if (compass.getEventManager().onPreCreate(null, object) == FilterOperation.YES) { return; } boolean performedCascading; Resource resource = marshallingStrategy.marshall(object); if (resource != null) { if (compass.getEventManager().onPreCreate(resource) == FilterOperation.YES) { return; } searchEngine.create(resource); ResourceKey key = ((InternalResource) resource).getResourceKey(); firstLevelCache.set(key, object); firstLevelCache.setResource(key, resource); context.addOperatedObjects(object); // if we found a resource, we perform the cascading based on its alias performedCascading = cascadingManager.cascade(key.getAlias(), object, Cascade.CREATE, context); } else { context.addOperatedObjects(object); // actuall, no root mapping to create a resource, try and create one based on the object performedCascading = cascadingManager.cascade(object, Cascade.CREATE, context); } if (resource == null && !performedCascading) { throw new MarshallingException("Object [" + object.getClass().getName() + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (resource != null) { compass.getEventManager().onPostCreate(resource); compass.getEventManager().onPostCreate(resource.getAlias(), object); } else { compass.getEventManager().onPostCreate(null, object); } } public void save(String alias, Object object) throws CompassException { checkClosed(); startTransactionIfNeeded(); save(alias, object, new DirtyOperationContext()); } public void save(String alias, Object object, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(object)) { return; } if (compass.getEventManager().onPreSave(alias, object) == FilterOperation.YES) { return; } Resource resource = marshallingStrategy.marshall(alias, object); if (resource != null) { if (compass.getEventManager().onPreSave(resource) == FilterOperation.YES) { return; } searchEngine.save(resource); ResourceKey key = ((InternalResource) resource).getResourceKey(); firstLevelCache.set(key, object); firstLevelCache.setResource(key, resource); } context.addOperatedObjects(object); boolean performedCascading = cascadingManager.cascade(alias, object, Cascade.SAVE, context); if (resource == null && !performedCascading) { throw new MarshallingException("Alias [" + alias + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (resource != null) { compass.getEventManager().onPostSave(resource); } compass.getEventManager().onPostSave(alias, object); } public void save(Object object) throws CompassException { checkClosed(); startTransactionIfNeeded(); save(object, new DirtyOperationContext()); } public void save(Object object, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(object)) { return; } if (compass.getEventManager().onPreSave(null, object) == FilterOperation.YES) { return; } boolean performedCascading; Resource resource = marshallingStrategy.marshall(object); if (resource != null) { if (compass.getEventManager().onPreSave(resource) == FilterOperation.YES) { return; } searchEngine.save(resource); ResourceKey key = ((InternalResource) resource).getResourceKey(); firstLevelCache.setResource(key, resource); firstLevelCache.set(key, object); context.addOperatedObjects(object); performedCascading = cascadingManager.cascade(key.getAlias(), object, Cascade.SAVE, context); } else { context.addOperatedObjects(object); performedCascading = cascadingManager.cascade(object, Cascade.SAVE, context); } if (resource == null && !performedCascading) { throw new MarshallingException("Object [" + object.getClass().getName() + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (resource != null) { compass.getEventManager().onPostSave(resource); compass.getEventManager().onPostSave(resource.getAlias(), object); } else { compass.getEventManager().onPostSave(null, object); } } public void delete(String alias, Object... ids) throws CompassException { delete(alias, (Object) ids); } public void delete(String alias, Object obj) throws CompassException { checkClosed(); startTransactionIfNeeded(); delete(alias, obj, new DirtyOperationContext()); } public void delete(String alias, Object obj, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(obj)) { return; } if (compass.getEventManager().onPreDelete(alias, obj) == FilterOperation.YES) { return; } boolean performedCascading = false; Resource idResource = marshallingStrategy.marshallIds(alias, obj); if (idResource != null) { if (compass.getEventManager().onPreDelete(idResource) == FilterOperation.YES) { return; } Object cascadeObj = null; // in case we need to do cascading, we need to load the object to we can delete its cascaded objects if (cascadingManager.shouldCascade(idResource.getAlias(), obj, Cascade.DELETE)) { Resource resouce = getResourceByIdResource(idResource); if (resouce != null) { cascadeObj = getByResource(resouce); } } delete(idResource); if (cascadeObj != null) { context.addOperatedObjects(cascadeObj); performedCascading = cascadingManager.cascade(idResource.getAlias(), cascadeObj, Cascade.DELETE, context); } } else { context.addOperatedObjects(obj); performedCascading = cascadingManager.cascade(alias, obj, Cascade.DELETE, context); } if (idResource == null && !performedCascading) { throw new MarshallingException("Alias [" + alias + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (idResource != null) { compass.getEventManager().onPostDelete(idResource); } compass.getEventManager().onPostDelete(alias, obj); } public void delete(Class clazz, Object... ids) throws CompassException { delete(clazz, (Object) ids); } public void delete(Class clazz, Object obj) throws CompassException { checkClosed(); startTransactionIfNeeded(); delete(clazz, obj, new DirtyOperationContext()); } public void delete(Class clazz, Object obj, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(obj)) { return; } if (compass.getEventManager().onPreDelete(clazz, obj) == FilterOperation.YES) { return; } boolean performedCascading = false; Resource idResource = marshallingStrategy.marshallIds(clazz, obj); if (idResource != null) { if (compass.getEventManager().onPreDelete(idResource) == FilterOperation.YES) { return; } Object cascadeObj = null; // in case we need to do cascading, we need to load the object to we can delete its cascaded objects if (cascadingManager.shouldCascade(idResource.getAlias(), obj, Cascade.DELETE)) { Resource resouce = getResourceByIdResource(idResource); if (resouce != null) { cascadeObj = getByResource(resouce); } } delete(idResource); if (cascadeObj != null) { context.addOperatedObjects(cascadeObj); performedCascading = cascadingManager.cascade(idResource.getAlias(), cascadeObj, Cascade.DELETE, context); } } else { context.addOperatedObjects(obj); performedCascading = cascadingManager.cascade(clazz, obj, Cascade.DELETE, context); } if (idResource == null && !performedCascading) { throw new MarshallingException("Object [" + clazz + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (idResource != null) { compass.getEventManager().onPostDelete(idResource); compass.getEventManager().onPostDelete(idResource.getAlias(), obj); } else { compass.getEventManager().onPostDelete(clazz, obj); } } public void delete(Object obj) throws CompassException { checkClosed(); startTransactionIfNeeded(); delete(obj, new DirtyOperationContext()); } public void delete(Object obj, DirtyOperationContext context) throws CompassException { if (context.alreadyPerformedOperation(obj)) { return; } if (compass.getEventManager().onPreDelete((String) null, obj) == FilterOperation.YES) { return; } boolean performedCascading = false; Resource idResource = marshallingStrategy.marshallIds(obj); if (idResource != null) { if (compass.getEventManager().onPreDelete(idResource) == FilterOperation.YES) { return; } Object cascadeObj = null; // in case we need to do cascading, we need to load the object to we can delete its cascaded objects if (cascadingManager.shouldCascade(idResource.getAlias(), obj, Cascade.DELETE)) { Resource resouce = getResourceByIdResource(idResource); if (resouce != null) { cascadeObj = getByResource(resouce); } } delete(idResource); if (cascadeObj != null) { context.addOperatedObjects(cascadeObj); performedCascading = cascadingManager.cascade(idResource.getAlias(), cascadeObj, Cascade.DELETE, context); } } else { context.addOperatedObjects(obj); performedCascading = cascadingManager.cascade(obj, Cascade.DELETE, context); } if (idResource == null && !performedCascading) { throw new MarshallingException("Object [" + obj.getClass().getName() + "] has no root mappings and no cascading defined, no operation was perfomed"); } if (idResource != null) { compass.getEventManager().onPostDelete(idResource); compass.getEventManager().onPostDelete(idResource.getAlias(), obj); } else { compass.getEventManager().onPostDelete((String) null, obj); } } public void delete(Resource resource) throws CompassException { checkClosed(); startTransactionIfNeeded(); firstLevelCache.evict(((InternalResource) resource).getResourceKey()); if (compass.getEventManager().onPreDelete(resource) == FilterOperation.YES) { return; } searchEngine.delete(resource); compass.getEventManager().onPostDelete(resource); } public void delete(CompassQuery query) throws CompassException { checkClosed(); startTransactionIfNeeded(); query.delete(); } public void evict(Object obj) { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(obj.getClass(), obj); ResourceKey key = ((InternalResource) idResource).getResourceKey(); firstLevelCache.evict(key); } public void evict(String alias, Object id) { checkClosed(); startTransactionIfNeeded(); Resource idResource = marshallingStrategy.marshallIds(alias, id); ResourceKey key = ((InternalResource) idResource).getResourceKey(); firstLevelCache.evict(key); } public void evict(Resource resource) { checkClosed(); ResourceKey key = ((InternalResource) resource).getResourceKey(); firstLevelCache.evict(key); } public void evictAll() { checkClosed(); firstLevelCache.evictAll(); } public void addDelegateClose(InternalSessionDelegateClose delegateClose) { this.delegateClose.add(delegateClose); } public void unbindTransaction() { transaction = null; } private boolean rolledback; public void rollback() throws CompassException { try { if (transaction != null) { rolledback = true; transaction.rollback(); } } finally { close(); } } public void commit() throws CompassException { try { if (transaction != null) { transaction.commit(); transaction = null; } } finally { close(); } } public void close() throws CompassException { if (closed) { return; } for (InternalSessionDelegateClose delegateClose : this.delegateClose) { delegateClose.close(); } // if we are closing this session, and we still have a transction open, we can commit it automatically // Note: committing a transaction does not always end up with actual commit, for example, if it is an // "outer" managed transaction CompassException ex = null; if (transaction != null) { try { transaction.commit(); } catch (CompassException e) { ex = e; try { transaction.rollback(); } catch (Exception e1) { logger.warn("Failed to rollback transaction after auto commit on session close, ignoring", e); } } } CompassSession transactionBoundSession = transactionFactory.getTransactionBoundSession(); // if there is no bounded transaction (it was already committed / rolledback, thus unbounded) // or if the current bounded session is not the same one, we need to close the session if (transactionBoundSession == null || transactionBoundSession != this) { closed = true; if (transaction != null && !rolledback) { logger.warn("Session close without being committed / rolledback, rolling back the transaction"); try { transaction.rollback(); } catch (CompassException e) { logger.debug("Failed to rollback un-committed transaction", e); } } firstLevelCache.evictAll(); searchEngine.close(); } if (ex != null) { throw ex; } } public boolean isClosed() { return this.closed; } public InternalCompass getCompass() { return compass; } public SearchEngine getSearchEngine() { return searchEngine; } public MarshallingStrategy getMarshallingStrategy() { return marshallingStrategy; } public FirstLevelCache getFirstLevelCache() { return firstLevelCache; } public CompassMapping getMapping() { return mapping; } public CompassMetaData getMetaData() { return compassMetaData; } public void startTransactionIfNeeded() { if (rolledback) { throw new CompassException("Transaction already rolled back"); } if (transaction == null) { synchronized (this) { if (transaction == null) { if (localTransaction) { transaction = beginLocalTransaction(); } else { transaction = beginTransaction(); } } } } } private void checkClosed() throws IllegalStateException { if (closed) { throw new IllegalStateException("CompassSession already closed"); } } }