org.bedework.carddav.server.dirHandlers.db.DbDirHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.bedework.carddav.server.dirHandlers.db.DbDirHandler.java

Source

/* ********************************************************************
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you 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.bedework.carddav.server.dirHandlers.db;

import org.bedework.access.*;
import org.bedework.access.Acl.CurrentAccess;
import org.bedework.carddav.server.CarddavCollection;
import org.bedework.carddav.server.SysIntf.GetLimits;
import org.bedework.carddav.server.SysIntf.GetResult;
import org.bedework.carddav.server.config.CardDAVConfig;
import org.bedework.carddav.server.config.DbDirHandlerConfig;
import org.bedework.carddav.server.config.DirHandlerConfig;
import org.bedework.carddav.server.dirHandlers.AbstractDirHandler;
import org.bedework.carddav.server.filter.Filter;
import org.bedework.carddav.vcard.Card;
import org.bedework.webdav.servlet.access.AccessHelper;
import org.bedework.webdav.servlet.access.AccessHelperI;
import org.bedework.webdav.servlet.access.SharedEntity;
import org.bedework.webdav.servlet.shared.UrlHandler;
import org.bedework.webdav.servlet.shared.WebdavException;
import org.bedework.webdav.servlet.shared.WebdavForbidden;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import java.io.StringReader;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

/** Provide some common methods for db based directory handlers.
 *
 * @author douglm
 *
 */
public abstract class DbDirHandler extends AbstractDirHandler implements PrivilegeDefs {
    protected DbDirHandlerConfig dbConfig;

    protected String userHomeRoot;

    /** When we were created for debugging */
    protected Timestamp objTimestamp;

    /** Current hibernate session - exists only across one user interaction
     */
    protected HibSession sess;

    /** We make this static for this implementation so that there is only one
     * SessionFactory per server for the card server.
     *
     * <p>static fields used this way are illegal in the j2ee specification
     * though we might get away with it here as the session factory only
     * contains parsed mappings for the card configuration. This should
     * be the same for any machine in a cluster so it might work OK.
     *
     * <p>It might be better to find some other approach for the j2ee world.
     */
    private static SessionFactory sessionFactory;
    //private static Statistics dbStats;

    /** For evaluating access control
     */
    private AccessHelperI access;

    /**
     * @author douglm
     *
     */
    private class AccessUtilCb extends AccessHelperI.CallBack {
        DbDirHandler hdlr;

        AccessUtilCb(final DbDirHandler hdlr) {
            this.hdlr = hdlr;
        }

        @Override
        public AccessPrincipal getPrincipal(final String href) throws WebdavException {
            return hdlr.getPrincipal(href);
        }

        @Override
        public String getUserHomeRoot() throws WebdavException {
            return hdlr.userHomeRoot;
        }

        @Override
        public String makeHref(final String id, final int whoType) throws AccessException {
            try {
                return hdlr.makePrincipalHref(id, whoType);
            } catch (Throwable t) {
                throw new AccessException(t);
            }
        }

        @Override
        public SharedEntity getCollection(final String path) throws WebdavException {
            DbCollection col = hdlr.getDbCollection(path);

            if (col == null) {
                if (path.equals("/")) {
                    // Make a root collection
                    col = new DbCollection();
                    col.setPath("/");

                    // Use this for owner/creator
                    col.setOwnerHref(dbConfig.getRootOwner());
                    col.setCreatorHref(dbConfig.getRootOwner());
                    col.setAccess(Access.getDefaultPublicAccess());
                } else {
                    return null;
                }
            }

            return col;
        }
    }

    @Override
    public void init(final CardDAVConfig cdConfig, final DirHandlerConfig dhConfig, final UrlHandler urlHandler)
            throws WebdavException {
        super.init(cdConfig, dhConfig, urlHandler);

        dbConfig = (DbDirHandlerConfig) dhConfig;

        userHomeRoot = cdConfig.getUserHomeRoot();
        if (!userHomeRoot.endsWith("/")) {
            userHomeRoot += "/";
        }

        AccessUtilCb acb = new AccessUtilCb(this);

        access = new AccessHelper();
        access.init(acb);

        try {
            objTimestamp = new Timestamp(System.currentTimeMillis());
        } catch (Throwable t) {
            throw new WebdavException(t);
        }
    }

    /* (non-Javadoc)
     * @see org.bedework.carddav.bwserver.DirHandler#open(java.lang.String)
     */
    @Override
    public void open(final String account) throws WebdavException {
        super.open(account);

        if (isOpen()) {
            return;
        }
        openSession();
        open = true;

        access.setAuthPrincipal(getPrincipal(makePrincipalHref(account, WhoDefs.whoTypeUser)));
    }

