cz.cuni.mff.d3s.been.mapstore.mongodb.MongoMapStore.java Source code

Java tutorial

Introduction

Here is the source code for cz.cuni.mff.d3s.been.mapstore.mongodb.MongoMapStore.java

Source

/*
 * Copyright (c) 2008-2013, Hazelcast, Inc. All Rights Reserved.
 *
 * 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 cz.cuni.mff.d3s.been.mapstore.mongodb;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;

import com.hazelcast.core.*;
import com.mongodb.*;
import cz.cuni.mff.d3s.been.core.task.TaskEntry;

/**
 * Implementation of {@link MapStore} for MongoDB.
 * 
 * The implementation is based on hazelcast-spring with modification for the
 * BEEN project. Licence honored.
 * 
 */
public class MongoMapStore implements MapStore, MapLoaderLifecycleSupport {

    protected static final Logger log = LoggerFactory.getLogger(MongoMapStore.class);

    private static final String FAILED_STORE_MIRROR_MAP_SUFFIX = "FAILOVER";

    // name of the Hazelcast List where are stored all names of already loaded maps
    private static final String LOADED_LIST = "LOADED_LIST";

    // TODO - consider configurable delay and period
    private static final long FLUSH_INITIAL_DELAY_SECONDS = 180;
    private static final long FLUSH_PERIOD_SECONDS = 180;

    private HazelcastInstance hazelcastInstance;

    // name of the map which is covered by this mapstore
    private String mapName;

    // name of the failover map for map covered by this mapstore
    private String failedStoreMapName;

    private MongoDBConverter converter;
    private DBCollection coll;
    private MongoTemplate mongoTemplate;

    // tells us if map covered by this mapstore has been already loaded into memory
    private volatile boolean mapIsLoaded;

