org.obiba.mica.micaConfig.rest.MicaConfigResource.java Source code

Java tutorial

Introduction

Here is the source code for org.obiba.mica.micaConfig.rest.MicaConfigResource.java

Source

/*
 * Copyright (c) 2018 OBiBa. All rights reserved.
 *
 * This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.obiba.mica.micaConfig.rest;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URISyntaxException;
import java.security.KeyStoreException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriInfo;

import com.google.common.base.Strings;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.obiba.mica.JSONUtils;
import org.obiba.mica.contact.event.IndexContactsEvent;
import org.obiba.mica.dataset.domain.DatasetVariable;
import org.obiba.mica.dataset.domain.HarmonizationDataset;
import org.obiba.mica.dataset.domain.StudyDataset;
import org.obiba.mica.dataset.event.IndexDatasetsEvent;
import org.obiba.mica.dataset.service.KeyStoreService;
import org.obiba.mica.file.event.IndexFilesEvent;
import org.obiba.mica.micaConfig.domain.MicaConfig;
import org.obiba.mica.micaConfig.event.TaxonomiesUpdatedEvent;
import org.obiba.mica.micaConfig.service.CacheService;
import org.obiba.mica.micaConfig.service.MicaConfigService;
import org.obiba.mica.micaConfig.service.MicaMetricsService;
import org.obiba.mica.micaConfig.service.OpalCredentialService;
import org.obiba.mica.micaConfig.service.OpalService;
import org.obiba.mica.micaConfig.service.TaxonomyService;
import org.obiba.mica.network.domain.Network;
import org.obiba.mica.network.event.IndexNetworksEvent;
import org.obiba.mica.project.domain.Project;
import org.obiba.mica.security.Roles;
import org.obiba.mica.study.domain.HarmonizationStudy;
import org.obiba.mica.study.domain.Study;
import org.obiba.mica.study.event.IndexStudiesEvent;
import org.obiba.mica.user.UserProfileService;
import org.obiba.mica.web.model.Dtos;
import org.obiba.mica.web.model.Mica;
import org.obiba.opal.web.model.Opal;
import org.obiba.opal.web.model.Projects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClientException;

import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

import static java.util.stream.Collectors.toList;

@Component
@Path("/config")
public class MicaConfigResource {

    private static final Logger logger = LoggerFactory.getLogger(MicaConfigResource.class);

    @Inject
    private MicaConfigService micaConfigService;

    @Inject
    private TaxonomyService taxonomyService;

    @Inject
    private OpalService opalService;

    @Inject
    private OpalCredentialService opalCredentialService;

    @Inject
    private KeyStoreService keyStoreService;

    @Inject
    private Dtos dtos;

    @Inject
    private CacheService cacheService;

    @Inject
    private EventBus eventBus;

    @Inject
    private UserProfileService userProfileService;

    @Inject
    private ApplicationContext applicationContext;

    @Inject
    private MicaMetricsService micaMetricsService;

    @GET
    @Timed
    @RequiresAuthentication
    public Mica.MicaConfigDto get() {
        return dtos.asDto(micaConfigService.getConfig());
    }

    @GET
    @Path("/_public")
    public Mica.PublicMicaConfigDto getPublic() {
        return dtos.asPublicDto(micaConfigService.getConfig());
    }

    @PUT
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response create(@SuppressWarnings("TypeMayBeWeakened") Mica.MicaConfigDto dto) {
        MicaConfig micaConfig = dtos.fromDto(dto);
        taxonomyService.refreshTaxonomyTaxonomyIfNeeded(micaConfigService.getConfig(), micaConfig);
        micaConfigService.save(micaConfig);
        return Response.noContent().build();
    }

    @GET
    @Path("/style.css")
    @Produces("text/css")
    public Response getStyle() {
        return Response.ok(micaConfigService.getConfig().getStyle(), "text/css")
                .header("Content-Disposition", "attachment; filename=\"style.css\"").build();
    }

    @GET
    @Path("/i18n/{locale}.json")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getJsonTranslations(@PathParam("locale") String locale, @QueryParam("default") boolean _default)
            throws IOException {
        StreamingOutput stream = os -> {
            try (Writer writer = new BufferedWriter(new OutputStreamWriter(os))) {
                writer.write(getGlobalTranslationsAsJson(locale, _default));
                writer.flush();
            }
        };
        return Response.ok(stream).build();
    }

    @GET
    @Path("/i18n/{locale}.po")
    @Produces(MediaType.TEXT_PLAIN)
    public Response getGettextTranslations(@PathParam("locale") String locale,
            @QueryParam("default") boolean _default) throws IOException {
        StreamingOutput stream = os -> {
            try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"))) {
                Properties properties = getGlobalTranslationsAsProperties(locale, _default);
                writer.println("# Translations extracted from Mica");
                writer.println("msgid \"\"");
                writer.println("msgstr \"\"");
                writer.println(String.format("\"Project-Id-Version: Mica %s\\n\"",
                        micaConfigService.getConfig().getMicaVersion()));
                writer.println(String.format("\"PO-Revision-Date: %s\\n\"", new Date()));
                writer.println("\"MIME-Version: 1.0\\n\"");
                writer.println("\"Content-Type: text/plain; charset=UTF-8\\n\"");
                writer.println("\"Content-Transfer-Encoding: 8bit\\n\"");
                writer.println(String.format("\"Language: %s\\n\"", locale));
                writer.println();
                properties.keySet().stream().sorted().forEach(key -> {
                    writer.println(String.format("msgid \"%s\"", key));
                    String value = properties.getProperty(key.toString());
                    if (!Strings.isNullOrEmpty(value)) {
                        value = value.replaceAll("\\{\\{([\\w]+)\\}\\}", "@$1");
                    }
                    writer.println(String.format("msgstr \"%s\"", value));
                    writer.println();
                });
                writer.flush();
            }
        };
        return Response.ok(stream).build();
    }

    @Path("/i18n/custom")
    public CustomTranslationsResource customTranslations() {
        return applicationContext.getBean(CustomTranslationsResource.class);
    }

    @PUT
    @Path("/keystore/{name}/{alias}")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response updateEncryptionKey(@PathParam("name") String name, @PathParam("alias") String alias,
            Mica.KeyForm keyForm) {
        if (keyForm.getKeyType() == Mica.KeyType.KEY_PAIR) {
            doCreateOrImportKeyPair(KeyStoreService.SYSTEM_KEY_STORE, "https", keyForm);
        } else {
            doImportCertificate(KeyStoreService.SYSTEM_KEY_STORE, "https", keyForm);
        }

        return Response.ok().build();
    }

    @GET
    @Path("/keystore/{name}/{alias}")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response getEncryptionKeyCertificate(@PathParam("name") String name, @PathParam("alias") String alias)
            throws IOException, KeyStoreException {

        if (!Sets.newHashSet(KeyStoreService.SYSTEM_KEY_STORE, OpalService.OPAL_KEYSTORE).contains(name)) {
            return Response.status(Response.Status.BAD_REQUEST).build();
        }

        if (keyStoreService.getKeyStore(name).aliasExists(alias)) {
            return Response.ok(keyStoreService.getPEMCertificate(name, alias), MediaType.TEXT_PLAIN_TYPE)
                    .header("Content-disposition",
                            String.format("attachment; filename=%s-%s-certificate.pem", name, alias))
                    .build();
        }

        return Response.status(Response.Status.NOT_FOUND).build();
    }

    @GET
    @Path("/opal-projects")
    @Timed
    @RequiresAuthentication
    public List<Projects.ProjectDto> getOpalProjects() throws URISyntaxException {
        try {
            return opalService.getProjectDtos(null);
        } catch (Exception e) {
            logger.warn("Failed at retrieving opal projects: {}", e.getMessage());
            return Lists.newArrayList();
        }
    }

    @GET
    @Path("/opal-credentials")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public List<Mica.OpalCredentialDto> getOpalCredentials() {
        return opalCredentialService.findAllOpalCredentials().stream().map(dtos::asDto).collect(toList());
    }

    @GET
    @Path("/opal-credential/{id}")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Mica.OpalCredentialDto getOpalCredential(@PathParam("id") String opalUrl) {
        return dtos.asDto(opalCredentialService.getOpalCredential(opalUrl));
    }

    @GET
    @Path("/opal-credential/{id}/certificate")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response getOpalCredentialCertificate(@PathParam("id") String opalUrl) {
        return Response.ok(opalCredentialService.getCertificate(opalUrl), MediaType.TEXT_PLAIN_TYPE)
                .header("Content-disposition", "attachment; filename=opal-mica-certificate.pem").build();
    }

    @POST
    @Path("/opal-credentials")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response createCredential(Mica.OpalCredentialDto opalCredentialDto, @Context UriInfo uriInfo) {
        createOrUpdateCredential(opalCredentialDto);

        return Response.created(
                uriInfo.getBaseUriBuilder().segment("opal-credential", opalCredentialDto.getOpalUrl()).build())
                .build();
    }

    @PUT
    @Path("/opal-credential/{id}")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response updateOpalCredential(@PathParam("id") String id, Mica.OpalCredentialDto opalCredentialDto) {
        createOrUpdateCredential(opalCredentialDto);

        return Response.ok().build();
    }

    @DELETE
    @Path("/opal-credential/{id}")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response updateOpalCredential(@PathParam("id") String id) {
        opalCredentialService.deleteOpalCredential(id);

        return Response.ok().build();
    }

    @PUT
    @Path("/_index")
    @Timed
    @RequiresRoles(Roles.MICA_ADMIN)
    public Response updateIndices() {
        eventBus.post(new IndexStudiesEvent());
        eventBus.post(new IndexFilesEvent());
        eventBus.post(new IndexContactsEvent());
        eventBus.post(new IndexNetworksEvent());
        eventBus.post(new IndexDatasetsEvent());
        eventBus.post(new TaxonomiesUpdatedEvent());
        return Response.noContent().build();
    }

    @GET
    @Path("/metrics")
    @RequiresAuthentication
    public Mica.MicaMetricsDto getMetrics() {
        return Mica.MicaMetricsDto.newBuilder()
                // Network
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(Network.class.getSimpleName()).setTotal(micaMetricsService.getDraftNetworksCount())
                        .setPublished(micaMetricsService.getPublishedNetworksCount())
                        .setEditing(micaMetricsService.getEditingNetworksCount()))
                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder().setType(Network.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftNetworkFilesCount())
                        .setPublished(micaMetricsService.getPublishedNetworkFilesCount()))

                // Individual Study
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(Study.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftIndividualStudiesCount())
                        .setPublished(micaMetricsService.getPublishedIndividualStudiesCount())
                        .setEditing(micaMetricsService.getEditingIndividualStudiesCount())
                        .setTotalWithVariable(micaMetricsService.getPublishedIndividualStudiesWithVariablesCount())
                        .setVariables(micaMetricsService.getPublishedIndividualStudiesVariablesCount()))

                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder().setType(Study.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftIndividualStudiesFilesCount())
                        .setPublished(micaMetricsService.getPublishedIndividualStudiesFilesCount()))

                // Harmonization Study
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(HarmonizationStudy.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftHarmonizationStudiesCount())
                        .setPublished(micaMetricsService.getPublishedHarmonizationStudiesCount())
                        .setEditing(micaMetricsService.getEditingHarmonizationStudiesCount())
                        .setVariables(micaMetricsService.getPublishedHarmonizationStudiesVariablesCount()))
                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(HarmonizationStudy.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftHarmonizationStudiesFilesCount())
                        .setPublished(micaMetricsService.getPublishedHarmonizationStudiesFilesCount()))

                // StudyDataset
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(StudyDataset.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftStudyDatasetsCount())
                        .setPublished(micaMetricsService.getPublishedStudyDatasetsCount())
                        .setEditing(micaMetricsService.getEditingStudyDatasetsCount()))
                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(StudyDataset.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftStudyDatasetFilesCount())
                        .setPublished(micaMetricsService.getPublishedStudyDatasetFilesCount()))

                // HarmonizarionDataset
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(HarmonizationDataset.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftHarmonizarionDatasetsCount())
                        .setPublished(micaMetricsService.getPublishedHarmonizationDatasetsCount())
                        .setEditing(micaMetricsService.getEditingHarmonizationDatasetsCount()))
                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(HarmonizationDataset.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftHarmonizationDatasetFilesCount())
                        .setPublished(micaMetricsService.getPublishedHarmonizationDatasetFilesCount()))

                // Projects
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(Project.class.getSimpleName()).setTotal(micaMetricsService.getDraftProjectsCount())
                        .setPublished(micaMetricsService.getPublishedProjectsCount())
                        .setEditing(micaMetricsService.getEditingProjectsCount()))
                .addFiles(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder().setType(Project.class.getSimpleName())
                        .setTotal(micaMetricsService.getDraftProjectFilesCount())
                        .setPublished(micaMetricsService.getPublishedProjectFilesCount()))

                // Variables
                .addDocuments(Mica.MicaMetricsDto.DocumentMetricsDto.newBuilder()
                        .setType(DatasetVariable.class.getSimpleName())
                        .setTotal(micaMetricsService.getPublishedVariablesCount())
                        .setPublished(micaMetricsService.getPublishedVariablesCount()))
                .build();
    }

    private DocumentContext getGlobalTranslations(String locale, boolean _default) throws IOException {
        String userProfileTranslations = getUserProfileTranslations(locale);
        String micaTranslations = micaConfigService.getTranslations(locale, _default);

        DocumentContext globalTranslations = JsonPath.parse(micaTranslations);
        globalTranslations.put("$", "userProfile", JsonPath.parse(userProfileTranslations).read("$"));

        return globalTranslations;
    }

    private String getGlobalTranslationsAsJson(String locale, boolean _default) throws IOException {
        return getGlobalTranslations(locale, _default).jsonString();
    }

    private Properties getGlobalTranslationsAsProperties(String locale, boolean _default) throws IOException {
        return JSONUtils.toProperties(getGlobalTranslationsAsJson(locale, _default));
    }

    private void createOrUpdateCredential(Mica.OpalCredentialDto opalCredentialDto) {
        if (opalCredentialDto.getType() == Mica.OpalCredentialType.USERNAME) {
            opalCredentialService.createOrUpdateOpalCredential(opalCredentialDto.getOpalUrl(),
                    opalCredentialDto.getUsername(), opalCredentialDto.getPassword());
        } else {
            opalCredentialService.saveOrUpdateOpalCertificateCredential(opalCredentialDto.getOpalUrl());

            if (opalCredentialDto.getKeyForm().getKeyType() == Mica.KeyType.KEY_PAIR)
                doCreateOrImportKeyPair(OpalService.OPAL_KEYSTORE, opalCredentialDto.getOpalUrl(),
                        opalCredentialDto.getKeyForm());
            else
                doImportCertificate(OpalService.OPAL_KEYSTORE, opalCredentialDto.getOpalUrl(),
                        opalCredentialDto.getKeyForm());
        }
    }

    private void doImportCertificate(String name, String alias, Mica.KeyForm keyForm) {
        keyStoreService.createOrUpdateCertificate(name, alias, keyForm.getPublicImport());
    }

    private void doCreateOrImportKeyPair(String name, String alias, Mica.KeyForm keyForm) {
        if (keyForm.hasPrivateForm() && keyForm.hasPublicForm()) {
            Mica.PublicKeyForm pkForm = keyForm.getPublicForm();
            keyStoreService.createOrUpdateCertificate(name, alias, keyForm.getPrivateForm().getAlgo(),
                    keyForm.getPrivateForm().getSize(), pkForm.getName(), pkForm.getOrganizationalUnit(),
                    pkForm.getOrganization(), pkForm.getLocality(), pkForm.getState(), pkForm.getCountry());
        } else if (keyForm.hasPrivateImport()) {
            doImportKeyPair(name, alias, keyForm);
        } else {
            throw new WebApplicationException("Missing private key", Response.Status.BAD_REQUEST);
        }
    }

    private void doImportKeyPair(String name, String alias, Mica.KeyForm keyForm) {
        if (keyForm.hasPublicForm()) {
            Mica.PublicKeyForm pkForm = keyForm.getPublicForm();
            keyStoreService.createOrUpdateCertificate(name, alias, keyForm.getPrivateImport(), pkForm.getName(),
                    pkForm.getOrganizationalUnit(), pkForm.getOrganization(), pkForm.getLocality(),
                    pkForm.getState(), pkForm.getCountry());
        } else if (keyForm.hasPublicImport()) {
            keyStoreService.createOrUpdateCertificate(name, alias, keyForm.getPrivateImport(),
                    keyForm.getPublicImport());
        } else {
            throw new WebApplicationException("Missing public key", Response.Status.BAD_REQUEST);
        }
    }

    private String getUserProfileTranslations(String locale) {
        try {
            return userProfileService.getUserProfileTranslations(locale);
        } catch (RestClientException e) {
            logger.warn("Cannot get translations about userProfile (from agate): {}", e.getMessage());
            return "{}";
        }
    }

    @GET
    @Path("/languages")
    @Timed
    @RequiresAuthentication
    public Map<String, String> getAvailableLanguages(@QueryParam("locale") @DefaultValue("en") String languageTag) {
        Locale locale = Locale.forLanguageTag(languageTag);
        return Arrays.stream(Locale.getISOLanguages())
                .collect(Collectors.toMap(lang -> lang, lang -> new Locale(lang).getDisplayLanguage(locale)));
    }

    /**
     * @deprecated kept for backward compatibility.
     * @return
       */
    @GET
    @Path("/taxonomies")
    @RequiresAuthentication
    @Deprecated
    public List<Opal.TaxonomyDto> getTaxonomies() {
        return opalService.getTaxonomyDtos();
    }

    /**
     * @deprecated kept for backward compatibility.
     * @return
       */
    @GET
    @Path("/taxonomies/summaries")
    @RequiresAuthentication
    @Deprecated
    public Opal.TaxonomiesDto getTaxonomySummaries() {
        return opalService.getTaxonomySummaryDtos();
    }

    /**
     * @deprecated kept for backward compatibility.
     * @return
       */
    @GET
    @Path("/taxonomies/vocabularies/summaries")
    @RequiresAuthentication
    @Deprecated
    public Opal.TaxonomiesDto getTaxonomyVocabularySummaries() {
        return opalService.getTaxonomyVocabularySummaryDtos();
    }

    /**
     * @deprecated kept for backward compatibility.
     * @return
       */
    @GET
    @Path("/studies")
    @RequiresAuthentication
    @Deprecated
    public Opal.TaxonomyDto getStudyTaxonomy() {
        return org.obiba.opal.web.taxonomy.Dtos.asDto(taxonomyService.getStudyTaxonomy());
    }
}