    /* (non-Javadoc)
     * @see org.bedework.carddav.bwserver.DirHandler#close()
     */
    @Override
    public void close() throws WebdavException {
        super.close();

        try {
            endTransaction();
        } catch (WebdavException wde) {
            try {
                rollbackTransaction();
            } catch (WebdavException wde1) {
            }
            throw wde;
        } finally {
            try {
                closeSession();
            } catch (WebdavException wde1) {
            }
        }
    }

    @Override
    public Card getCard(final String path, final String name) throws WebdavException {
        final DbCard card = getDbCard(path, name, privRead);

        if (card == null) {
            return null;
        }

        return makeVcard(card);
    }

    @Override
    public Card getCardByUid(final String path, final String uid) throws WebdavException {
        final DbCard card = getDbCardByUid(path, uid);

        if (card == null) {
            return null;
        }

        return makeVcard(card);
    }

    private final static String queryGetCards = "select card from " + DbCard.class.getName()
            + " card join card.properties props " + "where card.parentPath=:path";

    @Override
    @SuppressWarnings("unchecked")
    public GetResult getCards(final String path, final Filter filter, final GetLimits limits)
            throws WebdavException {
        verifyPath(path);

        final StringBuilder sb = new StringBuilder(queryGetCards);

        final DbFilter fltr = new DbFilter(sb);

        fltr.makeFilter(filter);

        sess.createQuery(sb.toString());
        sess.setString("path", ensureSlashAtEnd(path));

        fltr.parReplace(sess);

        /* We couldn't use DISTINCT in the query (it's a CLOB) so make it
         * distinct with a set
         */
        final Set<DbCard> cardSet = new TreeSet<DbCard>(sess.getList());

        final GetResult res = new GetResult();

        for (final DbCard dbc : cardSet) {
            res.cards.add(makeVcard(dbc));
        }

        return res;
    }

    private final static String queryGetCardNames = "select card.name from " + DbCard.class.getName()
            + " card where card.parentPath=:path";

    private class CardIterator implements Iterator<Card> {
        private String parentPath;
        private List<String> names;

        private Iterator<String> it;

        private HibSession sess;

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public Card next() {
            if (!it.hasNext()) {
                return null;
            }

            try {
                return getCard(parentPath, it.next());
            } catch (final WebdavException we) {
                throw new RuntimeException(we);
            }
        }

        @Override
        public void remove() {
        }
    }

    @Override
    public Iterator<Card> getAll(final String path) throws WebdavException {
        verifyPath(path);

        sess.createQuery(queryGetCardNames);
        sess.setString("path", ensureSlashAtEnd(path));

        final CardIterator ci = new CardIterator();
        ci.parentPath = path;
        //noinspection unchecked
        ci.names = sess.getList();
        ci.it = ci.names.iterator();
        ci.sess = sess;

        return ci;
    }

    @Override
    public CarddavCollection getCollection(final String path) throws WebdavException {
        verifyPath(path);

        final DbCollection col = getDbCollection(ensureEndSlash(path), privRead);

        if (col == null) {
            return null;
        }

        return makeCdCollection(col);
    }

    private static final String queryGetCollections = "from " + DbCollection.class.getName()
            + " col where col.parentPath=:path";

    @Override
    @SuppressWarnings("unchecked")
    public GetResult getCollections(final String path, final GetLimits limits) throws WebdavException {
        verifyPath(path);

        sess.createQuery(queryGetCollections);
        sess.setString("path", ensureSlashAtEnd(path));

        final List<DbCollection> l = sess.getList();

        final GetResult res = new GetResult();

        res.collections = new ArrayList<>();

        if (l == null) {
            return res;
        }

        for (final DbCollection col : l) {
            if (checkAccess(col, privRead, true) == null) {
                continue;
            }

            res.collections.add(makeCdCollection(col));
        }

        return res;
    }

    protected class CollectionsBatchImpl implements CollectionBatcher {
        private Collection<CarddavCollection> cols;
        private boolean called;

        @Override
        public Collection<CarddavCollection> next() throws WebdavException {
            if (called) {
                //noinspection unchecked
                return Collections.EMPTY_LIST;
            }

            called = true;
            return cols;
        }
    }

    @Override
    public CollectionBatcher getCollections(final String path) throws WebdavException {
        final CollectionsBatchImpl cbi = new CollectionsBatchImpl();

        final GetResult gr = getCollections(path, null);

        cbi.cols = Collections.unmodifiableCollection(gr.collections);

        return cbi;
    }

