All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gooddata.sdk.service.md.MetadataService Maven / Gradle / Ivy

There is a newer version: 3.11.1+api3
Show newest version
/*
 * (C) 2023 GoodData Corporation.
 * This source code is licensed under the BSD-style license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */
package com.gooddata.sdk.service.md;

import com.gooddata.sdk.common.GoodDataException;
import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.model.md.*;
import com.gooddata.sdk.model.md.report.ReportDefinition;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;

import java.util.*;
import java.util.stream.Collectors;

import static com.gooddata.sdk.common.util.Validate.*;
import static com.gooddata.sdk.model.md.Service.TIMEZONE_URI;
import static java.util.Arrays.asList;

/**
 * Query, create and update project metadata - attributes, facts, metrics, reports,...
 */
public class MetadataService extends AbstractService {

    public static final UriTemplate OBJ_TEMPLATE = new UriTemplate(Obj.OBJ_URI);
    private static final Set IRREGULAR_PLURAL_WORD_SUFFIXES = new HashSet<>(asList("s", "ch", "sh", "x", "o"));

    public MetadataService(final RestTemplate restTemplate, final GoodDataSettings settings) {
        super(restTemplate, settings);
    }

    /**
     * Create metadata object in given project
     *
     * @param project project
     * @param obj     metadata object to be created
     * @param      type of the object to be created
     * @return new metadata object
     * @throws ObjCreateException   if creation failed
     * @throws ObjNotFoundException if new metadata object not found after creation
     * @throws com.gooddata.sdk.common.GoodDataRestException   if GoodData REST API returns unexpected status code when getting
     *                                              the new object
     * @throws com.gooddata.sdk.common.GoodDataException       if no response from API or client-side HTTP error when getting the new object
     */
    @SuppressWarnings("unchecked")
    public  T createObj(Project project, T obj) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");
        notNull(obj, "obj");

        final T response;
        try {
            response = restTemplate.postForObject(Obj.CREATE_WITH_ID_URI, obj, (Class)obj.getClass(), project.getId());
        } catch (GoodDataRestException | RestClientException e) {
            throw new ObjCreateException(obj, e);
        }

