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

io.atlasmap.service.AtlasService Maven / Gradle / Ivy

/**
 * Copyright (C) 2017 Red Hat, Inc.
 *
 * 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 io.atlasmap.service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
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.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import io.atlasmap.v2.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;

import io.atlasmap.api.AtlasContext;
import io.atlasmap.api.AtlasException;
import io.atlasmap.api.AtlasSession;
import io.atlasmap.core.AtlasUtil;
import io.atlasmap.core.DefaultAtlasContextFactory;
import io.atlasmap.core.DefaultAtlasFieldActionService;
import io.atlasmap.service.AtlasLibraryLoader.AtlasLibraryLoaderListener;
import io.atlasmap.v2.ActionDetails;
import io.atlasmap.v2.AtlasMapping;
import io.atlasmap.v2.Audits;
import io.atlasmap.v2.Json;
import io.atlasmap.v2.Mapping;
import io.atlasmap.v2.ProcessMappingRequest;
import io.atlasmap.v2.ProcessMappingResponse;
import io.atlasmap.v2.StringMap;
import io.atlasmap.v2.StringMapEntry;
import io.atlasmap.v2.Validations;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

import static io.atlasmap.v2.MappingFileType.ADM;
import static io.atlasmap.v2.MappingFileType.GZ;

@Api
@Path("/")
public class AtlasService {

    static final String MAPPING_NAME_PREFIX = "UI.";
    static final String ATLASMAP_ADM_PATH = "atlasmap.adm.path";
    static final String ATLASMAP_WORKSPACE = "atlasmap.workspace";
    private static final Logger LOG = LoggerFactory.getLogger(AtlasService.class);

    private final DefaultAtlasContextFactory atlasContextFactory = DefaultAtlasContextFactory.getInstance();
    private final AtlasContext defaultContext;

    private String atlasmapCatalogName = "atlasmap-catalog.adm";
    private String atlasmapCatalogFilesName = "adm-catalog-files.gz";
    private String mappingFileNamePrefix = "atlasmapping";
    private String baseFolder = "";
    private String mappingFolder = "";
    private String libFolder = "";
    private AtlasLibraryLoader libraryLoader;

    public AtlasService() throws AtlasException {
        this.defaultContext = atlasContextFactory.createContext(new AtlasMapping());

        String atlasmapWorkspace = System.getProperty(ATLASMAP_WORKSPACE);
        if (atlasmapWorkspace != null && atlasmapWorkspace.length() > 0) {
            baseFolder = atlasmapWorkspace;
        }
        else {
            baseFolder = "target";
        }

        mappingFolder = baseFolder + File.separator + "mappings";
        libFolder = baseFolder + File.separator + "lib";

        String atlasmapAdmPath = System.getProperty(ATLASMAP_ADM_PATH);
        if (atlasmapAdmPath != null && atlasmapAdmPath.length() > 0) {
            initializeADMCatalog(atlasmapAdmPath);
        }

        this.libraryLoader = new AtlasLibraryLoader(libFolder);

        // Add atlas-core in case it runs on modular class loader
        this.libraryLoader.addAlternativeLoader(DefaultAtlasFieldActionService.class.getClassLoader());
        this.libraryLoader.addListener(new AtlasLibraryLoaderListener() {
            @Override
            public void onUpdate(AtlasLibraryLoader loader) {
                ((DefaultAtlasFieldActionService)atlasContextFactory.getFieldActionService()).init(libraryLoader);
            }
        });
        ((DefaultAtlasFieldActionService)atlasContextFactory.getFieldActionService()).init(libraryLoader);
    }

    /**
     * The user has specified an ADM file on inception. Clear current mappings and process
     * the specified ADM catalog file.
     *
     * @param atlasmapAdmPath - user specified ADM catalog file
     * @throws AtlasException
     */
    private void initializeADMCatalog(String atlasmapAdmPath) throws AtlasException {
        LOG.debug("Initializing with user specified catalog ADM path {}", atlasmapAdmPath);
        try {
            java.nio.file.Path admPath = Paths.get(atlasmapAdmPath);
            java.nio.file.Path mappingFolderPath = Paths.get(getMappingSubDirectory(0));
            File atlasDir = new File(getMappingSubDirectory(0));
            if (!atlasDir.exists()) {
                atlasDir.mkdirs();
            }
            atlasDir = new File(libFolder);
            if (!atlasDir.exists()) {
                atlasDir.mkdirs();
            }

            resetMappingById(0); // resets default mapping (id = 0)
            Files.copy(admPath.toAbsolutePath(),
                Paths.get(mappingFolderPath.toAbsolutePath().toString() + File.separator + generateMappingFileNameFromId(0, ADM)),
                StandardCopyOption.REPLACE_EXISTING);

            extractCompressedCatalog(0);

        } catch (Exception e) {
            LOG.error("Error processing the AtlasMap catalog file " + atlasmapAdmPath + "\n" +
                e.getMessage(), e);
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }
        System.setProperty(ATLASMAP_ADM_PATH, "");
    }

    @GET
    @Path("/fieldActions")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "List FieldActions", notes = "Retrieves a list of available field action")
    @ApiResponses(@ApiResponse(code = 200, response = ActionDetails.class, message = "Return a list of field action detail"))
    public Response listFieldActions(@Context UriInfo uriInfo) {
        ActionDetails details = new ActionDetails();

        if (atlasContextFactory == null || atlasContextFactory.getFieldActionService() == null) {
            return Response.ok().entity(toJson(details)).build();
        }

        details.getActionDetail().addAll(atlasContextFactory.getFieldActionService().listActionDetails());
        byte[] serialized = toJson(details);
        if (LOG.isDebugEnabled()) {
            LOG.debug(new String(serialized));
        }
        return Response.ok().entity(serialized).build();
    }

    @Deprecated
    @GET
    @Path("/mappings")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "List Mappings", notes = "Retrieves a list of mapping file name saved with specified mappingDefinitionId")
    @ApiResponses(@ApiResponse(code = 200, response = StringMap.class, message = "Return a list of a pair of mapping file name and content"))
    public Response listMappingsOld(@Context UriInfo uriInfo, @QueryParam("filter") final String filter)
    {
        return listMappings(uriInfo, filter, 0);
    }

    @GET
    @Path("/mappings/{mappingDefinitionId}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "List Mappings", notes = "Retrieves a list of mapping file name saved with specified mappingDefinitionId")
    @ApiResponses(@ApiResponse(code = 200, response = StringMap.class, message = "Return a list of a pair of mapping file name and content"))
    public Response listMappings(@Context UriInfo uriInfo, @QueryParam("filter") final String filter,
                                 @ApiParam("Mapping Definition ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId) {
        StringMap sMap = new StringMap();
        LOG.debug("listMappings with filter '{}'", filter);
        java.nio.file.Path mappingFolderPath = Paths.get(getMappingSubDirectory(mappingDefinitionId));
        File[] mappings = mappingFolderPath.toFile().listFiles(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                if (filter != null && name != null && !name.toLowerCase().contains(filter.toLowerCase())) {
                    return false;
                }
                return (name != null ? name.matches("atlasmapping-[a-zA-Z0-9\\.\\-]+.json") : false);
            }
        });

        if (mappings == null) {
            return Response.ok().entity(toJson(sMap)).build();
        }

        try {
            for (File mapping : mappings) {
                AtlasMapping map = Json.mapper().readValue(new File(mapping.getAbsolutePath()), AtlasMapping.class);
                if (map == null) {
                    LOG.warn("No mapping detected from file " + mapping.getAbsolutePath());
                    continue;
                }
                StringMapEntry mapEntry = new StringMapEntry();
                mapEntry.setName(map.getName());
                UriBuilder builder = uriInfo.getBaseUriBuilder().path("v2").path("atlas").path("mapping")
                        .path(map.getName());
                mapEntry.setValue(builder.build().toString());
                sMap.getStringMapEntry().add(mapEntry);
            }
        } catch (Exception e) {
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }

        byte[] serialized = toJson(sMap);
        if (LOG.isDebugEnabled()) {
            LOG.debug(new String(serialized));
        }
        return Response.ok().entity(serialized).build();
    }

    @Deprecated
    @DELETE
    @Path("/mapping")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove Mapping", notes = "Remove a mapping file saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, message = "Specified mapping file was removed successfully"),
        @ApiResponse(code = 204, message = "Mapping file was not found")})
    public Response removeMappingRequestOld() {
        return removeMappingRequest(0);
    }

    @DELETE
    @Path("/mapping/{mappingDefinitionId}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove Mapping", notes = "Remove a mapping file saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, message = "Specified mapping file was removed successfully"),
        @ApiResponse(code = 204, message = "Mapping file was not found")})
    public Response removeMappingRequest(@ApiParam("Mapping ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId) {

        java.nio.file.Path mappingFilePath = Paths
                .get(getMappingSubDirectory(mappingDefinitionId) +
                    File.separator +
                    generateMappingFileNameFromId(mappingDefinitionId, MappingFileType.JSON));

        File mappingFile = mappingFilePath.toFile();

        if (mappingFile == null || !mappingFile.exists()) {
            return Response.noContent().build();
        }

        if (!mappingFile.delete()) {
            String msg = "Unable to delete mapping file " + mappingFile.toString();
            LOG.error(msg);
            throw new WebApplicationException(msg, Status.INTERNAL_SERVER_ERROR);
        }

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

    @Deprecated
    @DELETE
    @Path("/mapping/RESET")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove Mapping by ID", notes = "Remove mapping file and catalogs related to specified ID")
    @ApiResponses({
        @ApiResponse(code = 200, message = "Mapping file and Catalogs were removed successfully"),
        @ApiResponse(code = 204, message = "Unable to remove mapping file and Catalogs for the specified ID")})
    public Response resetMappingByIdOld()
    {
        return resetMappingById(0);
    }

    @DELETE
    @Path("/mapping/RESET/{mappingDefinitionId}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove Mapping by ID", notes = "Remove mapping file and catalogs related to specified ID")
    @ApiResponses({
        @ApiResponse(code = 200, message = "Mapping file and Catalogs were removed successfully"),
        @ApiResponse(code = 204, message = "Unable to remove mapping file and Catalogs for the specified ID")})
    public Response resetMappingById(@ApiParam("Mapping ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId) {
        LOG.debug("resetMappingById {} ", mappingDefinitionId);

        java.nio.file.Path mappingFolderPath = Paths.get(getMappingSubDirectory(mappingDefinitionId));
        File[] mappings = mappingFolderPath.toFile().listFiles();

        if (mappings == null) {
            return Response.ok().build();
        }

        List  filesToDelete = getMappingFileNames(mappingDefinitionId);
        mappings = Arrays.stream(mappings)
            .filter(file -> filesToDelete.contains(file.getName()))
            .toArray(File[]::new);

        try {

            for (File mappingFile: mappings) {
                AtlasUtil.deleteDirectory(mappingFile);
            }

        } catch (Exception e) {
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }
        return Response.ok().build();
    }

    @DELETE
    @Path("/mapping/RESET/ALL")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove All Mappings", notes = "Remove all mapping files and catalogs saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, message = "All mapping files were removed successfully"),
        @ApiResponse(code = 204, message = "Unable to remove all mapping files")})
    public Response resetAllMappings() {
        LOG.debug("resetAllMappings");

        java.nio.file.Path mappingFolderPath = Paths.get(mappingFolder);
        File[] mappings = mappingFolderPath.toFile().listFiles();

        if (mappings == null) {
            return Response.ok().build();
        }

        try {

            for (File mappingFile: mappings) {
                AtlasUtil.deleteDirectory(mappingFile);
            }

        } catch (Exception e) {
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }
        return Response.ok().build();
    }

    private List getMappingFileNames(Integer mappingDefinitionId) {
        List  mappingFileNames = new ArrayList<>();
        mappingFileNames.add(generateMappingFileNameFromId(mappingDefinitionId, ADM));
        mappingFileNames.add(generateMappingFileNameFromId(mappingDefinitionId, MappingFileType.GZ));
        mappingFileNames.add(generateMappingFileNameFromId(mappingDefinitionId, MappingFileType.JSON));
        return mappingFileNames;
    }

    @DELETE
    @Path("/mapping/resetLibs")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Remove All User-Defined JAR libraries", notes = "Remove all user-defined JAR files saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, message = "All user-defined JAR files were removed successfully"),
        @ApiResponse(code = 204, message = "Unable to remove all user-defined JAR files")})
    public Response resetUserLibs() {
        LOG.debug("resetUserLibs");

        try {
            java.nio.file.Path libFolderPath = Paths.get(libFolder);
            AtlasUtil.deleteDirectoryContents(libFolderPath.toFile());
        } catch (Exception e) {
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }
        return Response.ok().build();
    }

    @Deprecated
    @GET
    @Path("/mapping/{mappingFormat}")
    @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.APPLICATION_OCTET_STREAM})
    @ApiOperation(value = "Get Mapping", notes = "Retrieve a mapping file saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, response = AtlasMapping.class, message = "Return a mapping file content"),
        @ApiResponse(code = 204, message = "Mapping file was not found"),
        @ApiResponse(code = 500, message = "Mapping file access error")})
    public Response getMappingRequestOld(
      @ApiParam("Mapping Format") @PathParam("mappingFormat") MappingFileType mappingFormat)
    {
        return getMappingRequest(mappingFormat, 0);
    }

    @GET
    @Path("/mapping/{mappingFormat}/{mappingDefinitionId}")
    @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.APPLICATION_OCTET_STREAM})
    @ApiOperation(value = "Get Mapping", notes = "Retrieve a mapping file saved on the server")
    @ApiResponses({
        @ApiResponse(code = 200, response = AtlasMapping.class, message = "Return a mapping file content"),
        @ApiResponse(code = 204, message = "Mapping file was not found"),
        @ApiResponse(code = 500, message = "Mapping file access error")})
    public Response getMappingRequest(
      @ApiParam("Mapping Format") @PathParam("mappingFormat") MappingFileType mappingFormat,
      @ApiParam("Mapping ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId) {
        LOG.debug("getMappingRequest: {} '{}'", mappingFormat, mappingDefinitionId);
        File mappingFile = getMappingFile(mappingFormat, mappingDefinitionId);

        if (mappingFile == null) {
            LOG.debug("getMappingRequest: {} '{}' not found", mappingFormat, mappingDefinitionId);
            return Response.noContent().build();
        }
        String mappingFilePath = mappingFile.getAbsolutePath();
        LOG.debug("getMappingRequest: {} '{}'", mappingFormat, mappingFilePath);

        switch (mappingFormat) {
        case JSON:
            AtlasMapping atlasMapping = null;
            try {
                atlasMapping = Json.mapper().readValue(new File(mappingFilePath), AtlasMapping.class);
            } catch (Exception e) {
                LOG.error("Error retrieving JSON mapping " + mappingFilePath, e);
                throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
            }
            byte[] serialized = toJson(atlasMapping);
            if (LOG.isDebugEnabled()) {
                LOG.debug(new String(serialized));
            }
            return Response.ok().entity(serialized).build();
        case GZ:
        case ZIP:
            byte[] binData = null;
            try {
                FileInputStream inputStream = new FileInputStream(mappingFilePath);
                int length = inputStream.available();
                binData = new byte[length];
                inputStream.read(binData);
                inputStream.close();
            } catch (Exception e) {
                LOG.error("Error getting compressed mapping documents.\n" + e.getMessage(), e);
                throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
            }
            return Response.ok().entity(binData).build();
        default:
            throw new WebApplicationException("Unrecognized mapping format: " + mappingFormat, Status.INTERNAL_SERVER_ERROR);
        }
    }

    @Deprecated
    @PUT
    @Path("/mapping/{mappingFormat}")
    @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.APPLICATION_OCTET_STREAM})
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Create Mapping", notes = "Save a mapping file on the server")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses({
        @ApiResponse(code = 200, message = "Succeeded"),
        @ApiResponse(code = 500, message = "Mapping file save error")})
    public Response createMappingRequestOld(InputStream mapping,
    @ApiParam("Mapping Format") @PathParam("mappingFormat") MappingFileType mappingFormat,
    @Context UriInfo uriInfo) {
        return createMappingRequest(mapping, mappingFormat, 0, uriInfo);
    }

    @PUT
    @Path("/mapping/{mappingFormat}/{mappingDefinitionId}")
    @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.APPLICATION_OCTET_STREAM})
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Create Mapping", notes = "Save a mapping file on the server")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses({
        @ApiResponse(code = 200, message = "Succeeded"),
        @ApiResponse(code = 500, message = "Mapping file save error")})
    public Response createMappingRequest(InputStream mapping,
      @ApiParam("Mapping Format") @PathParam("mappingFormat") MappingFileType mappingFormat,
      @ApiParam("Mapping ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId,
      @Context UriInfo uriInfo) {
        LOG.debug("createMappingRequest (save) with format '{}'", mappingFormat);
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        File mappingDir = new File(getMappingSubDirectory(mappingDefinitionId));
        if (!mappingDir.exists()) {
            mappingDir.mkdirs();
        }

        switch (mappingFormat) {
        case JSON:
           return saveMapping(mappingDefinitionId, fromJson(mapping, AtlasMapping.class), uriInfo);
        case GZ:

            String atlasmapCatalogFilesName = generateMappingFileNameFromId(mappingDefinitionId, MappingFileType.GZ);
            LOG.debug("  saveCompressedMappingRequest '{}' - ID: {}", atlasmapCatalogFilesName, mappingDefinitionId);
            try {
                createMappingFile(mappingDefinitionId, atlasmapCatalogFilesName, mapping);
                createCompressedCatalog(mappingDefinitionId);
            } catch (Exception e) {
                LOG.error("Error saving compressed mapping documents.\n" + e.getMessage(), e);
                throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
            }
            builder.path(atlasmapCatalogFilesName);
            return Response.ok().location(builder.build()).build();
        case ZIP:
            String atlasmapCatalogName = generateMappingFileNameFromId(mappingDefinitionId, ADM);
            LOG.debug("  saveCompressedADMRequest '{}'", atlasmapCatalogName);
            try {
                createMappingFile(mappingDefinitionId, atlasmapCatalogName, mapping);
                extractCompressedCatalog(mappingDefinitionId);
            } catch (Exception e) {
                LOG.error("Error saving compressed mapping documents.\n" + e.getMessage(), e);
                throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
            }
            builder.path(atlasmapCatalogName);
            return Response.ok().location(builder.build()).build();
        case XML:
            throw new WebApplicationException("XML mapping format is no longer supported. Please use JSON format instead.");
        default:
            throw new WebApplicationException("Unrecognized mapping format: " + mappingFormat, Status.INTERNAL_SERVER_ERROR);
        }
    }

    private String getMappingSubDirectory(Integer mappingDefinitionId) {
        return this.mappingFolder + File.separator + mappingDefinitionId;
    }

    @Deprecated
    @POST
    @Path("/mapping")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Update Mapping", notes = "Update existing mapping file on the server")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses(@ApiResponse(code = 200, message = "Succeeded"))
    public Response updateMappingRequestOld(
            InputStream mapping,
            @Context UriInfo uriInfo)
    {
        return updateMappingRequest(mapping, 0, uriInfo);
    }

    @POST
    @Path("/mapping/{mappingDefinitionId}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Update Mapping", notes = "Update existing mapping file on the server")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses(@ApiResponse(code = 200, message = "Succeeded"))
    public Response updateMappingRequest(
            InputStream mapping,
            @ApiParam("Mapping Definition ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId,
            @Context UriInfo uriInfo) {
        return saveMapping(mappingDefinitionId, fromJson(mapping, AtlasMapping.class), uriInfo);
    }

    @Deprecated
    @PUT
    @Path("/mapping/validate")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Validate Mapping", notes = "Validate mapping file")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses(@ApiResponse(code = 200, response = Validations.class, message = "Return a validation result"))
    public Response validateMappingRequest(InputStream mapping,
                                           @Context UriInfo uriInfo)
    {
        return validateMappingRequest(mapping, 0, uriInfo);
    }

    @PUT
    @Path("/mapping/validate/{mappingDefinitionId}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Validate Mapping", notes = "Validate mapping file")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "mapping", value = "Mapping file content", dataType = "io.atlasmap.v2.AtlasMapping"))
    @ApiResponses(@ApiResponse(code = 200, response = Validations.class, message = "Return a validation result"))
    public Response validateMappingRequest(InputStream mapping,
                                           @ApiParam("Mapping ID") @PathParam("mappingDefinitionId") Integer mappingDefinitionId,
                                           @Context UriInfo uriInfo) {
        try {
            AtlasMapping atlasMapping = fromJson(mapping, AtlasMapping.class);
            LOG.debug("Validate mappings: {}", atlasMapping.getName());
            return validateMapping(mappingDefinitionId, atlasMapping, uriInfo);
        } catch (AtlasException | IOException e) {
            throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
        }
    }

    @PUT
    @Path("/mapping/process")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Process Mapping", notes = "Process Mapping by feeding input data")
    @ApiImplicitParams(@ApiImplicitParam(
            name = "request", value = "ProcessMappingRequest object", dataType = "io.atlasmap.v2.ProcessMappingRequest"))
    @ApiResponses({
        @ApiResponse(code = 200, response = ProcessMappingResponse.class, message = "Return a mapping result"),
        @ApiResponse(code = 204, message = "Skipped empty mapping execution") })
    public Response processMappingRequest(InputStream request, @Context UriInfo uriInfo) {
        ProcessMappingRequest pmr = fromJson(request, ProcessMappingRequest.class);
        if (pmr.getAtlasMapping() != null) {
            throw new WebApplicationException("Whole mapping execution is not yet supported");
        }
        Mapping mapping = pmr.getMapping();
        if (mapping == null) {
            return Response.noContent().build();
        }
        Audits audits = null;
        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Preview request: {}", new String(toJson(mapping)));
            }
            audits = defaultContext.processPreview(mapping);
        } catch (AtlasException e) {
            throw new WebApplicationException("Unable to process mapping preview", e);
        }
        ProcessMappingResponse response = new ProcessMappingResponse();
        response.setMapping(mapping);
        if (audits != null) {
            response.setAudits(audits);
        }
        byte[] serialized = toJson(response);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Preview outcome: {}", new String(serialized));
        }
        return Response.ok().entity(serialized).build();
    }

    @GET
    @Path("/ping")
    @ApiOperation(value = "Ping", notes = "Simple liveness check method used in liveness checks. Must not be protected via authetication.")
    @ApiResponses(@ApiResponse(code = 200, response = String.class, message = "Return 'pong'"))
    public Response ping() {
	LOG.debug("Ping...  responding with 'pong'.");
	return Response.ok().entity(toJson("pong")).build();
    }

    @PUT
    @Path("/library")
    @ApiOperation(value = "Upload Library", notes = "Upload a Java library archive file")
    @Consumes({MediaType.APPLICATION_OCTET_STREAM})
    @ApiResponses(@ApiResponse(
            code = 200, message = "Library upload successful."))
    public Response uploadLibrary(InputStream requestIn) {
        if (requestIn == null) {
            throw new WebApplicationException("No library file found in request body");
        }

        try {
            libraryLoader.addJarFromStream(requestIn);
        } catch (Exception e) {
            if (LOG.isDebugEnabled()) {
                LOG.error("", e);
            }
            throw new WebApplicationException("Could not read file part: " + e.getMessage());
        }
        return Response.ok().build();
    }

    public AtlasLibraryLoader getLibraryLoader() {
        return this.libraryLoader;
    }

    protected Response validateMapping(Integer mappingDefinitionId, AtlasMapping mapping, UriInfo uriInfo) throws IOException, AtlasException {

        File mappingFile = createMappingFile(mappingDefinitionId, mapping.getName());
        try {
            Json.mapper().writeValue(mappingFile, mapping);
        } catch (Exception e) {
            String msg = "Error saving mapping " + mapping.getName() + " to file: " + e.getMessage();
            LOG.error(msg, e);
            throw new WebApplicationException(msg, e, Status.INTERNAL_SERVER_ERROR);
        }

        AtlasContext context = atlasContextFactory.createContext(mappingFile.toURI());
        AtlasSession session = context.createSession();
        context.processValidation(session);
        Validations validations = session.getValidations();

        if (session.getValidations() == null) {
            validations = new Validations();
        }

        return Response.ok().entity(toJson(validations)).build();
    }

    protected Response saveMapping(Integer mappingDefinitionId, AtlasMapping mapping, UriInfo uriInfo) {
        try {
            Json.mapper().writeValue(createMappingFile(mappingDefinitionId, mapping.getName()), mapping);
        } catch (Exception e) {
            String msg = "Error saving mapping " + mapping.getName() + " to file: " + e.getMessage();
            LOG.error(msg, e);
            throw new WebApplicationException(msg, e, Status.INTERNAL_SERVER_ERROR);
        }

        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        builder.path(mapping.getName());

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

    private File createMappingFile(Integer mappingDefinitionId, String mappingName) {
        File dir = new File(getMappingSubDirectory(mappingDefinitionId));
        if (!dir.exists()) {
            dir.mkdirs();
        }

        String fileName = getMappingSubDirectory(mappingDefinitionId) +
            File.separator +
            generateMappingFileName(mappingName);

        LOG.debug("Creating mapping file '{}'", fileName);
        return new File(fileName);
    }

    /**
     * Write to the specified ZIP output stream the specified local file.
     *
     * @param zipOut
     * @param fileToZip
     *
     * @throws IOException
     */
    private void addFileAsZip(ZipOutputStream zipOut, String fileToZip) throws IOException {
        byte[] buffer = new byte[2048];
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(fileToZip));
        int count = -1;
        while ((count = in.read(buffer)) != -1) {
            zipOut.write(buffer, 0, count);
        }
        in.close();
    }

    /**
     * Create a compressed (ZIP) ADM catalog.  The contents are:
     *
     *   - the user-specified instance/schema files (JSON/XML) and mappings (JSON) captured in a JSON
     *     document, compressed (GZIP).
     *   - the atlasmapping.json file (separate for camel runtime support)
     *   - the user-specified Java archives (JAR).
     *
     * @param mappingDefinitionId
     * @throws IOException
     */
    private void createCompressedCatalog(Integer mappingDefinitionId) throws IOException {
        String atlasmapCatalogName;
        String atlasmapCatalogFilesName;
        // Handling multiple mappings when creating compressed catalog
        atlasmapCatalogName = generateMappingFileNameFromId(mappingDefinitionId, ADM);
        atlasmapCatalogFilesName = generateMappingFileNameFromId(mappingDefinitionId, MappingFileType.GZ);


        String compressedCatalogName = getMappingSubDirectory(mappingDefinitionId) +
            File.separator +
            atlasmapCatalogName;

        String compressedCatalogFilesName = getMappingSubDirectory(mappingDefinitionId) +
            File.separator +
            atlasmapCatalogFilesName;

        try {
           // AtlasMap mapping XML + Binary, compressed instance/schema/mappings + java libraries ->
           //   target/mappings/atlasmap-catalog.adm
           LOG.debug("Creating compressed catalog ADM file '{}'", compressedCatalogName);
           ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(compressedCatalogName));

           ZipEntry catEntry = null;

           // atlasmapping-UI.{mappingDefinitionId}.json
           File mappingFile = getMappingFile(MappingFileType.JSON, mappingDefinitionId);

           if (mappingFile != null) {
               String mappingFileName = mappingFile.getName();
               LOG.debug("  Creating compressed catalog mapping file '{}'", mappingFileName);
               catEntry = new ZipEntry(mappingFileName);
               zipOut.putNextEntry(catEntry);
               String mappingFilePath = mappingFile.getAbsolutePath();
               addFileAsZip(zipOut, mappingFilePath);
               zipOut.closeEntry();
           }

           // adm-catalog-files.gz
           LOG.debug("  Creating compressed catalog GZ file '{}'", compressedCatalogFilesName);
           catEntry = new ZipEntry(atlasmapCatalogFilesName);
           zipOut.putNextEntry(catEntry);
           addFileAsZip(zipOut, compressedCatalogFilesName);
           zipOut.closeEntry();

           zipOut.putNextEntry(new ZipEntry("lib/"));
           zipOut.closeEntry();

           // .../target/lib
           java.nio.file.Path libFolderPath = Paths.get(libFolder);

           // User class libraries.
           File[] jars = libFolderPath.toFile().listFiles(new FilenameFilter() {
               @Override
               public boolean accept(File dir, String libName) {
                   if (libName != null) {
                       return true;
                   }
                   return false;
                }
           });

           if (jars != null) {
               try {
                   for (File jarFile : jars) {
                       LOG.debug("  Creating zip file entry '{}'", "lib/" + jarFile.getName());
                       ZipEntry libEntry = new ZipEntry("lib/" + jarFile.getName());
                       zipOut.putNextEntry(libEntry);
                       addFileAsZip(zipOut, libFolderPath.toString() + File.separator  + jarFile.getName());
                       zipOut.closeEntry();
                   }
               } catch (IOException e) {
                   throw new IOException(e.getMessage());
               }
           }

           zipOut.close();
       } catch (FileNotFoundException e) {
           throw new WebApplicationException("Error creating ADM catalog. " + e.getMessage(), e);
       }
    }

    /**
     * Unzip the ADM catalog file into its constituent parts:
     *
     * - target/mappings/adm-catalog-files.gz
     * - target/mappings/atlasmapping-UI.nnnnnn.json
     * - target/lib/...jar
     *
     * @throws IOException
     */
    private void extractCompressedCatalog(Integer mappingDefinitionId) throws IOException {
        byte[] buffer = new byte[2048];
        String mappingFileBasePath = getMappingSubDirectory(mappingDefinitionId);
        String catalogName = mappingFileBasePath + File.separator + generateMappingFileNameFromId(mappingDefinitionId, ADM);
        String atlasmapCatalogFilesName = generateMappingFileNameFromId(mappingDefinitionId, GZ);
        String catEntryname;
        String jsonCatEntryName = null;

        try {
            ZipInputStream zipIn = new ZipInputStream(new FileInputStream(catalogName));
            ZipEntry catEntry;
            BufferedOutputStream out;

            while ((catEntry = zipIn.getNextEntry()) != null) {
                catEntryname = catEntry.getName();
                if (catEntryname.contains("adm-catalog-files")) {
                    out = new BufferedOutputStream(new FileOutputStream(mappingFileBasePath +
                        File.separator + atlasmapCatalogFilesName));
                }
                else if (catEntryname.contains(".jar")) {
                    out = new BufferedOutputStream(new FileOutputStream(baseFolder + File.separator + catEntryname));
                }
                else if (catEntryname.contains(mappingFileNamePrefix)) {
                    out = new BufferedOutputStream(new FileOutputStream(mappingFileBasePath +
                        File.separator +
                        catEntryname));
                    jsonCatEntryName = catEntryname;
                }
                else {
                    continue;
                }
                LOG.debug("  Extracting ADM file entry '{}'", catEntryname);
                try {
                    int len = 0;
                    while ((len = zipIn.read(buffer)) > 0) {
                        out.write(buffer, 0, len);
                    }
                }
                finally {
                    if (out != null) out.close();
                }
            }
            zipIn.close();

            // If ZIP contains Json file, rename AtlasMapping inside it, and mapping File itself
            // to be consistent with current mappingDefinitionId
            if (jsonCatEntryName != null) {
                String oldMappingFilePath = mappingFileBasePath + File.separator + jsonCatEntryName;
                String mappingDefinitionName = "UI." + mappingDefinitionId;
                String newMappingFilePath =  mappingFileBasePath + File.separator + generateMappingFileName(mappingDefinitionName);
                // Rename AtlasMapping
                File oldMappingFile = new File(oldMappingFilePath);
                File newMappingFile = new File(newMappingFilePath);

                AtlasMapping oldAtlasMapping = Json.mapper().readValue(oldMappingFile, AtlasMapping.class);
                oldAtlasMapping.setName(mappingDefinitionName);
                // Update Mapping File
                Json.mapper().writeValue(oldMappingFile, oldAtlasMapping);
                // Rename Mapping File
                oldMappingFile.renameTo(newMappingFile);
                // Delete ADM file since its contents are no longer valid
                File admFileToDelete = new File (catalogName);
                admFileToDelete.delete();
            }

        } catch (IOException e) {
            throw new IOException(e.getMessage());
        }
    }

    /**
     * Create a file in the mapping folder based on the specified file name containing the specified stream.
     *
     * @param fileName
     * @param mapping
     * @throws IOException
     */
    private void createMappingFile(Integer mappingDefinitionId, String fileName, InputStream mapping) throws IOException {
        String outputFilePath = getMappingSubDirectory(mappingDefinitionId) + File.separator + fileName;
        LOG.debug("Creating mapping file '{}'", outputFilePath);
        FileOutputStream outputStream = new FileOutputStream(outputFilePath);

        int length = 0;
        byte[] data = null;
        do {
            length = mapping.available();
            if (length <= 0) {
                break;
            }
            data = new byte[length];
            if ((mapping.read(data)) == -1) {
                break;
            }
            outputStream.write(data);
        } while (true);
        outputStream.close();
        mapping.close();
    }

    /**
     * Return a File object based on the specified format and ID.
     *
     * @param mappingFormat
     * @param mappingDefinitionId
     * @return
     */
    private File getMappingFile(MappingFileType mappingFormat, Integer mappingDefinitionId) {
        File mappingFile = null;
        java.nio.file.Path mappingFilePath = null;
        mappingFilePath = Paths.get(getMappingSubDirectory(mappingDefinitionId) +
            File.separator +
            generateMappingFileNameFromId(mappingDefinitionId, mappingFormat));

        mappingFile = mappingFilePath.toFile();
        if (!mappingFile.exists()) {
            return null;
        }
        return mappingFile;
    }

    private String generateMappingFileName(String mappingName) {
        return String.format("atlasmapping-%s.json", mappingName);
    }

    private String generateMappingFileNameFromId(Integer mappingDefinitionId, MappingFileType mappingFileType){
        switch (mappingFileType){
            case GZ:
                return String.format("adm-catalog-files-%s.gz", mappingDefinitionId);
            case ZIP:
            case ADM:
                return String.format("atlasmap-catalog-%s.adm", mappingDefinitionId);

            case JSON:
                return String.format("atlasmapping-%s%s.json", MAPPING_NAME_PREFIX, mappingDefinitionId);
        }

        throw new WebApplicationException("Unrecognized Catalog File Type: " + mappingFileType, Status.INTERNAL_SERVER_ERROR);
    }

    private byte[] toJson(Object value) {
        try {
            return Json.mapper().writeValueAsBytes(value);
        } catch (JsonProcessingException e) {
            throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
        }
    }

    private  T fromJson(InputStream value, Classclazz) {
        try {
            if (LOG.isDebugEnabled()) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(value));
                StringBuffer buf = new StringBuffer();
                String line;
                while ((line = reader.readLine()) != null) {
                    buf.append(line);
                }
                LOG.debug(buf.toString());
                return Json.mapper().readValue(buf.toString(), clazz);
            }
            return Json.mapper().readValue(value, clazz);
        } catch (IOException e) {
            throw new WebApplicationException(e, Status.BAD_REQUEST);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy