/* * * * Copyright 2010-2012 the original author or authors. * * * * 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 org.springframework.data.cloudant.core; import com.cloudant.client.api.CloudantClient; import com.cloudant.client.api.Database; import com.cloudant.client.api.model.*; import com.google.gson.GsonBuilder; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.data.cloudant.config.ICloudantConnector; import org.springframework.data.cloudant.core.mapping.event.AfterSaveEvent; import org.springframework.data.cloudant.core.mapping.event.BeforeSaveEvent; import org.springframework.data.cloudant.core.mapping.event.CloudantMappingEvent; import org.springframework.data.cloudant.core.model.BaseDocument; import org.springframework.data.domain.Pageable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Created by justinsaul on 6/9/15. */ public class CloudantTemplate<T extends BaseDocument> implements CloudantOperations<T>, ApplicationEventPublisherAware { private CloudantClient client; private final CloudantExceptionTranslator exceptionTranslator = new CloudantExceptionTranslator(); private ApplicationEventPublisher eventPublisher; private Database database; private final Logger logger = LoggerFactory.getLogger(CloudantTemplate.class); // the constructor using false as default in create parameter public CloudantTemplate(final ICloudantConnector dbConnector){ this.client = dbConnector.getClient(); this.database = this.client.database(dbConnector.getDbName(), false); } // the constructor using false as default in create parameter public CloudantTemplate(final CloudantClient client, final String databaseName) { this.client = client; this.database = this.client.database(databaseName, false); } public CloudantTemplate(final ICloudantConnector dbConnector, Boolean create){ this.client = dbConnector.getClient(); this.database = this.client.database(dbConnector.getDbName(), create); } public CloudantTemplate(final CloudantClient client, final String databaseName, Boolean create) { this.client = client; this.database = this.client.database(databaseName, create); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; } @Override public T save(T obj) { maybeEmitEvent(new BeforeSaveEvent<T>(obj)); Response response = database.save(obj); if (response != null && response.getId() != null) { obj.setId(response.getId()); obj.setRevision(response.getRev()); } maybeEmitEvent(new AfterSaveEvent<T>(obj)); return obj; } @Override public List<T> save(List<T> batchOfObj) { for (T entity : batchOfObj) { maybeEmitEvent(new BeforeSaveEvent<T>(entity)); } database.bulk(batchOfObj); for (T entity : batchOfObj) { maybeEmitEvent(new AfterSaveEvent<T>(entity)); } return batchOfObj; } @Override public T update(T obj) { maybeEmitEvent(new BeforeSaveEvent<T>(obj)); Response response = database.update(obj); if (response != null && response.getReason() != null) { obj.setRevision(response.getRev()); } maybeEmitEvent(new AfterSaveEvent<T>(obj)); return obj; } @Override public List<T> update(List<T> batchOfObj) { for (T entity : batchOfObj) { maybeEmitEvent(new BeforeSaveEvent<T>(entity)); } database.bulk(batchOfObj); for (T entity : batchOfObj) { maybeEmitEvent(new AfterSaveEvent<T>(entity)); } return batchOfObj; } @Override public T findById(String id, Class<T> entityClass) { return database.find(entityClass, id); } @Override public List<T> findByIndex(String index, FindByIndexOptions query, Class<T> entityClass) { return database.findByIndex(index, entityClass, query); } public List<T> findByKeys(String view, List<String> keys, boolean descending, boolean reduce, boolean includeDocs, Class<T> entityClass) { return database.view(view).descending(descending).keys(keys).reduce(reduce).includeDocs(includeDocs).query(entityClass); } public List<T> findByKey(String view, Object[] key, boolean descending, boolean reduce, boolean includeDocs, Class<T> entityClass) { return database.view(view).descending(descending).key(key).reduce(reduce).includeDocs(includeDocs).query(entityClass); } public Page<T> queryView(String view, Pageable page, boolean includeDocs, Class<T> entityClass) { return database.view(view).queryPage(page.getPageSize(), null, entityClass); } public List<T> queryView(String view, boolean reduce, boolean includeDocs, Class<T> entityClass) { return database.view(view).descending(true).reduce(reduce).includeDocs(includeDocs).query(entityClass); } // The sorting not work properly public List<T> queryViewAndSort(String view, boolean descending, boolean reduce, boolean includeDocs, Class<T> entityClass) { return database.view(view).descending(descending).reduce(reduce).includeDocs(includeDocs).query(entityClass); } public ViewResult queryView(String view, Pageable pageable, Class<T> entityClass, String key) { // We need a class to compose the different query conditions if(key == null) { return database.view(view).includeDocs(true) .limit(pageable.getPageSize()).descending(true).skip(pageable.getOffset()) .queryView(String.class, Object.class, entityClass); } else { return database.view(view).includeDocs(true).descending(true) .limit(pageable.getPageSize()).skip(pageable.getOffset()) .key(key) .queryView(String.class, Object.class, entityClass); } } public List<T> queryViewGrouped(String view, boolean includeDocs, boolean group, Object startKey, Object endKey, Pageable pageable, Class<T> entityClass) { return database.view(view).startKey(startKey).endKey(endKey).limit(pageable.getPageSize()).skip(pageable.getOffset()).group(group).includeDocs(includeDocs).query(entityClass); } @Override public List<T> queryView(String view, boolean includeDocs, Object startKey, Object endKey, Pageable pageable, Class<T> entityClass) { return database.view(view).startKey(startKey).endKey(endKey).limit(pageable.getPageSize()).skip(pageable.getOffset()).includeDocs(includeDocs).query(entityClass); } public ViewResult queryViewByStartKey(String view, boolean includeDocs, Class<T> entityClass, Object[] startKey, Object[] endKey, Pageable pageable) { return queryViewByStartKey(view, includeDocs, entityClass, startKey, endKey, pageable, false); } public ViewResult queryViewByStartKey(String view, boolean includeDocs, Class<T> entityClass, Object[] startKey, Object[] endKey, Pageable pageable, Boolean descending) { return database.view(view).startKey(startKey).endKey(endKey).limit(pageable.getPageSize()) .skip(pageable.getOffset()).includeDocs(true).descending(descending) .queryView(Object.class, Object.class, entityClass); } public SearchResult<T> search(String indexName, Integer limit, boolean includeDocs, String query, Class<T> entityClass){ return database.search(indexName).limit(limit).includeDocs(includeDocs).querySearchResult(query, entityClass); } @Override public boolean exists(String id) { return database.contains(id); } @Override public void remove(T obj) { maybeEmitEvent(new BeforeSaveEvent<T>(obj)); handleResponse(database.remove(obj), obj); maybeEmitEvent(new AfterSaveEvent<T>(obj)); } @Override public void remove(List<T> batchOfObj) { for (T entity : batchOfObj) { maybeEmitEvent(new BeforeSaveEvent<T>(entity)); } List<Map> bulkDeleteObjects = new ArrayList<Map>(); for (T entity : batchOfObj) { bulkDeleteObjects.add(createDeleteDocument(entity.getId(),entity.getRevision())); } database.bulk(bulkDeleteObjects); for (T entity : batchOfObj) { maybeEmitEvent(new AfterSaveEvent<T>(entity)); } } @Override public CloudantClient getCloudantClient() { return client; } public void setUnmappedDataAdapter() { setDataAdapter(null); } public void setDataAdapter(Map<Class<?>, Object> adapters) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(DateTime.class, new DateTimeDataAdapter()); gsonBuilder.registerTypeHierarchyAdapter(BaseDocument.class, new UnmappedDataAdapter()); if(adapters != null) { for (Class<?> type : adapters.keySet()) { gsonBuilder.registerTypeAdapter(type, adapters.get(type)); } } gsonBuilder.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); this.client.setGsonBuilder(gsonBuilder); } protected <T> void maybeEmitEvent(final CloudantMappingEvent<T> event) { if (null != eventPublisher) { eventPublisher.publishEvent(event); } } protected Map<String, Object> createDeleteDocument(String id, String revision) { Map<String, Object> map = new HashMap<String, Object>(3); map.put("_id", id); map.put("_rev", revision); map.put("_deleted", true); return map; } protected void handleResponse(Response response, T entity) { if (response.getError() != null) { throw new RuntimeException(String.format("error %s, reason: %s, entity id: %s, rev: %s", response.getError(), response.getReason(), response.getId(), response.getRev())); } else if (entity != null) { // maybe set doc id and rev? entity.setRevision(response.getRev()); } } }