jp.aegif.nemaki.dao.impl.couch.ContentDaoServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for jp.aegif.nemaki.dao.impl.couch.ContentDaoServiceImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2013 aegif.
 *
 * This file is part of NemakiWare.
 *
 * NemakiWare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NemakiWare 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with NemakiWare.
 * If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     linzhixing(https://github.com/linzhixing) - initial API and implementation
 ******************************************************************************/
package jp.aegif.nemaki.dao.impl.couch;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ektorp.Attachment;
import org.ektorp.AttachmentInputStream;
import org.ektorp.CouchDbConnector;
import org.ektorp.ViewQuery;
import org.ektorp.ViewResult;
import org.ektorp.ViewResult.Row;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap;
import jp.aegif.nemaki.dao.ContentDaoService;
import jp.aegif.nemaki.dao.impl.couch.connector.ConnectorPool;
import jp.aegif.nemaki.model.Archive;
import jp.aegif.nemaki.model.AttachmentNode;
import jp.aegif.nemaki.model.Change;
import jp.aegif.nemaki.model.Content;
import jp.aegif.nemaki.model.Document;
import jp.aegif.nemaki.model.Folder;
import jp.aegif.nemaki.model.Item;
import jp.aegif.nemaki.model.NemakiPropertyDefinitionCore;
import jp.aegif.nemaki.model.NemakiPropertyDefinitionDetail;
import jp.aegif.nemaki.model.NemakiTypeDefinition;
import jp.aegif.nemaki.model.NodeBase;
import jp.aegif.nemaki.model.Policy;
import jp.aegif.nemaki.model.Relationship;
import jp.aegif.nemaki.model.Rendition;
import jp.aegif.nemaki.model.VersionSeries;
import jp.aegif.nemaki.model.couch.CouchArchive;
import jp.aegif.nemaki.model.couch.CouchAttachmentNode;
import jp.aegif.nemaki.model.couch.CouchChange;
import jp.aegif.nemaki.model.couch.CouchContent;
import jp.aegif.nemaki.model.couch.CouchDocument;
import jp.aegif.nemaki.model.couch.CouchFolder;
import jp.aegif.nemaki.model.couch.CouchItem;
import jp.aegif.nemaki.model.couch.CouchNodeBase;
import jp.aegif.nemaki.model.couch.CouchPolicy;
import jp.aegif.nemaki.model.couch.CouchPropertyDefinitionCore;
import jp.aegif.nemaki.model.couch.CouchPropertyDefinitionDetail;
import jp.aegif.nemaki.model.couch.CouchRelationship;
import jp.aegif.nemaki.model.couch.CouchRendition;
import jp.aegif.nemaki.model.couch.CouchTypeDefinition;
import jp.aegif.nemaki.model.couch.CouchVersionSeries;
import jp.aegif.nemaki.util.constant.NodeType;

/**
 * Dao Service implementation for CouchDB.
 *
 * @author linzhixing
 *
 */
@Component
public class ContentDaoServiceImpl implements ContentDaoService {

    private RepositoryInfoMap repositoryInfoMap;
    private ConnectorPool connectorPool;
    private static final Log log = LogFactory.getLog(ContentDaoServiceImpl.class);

    private static final String DESIGN_DOCUMENT = "_design/_repo";
    private static final String ATTACHMENT_NAME = "content";

    public ContentDaoServiceImpl() {

    }

    // ///////////////////////////////////////
    // Type & Property definition
    // ///////////////////////////////////////
    @Override
    public List<NemakiTypeDefinition> getTypeDefinitions(String repositoryId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("typeDefinitions");
        List<CouchTypeDefinition> l = connectorPool.get(repositoryId).queryView(query, CouchTypeDefinition.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            List<NemakiTypeDefinition> result = new ArrayList<NemakiTypeDefinition>();
            for (CouchTypeDefinition ct : l) {
                result.add(ct.convert());
            }
            return result;
        }
    }

    @Override
    public NemakiTypeDefinition getTypeDefinition(String repositoryId, String typeId) {
        throw new UnsupportedOperationException(Thread.currentThread().getStackTrace()[0].getMethodName()
                + ":this method is only for cahced service. No need for implementation.");
    }

    @Override
    public NemakiTypeDefinition createTypeDefinition(String repositoryId, NemakiTypeDefinition typeDefinition) {
        CouchTypeDefinition ct = new CouchTypeDefinition(typeDefinition);
        connectorPool.get(repositoryId).create(ct);
        return ct.convert();
    }

    @Override
    public NemakiTypeDefinition updateTypeDefinition(String repositoryId, NemakiTypeDefinition typeDefinition) {
        CouchTypeDefinition cp = connectorPool.get(repositoryId).get(CouchTypeDefinition.class,
                typeDefinition.getId());
        CouchTypeDefinition update = new CouchTypeDefinition(typeDefinition);
        update.setRevision(cp.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public void deleteTypeDefinition(String repositoryId, String nodeId) {
        delete(repositoryId, nodeId);
    }

    @Override
    public List<NemakiPropertyDefinitionCore> getPropertyDefinitionCores(String repositoryId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("propertyDefinitionCores");
        List<CouchPropertyDefinitionCore> l = connectorPool.get(repositoryId).queryView(query,
                CouchPropertyDefinitionCore.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            List<NemakiPropertyDefinitionCore> result = new ArrayList<NemakiPropertyDefinitionCore>();
            for (CouchPropertyDefinitionCore cpdc : l) {
                result.add(cpdc.convert());
            }
            return result;
        }
    }

    @Override
    public NemakiPropertyDefinitionCore getPropertyDefinitionCore(String repositoryId, String nodeId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("propertyDefinitionCores")
                .key(nodeId);
        List<CouchPropertyDefinitionCore> l = connectorPool.get(repositoryId).queryView(query,
                CouchPropertyDefinitionCore.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            return l.get(0).convert();
        }
    }

    @Override
    public NemakiPropertyDefinitionCore getPropertyDefinitionCoreByPropertyId(String repositoryId,
            String propertyId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT)
                .viewName("propertyDefinitionCoresByPropertyId").key(propertyId);
        List<CouchPropertyDefinitionCore> l = connectorPool.get(repositoryId).queryView(query,
                CouchPropertyDefinitionCore.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            return l.get(0).convert();
        }
    }

    @Override
    public NemakiPropertyDefinitionDetail getPropertyDefinitionDetail(String repositoryId, String nodeId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("propertyDefinitionDetails")
                .key(nodeId);
        List<CouchPropertyDefinitionDetail> l = connectorPool.get(repositoryId).queryView(query,
                CouchPropertyDefinitionDetail.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            return l.get(0).convert();
        }
    }

    @Override
    public List<NemakiPropertyDefinitionDetail> getPropertyDefinitionDetailByCoreNodeId(String repositoryId,
            String coreNodeId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT)
                .viewName("propertyDefinitionDetailsByCoreNodeId").key(coreNodeId);
        List<CouchPropertyDefinitionDetail> l = connectorPool.get(repositoryId).queryView(query,
                CouchPropertyDefinitionDetail.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            List<NemakiPropertyDefinitionDetail> result = new ArrayList<NemakiPropertyDefinitionDetail>();
            for (CouchPropertyDefinitionDetail cpdd : l) {
                result.add(cpdd.convert());
            }
            return result;
        }
    }

    @Override
    public NemakiPropertyDefinitionCore createPropertyDefinitionCore(String repositoryId,
            NemakiPropertyDefinitionCore propertyDefinitionCore) {
        CouchPropertyDefinitionCore cpc = new CouchPropertyDefinitionCore(propertyDefinitionCore);
        connectorPool.get(repositoryId).create(cpc);
        return cpc.convert();
    }

    @Override
    public NemakiPropertyDefinitionDetail createPropertyDefinitionDetail(String repositoryId,
            NemakiPropertyDefinitionDetail propertyDefinitionDetail) {
        CouchPropertyDefinitionDetail cpd = new CouchPropertyDefinitionDetail(propertyDefinitionDetail);
        connectorPool.get(repositoryId).create(cpd);
        return cpd.convert();
    }

    @Override
    public NemakiPropertyDefinitionDetail updatePropertyDefinitionDetail(String repositoryId,
            NemakiPropertyDefinitionDetail propertyDefinitionDetail) {

        CouchPropertyDefinitionDetail cpd = connectorPool.get(repositoryId).get(CouchPropertyDefinitionDetail.class,
                propertyDefinitionDetail.getId());

        CouchPropertyDefinitionDetail update = new CouchPropertyDefinitionDetail(propertyDefinitionDetail);
        update.setRevision(cpd.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    // ///////////////////////////////////////
    // Content
    // ///////////////////////////////////////
    @Override
    public NodeBase getNodeBase(String repositoryId, String objectId) {
        CouchNodeBase cnb = connectorPool.get(repositoryId).get(CouchNodeBase.class, objectId);
        return cnb.convert();
    }

    @Override
    public Content getContent(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("contentsById").key(objectId);

        ViewResult result = connectorPool.get(repositoryId).queryView(query);
        return convertJsonToEachBaeType(result);
    }

    private Content convertJsonToEachBaeType(ViewResult result) {
        if (result.getRows().isEmpty()) {
            return null;
        } else {
            Iterator<Row> iterator = result.getRows().iterator();
            while (iterator.hasNext()) {
                ObjectMapper mapper = new ObjectMapper();
                JsonNode jn = iterator.next().getValueAsNode();
                String baseType = jn.path("type").textValue();

                if (BaseTypeId.CMIS_DOCUMENT.value().equals(baseType)) {
                    CouchDocument cd = mapper.convertValue(jn, CouchDocument.class);
                    return cd.convert();
                } else if (BaseTypeId.CMIS_FOLDER.value().equals(baseType)) {
                    CouchFolder cf = mapper.convertValue(jn, CouchFolder.class);
                    return cf.convert();
                } else if (BaseTypeId.CMIS_POLICY.value().equals(baseType)) {
                    CouchPolicy cp = mapper.convertValue(jn, CouchPolicy.class);
                    return cp.convert();
                } else if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(baseType)) {
                    CouchRelationship cr = mapper.convertValue(jn, CouchRelationship.class);
                    return cr.convert();
                } else if (BaseTypeId.CMIS_ITEM.value().equals(baseType)) {
                    CouchItem ci = mapper.convertValue(jn, CouchItem.class);
                    return ci.convert();
                }
            }
        }

        return null;
    }

    // TODO Use view
    @Override
    public Document getDocument(String repositoryId, String objectId) {
        CouchDocument cd = connectorPool.get(repositoryId).get(CouchDocument.class, objectId);
        Document doc = cd.convert();
        return doc;
    }

    @Override
    public boolean existContent(String repositoryId, String objectTypeId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("countByObjectType")
                .key(objectTypeId);
        ViewResult l = connectorPool.get(repositoryId).queryView(query);
        List<Row> rows = l.getRows();
        if (CollectionUtils.isEmpty(rows)) {
            return false;
        } else {
            for (Row row : rows) {
                if (objectTypeId.equals(row.getKey())) {
                    int count = row.getValueAsInt();
                    if (count == 0) {
                        return false;
                    } else {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public List<Document> getCheckedOutDocuments(String repositoryId, String parentFolderId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("privateWorkingCopies");
        if (parentFolderId != null)
            query.key(parentFolderId);
        List<CouchDocument> l = connectorPool.get(repositoryId).queryView(query, CouchDocument.class);

        if (CollectionUtils.isEmpty(l))
            return null;
        List<Document> results = new ArrayList<Document>();
        for (CouchDocument cd : l) {
            results.add(cd.convert());
        }
        return results;
    }

    @Override
    public VersionSeries getVersionSeries(String repositoryId, String nodeId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("versionSeries").key(nodeId);
        List<CouchVersionSeries> l = connectorPool.get(repositoryId).queryView(query, CouchVersionSeries.class);

        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            return l.get(0).convert();
        }
    }

    @Override
    public List<Document> getAllVersions(String repositoryId, String versionSeriesId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("documentsByVersionSeriesId")
                .key(versionSeriesId);

        List<CouchDocument> cds = connectorPool.get(repositoryId).queryView(query, CouchDocument.class);
        if (CollectionUtils.isEmpty(cds))
            return null;
        List<Document> list = new ArrayList<Document>();
        for (CouchDocument cd : cds) {
            list.add(cd.convert());
        }
        return list;

    }

    @Override
    public Document getDocumentOfLatestVersion(String repositoryId, String versionSeriesId) {
        if (versionSeriesId == null)
            return null;
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("latestVersions")
                .key(versionSeriesId);
        List<CouchDocument> list = connectorPool.get(repositoryId).queryView(query, CouchDocument.class);

        if (list.size() == 1) {
            return list.get(0).convert();
        } else if (list.size() > 1) {
            log.warn("The latest version of [" + versionSeriesId + "] is duplicate.");
            return list.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public Document getDocumentOfLatestMajorVersion(String repositoryId, String versionSeriesId) {
        if (versionSeriesId == null)
            return null;
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("latestMajorVersions")
                .key(versionSeriesId);
        List<CouchDocument> list = connectorPool.get(repositoryId).queryView(query, CouchDocument.class);

        if (list.size() == 1) {
            return list.get(0).convert();
        } else if (list.size() > 1) {
            log.warn("The latest major version of [" + versionSeriesId + "] is duplicate.");
            return list.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public Folder getFolder(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("folders").key(objectId);
        List<CouchFolder> list = connectorPool.get(repositoryId).queryView(query, CouchFolder.class);

        if (CollectionUtils.isEmpty(list)) {
            return null;
        } else {
            return list.get(0).convert();
        }
    }

    @Override
    public Folder getFolderByPath(String repositoryId, String path) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("foldersByPath").key(path);
        List<CouchFolder> l = connectorPool.get(repositoryId).queryView(query, CouchFolder.class);

        if (CollectionUtils.isEmpty(l))
            return null;
        return l.get(0).convert();
    }

    @Override
    public List<Content> getChildren(String repositoryId, String parentId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("children").key(parentId);
        List<CouchContent> list = connectorPool.get(repositoryId).queryView(query, CouchContent.class);

        if (list != null && !list.isEmpty()) {
            List<Content> contents = new ArrayList<Content>();
            for (CouchContent cc : list) {
                contents.add(cc.convert());
            }
            return contents;
        } else {
            return null;
        }
    }

    @Override
    public Content getChildByName(String repositoryId, String parentId, String name) {
        class ViewKey {
            private String parentId;
            private String name;

            public ViewKey(String parentId, String name) {
                this.parentId = parentId;
                this.name = name;
            }

            public String getParentId() {
                return parentId;
            }

            public String getName() {
                return name;
            }

        }

        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("childByName")
                .key(new ViewKey(parentId, name));
        ViewResult result = connectorPool.get(repositoryId).queryView(query);

        return convertJsonToEachBaeType(result);
    }

    public List<String> getChildrenNames(String repositoryId, String parentId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("childrenNames").key(parentId);
        ViewResult result = connectorPool.get(repositoryId).queryView(query);

        List<String> list = new ArrayList<String>();
        if (result == null || result.isEmpty()) {
            return new ArrayList<String>();
        } else {
            Iterator<Row> itr = result.iterator();
            while (itr.hasNext()) {
                list.add(itr.next().getValue());
            }
        }

        return list;
    }

    @Override
    public Relationship getRelationship(String repositoryId, String objectId) {
        CouchRelationship cr = connectorPool.get(repositoryId).get(CouchRelationship.class, objectId);
        return cr.convert();
    }

    @Override
    public List<Relationship> getRelationshipsBySource(String repositoryId, String sourceId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("relationshipsBySource")
                .key(sourceId);
        List<CouchRelationship> crs = connectorPool.get(repositoryId).queryView(query, CouchRelationship.class);

        List<Relationship> result = new ArrayList<Relationship>();
        if (crs != null && !crs.isEmpty()) {
            for (CouchRelationship cr : crs) {
                result.add(cr.convert());
            }
        }
        return result;
    }

    @Override
    public List<Relationship> getRelationshipsByTarget(String repositoryId, String targetId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("relationshipsByTarget")
                .key(targetId);
        List<CouchRelationship> crs = connectorPool.get(repositoryId).queryView(query, CouchRelationship.class);

        List<Relationship> result = new ArrayList<Relationship>();
        if (crs != null && !crs.isEmpty()) {
            for (CouchRelationship cr : crs) {
                result.add(cr.convert());
            }
        }
        return result;
    }

    @Override
    public Policy getPolicy(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("policies").key(objectId);
        List<CouchPolicy> cps = connectorPool.get(repositoryId).queryView(query, CouchPolicy.class);
        if (!CollectionUtils.isEmpty(cps)) {
            return cps.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public List<Policy> getAppliedPolicies(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("policiesByAppliedObject")
                .key(objectId);
        List<CouchPolicy> cps = connectorPool.get(repositoryId).queryView(query, CouchPolicy.class);
        if (!CollectionUtils.isEmpty(cps)) {
            List<Policy> policies = new ArrayList<Policy>();
            for (CouchPolicy cp : cps) {
                policies.add(cp.convert());
            }
            return policies;
        } else {
            return null;
        }
    }

    @Override
    public Item getItem(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("items").key(objectId);
        List<CouchItem> cpi = connectorPool.get(repositoryId).queryView(query, CouchItem.class);
        if (!CollectionUtils.isEmpty(cpi)) {
            return cpi.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public Document create(String repositoryId, Document document) {
        CouchDocument cd = new CouchDocument(document);
        connectorPool.get(repositoryId).create(cd);
        return cd.convert();
    }

    @Override
    public VersionSeries create(String repositoryId, VersionSeries versionSeries) {
        CouchVersionSeries cvs = new CouchVersionSeries(versionSeries);
        connectorPool.get(repositoryId).create(cvs);
        return cvs.convert();
    }

    @Override
    public Folder create(String repositoryId, Folder folder) {
        CouchFolder cf = new CouchFolder(folder);
        connectorPool.get(repositoryId).create(cf);
        return cf.convert();
    }

    @Override
    public Relationship create(String repositoryId, Relationship relationship) {
        CouchRelationship cr = new CouchRelationship(relationship);
        connectorPool.get(repositoryId).create(cr);
        return cr.convert();
    }

    @Override
    public Policy create(String repositoryId, Policy policy) {
        CouchPolicy cp = new CouchPolicy(policy);
        connectorPool.get(repositoryId).create(cp);
        return cp.convert();
    }

    @Override
    public Item create(String repositoryId, Item item) {
        CouchItem ci = new CouchItem(item);
        connectorPool.get(repositoryId).create(ci);
        return ci.convert();
    }

    @Override
    public Document update(String repositoryId, Document document) {
        CouchDocument cd = connectorPool.get(repositoryId).get(CouchDocument.class, document.getId());

        // Set the latest revision for avoid conflict
        CouchDocument update = new CouchDocument(document);
        update.setRevision(cd.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public Document move(String repositoryId, Document document, String sourceId) {
        return update(repositoryId, document);
    }

    @Override
    public VersionSeries update(String repositoryId, VersionSeries versionSeries) {
        CouchVersionSeries cvs = connectorPool.get(repositoryId).get(CouchVersionSeries.class,
                versionSeries.getId());

        // Set the latest revision for avoid conflict
        CouchVersionSeries update = new CouchVersionSeries(versionSeries);
        update.setRevision(cvs.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public Folder update(String repositoryId, Folder folder) {
        CouchFolder cf = connectorPool.get(repositoryId).get(CouchFolder.class, folder.getId());
        // Set the latest revision for avoid conflict
        CouchFolder update = new CouchFolder(folder);
        update.setRevision(cf.getRevision());

        connectorPool.get(repositoryId).update(update);

        return update.convert();
    }

    @Override
    public Folder move(String repositoryId, Folder folder, String sourceId) {
        return update(repositoryId, folder);
    }

    @Override
    public Relationship update(String repositoryId, Relationship relationship) {
        CouchRelationship cp = connectorPool.get(repositoryId).get(CouchRelationship.class, relationship.getId());
        // Set the latest revision for avoid conflict
        CouchRelationship update = new CouchRelationship(relationship);
        update.setRevision(cp.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public Policy update(String repositoryId, Policy policy) {
        CouchPolicy cp = connectorPool.get(repositoryId).get(CouchPolicy.class, policy.getId());
        // Set the latest revision for avoid conflict
        CouchPolicy update = new CouchPolicy(policy);
        update.setRevision(cp.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public Item update(String repositoryId, Item item) {
        CouchItem ci = connectorPool.get(repositoryId).get(CouchItem.class, item.getId());
        // Set the latest revision for avoid conflict
        CouchItem update = new CouchItem(item);
        update.setRevision(ci.getRevision());

        connectorPool.get(repositoryId).update(update);
        return update.convert();
    }

    @Override
    public void delete(String repositoryId, String objectId) {
        CouchNodeBase cnb = connectorPool.get(repositoryId).get(CouchNodeBase.class, objectId);
        connectorPool.get(repositoryId).delete(cnb);
    }

    // ///////////////////////////////////////
    // Attachment
    // ///////////////////////////////////////
    @Override
    public AttachmentNode getAttachment(String repositoryId, String attachmentId) {

        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("attachments").key(attachmentId);
        List<CouchAttachmentNode> list = connectorPool.get(repositoryId).queryView(query,
                CouchAttachmentNode.class);

        if (CollectionUtils.isEmpty(list)) {
            return null;
        } else {
            CouchAttachmentNode can = list.get(0);

            Attachment a = can.getAttachments().get(ATTACHMENT_NAME);

            AttachmentNode an = new AttachmentNode();
            an.setId(can.getId());
            an.setMimeType(a.getContentType());
            an.setLength(a.getContentLength());

            return an;
        }
    }

    @Override
    public void setStream(String repositoryId, AttachmentNode attachmentNode) {
        AttachmentInputStream ais = connectorPool.get(repositoryId).getAttachment(attachmentNode.getId(),
                ATTACHMENT_NAME);
        attachmentNode.setInputStream(ais);
    }

    @Override
    public Rendition getRendition(String repositoryId, String objectId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("renditions").key(objectId);
        List<CouchRendition> list = connectorPool.get(repositoryId).queryView(query, CouchRendition.class);

        if (CollectionUtils.isEmpty(list)) {
            return null;
        } else {
            CouchRendition crd = list.get(0);
            Attachment a = crd.getAttachments().get(ATTACHMENT_NAME);

            Rendition rd = new Rendition();
            rd.setId(objectId);
            rd.setTitle(crd.getTitle());
            rd.setKind(crd.getKind());
            rd.setMimetype(a.getContentType());
            rd.setLength(a.getContentLength());
            AttachmentInputStream ais = connectorPool.get(repositoryId).getAttachment(objectId, ATTACHMENT_NAME);
            rd.setInputStream(ais);

            /*
             * try { BufferedImage bimg = ImageIO.read(ais); if (bimg != null) {
             * rd.setWidth(bimg.getWidth()); rd.setHeight(bimg.getHeight());
             * bimg.flush(); } } catch (IOException e) { // TODO logging // do
             * nothing }
             */

            return rd;
        }
    }

    @Override
    public String createAttachment(String repositoryId, AttachmentNode attachment, ContentStream contentStream) {
        CouchAttachmentNode ca = new CouchAttachmentNode(attachment);
        connectorPool.get(repositoryId).create(ca);

        AttachmentInputStream ais = new AttachmentInputStream(ATTACHMENT_NAME, contentStream.getStream(),
                contentStream.getMimeType(), contentStream.getLength());
        connectorPool.get(repositoryId).createAttachment(ca.getId(), ca.getRevision(), ais);

        return ca.getId();
    }

    @Override
    public String createRendition(String repositoryId, Rendition rendition, ContentStream contentStream) {
        CouchRendition cr = new CouchRendition(rendition);
        connectorPool.get(repositoryId).create(cr);

        AttachmentInputStream ais = new AttachmentInputStream(ATTACHMENT_NAME, contentStream.getStream(),
                contentStream.getMimeType(), contentStream.getLength());
        connectorPool.get(repositoryId).createAttachment(cr.getId(), cr.getRevision(), ais);

        return cr.getId();
    }

    @Override
    public void updateAttachment(String repositoryId, AttachmentNode attachment, ContentStream contentStream) {
        CouchAttachmentNode ca = connectorPool.get(repositoryId).get(CouchAttachmentNode.class, attachment.getId());
        CouchAttachmentNode update = new CouchAttachmentNode(attachment);
        // Set the latest revision for avoid conflict
        update.setRevision(ca.getRevision());

        String revisionAfterDeleted = connectorPool.get(repositoryId).deleteAttachment(ca.getId(), ca.getRevision(),
                ATTACHMENT_NAME);

        AttachmentInputStream ais = new AttachmentInputStream(ATTACHMENT_NAME, contentStream.getStream(),
                contentStream.getMimeType());
        connectorPool.get(repositoryId).createAttachment(attachment.getId(), revisionAfterDeleted, ais);
    }

    // ///////////////////////////////////////
    // Change event
    // ///////////////////////////////////////
    @Override
    public Change getChangeEvent(String repositoryId, String changeTokenId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("changesByToken")
                .key(changeTokenId);
        List<CouchChange> l = connectorPool.get(repositoryId).queryView(query, CouchChange.class);

        if (CollectionUtils.isEmpty(l))
            return null;
        return l.get(0).convert();
    }

    @Override
    public Change getLatestChange(String repositoryId) {
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("changesByToken").descending(true)
                .limit(1);
        List<CouchChange> l = connectorPool.get(repositoryId).queryView(query, CouchChange.class);
        if (CollectionUtils.isEmpty(l)) {
            return null;
        } else {
            return l.get(0).convert();
        }
    }

    @Override
    public List<Change> getLatestChanges(String repositoryId, String startToken, int maxItems) {
        List<Change> result = new ArrayList<Change>();
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("changesByToken").descending(false);

        if (StringUtils.isNotBlank(startToken)) {
            CouchDbConnector conn = connectorPool.get(repositoryId);
            try {
                CouchChange startChange = conn.get(CouchChange.class, startToken);
                Long startKey = startChange.getToken();
                query.startKey(startKey);
            } catch (org.ektorp.DocumentNotFoundException ex) {
                return null;
            }
        }
        if (maxItems > 0) {
            query.limit(maxItems);
        }

        List<CouchChange> l = connectorPool.get(repositoryId).queryView(query, CouchChange.class);
        if (CollectionUtils.isEmpty(l))
            return null;

        for (CouchChange cc : l) {
            result.add(cc.convert());
        }
        return result;
    }

    @Override
    public Change create(String repositoryId, Change change) {
        CouchChange cc = new CouchChange(change);
        connectorPool.get(repositoryId).create(cc);
        return cc.convert();
    }

    // ///////////////////////////////////////
    // Archive
    // ///////////////////////////////////////
    @Override
    public Archive getArchive(String repositoryId, String objectId) {
        String archive = repositoryInfoMap.getArchiveId(repositoryId);
        CouchArchive ca = connectorPool.get(archive).get(CouchArchive.class, objectId);
        return ca.convert();
    }

    @Override
    public Archive getArchiveByOriginalId(String repositoryId, String originalId) {
        String archive = repositoryInfoMap.getArchiveId(repositoryId);
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("all").key(originalId);
        List<CouchArchive> list = connectorPool.get(archive).queryView(query, CouchArchive.class);

        if (list != null && !list.isEmpty()) {
            return list.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public Archive getAttachmentArchive(String repositoryId, Archive archive) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        if (!archive.isDocument())
            return null;
        String attachmentId = archive.getAttachmentNodeId();
        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("attachments").key(attachmentId);
        List<CouchArchive> list = connectorPool.get(archiveId).queryView(query, CouchArchive.class);

        if (list != null && !list.isEmpty()) {
            return list.get(0).convert();
        } else {
            return null;
        }
    }

    @Override
    public List<Archive> getChildArchives(String repositoryId, Archive archive) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("children")
                .key(archive.getOriginalId());
        List<CouchArchive> list = connectorPool.get(archiveId).queryView(query, CouchArchive.class);

        if (list != null && !list.isEmpty()) {
            List<Archive> archives = new ArrayList<Archive>();
            for (CouchArchive ca : list) {
                archives.add(ca.convert());
            }
            return archives;
        } else {
            return null;
        }
    }

    @Override
    public List<Archive> getArchivesOfVersionSeries(String repositoryId, String versionSeriesId) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("versionSeries")
                .key(versionSeriesId);
        List<CouchArchive> list = connectorPool.get(archiveId).queryView(query, CouchArchive.class);

        if (list != null && !list.isEmpty()) {
            List<Archive> archives = new ArrayList<Archive>();
            for (CouchArchive ca : list) {
                archives.add(ca.convert());
            }
            return archives;
        } else {
            return null;
        }
    }

    @Override
    public List<Archive> getAllArchives(String repositoryId) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("all");
        List<CouchArchive> list = connectorPool.get(archiveId).queryView(query, CouchArchive.class);

        List<Archive> archives = new ArrayList<Archive>();
        for (CouchArchive ca : list) {
            archives.add(ca.convert());
        }

        return archives;
    }

    @Override
    public Archive createArchive(String repositoryId, Archive archive, Boolean deletedWithParent) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        CouchNodeBase cnb = connectorPool.get(repositoryId).get(CouchNodeBase.class, archive.getOriginalId());
        CouchArchive ca = new CouchArchive(archive);
        ca.setLastRevision(cnb.getRevision());

        // Write to DB
        connectorPool.get(archiveId).create(ca);
        return ca.convert();
    }

    @Override
    public Archive createAttachmentArchive(String repositoryId, Archive archive) {
        String archiveId = repositoryInfoMap.getArchiveId(repositoryId);

        CouchArchive ca = new CouchArchive(archive);
        CouchNodeBase cnb = connectorPool.get(repositoryId).get(CouchNodeBase.class, archive.getOriginalId());
        ca.setLastRevision(cnb.getRevision());

        connectorPool.get(archiveId).create(ca);
        return ca.convert();
    }

    @Override
    // FIXME return archiveId or something when successfully deleted
    public void deleteArchive(String repositoryId, String archiveId) {
        String archive = repositoryInfoMap.getArchiveId(repositoryId);

        try {
            CouchArchive ca = connectorPool.get(archive).get(CouchArchive.class, archiveId);
            connectorPool.get(archive).delete(ca);
        } catch (Exception e) {
            log.warn(buildLogMsg(archiveId, "the archive not found on db"));
            return;
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public void restoreContent(String repositoryId, Archive archive) {
        if (archive.isDocument()) {
            // Restore a content
            CouchDocument cd = connectorPool.get(repositoryId).get(CouchDocument.class, archive.getOriginalId(),
                    archive.getLastRevision());
            cd.setRevision(null);
            connectorPool.get(repositoryId).update(cd);
        } else if (archive.isFolder()) {
            CouchFolder cf = connectorPool.get(repositoryId).get(CouchFolder.class, archive.getOriginalId(),
                    archive.getLastRevision());
            cf.setRevision(null);
            connectorPool.get(repositoryId).update(cf);
        } else {
            // TODO Do nothing?
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public void restoreAttachment(String repositoryId, Archive archive) {
        CouchDbConnector connector = connectorPool.get(repositoryId);

        // Restore its attachment
        CouchAttachmentNode can = connector.get(CouchAttachmentNode.class, archive.getOriginalId(),
                archive.getLastRevision());
        can.setRevision(null);
        AttachmentInputStream is = connector.getAttachment(can.getId(), ATTACHMENT_NAME, archive.getLastRevision());
        connector.createAttachment(can.getId(), can.getRevision(), is);
        CouchAttachmentNode restored = connector.get(CouchAttachmentNode.class, can.getId());
        restored.setType(NodeType.ATTACHMENT.value());
        connector.update(restored);
    }

    // ///////////////////////////////////////
    // Other
    // ///////////////////////////////////////
    private String buildLogMsg(String objectId, String msg) {
        return "[objectId:" + objectId + "]" + msg;
    }

    public void setConnectorPool(ConnectorPool connectorPool) {
        this.connectorPool = connectorPool;
    }

    public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) {
        this.repositoryInfoMap = repositoryInfoMap;
    }

    @Override
    public void refreshCmisObjectData(String repositoryId, String objectId) {
        // this method is for cached service
    }
}