eu.trentorise.opendata.disiclient.UrlMapper.java Source code

Java tutorial

Introduction

Here is the source code for eu.trentorise.opendata.disiclient.UrlMapper.java

Source

/*
 * Copyright 2015 Trento Rise.
 *
 * 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 eu.trentorise.opendata.disiclient;

import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import eu.trentorise.opendata.commons.TodUtils;
import static eu.trentorise.opendata.commons.validation.Preconditions.checkNotEmpty;
import it.unitn.disi.sweb.webapi.model.kb.types.AttributeDefinition;
import java.net.MalformedURLException;
import java.net.URL;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Todo shouldn't stay here!!
 * Simple prefix-based converter to/from numerical IDs and urls.
 *
 * @author David Leoni
 */
@Immutable
@ParametersAreNonnullByDefault
public final class UrlMapper {

    private static final String CONCEPT_PREFIX = "/concepts";

    private static final String ENTITY_PREFIX = "/instances";

    private static final String ATTR_DEF_PREFIX = "/attributedefinitions";

    private static final String ETYPE_PREFIX = "/types";

    private static Logger LOG = LoggerFactory.getLogger(UrlMapper.class);

    private static final UrlMapper INSTANCE = new UrlMapper();

    private static final String DEBUG_GLOBAL_CONCEPT_ID = "debugGlobalConceptId";
    private static final String DEBUG_CONCEPT_ID = "debugConceptId";
    private static final String DEBUG_TYPE_ID = "debugTypeId";

    private String base;

    private UrlMapper() {
        this.base = "http://localhost";
    }

    private UrlMapper(String base) {
        this();
        checkNotNull(base);
        this.base = TodUtils.removeTrailingSlash(base);
    }

    /**
     * Values less than -1 are automatically converted to -1
     */
    private long parseId(String s) {
        checkNotNull(s);
        long ret = Long.parseLong(s);
        if (ret >= -1) {
            return ret;
        } else {
            LOG.warn("Found id " + ret + " which is less than -1, automatically converting it to -1");
            return -1;
        }
    }

