org.eclipse.che.api.factory.FactoryService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.api.factory.FactoryService.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2015 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.factory;

import org.eclipse.che.api.account.server.dao.AccountDao;
import org.eclipse.che.api.account.server.dao.Member;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.HttpJsonHelper;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.factory.dto.Author;
import org.eclipse.che.api.factory.dto.Factory;
import org.eclipse.che.api.factory.dto.FactoryV2_1;
import org.eclipse.che.api.project.server.ProjectConfig;
import org.eclipse.che.api.project.server.ProjectJson;
import org.eclipse.che.api.project.server.DtoConverter;
import org.eclipse.che.api.project.server.type.AttributeValue;
import org.eclipse.che.api.project.shared.dto.ProjectModule;
import org.eclipse.che.api.project.shared.dto.Source;
import org.eclipse.che.api.project.shared.Builders;
import org.eclipse.che.api.project.server.Project;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.api.project.shared.Runners;
import org.eclipse.che.api.project.shared.dto.ImportSourceDescriptor;
import org.eclipse.che.api.project.shared.dto.NewProject;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.commons.lang.URLEncodedUtils;
import org.eclipse.che.commons.user.User;
import org.eclipse.che.dto.server.DtoFactory;

import com.google.gson.JsonSyntaxException;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;

import org.apache.commons.fileupload.FileItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
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.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;

/** Service for factory rest api features */
@Api(value = "/factory", description = "Factory manager")
@Path("/factory")
public class FactoryService extends Service {
    private static final Logger LOG = LoggerFactory.getLogger(FactoryService.class);

    private String baseApiUrl;
    private FactoryStore factoryStore;
    private FactoryEditValidator factoryEditValidator;
    private FactoryCreateValidator createValidator;
    private FactoryAcceptValidator acceptValidator;
    private LinksHelper linksHelper;
    private FactoryBuilder factoryBuilder;
    private ProjectManager projectManager;
    private AccountDao accountDao;

    @Inject
    public FactoryService(@Named("api.endpoint") String baseApiUrl, FactoryStore factoryStore,
            AccountDao accountDao, FactoryCreateValidator createValidator, FactoryAcceptValidator acceptValidator,
            FactoryEditValidator factoryEditValidator, LinksHelper linksHelper, FactoryBuilder factoryBuilder,
            ProjectManager projectManager) {
        this.baseApiUrl = baseApiUrl;
        this.accountDao = accountDao;
        this.factoryStore = factoryStore;
        this.createValidator = createValidator;
        this.acceptValidator = acceptValidator;
        this.factoryEditValidator = factoryEditValidator;
        this.linksHelper = linksHelper;
        this.factoryBuilder = factoryBuilder;
        this.projectManager = projectManager;
    }