    protected void updateCollection(final DbCollection col) throws WebdavException {
        sess.update(col);
    }

    /* ====================================================================
     *  Protected methods.
     * ==================================================================== */

    private static final String queryGetCardByName = "from " + DbCard.class.getName()
            + " card where card.parentPath=:path" + " and card.name=:name";

    protected DbCard getDbCard(final String parentPath, final String name, final int access)
            throws WebdavException {
        verifyPath(parentPath);

        final DbCollection col = getDbCollection(ensureEndSlash(parentPath), access);

        if (col == null) {
            return null;
        }

        sess.createQuery(queryGetCardByName);
        sess.setString("path", ensureEndSlash(parentPath));
        sess.setString("name", name);

        return (DbCard) sess.getUnique();
    }

    private static final String queryGetCardByUid = "from " + DbCard.class.getName()
            + " card where card.parentPath=:path" + " and card.uid=:uid";

    protected DbCard getDbCardByUid(final String parentPath, final String uid) throws WebdavException {
        verifyPath(parentPath);

        sess.createQuery(queryGetCardByUid);
        sess.setString("path", ensureEndSlash(parentPath));
        sess.setString("uid", uid);

        return (DbCard) sess.getUnique();
    }

    private static final String queryGetCard = "from " + DbCard.class.getName() + " card where card.path=:path";

    protected DbCard getDbCard(final String path) throws WebdavException {
        verifyPath(path);

        sess.createQuery(queryGetCard);
        sess.setString("path", path);

        return (DbCard) sess.getUnique();
    }

    protected DbCollection getDbCollection(final String path, final int access) throws WebdavException {
        return (DbCollection) checkAccess(getDbCollection(path), access, true);
    }

    protected CarddavCollection makeCdCollection(final DbCollection col) throws WebdavException {
        final CarddavCollection cdc = new CarddavCollection();

        cdc.setAddressBook(col.getAddressBook());

        cdc.setCreated(col.getCreated());
        cdc.setLastmod(col.getLastmod());

        /* The name of this card comes from the attribute specified in the
         * config - addressbookEntryIdAttr
         */

        cdc.setName(col.getName());
        cdc.setDisplayName(col.getName());

        /** Ensure uniqueness - lastmod only down to second.
         */
        //private int sequence;

        cdc.setDescription(col.getDescription());

        // parent          CarddavCollection
        // addressBook     boolean

        cdc.setOwner(getPrincipal(col.getOwnerHref()));
        cdc.setPath(ensureSlashAtEnd(col.getParentPath()) + col.getName());
        cdc.setParentPath(col.getParentPath());

        return cdc;
    }

    private static final String queryGetCollection = "from " + DbCollection.class.getName()
            + " col where col.path=:path";

    private DbCollection getDbCollection(final String path) throws WebdavException {
        if (path.equals("/")) {
            // Make a root collection
            final DbCollection col = new DbCollection();
            col.setPath("/");

            col.setOwnerHref(dbConfig.getRootOwner());
            col.setCreatorHref(dbConfig.getRootOwner());
            //col.setAccess(Access.getDefaultPublicAccess());

            return col;
        }

        verifyPath(path);

        sess.createQuery(queryGetCollection);
        sess.setString("path", ensureEndSlash(path));

        return (DbCollection) sess.getUnique();
    }

    private static final String queryDeleteCollection = "delete " + DbCollection.class.getName()
            + " col where col.path=:path";

    private static final String queryGetColCards = "from " + DbCard.class.getName()
            + " card where card.parentPath=:path";

    protected int deleteDbCollection(final String path) throws WebdavException {
        if (path.equals("/")) {
            return 0;
        }

        if (getDbCollection(path, privUnbind) == null) {
            throw new WebdavForbidden();
        }

        verifyPath(path);

        sess.createQuery(queryGetColCards);
        sess.setString("path", ensureEndSlash(path));

        /* We couldn't use DISTINCT in the query (it's a CLOB) so make it
         * distinct with a set
         */
        final Set<DbCard> cardSet = new TreeSet<DbCard>(sess.getList());

        for (final DbCard cd : cardSet) {
            deleteDbCard(cd);
        }

        sess.createQuery(queryDeleteCollection);
        sess.setString("path", ensureEndSlash(path));

        return cardSet.size() + sess.executeUpdate();
    }

    /**
     * @param dbcard dbcopy
     * @return a Card object
     * @throws WebdavException
     */
    protected Card makeVcard(final DbCard dbcard) throws WebdavException {
        final Card card = new Card(dbcard.getVcard());

        card.setCreated(dbcard.getCreated());
        card.setLastmod(dbcard.getLastmod());
        card.setName(dbcard.getName());

        return card;
    }