    /**
     * Create a new MongoDB MapStore
     *
     * @param mongoTemplate Spring template for MongoDB persistence
     */
    public MongoMapStore(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    private IMap failedStoreMap;

    /**
     * Get failover map in case database is down
     *
     * @return The failover map
     */
    public IMap getFailedStoreMap() {
        return failedStoreMap;
    }

    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    @Override
    public void store(Object key, Object value) {
        if (!storePersistent(key, value)) {
            getFailedStoreMap().put(key, value);
        } else {
            resolveFailedObjects();
        }
    }

    @Override
    public void storeAll(Map map) {
        for (Object key : map.keySet()) {
            store(key, map.get(key));
        }
        resolveFailedObjects();
    }

    @Override
    public void delete(Object key) {
        if (!deletePersistent(key)) {
            getFailedStoreMap().put(key, null);
        }
    }

    @Override
    public void deleteAll(Collection keys) {
        BasicDBList dbo = new BasicDBList();
        for (Object key : keys) {
            dbo.add(new BasicDBObject("_id", key));
        }
        BasicDBObject dbb = new BasicDBObject("$or", dbo);
        coll.remove(dbb);
    }

    @Override
    public Object load(Object key) {
        try {
            DBObject dbo = new BasicDBObject();
            dbo.put("_id", key);
            DBObject obj = coll.findOne(dbo);
            if (obj == null)
                return null;

            try {
                Class clazz = Class.forName(obj.get("_class").toString());
                Object object = converter.toObject(clazz, obj);

                if (object instanceof TaskEntry) {
                    setLoadedFromMapStore((TaskEntry) object);
                }

                return object;
            } catch (ClassNotFoundException e) {
                log.error(e.getMessage(), e);
            }
            return null;
        } catch (Throwable t) {
            String msg = String.format("Cannot load key '%s' of map '%s'", key.toString(), mapName);
            log.error(msg, t);
            return null;
        }
    }

    @Override
    public Map loadAll(Collection keys) {
        try {
            Map map = new HashMap();
            BasicDBList dbo = new BasicDBList();
            for (Object key : keys) {
                dbo.add(new BasicDBObject("_id", key));
            }
            BasicDBObject dbb = new BasicDBObject("$or", dbo);
            DBCursor cursor = coll.find(dbb);
            while (cursor.hasNext()) {
                try {
                    DBObject obj = cursor.next();
                    Class clazz = Class.forName(obj.get("_class").toString());

                    Object object = converter.toObject(clazz, obj);

                    if (object instanceof TaskEntry) {
                        setLoadedFromMapStore((TaskEntry) object);
                    }

                    map.put(obj.get("_id"), object);
                } catch (ClassNotFoundException e) {
                    log.error(e.getMessage(), e);
                }
            }
            return map;
        } catch (Throwable t) {
            log.warn("Cannot load collection from MongoDB", t);
        } finally {
            mapIsLoaded = true;
        }
        return null;
    }

    @Override
    public Set loadAllKeys() {

        boolean loaded = false;
        ILock l = hazelcastInstance.getLock("LOADING_" + mapName);
        l.lock();

        IList loadedList = hazelcastInstance.getList(LOADED_LIST);
        if (loadedList.contains(mapName)) {
            loaded = true;
        }

        Set keyset = Collections.emptySet();

        if (!loaded) {
            try {
                keyset = new HashSet();
                BasicDBList dbo = new BasicDBList();
                dbo.add("_id");
                DBCursor cursor = coll.find(null, dbo);
                while (cursor.hasNext()) {
                    keyset.add(cursor.next().get("_id"));
                }
                loadedList.add(mapName);
            } catch (Throwable t) {
                keyset = Collections.emptySet();
                log.warn("Cannot load keys from MongoDB", t);
            }
        }
        l.unlock();

        return keyset;
    }

    @Override
    public void init(HazelcastInstance hazelcastInstance, Properties properties, String mapName) {
        if (properties.get("collection") != null) {
            this.mapName = (String) properties.get("collection");
        } else {
            this.mapName = mapName;
        }
        this.failedStoreMapName = String.format("%s_%s", this.mapName, FAILED_STORE_MIRROR_MAP_SUFFIX);
        this.failedStoreMap = hazelcastInstance.getMap(failedStoreMapName);
        this.hazelcastInstance = hazelcastInstance;
        this.coll = mongoTemplate.getCollection(this.mapName);
        this.converter = new SpringMongoDBConverter(mongoTemplate);

        scheduleFailoverMapFlusher();
    }

    @Override
    public void destroy() {
        // the last attempt to save the cluster from this mapstore. If this is the last node in the cluster and
        // failover map is not empty, cluster will be in inconsistent state after restart - maybe recoverable,
        // maybe not.
        resolveFailedObjects();
    }

    private boolean deletePersistent(Object key) {
        try {
            DBObject dbo = new BasicDBObject();
            dbo.put("_id", key);
            coll.remove(dbo);
        } catch (Throwable e) {
            String message = String.format(
                    "Item with key '%s' couldn't be deleted from hazelcast persistent mapstore, reason: %s",
                    key.toString(), e.getMessage());
            log.debug(message, e);
            return false;
        }
        return true;
    }

    private synchronized void resolveFailedObjects() {
        IMap failedStoreMap = getFailedStoreMap();

        if (failedStoreMap.size() > 0 && failedStoreMap.lockMap(5, TimeUnit.SECONDS)) {
            try {
                for (Object failedKey : failedStoreMap.keySet()) {
                    Object failedValue = failedStoreMap.get(failedKey);
                    if (failedValue == null) {
                        if (deletePersistent(failedKey)) {
                            break;
                        }
                        failedStoreMap.remove(failedKey);
                    } else {
                        if (storePersistent(failedKey, failedValue)) {
                            break;
                        }
                        failedStoreMap.remove(failedKey);
                    }
                }
            } finally {
                failedStoreMap.unlockMap();
            }
        }
    }

    private void scheduleFailoverMapFlusher() {
        final Runnable flusher = new Runnable() {
            public void run() {
                if (mapIsLoaded) {
                    try {
                        resolveFailedObjects();
                    } catch (Exception e) {
                        log.error(String.format("Periodic flushing of map '%s' failed", mapName), e);
                    }
                }
            }
        };
        scheduler.scheduleAtFixedRate(flusher, FLUSH_INITIAL_DELAY_SECONDS, FLUSH_PERIOD_SECONDS, TimeUnit.SECONDS);
    }

    private boolean storePersistent(Object key, Object value) {
        try {
            DBObject dbo = converter.toDBObject(value);
            dbo.put("_id", key);
            coll.save(dbo);
        } catch (Throwable e) {
            String message = String.format(
                    "Item with key '%s' couldn't be stored in hazelcast persistent mapstore, reason: %s",
                    key.toString(), e.getMessage());
            log.debug(message, e);
            return false;
        }
        return true;
    }

    private void setLoadedFromMapStore(final TaskEntry entry) {
        entry.setLoadedFromPersistence(true);
    }
}