/* * Copyright (C) 2011 lightcouch.org * * 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.lightcouch; import static java.lang.String.format; import static org.lightcouch.CouchDbUtil.assertNotEmpty; import static org.lightcouch.CouchDbUtil.listResources; import static org.lightcouch.CouchDbUtil.readFile; import static org.lightcouch.CouchDbUtil.removeExtension; import static org.lightcouch.URIBuilder.buildUri; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.lightcouch.DesignDocument.MapReduce; import com.google.gson.JsonArray; import com.google.gson.JsonObject; /** * Provides API to work with design documents. * <h3>Usage Example:</h3> * <pre> * // read from system files * DesignDocument design1 = dbClient.design().getFromDesk("example"); * * // sync with the database * dbClient.design().synchronizeWithDb(design1); * * // sync all with the database * dbClient.syncDesignDocsWithDb(); * * // read from the database * DesignDocument design2 = dbClient.design().getFromDb("_design/example"); * </pre> * @see {@link CouchDbClient#design() dbClient.design()} to access the API. * @see DesignDocument * @since 0.0.2 * @author Ahmed Yehia */ public class CouchDbDesign { private static final String DESIGN_DOCS_DIR = "design-docs"; private static final String JAVASCRIPT = "javascript"; private static final String DESIGN_PREFIX = "_design/"; private static final String VALIDATE_DOC = "validate_doc_update"; private static final String VIEWS = "views"; private static final String FILTERS = "filters"; private static final String SHOWS = "shows"; private static final String LISTS = "lists"; private static final String UPDATES = "updates"; private static final String REWRITES = "rewrites"; private static final String FULLTEXT = "fulltext"; private static final String INDEXES = "indexes"; private static final String MAP_JS = "map.js"; private static final String REDUCE_JS = "reduce.js"; private CouchDbClientBase dbc; CouchDbDesign(CouchDbClientBase dbc) { this.dbc = dbc; } /** * Synchronizes a design document to the Database. * <p>This method will first try to find a document in the database with the same id * as the given document, if it is not found then the given document will be saved to the database. * <p>If the document was found in the database, it will be compared with the given document using * {@code equals()}. If both documents are not equal, then the given document will be saved to the * database and updates the existing document. * @param document The design document to synchronize * @return {@link Response} as a result of a document save or update, or returns {@code null} if no * action was taken and the document in the database is up-to-date with the given document. */ public Response synchronizeWithDb(DesignDocument document) { assertNotEmpty(document, "Document"); DesignDocument documentFromDb = null; try { documentFromDb = getFromDb(document.getId()); } catch (NoDocumentException e) { return dbc.save(document); } if(!document.equals(documentFromDb)) { document.setRevision(documentFromDb.getRevision()); return dbc.update(document); } return null; } /** * Synchronize all design documents on desk to the database. * @see #synchronizeWithDb(DesignDocument) * @see CouchDbClient#syncDesignDocsWithDb() */ public void synchronizeAllWithDb() { List<DesignDocument> documents = getAllFromDesk(); for (DesignDocument dd : documents) { synchronizeWithDb(dd); } } /** * Gets a design document from the database. * @param id The document id * @return {@link DesignDocument} */ public DesignDocument getFromDb(String id) { assertNotEmpty(id, "id"); final URI uri = buildUri(dbc.getDBUri()).path(id).build(); return dbc.get(uri, DesignDocument.class); } /** * Gets a design document from the database. * @param id The document id * @param rev The document revision * @return {@link DesignDocument} */ public DesignDocument getFromDb(String id, String rev) { assertNotEmpty(id, "id"); assertNotEmpty(id, "rev"); final URI uri = buildUri(dbc.getDBUri()).path(id).query("rev", rev).build(); return dbc.get(uri, DesignDocument.class); } /** * Gets all design documents from desk. * @see #getFromDesk(String) */ public List<DesignDocument> getAllFromDesk() { final List<DesignDocument> designDocsList = new ArrayList<DesignDocument>(); for (String docName : listResources(format("%s/", DESIGN_DOCS_DIR))) { designDocsList.add(getFromDesk(docName)); } return designDocsList; } /** * Gets a design document from desk. * @param id The document id to get. * @return {@link DesignDocument} */ public DesignDocument getFromDesk(String id) { assertNotEmpty(id, "id"); final DesignDocument dd = new DesignDocument(); final String rootPath = format("%s/%s/", DESIGN_DOCS_DIR, id); final List<String> elements = listResources(rootPath); if(elements == null) { throw new IllegalArgumentException("Design docs directory cannot be empty."); } // Views Map<String, MapReduce> views = null; if(elements.contains(VIEWS)) { views = new HashMap<String, MapReduce>(); final String viewsPath = format("%s%s/", rootPath, VIEWS); for (String viewDirName : listResources(viewsPath)) { // views sub-dirs final MapReduce mr = new MapReduce(); final String viewPath = format("%s%s/", viewsPath, viewDirName); final List<String> dirList = listResources(viewPath); for (String fileName : dirList) { // view files final String def = readFile(format("/%s%s", viewPath, fileName)); if(MAP_JS.equals(fileName)) mr.setMap(def); else if(REDUCE_JS.equals(fileName)) mr.setReduce(def); } // /foreach view files views.put(viewDirName, mr); } // /foreach views sub-dirs } // /views dd.setId(DESIGN_PREFIX + id); dd.setLanguage(JAVASCRIPT); dd.setViews(views); dd.setFilters(populateMap(rootPath, elements, FILTERS)); dd.setShows(populateMap(rootPath, elements, SHOWS)); dd.setLists(populateMap(rootPath, elements, LISTS)); dd.setUpdates(populateMap(rootPath, elements, UPDATES)); dd.setValidateDocUpdate(readContent(elements, rootPath, VALIDATE_DOC)); dd.setRewrites(dbc.getGson().fromJson(readContent(elements, rootPath, REWRITES), JsonArray.class)); dd.setFulltext(dbc.getGson().fromJson(readContent(elements, rootPath, FULLTEXT), JsonObject.class)); dd.setIndexes(dbc.getGson().fromJson(readContent(elements, rootPath, INDEXES), JsonObject.class)); return dd; } private Map<String, String> populateMap(String rootPath, List<String> elements, String element) { Map<String, String> functionsMap = null; if(elements.contains(element)) { functionsMap = new HashMap<String, String>(); String path = format("%s%s/", rootPath, element); for (String fileName : listResources(path)) { String contents = readFile(format("/%s%s", path, fileName)); functionsMap.put(removeExtension(fileName), contents); } } return functionsMap; } public String readContent(List<String> elements, String rootPath, String element) { if(elements.contains(element)) { String path = format("%s%s/", rootPath, element); List<String> dirList = listResources(path); for (String file : dirList) { String contents = readFile(format("/%s%s", path, file)); return contents; } } return null; } }