    /**
     * @throws WebdavException
     */
    protected void deleteDbCard(final DbCard dbcard) throws WebdavException {
        sess.delete(dbcard);
    }

    /* ====================================================================
     *                   Session methods
     * ==================================================================== */

    protected void checkOpen() throws WebdavException {
        if (!isOpen()) {
            throw new WebdavException("Session call when closed");
        }
    }

    protected synchronized void openSession() throws WebdavException {
        if (isOpen()) {
            throw new WebdavException("Already open");
        }

        open = true;

        if (sess != null) {
            warn("Session is not null. Will close");
            try {
                close();
            } finally {
            }
        }

        if (sess == null) {
            if (debug) {
                trace("New hibernate session for " + objTimestamp);
            }
            sess = new HibSessionImpl();
            sess.init(getSessionFactory(), getLogger());
            trace("Open session for " + objTimestamp);
        }

        beginTransaction();
    }

    protected synchronized void closeSession() throws WebdavException {
        if (!isOpen()) {
            if (debug) {
                trace("Close for " + objTimestamp + " closed session");
            }
            return;
        }

        if (debug) {
            trace("Close for " + objTimestamp);
        }

        try {
            if (sess != null) {
                if (sess.rolledback()) {
                    sess = null;
                    return;
                }

                if (sess.transactionStarted()) {
                    sess.rollback();
                }
                //        sess.disconnect();
                sess.close();
                sess = null;
            }
        } catch (Throwable t) {
            try {
                sess.close();
            } catch (Throwable t1) {
            }
            sess = null; // Discard on error
        } finally {
            open = false;
        }
    }

    protected void beginTransaction() throws WebdavException {
        checkOpen();

        if (debug) {
            trace("Begin transaction for " + objTimestamp);
        }
        sess.beginTransaction();
    }

    protected void endTransaction() throws WebdavException {
        checkOpen();

        if (debug) {
            trace("End transaction for " + objTimestamp);
        }

        if (!sess.rolledback()) {
            sess.commit();
        }
    }

    protected void rollbackTransaction() throws WebdavException {
        try {
            checkOpen();
            sess.rollback();
        } finally {
        }
    }

    /*
    private void flush() throws WebdavException {
      if (debug) {
        trace("flush for " + objTimestamp);
      }
      if (sess.isOpen()) {
        sess.flush();
      }
    }*/

    protected String ensureSlashAtEnd(final String val) {
        if (val.endsWith("/")) {
            return val;
        }

        return val + "/";
    }

    protected SharedEntity checkAccess(final SharedEntity ent, final int desiredAccess,
            final boolean alwaysReturnResult) throws WebdavException {
        if (ent == null) {
            return null;
        }

        if (superUser) {
            return ent;
        }

        final boolean noAccessNeeded = desiredAccess == privNone;

        CurrentAccess ca = access.checkAccess(ent, desiredAccess, alwaysReturnResult || noAccessNeeded);

        if (!noAccessNeeded && !ca.getAccessAllowed()) {
            return null;
        }

        return ent;
    }

    /* ====================================================================
     *                   private methods
     * ==================================================================== */

    private SessionFactory getSessionFactory() throws WebdavException {
        if (sessionFactory != null) {
            return sessionFactory;
        }

        synchronized (this) {
            if (sessionFactory != null) {
                return sessionFactory;
            }

            /** Get a new hibernate session factory. This is configured from an
             * application resource hibernate.cfg.xml together with some run time values
             */
            try {
                Configuration conf = new Configuration();

                /*
                if (props != null) {
                  String cachePrefix = props.getProperty("cachePrefix");
                  if (cachePrefix != null) {
                    conf.setProperty("hibernate.cache.use_second_level_cache",
                         props.getProperty("cachingOn"));
                    conf.setProperty("hibernate.cache.region_prefix",
                         cachePrefix);
                  }
                }
                */

                StringBuilder sb = new StringBuilder();

                List<String> ps = dbConfig.getHibernateProperties();

                for (String p : ps) {
                    sb.append(p);
                    sb.append("\n");
                }

                Properties hprops = new Properties();
                hprops.load(new StringReader(sb.toString()));

                conf.addProperties(hprops).configure();

                sessionFactory = conf.buildSessionFactory();

                return sessionFactory;
            } catch (Throwable t) {
                // Always bad.
                error(t);
                throw new WebdavException(t);
            }
        }
    }
}