All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.semispace.persistence.SemiSpacePersistentAdmin Maven / Gradle / Ivy

Go to download

Persistence module of SemiSpace, able to provide JDBC storage of long lived objects.

There is a newer version: 1.2.0
Show newest version
/*
 * ============================================================================
 *
 *  File:     SemiSpacePersistentAdmin.java
 *----------------------------------------------------------------------------
 *
 * Copyright 2008 Erlend Nossum
 *
 * 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.
 *
 *  Description:  See javadoc below
 *
 *  Created:      May 18, 2008
 * ============================================================================ 
 */

package org.semispace.persistence;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.semispace.EventDistributor;
import org.semispace.Holder;
import org.semispace.SemiSpace;
import org.semispace.admin.SemiSpaceAdmin;
import org.semispace.admin.SemiSpaceAdminInterface;
import org.semispace.event.SemiAvailabilityEvent;
import org.semispace.event.SemiExpirationEvent;
import org.semispace.event.SemiRenewalEvent;
import org.semispace.event.SemiTakenEvent;
import org.semispace.persistence.dao.HibernateLeaseDao;
import org.semispace.persistence.dao.LeaseMeta;
import org.semispace.persistence.domain.Lease;
import org.semispace.persistence.domain.Tag;
import org.springframework.beans.factory.DisposableBean;