        if (response == null) {
            throw new ObjCreateException("Received empty response from API call.", obj);
        }
        return response;
    }

    /**
     * Get metadata object by URI (format is /gdc/md/{PROJECT_ID}/obj/{OBJECT_ID})
     *
     * @param uri URI in format /gdc/md/{PROJECT_ID}/obj/{OBJECT_ID}
     * @param cls class of the resulting object
     * @param  type of the object to be returned
     * @return the metadata object
     * @throws ObjNotFoundException if metadata object not found
     * @throws com.gooddata.sdk.common.GoodDataRestException   if GoodData REST API returns unexpected status code
     * @throws com.gooddata.sdk.common.GoodDataException       if no response from API or client-side HTTP error
     */
    public  T getObjByUri(String uri, Class cls) {
        notNull(uri, "uri");
        notNull(cls, "cls");
        try {
            final T result = restTemplate.getForObject(uri, cls);

            if (result != null) {
                return result;
            } else {
                throw new GoodDataException("Received empty response from API call.");
            }
        } catch (GoodDataRestException e) {
            if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
                throw new ObjNotFoundException(uri, cls, e);
            } else {
                throw e;
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to get " + cls.getSimpleName().toLowerCase() + " " + uri, e);
        }
    }

    /**
     * Retrieves a collection of objects corresponding to the supplied collection of URIs.
     *
     * @param project project that contains the objects to be retrieved
     * @param uris collection of URIs
     * @return collection of metadata objects corresponding to the supplied URIs
     */
    public Collection getObjsByUris(Project project, Collection uris) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");
        notNull(uris, "uris");

        try {
            final BulkGet result = restTemplate.postForObject(BulkGet.URI, new BulkGetUris(uris), BulkGet.class, project.getId());

            if (result != null) {
                return result.getItems();
            } else {
                throw new GoodDataException("Received empty response from API call.");
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to get objects. Some of the supplied URIs may be malformed.", e);
        }
    }

    /**
     * Update given metadata object.
     *
     * @param obj object to update
     * @param  type of the updated object
     * @return updated metadata object
     * @throws ObjUpdateException in case of error
     */
    @SuppressWarnings("unchecked")
    public  T updateObj(T obj) {
        notNull(obj, "obj");
        notNull(obj.getUri(), "obj.uri");
        try {
            restTemplate.put(obj.getUri(), obj);
            return getObjByUri(obj.getUri(), (Class) obj.getClass());
        } catch (GoodDataException | RestClientException e) {
            throw new ObjUpdateException(obj, e);
        }
    }

    /**
     * Remove metadata object URI
     *
     * @param obj metadata object to remove
     * @throws ObjNotFoundException if metadata object not found
     * @throws com.gooddata.sdk.common.GoodDataRestException   if GoodData REST API returns unexpected status code
     * @throws com.gooddata.sdk.common.GoodDataException       if no response from API or client-side HTTP error
     */
    public void removeObj(Obj obj) {
        notNull(obj, "obj");
        notNull(obj.getUri(), "obj.uri");
        try {
            restTemplate.delete(obj.getUri());
        } catch (GoodDataRestException e) {
            if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
                throw new ObjNotFoundException(obj);
            } else {
                throw e;
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to remove " + obj.getClass().getSimpleName().toLowerCase() + " " + obj.getUri(), e);
        }
    }

    /**
     * Remove metadata object by URI (format is /gdc/md/{PROJECT_ID}/obj/{OBJECT_ID})
     *
     * @param uri URI in format /gdc/md/{PROJECT_ID}/obj/{OBJECT_ID}
     * @throws ObjNotFoundException if metadata object not found
     * @throws com.gooddata.sdk.common.GoodDataRestException   if GoodData REST API returns unexpected status code
     * @throws com.gooddata.sdk.common.GoodDataException       if no response from API or client-side HTTP error
     */
    public void removeObjByUri(String uri) {
        notNull(uri, "uri");
        try {
            restTemplate.delete(uri);
        } catch (GoodDataRestException e) {
            if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
                throw new ObjNotFoundException(uri);
            } else {
                throw e;
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to remove " + uri, e);
        }
    }

    /**
     * Get metadata object by id.
     *
     * @param project project where to search for the object
     * @param id      id of the object
     * @param cls     class of the resulting object
     * @param      type of the object to be returned
     * @return the metadata object
     * @throws ObjNotFoundException if metadata object not found
     * @throws com.gooddata.sdk.common.GoodDataRestException   if GoodData REST API returns unexpected status code
     * @throws com.gooddata.sdk.common.GoodDataException       if no response from API or client-side HTTP error
     */
    public  T getObjById(Project project, String id, Class cls) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");
        notNull(id, "id");
        notNull(cls, "cls");
        return getObjByUri(OBJ_TEMPLATE.expand(project.getId(), id).toString(), cls);
    }

    /**
     * Get metadata object URI by restrictions like identifier, title or summary.
     *
     * @param project      project where to search for the object
     * @param cls          class of the resulting object
     * @param restrictions query restrictions
     * @param           type of the object to be returned
     * @return the URI of metadata object
     * @throws ObjNotFoundException  if metadata object not found
     * @throws NonUniqueObjException if more than one object corresponds to search restrictions
     */
    public  String getObjUri(Project project, Class cls, Restriction... restrictions) {
        final Collection results = findUris(project, cls, restrictions);
        if (results == null || results.isEmpty()) {
            throw new ObjNotFoundException(cls);
        } else if (results.size() != 1) {
            throw new NonUniqueObjException(cls, results);
        }
        return results.iterator().next();
    }

    /**
     * Get metadata object by restrictions like identifier, title or summary.
     *
     * @param project      project where to search for the object
     * @param cls          class of the resulting object
     * @param restrictions query restrictions
     * @param           type of the object to be returned
     * @return metadata object
     * @throws ObjNotFoundException  if metadata object not found
     * @throws NonUniqueObjException if more than one object corresponds to search restrictions
     */
    public  T getObj(Project project, Class cls, Restriction... restrictions) {
        final Collection results = findUris(project, cls, restrictions);
        if (results == null || results.isEmpty()) {
            throw new ObjNotFoundException(cls);
        } else if (results.size() != 1) {
            throw new NonUniqueObjException(cls, results);
        }
        return getObjByUri(results.iterator().next(), cls);
    }

    /**
     * Find metadata by restrictions like identifier, title or summary.
     *
     * @param project      project where to search for the metadata
     * @param cls          class of searched metadata
     * @param restrictions query restrictions
     * @param           type of the metadata referenced in returned entries
     * @return the collection of metadata entries
     * @throws com.gooddata.sdk.common.GoodDataException if unable to query metadata
     */
    public  Collection find(Project project, Class cls, Restriction... restrictions) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");
        notNull(cls, "cls");

        final String type = getQueryType(cls);
        try {
            final Query queryResult = restTemplate.getForObject(Query.URI, Query.class, project.getId(), type);

            if (queryResult != null && queryResult.getEntries() != null) {
                return filterEntries(queryResult.getEntries(), restrictions);
            } else {
                throw new GoodDataException("Received empty response from API call.");
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to query metadata: " + type, e);
        }
    }

    /**
     * Find metadata URIs by restrictions like identifier, title or summary.
     *
     * @param project      project where to search for the metadata
     * @param cls          class of searched metadata
     * @param restrictions query restrictions
     * @param           type of the metadata referenced by returned URIs
     * @return the collection of metadata URIs
     * @throws com.gooddata.sdk.common.GoodDataException if unable to query metadata
     */
    public  Collection findUris(Project project,
                                                             Class cls,
                                                             Restriction... restrictions) {
        final Collection entries = find(project, cls, restrictions);
        final Collection result = new ArrayList<>(entries.size());
        result.addAll(entries.stream().map(Entry::getUri).collect(Collectors.toList()));
        return result;
    }

    /**
     * Find all objects which use the given object.
     * @param project project
     * @param obj     object to find using objects for
     * @param nearest find nearest objects only
     * @param types   what types (categories) to search for (for example 'reportDefinition', 'report', 'tableDataLoad',
     *                'table'...)
     * @return objects using given objects.
     */
    @SuppressWarnings("unchecked")
    public Collection usedBy(Project project, Obj obj, boolean nearest, Class... types) {
        notNull(obj, "obj");
        return usedBy(project, obj.getUri(), nearest, types);
    }

    /**
     * Find all objects which use the given object.
     * @param project project
     * @param uri     URI of object to find using objects for
     * @param nearest find nearest objects only
     * @param types   what types (categories) to search for (for example 'reportDefinition', 'report', 'tableDataLoad',
     *                'table'...)
     * @return objects using given objects.
     * @see #usedBy(Project, Collection, boolean, Class[])
     */
    @SuppressWarnings("unchecked")
    public Collection usedBy(Project project, String uri, boolean nearest, Class... types) {
        notNull(uri, "uri");
        notNull(project, "project");

        final Collection usages = usedBy(project, asList(uri), nearest, types);
        return usages.size() > 0 ? usages.iterator().next().getUsedBy() : Collections.emptyList();
    }

    /**
     * Find all objects which use the given objects. Batch alternative to {@link #usedBy(Project, String, boolean, Class[])}
     * @param project project
     * @param uris    URIs of object to find using objects for
     * @param nearest find nearest objects only
     * @param types   what types (categories) to search for (for example 'reportDefinition', 'report', 'tableDataLoad',
     *                'table'...), returns all objects if no type is provided
     * @return objects usages
     * @see #usedBy(Project, String, boolean, Class[])
     */
    @SuppressWarnings("unchecked")
    public Collection usedBy(Project project, Collection uris, boolean nearest, Class... types) {
        notNull(uris, "uris");
        notNull(project, "project");
        notNull(project.getId(), "project.id");

        final UseMany response;
        try {
            response = restTemplate.postForObject(InUseMany.USEDBY_URI, new InUseMany(uris, nearest, types), UseMany.class, project.getId());
        } catch (GoodDataRestException | RestClientException e) {
            throw new GoodDataException("Unable to find objects.", e);
        }
        final List usages = new ArrayList<>(uris.size());
        final Collection useManyEntries = notNullState(response, "usedBy response").getUseMany();
        usages.addAll(useManyEntries.stream().map(useMany -> new Usage(useMany.getUri(), useMany.getEntries())).collect(Collectors.toList()));
        return usages;
    }

    /**
     * Find metadata URIs by restrictions. Identifier is the only supported restriction.
     *
     * @param project      project where to search for the metadata
     * @param restrictions query restrictions
     * @return the collection of metadata URIs
     * @throws com.gooddata.sdk.common.GoodDataException if unable to query metadata
     */
    public Collection findUris(Project project, Restriction... restrictions) {
        notNull(project, "project" );
        noNullElements(restrictions, "restrictions");

        final List ids = new ArrayList<>(restrictions.length);
        for (Restriction restriction: restrictions) {
            if (!Restriction.Type.IDENTIFIER.equals(restriction.getType())) {
                throw new IllegalArgumentException("All restrictions have to be of type " + Restriction.Type.IDENTIFIER);
            }
            ids.add(restriction.getValue());
        }

        return getUrisForIdentifiers(project, ids).getUris();
    }

    /**
     * Find metadata URIs for given identifiers.
     *
     * @param project     project where to search for the metadata
     * @param identifiers query restrictions
     * @return the map of identifiers as keys and metadata URIs as values
     * @throws com.gooddata.sdk.common.GoodDataException if unable to query metadata
     * @see #findUris(Project, Restriction...)
     */
    public Map identifiersToUris(Project project, Collection identifiers) {
        notNull(project, "project" );
        noNullElements(identifiers, "identifiers");

        return getUrisForIdentifiers(project, identifiers).asIdentifierToUri();
    }

    /**
     * Fetches attribute elements for given attribute using default display form.
     *
     * @param attribute attribute to fetch elements for
     * @return attribute elements or empty set if there is no link for elements in default display form
     */
    public List getAttributeElements(Attribute attribute) {
        notNull(attribute, "attribute");

        return getAttributeElements(attribute.getDefaultDisplayForm());
    }

    /**
     * Fetches attribute elements by given display form.
     *
     * @param displayForm display form to fetch attributes for
     * @return attribute elements or empty set if there is no link for elements
     */
    public List getAttributeElements(DisplayForm displayForm) {
        notNull(displayForm, "displayForm");

        final String elementsUri = displayForm.getElementsUri();
        if (StringUtils.isEmpty(elementsUri)) {
            return Collections.emptyList();
        }

        try {
            final AttributeElements attributeElements = restTemplate.getForObject(elementsUri, AttributeElements.class);
            return notNullState(attributeElements, "attributeElements").getElements();
        } catch (GoodDataRestException | RestClientException e) {
            throw new GoodDataException("Unable to get attribute elements from " + elementsUri + ".", e);
        }
    }

    /**
     * Get project/workspace timezone.
     *
     * @param project project from what to return the timezone
     * @return string identifier of the timezone (see IANA/Olson tz database for possible values)
     * @throws com.gooddata.sdk.common.GoodDataRestException if GoodData REST API returns unexpected status code
     * @throws com.gooddata.sdk.common.GoodDataException     if no response from API or client-side HTTP error
     */
    public String getTimezone(final Project project) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");

        try {
            final Service result = restTemplate.getForObject(TIMEZONE_URI, Service.class, project.getId());

            if (result != null) {
                return result.getTimezone();
            } else {
                throw new GoodDataException("Received empty response from API call.");
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to get timezone of project/workspace " + project.getId(), e);
        }
    }

    /**
     * Set project/workspace timezone.
     *
     * @param project the project/workspace where to set the timezone
     * @param timezone the timezone to be set (see IANA/Olson tz database for possible values)
     */
    public void setTimezone(final Project project, final String timezone) {
        notNull(project, "project");
        notNull(project.getId(), "project.id");
        notNull(timezone, "timezone");
        notEmpty(timezone, "timezone");

        try {
            final Service result = restTemplate.postForObject(TIMEZONE_URI, new Service(timezone), Service.class,
                    project.getId());

            if (result == null) {
                throw new GoodDataException("Unexpected empty result from API call.");
            }
        } catch (RestClientException e) {
            throw new GoodDataException("Unable to set timezone of project/workspace " + project.getId(), e);
        }
    }


    private Collection filterEntries(Collection entries, Restriction... restrictions) {
        if (restrictions == null || restrictions.length == 0) {
            return entries;
        }
        final Collection result = new ArrayList<>(entries.size());
        for (Entry entry : entries) {
            for (Restriction restriction : restrictions) {
                switch (restriction.getType()) {
                    case IDENTIFIER:
                        if (restriction.getValue().equals(entry.getIdentifier())) result.add(entry);
                        break;
                    case TITLE:
                        if (restriction.getValue().equals(entry.getTitle())) result.add(entry);
                        break;
                    case SUMMARY:
                        if (restriction.getValue().equals(entry.getSummary())) result.add(entry);
                        break;
                }
            }
        }
        return result;
    }

    private IdentifiersAndUris getUrisForIdentifiers(final Project project, final Collection identifiers) {
        final IdentifiersAndUris response;
        try {
            response = restTemplate.postForObject(IdentifiersAndUris.URI, new IdentifierToUri(identifiers), IdentifiersAndUris.class, project.getId());
        } catch (GoodDataRestException | RestClientException e) {
            throw new GoodDataException("Unable to get URIs from identifiers.", e);
        }
        return response;
    }

    private  String getQueryType(final Class cls) {
        final String typeBase = cls.getSimpleName().toLowerCase();

        if (ReportDefinition.class.equals(cls)) {
            return typeBase;
        }

        if (IRREGULAR_PLURAL_WORD_SUFFIXES.stream().anyMatch(typeBase::endsWith)) {
            return typeBase + "es";
        }

        return typeBase + "s";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy