org.mksmart.ecapi.couchdb.CouchDbSanityChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.mksmart.ecapi.couchdb.CouchDbSanityChecker.java

Source

/*
 * 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.mksmart.ecapi.couchdb;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.lang.NotImplementedException;
import org.json.JSONException;
import org.json.JSONObject;
import org.mksmart.ecapi.api.AssemblyProvider;
import org.mksmart.ecapi.api.MisconfiguredException;
import org.mksmart.ecapi.api.SanityChecker;
import org.mksmart.ecapi.commons.couchdb.client.DocumentProvider;
import org.mksmart.ecapi.commons.couchdb.client.DocumentWriter;
import org.mksmart.ecapi.commons.couchdb.client.LocalDocumentProvider;
import org.skyscreamer.jsonassert.FieldComparisonFailure;
import org.skyscreamer.jsonassert.JSONCompare;
import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult;
import org.skyscreamer.jsonassert.comparator.DefaultComparator;
import org.skyscreamer.jsonassert.comparator.JSONComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link SanityChecker}
 * 
 * @author alessandro <alexdma@apache.org>
 *
 */
public class CouchDbSanityChecker implements SanityChecker<String> {

    protected static String DIR_DOC_DESIGN_CONFIG = "couchdb/config_db/_design";

    protected JSONComparator comparator;

    private DocumentProvider<JSONObject> configLocalProvider, storeLocalProvider;

    /**
     * FIXME move out of here and instantiate
     */
    private DocumentWriter<JSONObject> configRemoteWriter;

    private Logger log = LoggerFactory.getLogger(getClass());

    protected boolean severe;

    public CouchDbSanityChecker() {
        this(false);
    }

    /**
     * Constructs a new instance of {@link CouchDbSanityChecker}.
     * 
     * @param severe
     *            if true, the checker's methods will throw runtime exceptions whenever something is wrong.
     */
    public CouchDbSanityChecker(boolean severe) {
        this.severe = severe;
        this.comparator = new DefaultComparator(JSONCompareMode.LENIENT);
        Properties config = new Properties();
        URL base = getClass().getResource("/couchdb");
        config.put("couchdb.url", base.toString());
        config.put("couchdb.db", "config_db");
        new Config(config);
        configLocalProvider = new LocalDocumentProvider(config.getProperty("couchdb.url"),
                config.getProperty("couchdb.db"));
    }

    @Override
    public Set<Object> getFailingItems(AssemblyProvider<String> provider) {
        if (!getSupportedProviderTypes().contains(provider.getClass()))
            throw new UnsupportedOperationException(
                    "AssemblyProviders of type" + provider.getClass().getName() + " are not supported.");
        Set<Object> failed = new HashSet<>();
        DocumentProvider<JSONObject> rp = ((CouchDbAssemblyProvider) provider).getDocumentProvider();
        try {
            for (String s : listSubResources(DIR_DOC_DESIGN_CONFIG))
                processResource(s, DIR_DOC_DESIGN_CONFIG, rp, configRemoteWriter, false, failed);
        } catch (URISyntaxException | IOException e) {
            throw new IllegalStateException(
                    "Something's wrong: could not look up resources in classpath for database documents!", e);
        }
        return failed;
    }

    @Override
    public Set<Object> getSaneItems(AssemblyProvider<String> provider) {
        throw new NotImplementedException("NIY");
    }

    @Override
    public Set<Class<?>> getSupportedProviderTypes() {
        Set<Class<?>> se = new HashSet<>();
        se.add(CouchDbAssemblyProvider.class);
        return Collections.unmodifiableSet(se);
    }

    @Override
    public boolean isSane(AssemblyProvider<String> provider) {
        return getFailingItems(provider).isEmpty();
    }

    /**
     * Utility method, might need to move elsewhere.
     * 
     * @param path
     * @return
     * @throws URISyntaxException
     * @throws IOException
     */
    protected String[] listSubResources(String path) throws URISyntaxException, IOException {
        URL dirURL = getClass().getResource(path);
        if (dirURL != null && dirURL.getProtocol().equals("file")) {
            // A file path: easy enough
            return new File(dirURL.toURI()).list();
        }
        // In case of a jar file, we can't actually find a directory.
        // Have to assume the same jar as this class.
        if (dirURL == null) {
            String me = getClass().getName().replace(".", "/") + ".class";
            dirURL = getClass().getClassLoader().getResource(me);
        }
        // a JAR path
        if (dirURL.getProtocol().equals("jar")) {
            // strip out only the JAR file
            String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!"));
            JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
            Enumeration<JarEntry> entries = jar.entries(); // gives ALL entries in jar
            Set<String> result = new HashSet<String>(); // avoid duplicates in case it is a subdirectory
            while (entries.hasMoreElements()) {
                String name = entries.nextElement().getName();
                if (name.startsWith(path)) { // filter according to the path
                    String entry = name.substring(path.length());
                    if (entry.startsWith("/"))
                        entry = entry.substring(1);
                    int checkSubdir = entry.indexOf("/");
                    // if it is a subdirectory, we just return the directory name
                    if (checkSubdir >= 0)
                        entry = entry.substring(0, checkSubdir);
                    if (!entry.isEmpty())
                        result.add(entry);
                }
            }
            jar.close();
            return result.toArray(new String[result.size()]);
        }
        throw new UnsupportedOperationException("Cannot list files for URL " + dirURL);
    }

    private void processResource(String name, String dir, DocumentProvider<JSONObject> rp,
            DocumentWriter<JSONObject> writer, boolean repair, final Set<Object> failing) {
        log.debug("Checking remote design document that matches {}", name);
        log.debug(" ... Looking at {}", '/' + dir + '/' + name);
        URL resUrl = getClass().getResource('/' + dir + '/' + name);
        log.debug(" ... found at URL {}", resUrl);
        if (resUrl == null)
            throw new IllegalStateException("Could not find expected internal resource " + name);
        JSONObject ddocLocal;
        try {
            ddocLocal = configLocalProvider.getResource(resUrl.toString());
        } catch (JSONException jex) {
            throw new IllegalStateException(
                    "Expected design document " + resUrl + " is not valid JSON! This should not happen"
                            + " and it could be an indication that the executable is corrupted.",
                    jex);
        }
        if (ddocLocal != null)
            log.debug(" ... local resource is well-formed JSON and contains {} attributes",
                    ddocLocal.keySet().size());
        String remoteLocation = "_design/" + name.replaceAll(".json$", "");
        log.debug(" ... now checking for remote design document in {}", remoteLocation);
        JSONObject ddocRemote;
        try {
            ddocRemote = rp.getDocument(remoteLocation);
        } catch (JSONException jex) {
            log.error("Document at {} is not valid JSON! Is that a real working CouchDB?", remoteLocation);
            failing.add(remoteLocation);
            if (severe)
                reactSeverely(failing);
            return;
        }
        if (ddocRemote != null)
            log.debug(" ... remote resource is well-formed JSON and contains {} attributes",
                    ddocRemote.keySet().size());
        // Now check the objects one by one...
        JSONCompareResult res = JSONCompare.compareJSON(ddocLocal, ddocRemote, this.comparator);
        log.trace(" ... Checking if values are equal... {}", res.passed());
        for (FieldComparisonFailure fail : res.getFieldMissing())
            log.warn(" * Field {} : expected value, none found.", fail.getField());
        if (repair) {
            log.info("Attempting repair...");
            throw new NotImplementedException("NIY");
        } else {
            for (FieldComparisonFailure fail : res.getFieldFailures()) {
                log.warn(" * Field {} : values do not match.", fail.getField());
                log.trace(" ** expected:\r\n{}", fail.getExpected());
                log.trace(" ** got:\r\n{}", fail.getActual());
            }
            if (res.failed()) {
                failing.add(remoteLocation);
                if (severe)
                    reactSeverely(failing);
            } else
                log.debug("<== OK. Expected fields match, document at {} is valid.", remoteLocation);
        }
    }

    private void reactSeverely(Set<Object> failed) {
        throw new MisconfiguredException("Found " + failed.size() + " misconfigured objects."
                + " This sanity checker is set to be severe, so exiting without further checks.", failed);
    }

    @Override
    public void rebuild(AssemblyProvider<String> provider) {
        throw new NotImplementedException("NIY");
    }

    @Override
    public void repair(AssemblyProvider<String> provider) {
        if (!getSupportedProviderTypes().contains(provider.getClass()))
            throw new UnsupportedOperationException(
                    "AssemblyProviders of type" + provider.getClass().getName() + " are not supported.");
        DocumentProvider<JSONObject> rp = ((CouchDbAssemblyProvider) provider).getDocumentProvider();
        try {
            for (String s : listSubResources(DIR_DOC_DESIGN_CONFIG))
                processResource(s, DIR_DOC_DESIGN_CONFIG, rp, configRemoteWriter, true, new HashSet<>());
        } catch (URISyntaxException | IOException e) {
            throw new IllegalStateException(
                    "Something's wrong: could not look up resources in classpath for database documents!", e);
        }
    }

}