    /**
     * Save factory to storage and return stored data. Field 'factoryUrl' should contains factory url information.
     * Fields with images should be named 'image'. Acceptable image size 100x100 pixels.
     * If vcs is not set in factory URL it will be set with "git" value.
     *
     * @param formData
     *         - http request form data
     * @param uriInfo
     *         - url context
     * @return - stored data
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.ConflictException} when factory url json is not found
     *         - {@link org.eclipse.che.api.core.ConflictException} when vcs is unsupported
     *         - {@link org.eclipse.che.api.core.ConflictException} when image content can't be read
     *         - {@link org.eclipse.che.api.core.ConflictException} when image media type is unsupported
     *         - {@link org.eclipse.che.api.core.ConflictException} when image height or length isn't equal to 100 pixels
     *         - {@link org.eclipse.che.api.core.ConflictException} when if image is too big
     *         - {@link org.eclipse.che.api.core.ServerException} when internal server error occurs
     */
    @ApiOperation(value = "Create a Factory and return data", notes = "Save factory to storage and return stored data. Field 'factoryUrl' should contains factory url information.", response = Factory.class, position = 1)
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 409, message = "Conflict error. Some parameter is missing"),
            @ApiResponse(code = 500, message = "Unable to identify user from context") })
    @RolesAllowed("user")
    @POST
    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    @Produces({ MediaType.APPLICATION_JSON })
    public Factory saveFactory(Iterator<FileItem> formData, @Context UriInfo uriInfo) throws ApiException {
        try {
            EnvironmentContext context = EnvironmentContext.getCurrent();
            if (context.getUser() == null || context.getUser().getName() == null
                    || context.getUser().getId() == null) {
                throw new ServerException("Unable to identify user from context");
            }

            Set<FactoryImage> images = new HashSet<>();
            Factory factory = null;

            while (formData.hasNext()) {
                FileItem item = formData.next();
                String fieldName = item.getFieldName();
                if (fieldName.equals("factoryUrl")) {
                    try {
                        factory = factoryBuilder.buildEncoded(item.getInputStream());
                    } catch (JsonSyntaxException e) {
                        throw new ConflictException("You have provided an invalid JSON.  For more information, "
                                + "please visit http://docs.codenvy.com/user/creating-factories/factory-parameter-reference/");
                    }
                } else if (fieldName.equals("image")) {
                    try (InputStream inputStream = item.getInputStream()) {
                        FactoryImage factoryImage = FactoryImage.createImage(inputStream, item.getContentType(),
                                NameGenerator.generate(null, 16));
                        if (factoryImage.hasContent()) {
                            images.add(factoryImage);
                        }
                    }
                }
            }

            if (factory == null) {
                LOG.warn("No factory URL information found in 'factoryUrl' section of multipart form-data.");
                throw new ConflictException(
                        "No factory URL information found in 'factoryUrl' section of multipart/form-data.");
            }

            processDefaults(factory);
            createValidator.validateOnCreate(factory);
            String factoryId = factoryStore.saveFactory(factory, images);
            factory = factoryStore.getFactory(factoryId);
            factory = factory.withLinks(linksHelper.createLinks(factory, images, uriInfo));

            LOG.info(
                    "EVENT#factory-created# WS#{}# USER#{}# PROJECT#{}# TYPE#{}# REPO-URL#{}# FACTORY-URL#{}# AFFILIATE-ID#{}# ORG-ID#{}#",
                    "", context.getUser().getName(), "",
                    nullToEmpty(factory.getProject() != null ? factory.getProject().getType() : null),
                    factory.getSource().getProject().getLocation(),
                    linksHelper.getLinkByRelation(factory.getLinks(), "create-project").iterator().next().getHref(),
                    "", nullToEmpty(factory.getCreator().getAccountId()));

            return factory;
        } catch (IOException e) {
            LOG.error(e.getLocalizedMessage(), e);
            throw new ServerException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Get factory information from storage by its id.
     *
     * @param id
     *         - id of factory
     * @param uriInfo
     *         - url context
     * @return - stored data, if id is correct.
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.NotFoundException} when factory with given id doesn't exist
     */

    @ApiOperation(value = "Get Factory information by its ID", notes = "Get JSON with Factory information. Factory ID is passed in a path parameter", response = Factory.class, position = 2)
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Factory not found"),
            @ApiResponse(code = 409, message = "Failed to validate Factory URL"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @GET
    @Path("/{id}")
    @Produces({ MediaType.APPLICATION_JSON })
    public Factory getFactory(@ApiParam(value = "Factory ID", required = true) @PathParam("id") String id,
            @ApiParam(value = "Legacy. Whether or not to transform Factory into the most recent format", allowableValues = "true,false", defaultValue = "false") @DefaultValue("false") @QueryParam("legacy") Boolean legacy,
            @ApiParam(value = "Whether or not to validate values like it is done when accepting a Factory", allowableValues = "true,false", defaultValue = "false") @DefaultValue("false") @QueryParam("validate") Boolean validate,
            @Context UriInfo uriInfo) throws ApiException {
        Factory factoryUrl = factoryStore.getFactory(id);
        if (factoryUrl == null) {
            LOG.warn("Factory URL with id {} is not found.", id);
            throw new NotFoundException("Factory URL with id " + id + " is not found.");
        }

        if (legacy) {
            factoryUrl = factoryBuilder.convertToLatest(factoryUrl);
        }

        try {
            factoryUrl = factoryUrl.withLinks(
                    linksHelper.createLinks(factoryUrl, factoryStore.getFactoryImages(id, null), uriInfo));
        } catch (UnsupportedEncodingException e) {
            throw new ServerException(e.getLocalizedMessage());
        }
        if (validate) {
            acceptValidator.validateOnAccept(factoryUrl, true);
        }
        return factoryUrl;
    }

    /**
     * Removes factory information from storage by its id.
     *
     * @param id
     *         id of factory
     * @param uriInfo
     *         url context
     * @throws NotFoundException
     *         when factory with given id doesn't exist
     */
    @ApiOperation(value = "Removes Factory by its ID", notes = "Removes factory based on the Factory ID which is passed in a path parameter")
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Factory not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @DELETE
    @Path("/{id}")
    @RolesAllowed("user")
    public void removeFactory(@ApiParam(value = "Factory ID", required = true) @PathParam("id") String id,
            @Context UriInfo uriInfo) throws ApiException {

        // Check we've a user
        final User user = EnvironmentContext.getCurrent().getUser();
        if (user == null) {
            // well this shouldn't happen if only user is authorized to call the method
            throw new ForbiddenException("No authenticated user");
        }

        // Do we have a factory for this id ?
        Factory factory = factoryStore.getFactory(id);
        if (factory == null) {
            throw new NotFoundException("Factory with id " + id + " is not found.");
        }

        // Gets the User id
        String userId = user.getId();

        // Validate the factory against the current user
        factoryEditValidator.validate(factory, userId);

        // if validator didn't fail it means that the access is granted
        factoryStore.removeFactory(id);
    }

    /**
     * Updates factory with a new factory content
     *
     * @param id
     *         id of factory
     * @param newFactory
     *         the new data for the factory
     * @throws NotFoundException
     *         when factory with given id doesn't exist
     * @throws org.eclipse.che.api.core.ServerException
     *         if given factory is null
     */
    @ApiOperation(value = "Updates factory information by its ID", notes = "Updates factory based on the Factory ID which is passed in a path parameter")
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Factory not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @PUT
    @Path("/{id}")
    @RolesAllowed("user")
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_JSON })
    public Factory updateFactory(@ApiParam(value = "Factory ID", required = true) @PathParam("id") String id,
            Factory newFactory) throws ApiException {

        // forbid null update
        if (newFactory == null) {
            throw new ServerException("The updating factory shouldn't be null");
        }

        // Do we have a factory for this id ?
        Factory existingFactory = factoryStore.getFactory(id);
        if (existingFactory == null) {
            throw new NotFoundException("Factory with id " + id + " does not exist.");
        }

        // Gets the User id
        final User user = EnvironmentContext.getCurrent().getUser();
        String userId = user.getId();

        // Validate the factory against the current user
        factoryEditValidator.validate(existingFactory, userId);

        processDefaults(newFactory);

        newFactory.getCreator().withCreated(existingFactory.getCreator().getCreated());
        newFactory.setId(existingFactory.getId());

        // validate the new content
        createValidator.validateOnCreate(newFactory);

        // access granted, user can update the factory
        factoryStore.updateFactory(id, newFactory);

        // create links
        try {
            newFactory.setLinks(
                    linksHelper.createLinks(newFactory, factoryStore.getFactoryImages(id, null), uriInfo));
        } catch (UnsupportedEncodingException e) {
            throw new ServerException(e.getLocalizedMessage());
        }

        return newFactory;
    }

    /**
     * Get list of factory links which conform specified attributes.
     *
     * @param uriInfo
     *         - url context
     * @return - stored data, if id is correct.
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.NotFoundException} when factory with given id doesn't exist
     */
    @RolesAllowed({ "user", "system/manager" })
    @GET
    @Path("/find")
    @Produces({ MediaType.APPLICATION_JSON })
    @SuppressWarnings("unchecked")
    public List<Link> getFactoryByAttribute(@Context UriInfo uriInfo) throws ApiException {
        List<Link> result = new ArrayList<>();
        URI uri = UriBuilder.fromUri(uriInfo.getRequestUri()).replaceQueryParam("token").build();
        Map<String, Set<String>> queryParams = URLEncodedUtils.parse(uri, "UTF-8");
        if (queryParams.isEmpty()) {
            throw new IllegalArgumentException("Query must contain at least one attribute.");
        }
        ArrayList<Pair> pairs = new ArrayList<>();
        for (Map.Entry<String, Set<String>> entry : queryParams.entrySet()) {
            if (!entry.getValue().isEmpty())
                pairs.add(Pair.of(entry.getKey(), entry.getValue().iterator().next()));
        }
        List<Factory> factories = factoryStore.findByAttribute(pairs.toArray(new Pair[pairs.size()]));
        for (Factory factory : factories) {
            result.add(DtoFactory.getInstance().createDto(Link.class).withMethod(HttpMethod.GET).withRel("self")
                    .withProduces(MediaType.APPLICATION_JSON).withConsumes(null)
                    .withHref(UriBuilder.fromUri(uriInfo.getBaseUri()).path(FactoryService.class)
                            .path(FactoryService.class, "getFactory").build(factory.getId()).toString())
                    .withParameters(null));
        }
        return result;
    }

    /**
     * Get image information by its id.
     *
     * @param factoryId
     *         - id of factory
     * @param imageId
     *         - image id.
     * @return - image information if ids are correct. If imageId is not set, random image of factory will be returned. But if factory has
     * no images, exception will be thrown.
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.NotFoundException} when factory with given id doesn't exist
     *         - {@link org.eclipse.che.api.core.NotFoundException} when imgId is not set in request and there is no default image for factory
     *         with given id
     *         - {@link org.eclipse.che.api.core.NotFoundException} when image with given image id doesn't exist
     */
    @ApiOperation(value = "Get Factory image information", notes = "Get Factory image information by Factory and image ID", position = 3)
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Factory or Image ID Not Found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @GET
    @Path("/{factoryId}/image")
    @Produces("image/*")
    public Response getImage(
            @ApiParam(value = "Factory ID", required = true) @PathParam("factoryId") String factoryId,
            @ApiParam(value = "Image ID", required = true) @DefaultValue("") @QueryParam("imgId") String imageId)
            throws ApiException {
        Set<FactoryImage> factoryImages = factoryStore.getFactoryImages(factoryId, null);
        if (factoryImages == null) {
            LOG.warn("Factory URL with id {} is not found.", factoryId);
            throw new NotFoundException("Factory URL with id " + factoryId + " is not found.");
        }
        if (imageId.isEmpty()) {
            if (factoryImages.size() > 0) {
                FactoryImage image = factoryImages.iterator().next();
                return Response.ok(image.getImageData(), image.getMediaType()).build();
            } else {
                LOG.warn("Default image for factory {} is not found.", factoryId);
                throw new NotFoundException("Default image for factory " + factoryId + " is not found.");
            }
        } else {
            for (FactoryImage image : factoryImages) {
                if (image.getName().equals(imageId)) {
                    return Response.ok(image.getImageData(), image.getMediaType()).build();
                }
            }
        }
        LOG.warn("Image with id {} is not found.", imageId);
        throw new NotFoundException("Image with id " + imageId + " is not found.");
    }

    /**
     * Get factory snippet by factory id and snippet type. If snippet type is not set, "url" type will be used as default.
     *
     * @param id
     *         - factory id.
     * @param type
     *         - type of snippet.
     * @param uriInfo
     *         - url context
     * @return - snippet content.
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.NotFoundException} when factory with given id doesn't exist - with response code 400 if
     *         snippet
     *         type
     *         is unsupported
     */
    @ApiOperation(value = "Get Factory snippet by ID", notes = "Get Factory snippet by ID", position = 4)
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Factory not Found"),
            @ApiResponse(code = 409, message = "Unknown snippet type"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @GET
    @Path("/{id}/snippet")
    @Produces({ MediaType.TEXT_PLAIN })
    public String getFactorySnippet(@ApiParam(value = "Factory ID", required = true) @PathParam("id") String id,
            @ApiParam(value = "Snippet type", required = true, allowableValues = "url,html,iframe,markdown", defaultValue = "url") @DefaultValue("url") @QueryParam("type") String type,
            @Context UriInfo uriInfo) throws ApiException {
        Factory factory = factoryStore.getFactory(id);
        if (factory == null) {
            LOG.warn("Factory URL with id {} is not found.", id);
            throw new NotFoundException("Factory URL with id " + id + " is not found.");
        }

        final String baseUrl = UriBuilder.fromUri(uriInfo.getBaseUri()).replacePath("").build().toString();

        switch (type) {
        case "url":
            return UriBuilder.fromUri(uriInfo.getBaseUri()).replacePath("factory").queryParam("id", id).build()
                    .toString();
        case "html":
            return SnippetGenerator.generateHtmlSnippet(baseUrl, id);
        case "iframe":
            return SnippetGenerator.generateiFrameSnippet(baseUrl, id);
        case "markdown":
            Set<FactoryImage> factoryImages = factoryStore.getFactoryImages(id, null);
            String imageId = (factoryImages.size() > 0) ? factoryImages.iterator().next().getName() : null;

            try {
                return SnippetGenerator.generateMarkdownSnippet(baseUrl, factory, imageId);
            } catch (IllegalArgumentException e) {
                throw new ConflictException(e.getMessage());
            }
        default:
            LOG.warn("Snippet type {} is unsupported", type);
            throw new ConflictException("Snippet type \"" + type + "\" is unsupported.");
        }
    }

    /**
     * Generate project configuration.
     *
     * @param workspace
     *         - workspace id.
     * @param path
     *         - project path.
     * @throws org.eclipse.che.api.core.ApiException
     *         - {@link org.eclipse.che.api.core.ConflictException} when project is not under source control.
     */
    @ApiOperation(value = "Get project Factory parameters", notes = "This call returns a Factory.json that is used to create a Factory. "
            + "To be able to get project's configuration json file, it should be under Git")
    @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Workspace or project not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @GET
    @Path("/{ws-id}/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getFactoryJson(
            @ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace,
            @ApiParam(value = "Project name", required = true) @PathParam("path") String path) throws ApiException {
        final Project project = projectManager.getProject(workspace, path);

        if (project == null) {
            throw new NotFoundException("Project " + path + " are not found in workspace " + workspace);
        }
        final DtoFactory dtoFactory = DtoFactory.getInstance();
        ImportSourceDescriptor source;
        NewProject newProject;
        try {
            final ProjectConfig projectDescription = project.getConfig();
            Map<String, AttributeValue> attributes = projectDescription.getAttributes();
            String vcs;
            if (attributes.containsKey("vcs.provider.name")
                    && attributes.get("vcs.provider.name").getList().contains("git")) {
                vcs = "git";
            } else if (attributes.containsKey("svn.repository.url")) {
                vcs = "svn";
            } else {
                throw new ConflictException(
                        "Not able to generate project configuration, project has to be under version control system");
            }
            final Link importSourceLink = dtoFactory.createDto(Link.class).withMethod(HttpMethod.GET)
                    .withHref(UriBuilder.fromUri(baseApiUrl).path(vcs).path(workspace)
                            .path("import-source-descriptor").build().toString());
            source = HttpJsonHelper.request(ImportSourceDescriptor.class, importSourceLink,
                    new Pair<>("projectPath", path));

            // Read again project.json file even we already have all information about project in 'projectDescription' variable.
            // We do so because 'projectDescription' variable contains all attributes of project including 'calculated' attributes but we
            // don't need 'calculated' attributes in this case. Such attributes exists only in runtime and may be restored from the project.
            // TODO: improve this once we will be able to detect different type of attributes. In this case just need get attributes from
            // 'projectDescription' variable and skip all attributes that aren't defined in project.json file.
            final ProjectJson projectJson = ProjectJson.load(project);
            newProject = dtoFactory.createDto(NewProject.class).withName(project.getName())
                    .withType(projectJson.getType()).withAttributes(projectJson.getAttributes())
                    .withVisibility(project.getVisibility()).withDescription(projectJson.getDescription());
            newProject.setMixins(projectJson.getMixinTypes());

            for (Project module : projectManager.getProjectModules(project)) {
                ProjectConfig moduleConfig = module.getConfig();
                String moduleRelativePath = module.getPath().substring(project.getPath().length());
                final ProjectJson moduleJson = ProjectJson.load(module);
                newProject.getModules()
                        .add(DtoFactory.newDto(ProjectModule.class).withType(moduleConfig.getTypeId())
                                .withPath(moduleRelativePath).withAttributes(moduleJson.getAttributes())
                                .withBuilders(DtoConverter.toDto(moduleConfig.getBuilders()))
                                .withRunners(DtoConverter.toDto(moduleConfig.getRunners()))
                                .withMixins(moduleConfig.getMixinTypes())
                                .withDescription(moduleJson.getDescription()));
            }

            final Builders builders = projectJson.getBuilders();
            if (builders != null) {
                newProject.withBuilders(DtoConverter.toDto(builders));
            }
            final Runners runners = projectJson.getRunners();
            if (runners != null) {
                newProject.withRunners(DtoConverter.toDto(runners));
            }
        } catch (IOException e) {
            throw new ServerException(e.getLocalizedMessage());
        }
        return Response
                .ok(dtoFactory.createDto(FactoryV2_1.class).withProject(newProject)
                        .withSource(dtoFactory.createDto(Source.class).withProject(source)).withV("2.1"),
                        MediaType.APPLICATION_JSON)
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + path + ".json").build();
    }

    private void processDefaults(Factory factory) throws ApiException {

        User currentUser = EnvironmentContext.getCurrent().getUser();
        if (factory.getCreator() == null) {
            factory.setCreator(DtoFactory.getInstance().createDto(Author.class).withUserId(currentUser.getId())
                    .withCreated(System.currentTimeMillis()));
        } else {
            if (isNullOrEmpty(factory.getCreator().getUserId())) {
                factory.getCreator().setUserId(currentUser.getId());
            }
            if (factory.getCreator().getCreated() == null) {
                factory.getCreator().setCreated(System.currentTimeMillis());
            }
        }

        if (factory.getWorkspace() != null && "owner".equals(factory.getWorkspace().getLocation())
                && factory.getCreator().getAccountId() == null) {
            List<Member> ownedAccounts = accountDao.getByMember(currentUser.getId()).stream()
                    .filter(member -> member.getRoles().contains("account/owner")).collect(Collectors.toList());
            switch (ownedAccounts.size()) {
            case 0: {
                // must never happen but who knows
                throw new ForbiddenException(
                        "You are not owner of any account, so you can't create factory with such workspace location.");
            }
            case 1: {
                factory.getCreator().setAccountId(ownedAccounts.get(0).getAccountId());
                break;
            }
            default: {
                throw new ForbiddenException(
                        "You are owner of more than one account. Please indicate which one to use using creator/accountId property.");
            }
            }
        }
    }
}