/**
 * Administration module that is able to persist data into
 * the database. This can be used for persisting long term
 * query objects, such as cache results. 
 * 
 * 

The benefits are *

    *
  • If you are not using terracotta, you still get persistence during restarts
  • *
  • When using terracotta, you can (more safely) upgrade
  • *
  • The persistence module can be hooked on and off an existing space for snapshot purposes
  • *
*

*

Beware of delays, though: If a long term object is inserted into the space, * and the space is terminated with a kill immediately after, the object is lost. * Your implementation must allow losing long term objects. *

*

Beware of re-insertion: You can either insert duplicates, or not. Both * strategies can give you mismatches. If you insert duplicates, all objects * are reintroduced to the space, and a repetitive restart will give the space * duplicates If you are not, a query is performed to see if the object is already * present. If it is, it is not re-introduced. If you have more than one object of * the same type, one copy is lost. * * @TODO JavaSpaces interface v.2.1 can solve the problem of loosing a copy when restarting. */ public class SemiSpacePersistentAdmin extends SemiSpaceAdmin implements DisposableBean { private static final Logger log = LoggerFactory.getLogger(SemiSpacePersistentAdmin.class); private boolean ready = false; private DatabaseService dbservice; private int delayBeforePollingInMs; private long necessaryLifeTimeForPersistingMs; private DelayQueue delayQueue; private ExecutorService pollExecutor; private boolean insertDuplicates; private Set ignored; private Set inQueue; /** * Read / write lock */ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); /** * The default constructor with a 20 second delay before polling, * and 60 second minimum lifetime for the element to be persisted. * It does not insert duplicates. * * @param space The space to be used. */ private SemiSpacePersistentAdmin(SemiSpace space) { super(space); delayBeforePollingInMs = 20 * 1000; necessaryLifeTimeForPersistingMs = delayBeforePollingInMs; delayQueue = new DelayQueue(); pollExecutor = Executors.newSingleThreadExecutor(); insertDuplicates = false; ignored = new HashSet(); inQueue = new HashSet(); } /** * Create and initialize the persistent admin connection to semispace. * Notice that the old admin process will just be unreferenced. * This method is a factory method which will create a new instance for * each call. * * @param delayBeforePollingInMs How long to wait before examining space contents. * Normal value would be 20 seconds * @param insertDuplicates Normally false: * During startup, shall the space be searched for already existing entries? * If set to false, elements read from the database is examined for existence in space * and will not be re-introduced if it already exists. Note that * this will make duplicates disappear, whereas true will give the space more duplicates than * actually should exist * @param necessaryLifeTimeForPersistingMs How long the lifetime of an object. A normal value would * be 60 seconds (given as milliseconds). * must be before persisting it (if still present and alive) */ public static synchronized SemiSpacePersistentAdmin createConnectedAdminInstance( SemiSpace space, int delayBeforePollingInMs, boolean insertDuplicates, int necessaryLifeTimeForPersistingMs ) { final SemiSpaceAdminInterface oldAdmin = space.getAdmin(); if ( necessaryLifeTimeForPersistingMs < delayBeforePollingInMs ) { throw new RuntimeException("Time for persisting cannot be less than the polling time. It does not make sense as " + "an element that should have been persisted is not due to that it has not yet been polled. " + "necessaryLifeTimeForPersistingMs: "+necessaryLifeTimeForPersistingMs+", delayBeforePollingInMs: "+delayBeforePollingInMs ); } SemiSpacePersistentAdmin persistentAdmin = new SemiSpacePersistentAdmin( space ); persistentAdmin.delayBeforePollingInMs = delayBeforePollingInMs; persistentAdmin.necessaryLifeTimeForPersistingMs = necessaryLifeTimeForPersistingMs; persistentAdmin.insertDuplicates = insertDuplicates; log.info("Exchanging admin instance for space with this."); persistentAdmin.getSpace().setAdmin(persistentAdmin); if ( oldAdmin instanceof SemiSpaceAdmin ) { ((SemiSpaceAdmin)oldAdmin).removeShutDownHook(); if ( oldAdmin instanceof SemiSpacePersistentAdmin ) { log.info("Old admin was of persistent admin type. Removing shutdown hook."); ((SemiSpacePersistentAdmin)oldAdmin).shutdownAndAwaitTermination(); } } persistentAdmin.performLocalInitialization(); return persistentAdmin; } /** * Override and broaden return value. * @see org.semispace.admin.SemiSpaceAdmin#getSpace() */ @Override public SemiSpace getSpace() { return (SemiSpace) super.getSpace(); } @Override public void notifyAboutEvent(EventDistributor event) { super.notifyAboutEvent(event); if ( ! super.hasBeenInitialized() || !ready ) { log.debug("Ignoring event as not having been initialized. Holder id: "+event.getEvent().getId()+", event class "+event.getEvent().getClass().getName()); return; } //log.debug("Got incoming event. Holder id: "+event.getEvent().getId()+", event class "+event.getEvent().getClass().getName()); if ( event.getEvent() instanceof SemiAvailabilityEvent ) { treatEvent((SemiAvailabilityEvent)event.getEvent()); } else if ( event.getEvent() instanceof SemiExpirationEvent ) { treatEvent((SemiExpirationEvent)event.getEvent()); } else if ( event.getEvent() instanceof SemiTakenEvent ) { treatEvent((SemiTakenEvent)event.getEvent()); } else if ( event.getEvent() instanceof SemiRenewalEvent ) { treatEvent((SemiRenewalEvent)event.getEvent()); } else { throw new RuntimeException("Unexpected event type of class "+event.getEvent().getClass().getName()); } } private void treatEvent(SemiRenewalEvent event) { //log.debug("Update object in database"); rwl.writeLock().lock(); try { Lease leaseDto = dbservice.getLeaseDao().retrieveleaseByHolderId(event.getId()); if ( leaseDto != null ) { leaseDto.setLiveUntil(event.getLiveUntil()); dbservice.getLeaseDao().savelease(leaseDto); } else { // Need to reintroduce element in case it is eligible for storing now SemiAvailabilityEvent fake = new SemiAvailabilityEvent( event.getId()); treatEvent(fake); } } finally { rwl.writeLock().unlock(); } } private void treatEvent(SemiAvailabilityEvent event ) { DelayedId delayed = new DelayedId( event.getId(), delayBeforePollingInMs); rwl.writeLock().lock(); try { if ( inQueue.contains(Long.valueOf( delayed.getId()))) { log.error("It is not expected that an object shall be registered twice with the same identity."); throw new RuntimeException("Consistency error. Element with id already exists: "+event.getId()); } inQueue.add(Long.valueOf( delayed.getId())); delayQueue.add(delayed); } finally { rwl.writeLock().unlock(); } //log.debug("Added element to delayQueue. Id "+delayed.getId()); pollExecutor.execute(new Runnable() { @SuppressWarnings("synthetic-access") public void run() { DelayedId elem = null; try { //log.debug("Polling delayQueue which now has "+delayQueue.size()+" elements with delay "+delayBeforePollingInMs); // Double the wait time in order to be "certain" something is read. Will break during extreme load, probably. Do not case... elem = delayQueue.poll(delayBeforePollingInMs*2, TimeUnit.MILLISECONDS); //log.debug("Poll finished on delayQueue which now has "+delayQueue.size()+" elements, got "+(elem==null?"null":"an object")); if ( elem != null ) { rwl.writeLock().lock(); try { // If the element is not in the inQueue, it has, well, been removed. if ( inQueue.remove(Long.valueOf( elem.getId()))) { writeElementToDatabase(elem.getId()); } } finally { rwl.writeLock().unlock(); } } else { //log.debug("No object found when polling. delayQueue size "+delayQueue.size()); } } catch (InterruptedException e) { log.error("Got, and ignored, exception, which probably is related to shutdown of space. Masked, the exception is: "+ e); } } }); } /** * Read element out of space, and write it to the database. */ private void writeElementToDatabase(long id ) { rwl.writeLock().lock(); try { Holder holder = getSpace().readHolderById(id); if ( holder == null ) { //log.debug("Object expired before being eligible for persistence."); return; } //log.info("===> "+(holder.getLiveUntil() - calculateTime())+" > "+necessaryLifeTimeForPersistingMs+" ??"); if ( holder.getLiveUntil() - calculateTime() > necessaryLifeTimeForPersistingMs ) { // Only write if lifetime is long enough that it should be persisted LeaseMeta leaseMeta = LeaseMeta.createLeaseMeta(holder); try { dbservice.getLeaseDao().write(leaseMeta, holder.getLiveUntil()); } catch (Exception e) { log.error("Could not persist element. Adding it to ignore delayQueue. XML\n"+holder.getXml(), e); ignored.add( Long.valueOf(id)); } } else { ignored.add( Long.valueOf(id)); } } finally { rwl.writeLock().unlock(); } } private void treatEvent(SemiExpirationEvent event) { //log.debug("Remove object from database (immediate / expiration)"); removeElement(event.getId()); } private void removeElement(long id) { rwl.writeLock().lock(); try { if ( ignored.remove(Long.valueOf(id) )) { //log.debug("Message resided in ignore set, and was just removed from it."); } else if ( inQueue.remove( Long.valueOf(id))) { //log.debug("Message resided in inQueue, and just removed from it."); } else { //log.debug("(Trying) to remove element from DB"); removeFromDb(id); } } finally { rwl.writeLock().unlock(); } } private void treatEvent(SemiTakenEvent event) { //log.debug("Remove object from database (immediate / taken)"); removeElement(event.getId()); } /** * NB rwl presumed locked upon entry. */ private void removeFromDb(long holderId) { Lease leaseDto = dbservice.getLeaseDao().retrieveleaseByHolderId(holderId); if ( leaseDto != null ) { //log.debug("Deleting holder with holder id "+holderId+"."); dbservice.getLeaseDao().deleteleaseById(leaseDto.getId()); } else { if ( log.isDebugEnabled()) { StringBuffer info = new StringBuffer(); info.append("delayQueue{"); for ( Long did : inQueue) { info.append(" "+did); if ( did.longValue() == holderId ) { info.append("Strange: Found holder id in delayQueue."); } } info.append("} ignored {"); for ( Long id : ignored) { info.append(" "+id); if ( id.longValue() == holderId ) { info.append("Strange: Found holder id in ignored list"); } } info.append("} "); log.debug("Could not find holder with holder id "+holderId+" in database. This could be due to resynchronization when having elements of same type and searchmap. " + "It could also be that the element was removed before it was even picked by the admin. "+info); // Can consider to throw exception. Does not really matter though, as this problem will not be a consistency error. //throw new RuntimeException("Tried to delete "+holderId+"\nList of ignored: "+ignored+" queued: "+delayQueue+"\nChecked against "+new DelayedId( holderId, delayBeforePollingInMs)); } } } /** * This method presumes that the SemiSpace has been set on this object * @see org.semispace.admin.SemiSpaceAdmin#performInitialization() */ private void performLocalInitialization() { if ( ! super.hasBeenInitialized()) { super.performInitialization(); } log.info("Initializing persistent connection"); dbservice = DatabaseFactory.retrieveDatabaseService(); dbservice.getLeaseDao().cleanup(); // First invert all IDs. HibernateLeaseDao leaseDao = dbservice.getLeaseDao(); leaseDao.invertHolderIds(); Long[] presentHolderIds = getSpace().findAllHolderIds(); ready = true; // Take all elements and re-insert them into the space. insertElementsWithNegativeIdIntoSpace(leaseDao); // Take elements already present, which may have not been added already, and add them insertElementsWithNonPresentHolderId( presentHolderIds, leaseDao ); } private void insertElementsWithNonPresentHolderId(Long[] presentHolderIds, HibernateLeaseDao leaseDao) { for ( Long hid : presentHolderIds ) { if ( leaseDao.retrieveleaseByHolderId(hid.longValue()) == null ) { // Element not yet registered - faking availability event SemiAvailabilityEvent fake = new SemiAvailabilityEvent(hid.longValue()); treatEvent(fake); } } } /** * Notice that if the space is not set to insert duplicates, copies * of an object that resides in the persistent storage gets lost. */ private void insertElementsWithNegativeIdIntoSpace(HibernateLeaseDao leaseDao) { Integer[] ids = leaseDao.retriveLeaseIdsForAllNegativeHolderIds(); // For each element, reintroduce into space for (Integer id : ids) { rwl.writeLock().lock(); try { Lease dblease = leaseDao.retrieveleaseById(id.intValue()); boolean shallInsert = true; final Map searchMap = transformTagsToMap(dblease.getTags()); if (!insertDuplicates && getSpace().findOrWaitLeaseForTemplate(searchMap, 0, false) != null) { // Element already present shallInsert = false; } if (shallInsert) { getSpace().writeToElements(dblease.getDoctype(), dblease.getLiveUntil() - System.currentTimeMillis(), dblease.getActual(), searchMap); } // Removing lease from DB as it has been re-inserted into space. leaseDao.deleteleaseById(dblease.getId()); } finally { rwl.writeLock().unlock(); } } } private Map transformTagsToMap(Set tags) { Map searchMap = new HashMap(); for ( Tag tag : tags ) { searchMap.put(tag.getName(), tag.getContent()); } return searchMap; } /** * Method exists and is protected for the benefit of junit tests. */ protected DatabaseService getDbservice() { return this.dbservice; } @Override protected void shutdownAndAwaitTermination() { // Cancel poll executor pollExecutor.shutdown(); log.debug("Shall (potentially) persist "+inQueue.size()+" elements."); Long[] elements = inQueue.toArray(new Long[0]); rwl.writeLock().lock(); try { Iterator dit = delayQueue.iterator(); while ( dit.hasNext() ) { delayQueue.remove(dit.next()); } } finally { rwl.writeLock().unlock(); } for ( Long id : elements ) { rwl.writeLock().lock(); try { writeElementToDatabase(id.longValue()); } finally { rwl.writeLock().unlock(); } } try { if (!pollExecutor.awaitTermination(10, TimeUnit.SECONDS)) { pollExecutor.shutdownNow(); if (!pollExecutor.awaitTermination(60, TimeUnit.SECONDS)) log.warn("Pool did not terminate"); } } catch (InterruptedException ie) { pollExecutor.shutdownNow(); log.debug("Ignoring exception during shutdown.", ie); } ignored.clear(); super.shutdownAndAwaitTermination(); } /** * In order to let the object support shutdown from spring framework. * @see org.springframework.beans.factory.DisposableBean#destroy() */ public void destroy() { log.debug("Spring calls destroy."); if ( !pollExecutor.isShutdown()) { shutdownAndAwaitTermination(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy