v7cr.vaadin.DBCollectionContainer.java Source code

Java tutorial

Introduction

Here is the source code for v7cr.vaadin.DBCollectionContainer.java

Source

/**
 * Copyright (c) 2011, Thilo Planz. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package v7cr.vaadin;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bson.BSONObject;

import v7cr.v7db.SchemaDefinition;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.Ordered;
import com.vaadin.data.util.AbstractContainer;
import com.vaadin.data.util.BeanContainer;

/**
 * A Vaadin Container backed by a MongoDB collection.
 * 
 * <p>
 * It is "partially lazy-loading": Upon construction, it loads all documents'
 * _id fields (so it probably does not work with capped collections) into an
 * ordered in-memory collection. The rest of the document gets loaded on demand
 * and in pages.
 * 
 * <p>
 * Every document becomes a Vaadin Item. Since MongoDB is schema-free, there is
 * no database metadata to get the propertyIds for these items. If you do not
 * specify any, the first page of documents is loaded and all top-level
 * non-object field names found there are added as propertyIds. The types of
 * these properties are set to the first value found.
 * 
 * <p>
 * You can add nested properties ("a.x") manually, just like with the
 * {@link BeanContainer}.
 * 
 * 
 * @author Thilo Planz, based on the Vaadin SQLContainer addon
 * 
 */

public class DBCollectionContainer extends AbstractContainer implements Ordered, Indexed {

    private final List<Object> _ids;

    private final DBCollection collection;

    /** Container properties = column names, data types and statuses */
    private final List<String> propertyIds = new ArrayList<String>();
    private final Map<String, Class<?>> propertyTypes = new HashMap<String, Class<?>>();
    private final Map<String, Boolean> propertyReadOnly = new HashMap<String, Boolean>();
    private final Map<String, Boolean> propertyNullable = new HashMap<String, Boolean>();

    /** Page length = number of items contained in one page */
    private final int pageLength = DEFAULT_PAGE_LENGTH;
    public static final int DEFAULT_PAGE_LENGTH = 100;

    /** Starting row number of the currently fetched page */
    private int currentOffset = -1;

    /** Item and index caches */
    private final Map<Integer, Object> itemIndexes = new HashMap<Integer, Object>();
    private final Map<Object, BSONItem> cachedItems = new HashMap<Object, BSONItem>();

    public DBCollectionContainer(DBCollection collection, DBCursor cursor) {
        this.collection = collection;
        _ids = initIds(cursor);
        initPropertyIds();
    }

    private List<Object> initIds(DBCursor cursor) {
        List<DBObject> x = cursor.toArray();
        List<Object> _ids = new ArrayList<Object>(x.size());
        for (DBObject o : x) {
            _ids.add(o.get("_id"));
        }
        return _ids;
    }

    private void initPropertyIds() {
        if (propertyIds.isEmpty()) {
            getPage();

            for (BSONItem b : cachedItems.values())
                for (String s : b.getBSONObject().keySet())
                    if (!propertyIds.contains(s))
                        if (!(b.getBSONObject().get(s) instanceof BSONObject)) {
                            propertyIds.add(s);
                            propertyTypes.put(s, b.getBSONObject().get(s).getClass());
                        }
        }

    }

    private void initPropertyIds(SchemaDefinition schema) {
        if (schema != null)
            for (String s : schema.getFieldNames())
                if (!propertyIds.contains(s)) {
                    propertyIds.add(s);
                    propertyTypes.put(s, String.class);
                }

        initPropertyIds();

    }

    public DBCollectionContainer(DBCollection collection, String sortBy, boolean ascending) {
        this(null, collection, null, sortBy, ascending);
    }

    public DBCollectionContainer(SchemaDefinition schema, DBCollection collection, DBObject filter, String sortBy,
            boolean ascending) {
        this.collection = collection;
        DBObject _null = new BasicDBObject();
        if (filter == null)
            filter = _null;
        DBCursor cursor = collection.find(filter, _null);
        if (sortBy != null) {
            cursor = cursor.sort(new BasicDBObject(sortBy, ascending ? 1 : -1));
        }
        _ids = initIds(cursor);
        initPropertyIds(schema);
    }

    private void getPage() {

        if (_ids.isEmpty())
            return;

        int fromIndex;
        if (currentOffset < 0) {
            fromIndex = 0;
        } else {
            cachedItems.clear();
            itemIndexes.clear();
            fromIndex = currentOffset + pageLength;
        }

        int toIndex = fromIndex + pageLength;
        if (toIndex >= _ids.size()) {
            toIndex = _ids.size() - 1;
        }
        // TODO: is there a way to do bulk loading?
        int idx = fromIndex;
        for (Object id : _ids.subList(fromIndex, toIndex)) {
            // TODO: only load the fields we need
            cachedItems.put(id, new BSONItem(collection.findOne(id)));
            itemIndexes.put(idx++, id);
        }
        currentOffset = fromIndex;
    }

    public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue)
            throws UnsupportedOperationException {

        if (propertyIds.contains(propertyId))
            return false;

        if (!(propertyId instanceof String))
            return false;

        String pid = (String) propertyId;
        propertyIds.add(pid);
        propertyTypes.put(pid, type);
        return true;

    }

    public Object addItem() throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public Item addItem(Object itemId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public boolean containsId(Object itemId) {
        return _ids.contains(itemId);
    }

    public Property getContainerProperty(Object itemId, Object propertyId) {
        Item item = getItem(itemId);
        if (item == null) {
            return null;
        }
        return item.getItemProperty(propertyId);
    }

    public Collection<?> getContainerPropertyIds() {
        return Collections.unmodifiableCollection(propertyIds);
    }

    public Item getItem(Object itemId) {
        BSONItem cached = cachedItems.get(itemId);
        if (cached != null)
            return cached;

        BSONObject b = collection.findOne(itemId);
        if (b == null)
            return null;

        BSONItem i = new BSONItem(b);
        cachedItems.put(itemId, i);

        return i;
    }

    public Collection<?> getItemIds() {
        return Collections.unmodifiableCollection(_ids);
    }

    public Class<?> getType(Object propertyId) {
        return propertyTypes.get(propertyId);
    }

    public boolean removeAllItems() throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return false;
    }

    public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return false;
    }

    public boolean removeItem(Object itemId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return false;
    }

    public int size() {
        return _ids.size();
    }

    public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public Object firstItemId() {
        return _ids.get(0);
    }

    public boolean isFirstId(Object itemId) {
        if (!_ids.get(0).equals(itemId))
            return false;
        return true;
    }

    public boolean isLastId(Object itemId) {
        int idx = _ids.size() - 1;
        if (!_ids.get(idx).equals(itemId))
            return false;
        return true;
    }

    public Object lastItemId() {
        int idx = _ids.size() - 1;
        return _ids.get(idx);
    }

    public Object nextItemId(Object itemId) {
        // TODO: linear search, not so nice
        boolean theNext = false;
        for (Object id : _ids) {
            if (theNext)
                return id;
            if (itemId.equals(id))
                theNext = true;
        }
        return null;
    }

    public Object prevItemId(Object itemId) {
        // TODO: linear search, not so nice
        Object prev = null;
        for (Object id : _ids) {
            if (itemId.equals(id))
                return prev;
            prev = id;
        }
        return null;
    }

    public Object addItemAt(int index) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
        // TODO Auto-generated method stub
        return null;
    }

    public Object getIdByIndex(int index) {
        return _ids.get(index);
    }

    public int indexOfId(Object itemId) {
        return _ids.indexOf(itemId);
    }

}