/* * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.com) * * 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 org.lightcouch.CouchDbUtil.*; import static org.lightcouch.URIBuilder.builder; import java.io.File; import java.io.FileNotFoundException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.lightcouch.DesignDocument.MapReduce; /** * Provides methods to create and save CouchDB design documents. * <h3>Usage Example:</h3> * <pre> * DesignDocument exampleDoc = dbClient.design().getFromDesk("example"); * Response response = dbClient.design().synchronizeWithDb(exampleDoc); * DesignDocument documentFromDb = dbClient.design().getFromDb("_design/example"); * </pre> * @see DesignDocument * @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 MAP_JS = "map.js"; private static final String REDUCE_JS = "reduce.js"; private CouchDbClient dbc; CouchDbDesign(CouchDbClient 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 from desk to the database. * @see #synchronizeWithDb(DesignDocument) */ 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"); URI uri = builder(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"); URI uri = builder(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() { File rootDir = null; try { rootDir = new File(getURL(DESIGN_DOCS_DIR).toURI()); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } List<DesignDocument> designDocsList = new ArrayList<DesignDocument>(); for (String docName : rootDir.list()) { 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"); File designDoc = null; try { designDoc = new File(new File(getURL(DESIGN_DOCS_DIR).toURI()), id); if(!designDoc.exists()) { throw new FileNotFoundException("Design docs directory not found"); } } catch (Exception e) { throw new IllegalArgumentException(e); } DesignDocument dd = new DesignDocument(); List<String> elements = Arrays.asList(designDoc.list()); if(elements.contains(VALIDATE_DOC)) { // validate_doc_update File validateDir = new File(designDoc, VALIDATE_DOC); String[] validateFunctions = validateDir.list(); if(validateFunctions.length != 1) { throw new IllegalArgumentException("Expecting exactly one validate_doc_update function file"); } File validateFile = new File(validateDir, validateFunctions[0]); dd.setValidateDocUpdate(readFile(validateFile)); } // /validate_doc_update Map<String, MapReduce> views = null; if(elements.contains(VIEWS)) { // views File viewsRootDir = new File(designDoc, VIEWS); views = new HashMap<String, MapReduce>(); for (String viewDirName : viewsRootDir.list()) { // views sub-dirs MapReduce mr = dd.new MapReduce(); File viewDir = new File(viewsRootDir, viewDirName); for (String fileName : viewDir.list()) { // view files String def = readFile(new File(viewDir, 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(designDoc, elements, FILTERS)); dd.setShows(populateMap(designDoc, elements, SHOWS)); dd.setLists(populateMap(designDoc, elements, LISTS)); return dd; } private Map<String, String> populateMap(File designDoc, List<String> elements, String element) { Map<String, String> functionsMap = null; if(elements.contains(element)) { File functionsDir = new File(designDoc, element); functionsMap = new HashMap<String, String>(); for (String functionFileName : functionsDir.list()) { File functionFile = new File(functionsDir, functionFileName); functionsMap.put(removeExtension(functionFileName), readFile(functionFile)); } } return functionsMap; } }