    /**
     * For extracting i.e. id '123' from
     * http://my-website.com/concepts/123?some-param=bla
     *
     * @param prefix prefix like '/concepts/'
     * @throws IllegalArgumentException on invalid URL
     */
    private long parseIdFromPrefix(String prefix, String url) {
        URL u;
        try {
            u = new URL(url);
        } catch (MalformedURLException ex) {
            throw new IllegalArgumentException("Found invalid url: " + url, ex);
        }

        String urlWithoutQuery = u.getProtocol() + "://" + u.getAuthority() + u.getPath();
        String toStrip = base + prefix + "/";

        String s;
        if (toStrip.length() > 0) {
            int pos = urlWithoutQuery.indexOf(toStrip);
            if (pos != 0) {
                throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url);
            }
            s = urlWithoutQuery.substring(toStrip.length());
        } else {
            s = urlWithoutQuery;
        }
        try {
            return parseId(s);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Invalid URL for prefix " + prefix + ": " + url, ex);
        }
    }

    /**
     * Extracts from multimap the first item correspond to {@code property}
     *
     * @throws IllegalArgumentException if there is not exactly one {
     * @property}
     */
    private String getFirst(Multimap<String, String> m, String property) {
        int sz = m.get(property).size();
        if (sz != 1) {
            throw new IllegalArgumentException("Expected one " + property + ", found " + sz + " instead.");
        } else {
            return Iterables.getFirst(m.get(property), "");
        }
    }

    /**
     * For an explanation of global vs local id, see {@link #conceptIdToUrl(java.lang.Long, java.lang.Long)
     * }
     *    
     */
    public long conceptUrlToId(String url) {
        return parseIdFromPrefix(CONCEPT_PREFIX, url);
    }

    /**
     * Returns the etype id as an url.
     *
     * @param etypeId if unknown use -1
     */
    // using Long as param instead of long because with the latter it might 
    // be hard to interpret implicit cast failures.
    public String etypeIdToUrl(Long etypeId) {
        checkValidId(etypeId, "Invalid etype id!");
        checkCongruent(etypeId);
        return base + ETYPE_PREFIX + "/" + etypeId;
    }

    /**
     *
     * @throws IllegalArgumentException on unparseable URL
     */
    public long etypeUrlToId(String url) {
        return parseIdFromPrefix(ETYPE_PREFIX, url);
    }

    private long parseIdFromParam(String paramName, String url) {
        Multimap<String, String> params = TodUtils.parseUrlParams(url);
        return parseId(getFirst(params, paramName));
    }

    /**
     *
     * @throws IllegalArgumentException on unparseable URL
     */
    public long entityUrlToId(String url) {
        return parseIdFromPrefix(ENTITY_PREFIX, url);
    }

    /**
     * Returns the entity id as an url
     *
     * @param id if unknown use -1
     */
    // using Long as param instead of long because with the latter it might 
    // be hard to interpret implicit cast failures.
    public String entityIdToUrl(Long id) {
        checkValidId(id, "Invalid entity id!");
        return base + ENTITY_PREFIX + "/" + id;
    }

    /**
     *
     * @throws IllegalArgumentException on unparseable URL
     */
    public long entityNewUrlToId(String url) {
        return parseIdFromPrefix(ENTITY_PREFIX + "/new", url);
    }

    /**
     * Returns the entity id as an url
     *
     * @param id if unknown use -1
     */
    // using Long as param instead of long because with the latter it might 
    // be hard to interpret implicit cast failures.
    public String entityNewIdToUrl(Long id) {
        checkValidId(id, "Invalid entity id!");
        return base + ENTITY_PREFIX + "/new/" + id;
    }

    private void checkCongruent(long... ids) {
        boolean foundMinusOne = false;
        for (long id : ids) {
            if (id == -1) {
                foundMinusOne = true;
            } else {
                if (foundMinusOne) {
                    throw new IllegalArgumentException("All ids must be either -1 or proper ids");
                }
            }
        }
    }

    private void checkValidId(Long id, @Nullable String prependedErrorMessage) {
        if (id == null) {
            throw new IllegalArgumentException(String.valueOf(prependedErrorMessage) + " - Found null id!");
        }
        if (id < -1) {
            throw new IllegalArgumentException(
                    String.valueOf(prependedErrorMessage) + " - Found id less than -1: " + id);
        }
    }

    /**
     * Returns the concept id as an url . Concepts for some reason have TWO ids,
     * one local and one global. The local one seems the 'most' important, so we
     * use that one for urls. Notice column recognizers seems to prefer the
     * global ids, so hacks are needed just for it. Bleah.
     *
     * @param id if unknown use -1
     */
    // using Long as param instead of long because with the latter it might 
    // be hard to interpret implicit cast failures.
    public String conceptIdToUrl(Long id) {
        checkValidId(id, "Invalid concept id!");
        return base + CONCEPT_PREFIX + "/" + id;
    }

    /**
     * For an explanation about ids, see {@link #attrDefIdToUrl(java.lang.Long, java.lang.Long)
     * }
     *
     * @throws IllegalArgumentException on unparseable URL
     *
     * @see #attrDefUrlToId(java.lang.String)
     */
    public long attrDefUrlToConceptId(String url) {
        return parseIdFromParam(DEBUG_CONCEPT_ID, url);
    }

    /**
     * @see #attrDefIdToUrl(java.lang.Long, java.lang.Long) 
     */
    public String attrDefToUrl(AttributeDefinition attrDef) {
        return attrDefIdToUrl(attrDef.getId(), attrDef.getConceptId());
    }

    /**
     * Returns the attr def id as an url. Sometimes it may be useful to identify
     * attribute definitioins with their concepts, for this reason we force the
     * presence of the (local) concept id in the url
     *
     * @param attrDefId if unknown use -1
     * @param globalId If unknown use -1
     */
    // using Long as param instead of long because with the latter it might 
    // be hard to interpret implicit cast failures.
    public String attrDefIdToUrl(Long attrDefId, Long conceptId) {
        checkValidId(attrDefId, "Invalid concept id!");
        checkValidId(conceptId, "Invalid concept id!");
        checkCongruent(attrDefId, conceptId);
        return base + ATTR_DEF_PREFIX + "/" + attrDefId + "?" + DEBUG_CONCEPT_ID + "=" + conceptId;
    }

    /**
     * For an explanation about ids, see {@link #attrDefIdToUrl(java.lang.Long, java.lang.Long)
     * }
     *
     * @throws IllegalArgumentException on unparseable URL
     *
     * @see #attrDefUrlToConceptId(java.lang.String)
     */
    public long attrDefUrlToId(String url) {
        return parseIdFromPrefix(ATTR_DEF_PREFIX, url);
    }

    public boolean isEntityURL(String entityUrl) {
        checkNotEmpty(entityUrl, "Invalid url!");
        return entityUrl.contains(ENTITY_PREFIX);
    }

    public boolean isConceptUrl(String conceptUrl) {
        checkNotEmpty(conceptUrl, "Invalid url!");
        return conceptUrl.contains(CONCEPT_PREFIX);
    }

    public boolean isEtypeUrl(String etypeUrl) {
        checkNotEmpty(etypeUrl, "Invalid url!");
        return etypeUrl.contains(ETYPE_PREFIX);
    }

    public boolean isAttrDefURL(String attrDefUrl) {
        checkNotEmpty(attrDefUrl, "Invalid url!");
        return attrDefUrl.contains(ATTR_DEF_PREFIX);
    }

    /**
     * Returns default mapper with localhost address.
     */
    public static UrlMapper of() {
        return INSTANCE;
    }

    /**
     * Returns an UrlMapper with provided endpoint url, like
     * 'http://entitypedia.org/api'
     */
    public static UrlMapper of(String baseUrl) {
        return new UrlMapper(baseUrl);
    }
}