devfilesPage =
+ userDevfileDao.getByNamespace(namespace, maxItems, skipCount);
+ return devfilesPage;
+ }
+ /**
+ * Updates an existing user devfile in accordance to the new configuration.
+ *
+ * Note: Replace strategy is used for user devfile update, it means that existing devfile data
+ * will be replaced with given {@code update}.
+ *
+ * @param update user devfile update
+ * @return updated user devfile
+ * @throws NullPointerException when {@code update} is null
+ * @throws ConflictException when any conflict occurs.
+ * @throws NotFoundException when user devfile with given id not found
+ * @throws ServerException when any server error occurs
+ */
+ public UserDevfile updateUserDevfile(UserDevfile update)
+ throws ConflictException, NotFoundException, ServerException {
+ requireNonNull(update);
+ Optional result = userDevfileDao.update(update);
+ UserDevfile devfile =
+ result.orElseThrow(
+ () ->
+ new NotFoundException(
+ format("Devfile with id '%s' doesn't exist", update.getId())));
+ LOG.debug(
+ "UserDevfile '{}' with id '{}' update by user '{}'",
+ devfile.getName(),
+ devfile.getId(),
+ EnvironmentContext.getCurrent().getSubject().getUserName());
+ eventService.publish(new DevfileUpdatedEvent(devfile));
+ return devfile;
+ }
+
+ /**
+ * Removes stored {@link UserDevfile} by given id.
+ *
+ * @param id user devfile identifier
+ * @throws NullPointerException when {@code id} is null
+ * @throws ServerException when any server errors occurs
+ */
+ public void removeUserDevfile(String id) throws ServerException {
+ requireNonNull(id);
+ userDevfileDao.remove(id);
+ LOG.debug(
+ "UserDevfile with id '{}' removed by user '{}'",
+ id,
+ EnvironmentContext.getCurrent().getSubject().getUserName());
+ }
+
+ /**
+ * Gets list of devfiles. Parameters, returned values and possible exceptions are the same as in
+ * {@link UserDevfileDao#getDevfiles(int, int, List, List)}
+ */
+ public Page getUserDevfiles(
+ int maxItems,
+ int skipCount,
+ List> filter,
+ List> order)
+ throws ServerException {
+ return userDevfileDao.getDevfiles(maxItems, skipCount, filter, order);
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java
new file mode 100644
index 0000000000..47fa4b108c
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.event;
+
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.core.notification.EventOrigin;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.core.db.cascade.event.RemoveEvent;
+
+/** Published before {@link UserDevfile user devfile} removed. */
+@EventOrigin("user")
+public class BeforeDevfileRemovedEvent extends RemoveEvent {
+
+ private final UserDevfileImpl userDevfile;
+
+ public BeforeDevfileRemovedEvent(UserDevfileImpl userDevfile) {
+ this.userDevfile = userDevfile;
+ }
+
+ /** Returns user which is going to be removed. */
+ public UserDevfileImpl getUserDevfile() {
+ return userDevfile;
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java
new file mode 100644
index 0000000000..e12492790f
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.jpa;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.UserDevfileSearchQueryBuilder.newBuilder;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.persist.Transactional;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import org.eclipse.che.account.shared.model.Account;
+import org.eclipse.che.account.spi.AccountDao;
+import org.eclipse.che.api.core.ConflictException;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.core.Page;
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+import org.eclipse.che.commons.lang.Pair;
+import org.eclipse.che.core.db.jpa.DuplicateKeyException;
+import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException;
+
+/** JPA based implementation of {@link UserDevfileDao}. */
+@Singleton
+@Beta
+public class JpaUserDevfileDao implements UserDevfileDao {
+
+ protected final Provider managerProvider;
+ protected final AccountDao accountDao;
+ protected final EventService eventService;
+ /** sorting order that would be used by default during search. */
+ public static final List> DEFAULT_ORDER =
+ ImmutableList.of(new Pair<>("id", "ASC"));
+ /** Set of field that is eligible to use for search. */
+ public static final Set VALID_SEARCH_FIELDS = ImmutableSet.of("name");
+ /** Set of field that is eligible to use for sorting during search. */
+ public static final Set VALID_ORDER_FIELDS = ImmutableSet.of("id", "name");
+
+ @Inject
+ public JpaUserDevfileDao(
+ Provider managerProvider, AccountDao accountDao, EventService eventService) {
+ this.managerProvider = managerProvider;
+ this.accountDao = accountDao;
+ this.eventService = eventService;
+ }
+
+ @Override
+ public UserDevfile create(UserDevfile userDevfile) throws ConflictException, ServerException {
+ requireNonNull(userDevfile);
+ try {
+ Account account = accountDao.getByName(userDevfile.getNamespace());
+ UserDevfileImpl userDevfileImpl = new UserDevfileImpl(userDevfile, account);
+ doCreate(userDevfileImpl);
+ return userDevfileImpl;
+ } catch (DuplicateKeyException ex) {
+ throw new ConflictException(
+ format(
+ "Devfile with name '%s' already exists in the specified account '%s'",
+ userDevfile.getName(), userDevfile.getNamespace()));
+ } catch (IntegrityConstraintViolationException ex) {
+ throw new ConflictException(
+ "Could not create devfile with creator that refers to a non-existent user");
+ } catch (RuntimeException ex) {
+ throw new ServerException(ex.getMessage(), ex);
+ } catch (NotFoundException e) {
+ throw new ConflictException(
+ format(
+ "Not able to create devfile in requested namespace %s bacause it is not found",
+ userDevfile.getNamespace()));
+ }
+ }
+
+ @Override
+ public Optional update(UserDevfile userDevfile)
+ throws ConflictException, ServerException, NotFoundException {
+ requireNonNull(userDevfile);
+ try {
+ Account account = accountDao.getByName(userDevfile.getNamespace());
+ return doUpdate(new UserDevfileImpl(userDevfile, account)).map(UserDevfileImpl::new);
+ } catch (DuplicateKeyException ex) {
+ throw new ConflictException(
+ format(
+ "Devfile with name '%s' already exists in current account '%s'",
+ userDevfile.getName(), userDevfile.getNamespace()));
+ } catch (RuntimeException ex) {
+ throw new ServerException(ex.getLocalizedMessage(), ex);
+ }
+ }
+
+ @Override
+ public void remove(String id) throws ServerException {
+ requireNonNull(id);
+ try {
+ doRemove(id);
+ } catch (RuntimeException ex) {
+ throw new ServerException(ex.getLocalizedMessage(), ex);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackOn = {ServerException.class, RuntimeException.class})
+ public Optional getById(String id) throws ServerException {
+ requireNonNull(id);
+ try {
+ final UserDevfileImpl devfile = managerProvider.get().find(UserDevfileImpl.class, id);
+ if (devfile == null) {
+ return Optional.empty();
+ }
+ return Optional.of(new UserDevfileImpl(devfile));
+ } catch (RuntimeException ex) {
+ throw new ServerException(ex.getLocalizedMessage(), ex);
+ }
+ }
+
+ @Transactional(rollbackOn = {ServerException.class, RuntimeException.class})
+ @Override
+ public Page getByNamespace(String namespace, int maxItems, long skipCount)
+ throws ServerException {
+ requireNonNull(namespace, "Required non-null namespace");
+ try {
+ final EntityManager manager = managerProvider.get();
+ final List list =
+ manager
+ .createNamedQuery("UserDevfile.getByNamespace", UserDevfileImpl.class)
+ .setParameter("namespace", namespace)
+ .setMaxResults(maxItems)
+ .setFirstResult((int) skipCount)
+ .getResultList()
+ .stream()
+ .map(UserDevfileImpl::new)
+ .collect(Collectors.toList());
+ final long count =
+ manager
+ .createNamedQuery("UserDevfile.getByNamespaceCount", Long.class)
+ .setParameter("namespace", namespace)
+ .getSingleResult();
+ return new Page<>(list, skipCount, maxItems, count);
+ } catch (RuntimeException x) {
+ throw new ServerException(x.getLocalizedMessage(), x);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackOn = {ServerException.class})
+ public Page getDevfiles(
+ int maxItems,
+ int skipCount,
+ List> filter,
+ List> order)
+ throws ServerException {
+
+ checkArgument(maxItems > 0, "The number of items has to be positive.");
+ checkArgument(
+ skipCount >= 0,
+ "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
+
+ return doGetDevfiles(
+ maxItems, skipCount, filter, order, () -> newBuilder(managerProvider.get()));
+ }
+
+ @Transactional(rollbackOn = {ServerException.class})
+ protected Page doGetDevfiles(
+ int maxItems,
+ int skipCount,
+ List> filter,
+ List> order,
+ Supplier queryBuilderSupplier)
+ throws ServerException {
+ if (filter != null && !filter.isEmpty()) {
+ List> invalidFilter =
+ filter
+ .stream()
+ .filter(p -> !VALID_SEARCH_FIELDS.contains(p.first.toLowerCase()))
+ .collect(toList());
+ if (!invalidFilter.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Filtering allowed only by " + VALID_SEARCH_FIELDS + " but got: " + invalidFilter);
+ }
+ }
+ List> effectiveOrder = DEFAULT_ORDER;
+ if (order != null && !order.isEmpty()) {
+ List> invalidOrder =
+ order
+ .stream()
+ .filter(p -> !VALID_ORDER_FIELDS.contains(p.first.toLowerCase()))
+ .collect(toList());
+ if (!invalidOrder.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Order allowed only by " + VALID_ORDER_FIELDS + "¬ but got: " + invalidOrder);
+ }
+
+ List> invalidSortOrder =
+ order
+ .stream()
+ .filter(p -> !p.second.equalsIgnoreCase("asc") && !p.second.equalsIgnoreCase("desc"))
+ .collect(Collectors.toList());
+ if (!invalidSortOrder.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Invalid sort order direction. Possible values are 'asc' or 'desc' but got: "
+ + invalidSortOrder);
+ }
+ effectiveOrder = order;
+ }
+ try {
+ final long count =
+ queryBuilderSupplier.get().withFilter(filter).buildCountQuery().getSingleResult();
+
+ if (count == 0) {
+ return new Page<>(emptyList(), skipCount, maxItems, count);
+ }
+ List result =
+ queryBuilderSupplier
+ .get()
+ .withFilter(filter)
+ .withOrder(effectiveOrder)
+ .withMaxItems(maxItems)
+ .withSkipCount(skipCount)
+ .buildSelectItemsQuery()
+ .getResultList()
+ .stream()
+ .map(UserDevfileImpl::new)
+ .collect(toList());
+ return new Page<>(result, skipCount, maxItems, count);
+
+ } catch (RuntimeException x) {
+ throw new ServerException(x.getLocalizedMessage(), x);
+ }
+ }
+
+ @Override
+ @Transactional
+ public long getTotalCount() throws ServerException {
+ try {
+ return managerProvider
+ .get()
+ .createNamedQuery("UserDevfile.getTotalCount", Long.class)
+ .getSingleResult();
+ } catch (RuntimeException x) {
+ throw new ServerException(x.getLocalizedMessage(), x);
+ }
+ }
+
+ @Transactional
+ protected void doCreate(UserDevfileImpl devfile) {
+ final EntityManager manager = managerProvider.get();
+ manager.persist(devfile);
+ manager.flush();
+ }
+
+ @Transactional
+ protected Optional doUpdate(UserDevfileImpl update) {
+ final EntityManager manager = managerProvider.get();
+ if (manager.find(UserDevfileImpl.class, update.getId()) == null) {
+ return Optional.empty();
+ }
+ UserDevfileImpl merged = manager.merge(update);
+ manager.flush();
+ return Optional.of(merged);
+ }
+
+ @Transactional(rollbackOn = {RuntimeException.class, ServerException.class})
+ protected void doRemove(String id) throws ServerException {
+ final EntityManager manager = managerProvider.get();
+ final UserDevfileImpl devfile = manager.find(UserDevfileImpl.class, id);
+ if (devfile != null) {
+ eventService
+ .publish(new BeforeDevfileRemovedEvent(new UserDevfileImpl(devfile)))
+ .propagateException();
+ manager.remove(devfile);
+ manager.flush();
+ }
+ }
+
+ public static class UserDevfileSearchQueryBuilder {
+ protected EntityManager entityManager;
+ protected int maxItems;
+ protected int skipCount;
+ protected String filter;
+ protected Map params;
+ protected String order;
+
+ public UserDevfileSearchQueryBuilder(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ this.params = new HashMap<>();
+ this.filter = "";
+ this.order = "";
+ }
+
+ public static UserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) {
+ return new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(entityManager);
+ }
+
+ public UserDevfileSearchQueryBuilder withMaxItems(int maxItems) {
+ this.maxItems = maxItems;
+ return this;
+ }
+
+ public UserDevfileSearchQueryBuilder withSkipCount(int skipCount) {
+ this.skipCount = skipCount;
+ return this;
+ }
+
+ public UserDevfileSearchQueryBuilder withFilter(List> filter) {
+ if (filter == null || filter.isEmpty()) {
+ return this;
+ }
+ final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " ");
+ int i = 0;
+ for (Pair attribute : filter) {
+ if (!VALID_SEARCH_FIELDS.contains(attribute.first.toLowerCase())) {
+ throw new IllegalArgumentException(
+ "Filtering allowed only by " + VALID_SEARCH_FIELDS + " but got: " + attribute.first);
+ }
+ final String parameterName = "parameterName" + i++;
+ if (attribute.second.startsWith("like:")) {
+ params.put(parameterName, attribute.second.substring(5));
+ matcher.add("userdevfile." + attribute.first + " LIKE :" + parameterName);
+ } else {
+ params.put(parameterName, attribute.second);
+ matcher.add("userdevfile." + attribute.first + " = :" + parameterName);
+ }
+ }
+ this.filter = matcher.toString();
+ return this;
+ }
+
+ public UserDevfileSearchQueryBuilder withOrder(List> order) {
+ if (order == null || order.isEmpty()) {
+ return this;
+ }
+ final StringJoiner matcher = new StringJoiner(", ", " ORDER BY ", " ");
+ for (Pair pair : order) {
+ if (!VALID_ORDER_FIELDS.contains(pair.first.toLowerCase())) {
+ throw new IllegalArgumentException(
+ "Order allowed only by " + VALID_ORDER_FIELDS + " but got: " + pair.first);
+ }
+ matcher.add("userdevfile." + pair.first + " " + pair.second);
+ }
+ this.order = matcher.toString();
+
+ return this;
+ }
+
+ public TypedQuery buildCountQuery() {
+ StringBuilder query =
+ new StringBuilder()
+ .append("SELECT ")
+ .append(" COUNT(userdevfile) ")
+ .append("FROM UserDevfile userdevfile")
+ .append(filter);
+ TypedQuery typedQuery = entityManager.createQuery(query.toString(), Long.class);
+ params.forEach((k, v) -> typedQuery.setParameter(k, v));
+ return typedQuery;
+ }
+
+ public TypedQuery buildSelectItemsQuery() {
+
+ StringBuilder query =
+ new StringBuilder()
+ .append("SELECT ")
+ .append(" userdevfile ")
+ .append("FROM UserDevfile userdevfile")
+ .append(filter)
+ .append(order);
+ TypedQuery typedQuery =
+ entityManager
+ .createQuery(query.toString(), UserDevfileImpl.class)
+ .setFirstResult(skipCount)
+ .setMaxResults(maxItems);
+ params.forEach((k, v) -> typedQuery.setParameter(k, v));
+ return typedQuery;
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java
new file mode 100644
index 0000000000..e33ec933c9
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileJpaModule.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.jpa;
+
+import com.google.common.annotations.Beta;
+import com.google.inject.AbstractModule;
+import org.eclipse.che.api.devfile.server.RemoveUserDevfileBeforeAccountRemovedEventSubscriber;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+
+@Beta
+public class UserDevfileJpaModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(UserDevfileDao.class).to(JpaUserDevfileDao.class);
+ bind(RemoveUserDevfileBeforeAccountRemovedEventSubscriber.class).asEagerSingleton();
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java
new file mode 100644
index 0000000000..917bc8c933
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.model.impl;
+
+import com.google.common.annotations.Beta;
+import java.util.Objects;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import org.eclipse.che.account.shared.model.Account;
+import org.eclipse.che.account.spi.AccountImpl;
+import org.eclipse.che.api.core.model.workspace.devfile.Devfile;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl;
+
+@Entity(name = "UserDevfile")
+@Table(name = "userdevfile")
+@NamedQueries({
+ @NamedQuery(
+ name = "UserDevfile.getByNamespace",
+ query = "SELECT d FROM UserDevfile d WHERE d.account.name = :namespace"),
+ @NamedQuery(
+ name = "UserDevfile.getByNamespaceCount",
+ query = "SELECT COUNT(d) FROM UserDevfile d WHERE d.account.name = :namespace "),
+ @NamedQuery(name = "UserDevfile.getAll", query = "SELECT d FROM UserDevfile d ORDER BY d.id"),
+ @NamedQuery(name = "UserDevfile.getTotalCount", query = "SELECT COUNT(d) FROM UserDevfile d"),
+})
+@Beta
+public class UserDevfileImpl implements UserDevfile {
+
+ /**
+ * In {@MetadataImpl} name is mandatory and generateName is transient. That is not suitable for
+ * UserDevfile because we need to handle situations when the name is not defined and generateName
+ * is defined. To workaround that original name and generateName stored in individual fields
+ * meta_name and meta_generated_name. But at the same time, we can't leave metadata filed null in
+ * devfile because of database hard constrain. To replace that FAKE_META is used.
+ */
+ private static final MetadataImpl FAKE_META = new MetadataImpl("name");
+
+ @Id
+ @Column(name = "id", nullable = false)
+ private String id;
+
+ @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
+ @JoinColumn(name = "devfile_id")
+ private DevfileImpl devfile;
+
+ @Column(name = "meta_generated_name")
+ private String metaGeneratedName;
+
+ @Column(name = "meta_name")
+ private String metaName;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @Column(name = "description")
+ private String description;
+
+ @ManyToOne
+ @JoinColumn(name = "accountid", nullable = false)
+ private AccountImpl account;
+
+ public UserDevfileImpl() {}
+
+ public UserDevfileImpl(String id, Account account, UserDevfile userDevfile) {
+ this(
+ id, account, userDevfile.getName(), userDevfile.getDescription(), userDevfile.getDevfile());
+ }
+
+ public UserDevfileImpl(UserDevfile userDevfile, Account account) {
+ this(userDevfile.getId(), account, userDevfile);
+ }
+
+ public UserDevfileImpl(UserDevfileImpl userDevfile) {
+ this(
+ userDevfile.id,
+ userDevfile.account,
+ userDevfile.getName(),
+ userDevfile.getDescription(),
+ userDevfile.getDevfile());
+ }
+
+ public UserDevfileImpl(
+ String id, Account account, String name, String description, Devfile devfile) {
+ this.id = id;
+ this.account = new AccountImpl(account);
+ this.name = name;
+ this.description = description;
+ this.devfile = new DevfileImpl(devfile);
+ syncMeta();
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getNamespace() {
+ return account.getName();
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public Devfile getDevfile() {
+ return new DevfileImpl(
+ devfile.getApiVersion(),
+ devfile.getProjects(),
+ devfile.getComponents(),
+ devfile.getCommands(),
+ devfile.getAttributes(),
+ new MetadataImpl(metaName, metaGeneratedName));
+ }
+
+ public void setDevfile(DevfileImpl devfile) {
+ this.devfile = devfile;
+ syncMeta();
+ }
+
+ public AccountImpl getAccount() {
+ return account;
+ }
+
+ public void setAccount(AccountImpl account) {
+ this.account = account;
+ }
+
+ private void syncMeta() {
+ MetadataImpl metadata = devfile.getMetadata();
+ metaGeneratedName = metadata.getGenerateName();
+ metaName = metadata.getName();
+ devfile.setMetadata(FAKE_META);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ UserDevfileImpl that = (UserDevfileImpl) o;
+ return Objects.equals(id, that.id)
+ && Objects.equals(devfile, that.devfile)
+ && Objects.equals(metaGeneratedName, that.metaGeneratedName)
+ && Objects.equals(metaName, that.metaName)
+ && Objects.equals(name, that.name)
+ && Objects.equals(description, that.description)
+ && Objects.equals(account, that.account);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, devfile, metaGeneratedName, metaName, name, description, account);
+ }
+
+ @Override
+ public String toString() {
+ return "UserDevfileImpl{"
+ + "id='"
+ + id
+ + '\''
+ + ", devfile="
+ + devfile
+ + ", metaGeneratedName='"
+ + metaGeneratedName
+ + '\''
+ + ", metaName='"
+ + metaName
+ + '\''
+ + ", name='"
+ + name
+ + '\''
+ + ", description='"
+ + description
+ + '\''
+ + ", account="
+ + account
+ + '}';
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java
new file mode 100644
index 0000000000..13872581b9
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.spi;
+
+import com.google.common.annotations.Beta;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.che.api.core.ConflictException;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.core.Page;
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.commons.lang.Pair;
+
+/** Defines data access object contract for {@code UserDevfileImpl}. */
+@Beta
+public interface UserDevfileDao {
+
+ /**
+ * Creates Devfile.
+ *
+ * @param devfile devfile to create
+ * @return created devfile
+ * @throws NullPointerException when {@code devfile} is null
+ * @throws ServerException when any other error occurs
+ * @throws ConflictException when required namespace is not found.
+ */
+ UserDevfile create(UserDevfile devfile) throws ServerException, ConflictException;
+
+ /**
+ * Updates devfile to the new entity, using replacement strategy.
+ *
+ * @param devfile devfile to update
+ * @return updated devfile
+ * @throws NullPointerException when {@code devfile} is null
+ * @throws ConflictException when any conflict situation occurs
+ * @throws ServerException when any other error occurs
+ */
+ Optional update(UserDevfile devfile)
+ throws ConflictException, ServerException, NotFoundException;
+
+ /**
+ * Removes devfile.
+ *
+ * @param id devfile identifier
+ * @throws NullPointerException when {@code id} is null
+ * @throws ServerException when any other error occurs
+ */
+ void remove(String id) throws ServerException;
+
+ /**
+ * Gets devfile by identifier.
+ *
+ * @param id devfile identifier
+ * @return devfile instance, never null
+ * @throws NullPointerException when {@code id} is null
+ * @throws ServerException when any other error occurs
+ */
+ Optional getById(String id) throws ServerException;
+
+ /**
+ * Gets list of UserDevfiles in given namespace.
+ *
+ * @param namespace devfiles namespace
+ * @return list of devfiles in given namespace. Always returns list(even when there are no devfile
+ * in given namespace), never null
+ * @throws NullPointerException when {@code namespace} is null
+ * @throws ServerException when any other error occurs during workspaces fetching
+ */
+ Page getByNamespace(String namespace, int maxItems, long skipCount)
+ throws ServerException;
+
+ /**
+ * Gets all devfiles which user can read filtered by given parameters in a given order
+ *
+ * @param maxItems the maximum number of workspaces to return
+ * @param skipCount the number of workspaces to skip
+ * @param filter additional conditions for the desired devfiles. Conditions represented as pairs
+ * of the filed and the value. All pairs would be joined with AND condition. Value of
+ * the pair can start with 'like:' prefix. In this case would be used LIKE query,
+ * otherwise = condition.
+ * @param order - a list of fields and directions of sort. By default items would be sorted by id.
+ * @return list of devfiles which user can read, never null
+ * @throws ServerException when any other error occurs during devfile fetching
+ * @throws IllegalArgumentException when maxItems < 1 or skipCount < 0 or sort order is not 'asc'
+ * or 'desc'.
+ */
+ Page getDevfiles(
+ int maxItems,
+ int skipCount,
+ List> filter,
+ List> order)
+ throws ServerException;
+
+ /**
+ * Get the count of all user devfiles from the persistent layer.
+ *
+ * @return workspace count
+ * @throws ServerException when any error occurs
+ */
+ long getTotalCount() throws ServerException;
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java
new file mode 100644
index 0000000000..731a52f331
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceLinksInjectorTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.UriBuilder;
+import org.eclipse.che.api.core.rest.ServiceContext;
+import org.eclipse.che.api.devfile.shared.Constants;
+import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto;
+import org.eclipse.che.dto.server.DtoFactory;
+import org.everrest.core.impl.uri.UriBuilderImpl;
+import org.mockito.Mock;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(MockitoTestNGListener.class)
+public class DevfileServiceLinksInjectorTest {
+ private static final String URI_BASE = "http://localhost:8080";
+ private static final String SERVICE_PATH = "/devfile";
+
+ @Mock ServiceContext context;
+
+ @BeforeMethod
+ public void setUp() {
+ final UriBuilder uriBuilder = new UriBuilderImpl();
+ uriBuilder.uri(URI_BASE);
+
+ when(context.getBaseUriBuilder()).thenReturn(uriBuilder);
+ }
+
+ @Test
+ public void shouldInjectLinks() {
+ // given
+ final UserDevfileDto userDevfileDto = DtoFactory.newDto(UserDevfileDto.class).withId("id123");
+ DevfileServiceLinksInjector linksInjector = new DevfileServiceLinksInjector();
+ // when
+ final UserDevfileDto withLinks = linksInjector.injectLinks(userDevfileDto, context);
+ // then
+ assertEquals(withLinks.getLinks().size(), 1);
+ assertNotNull(withLinks.getLink(Constants.LINK_REL_SELF));
+ assertEquals(withLinks.getLinks().get(0).getMethod(), HttpMethod.GET);
+ assertEquals(withLinks.getLinks().get(0).getHref(), URI_BASE + SERVICE_PATH + "/id123");
+ assertEquals(withLinks.getLinks().get(0).getProduces(), MediaType.APPLICATION_JSON);
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java
new file mode 100644
index 0000000000..a7536ec564
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/DevfileServiceTest.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server;
+
+import static com.jayway.restassured.RestAssured.given;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_ACCOUNT;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_SUBJECT;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.USER_DEVFILE_ID;
+import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
+import static org.eclipse.che.dto.server.DtoFactory.newDto;
+import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME;
+import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD;
+import static org.everrest.assured.JettyHttpServer.SECURE_PATH;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.jayway.restassured.response.Response;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.core.Page;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.core.rest.ApiExceptionMapper;
+import org.eclipse.che.api.core.rest.CheJsonProvider;
+import org.eclipse.che.api.core.rest.ServiceContext;
+import org.eclipse.che.api.core.rest.WebApplicationExceptionMapper;
+import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto;
+import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
+import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
+import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
+import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator;
+import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
+import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto;
+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.dto.server.DtoFactory;
+import org.everrest.assured.EverrestJetty;
+import org.everrest.core.Filter;
+import org.everrest.core.GenericContainerRequest;
+import org.everrest.core.RequestFilter;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners({EverrestJetty.class, MockitoTestNGListener.class})
+public class DevfileServiceTest {
+
+ @SuppressWarnings("unused") // is declared for deploying by everrest-assured
+ ApiExceptionMapper exceptionMapper = new ApiExceptionMapper();
+
+ WebApplicationExceptionMapper exceptionMapper2 = new WebApplicationExceptionMapper();
+
+ private DevfileSchemaProvider schemaProvider = new DevfileSchemaProvider();
+
+ private static final EnvironmentFilter FILTER = new EnvironmentFilter();
+
+ private DevfileParser devfileParser =
+ new DevfileParser(
+ new DevfileSchemaValidator(new DevfileSchemaProvider()),
+ new DevfileIntegrityValidator(Collections.emptyMap()));
+ DevfileEntityProvider devfileEntityProvider = new DevfileEntityProvider(devfileParser);
+ UserDevfileEntityProvider userDevfileEntityProvider =
+ new UserDevfileEntityProvider(devfileParser);
+ private CheJsonProvider jsonProvider = new CheJsonProvider(new HashSet<>());
+
+ @Mock UserDevfileDao userDevfileDao;
+ @Mock UserDevfileManager userDevfileManager;
+ @Mock EventService eventService;
+ @Mock DevfileServiceLinksInjector linksInjector;
+
+ DevfileService userDevfileService;
+
+ @Test
+ public void shouldRetrieveSchema() throws Exception {
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .when()
+ .get(SECURE_PATH + "/devfile");
+
+ assertEquals(response.getStatusCode(), 200);
+ assertEquals(
+ response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION));
+ }
+
+ @BeforeMethod
+ public void setup() {
+ this.userDevfileService = new DevfileService(schemaProvider, userDevfileManager, linksInjector);
+ lenient()
+ .when(linksInjector.injectLinks(any(UserDevfileDto.class), any(ServiceContext.class)))
+ .thenAnswer((Answer) invocation -> invocation.getArgument(0));
+ }
+
+ @Test(dataProvider = "validUserDevfiles")
+ public void shouldCreateUserDevfileFromJson(UserDevfileDto userDevfileDto) throws Exception {
+ final UserDevfileImpl userDevfileImpl =
+ new UserDevfileImpl("id-123123", TEST_ACCOUNT, userDevfileDto);
+
+ when(userDevfileManager.createDevfile(any(UserDevfile.class))).thenReturn(userDevfileImpl);
+
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .body(DtoFactory.getInstance().toJson(userDevfileDto))
+ .when()
+ .post(SECURE_PATH + "/devfile");
+
+ assertEquals(response.getStatusCode(), 201);
+ UserDevfileDto dto = unwrapDto(response, UserDevfileDto.class);
+ assertEquals(dto.getNamespace(), TEST_ACCOUNT.getName());
+ assertEquals(new UserDevfileImpl(dto, TEST_ACCOUNT), userDevfileImpl);
+ verify(userDevfileManager).createDevfile(any(UserDevfile.class));
+ }
+
+ @Test(dataProvider = "invalidUserDevfiles")
+ public void shouldFailToCreateInvalidUserDevfileFromJson(
+ UserDevfileDto userDevfileDto, String expectedErrorMessage) throws Exception {
+
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .body(DtoFactory.getInstance().toJson(userDevfileDto))
+ .when()
+ .post(SECURE_PATH + "/devfile");
+
+ assertEquals(response.getStatusCode(), 400);
+ ServiceError error = unwrapDto(response, ServiceError.class);
+ assertNotNull(error);
+ assertEquals(error.getMessage(), expectedErrorMessage);
+ verifyNoMoreInteractions(userDevfileManager);
+ }
+
+ @Test
+ public void shouldGetUserDevfileById() throws Exception {
+ final UserDevfileImpl userDevfile = TestObjectGenerator.createUserDevfile();
+ when(userDevfileManager.getById(eq("id-22323"))).thenReturn(userDevfile);
+
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .when()
+ .expect()
+ .statusCode(200)
+ .get(SECURE_PATH + "/devfile/id-22323");
+
+ assertEquals(
+ new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT), userDevfile);
+ verify(userDevfileManager).getById(eq("id-22323"));
+ verify(linksInjector).injectLinks(any(), any());
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenUserDevfileIsNotExistOnGetById() throws Exception {
+
+ final String errMessage = format("UserDevfile with id %s is not found", USER_DEVFILE_ID);
+ doThrow(new NotFoundException(errMessage)).when(userDevfileManager).getById(anyString());
+
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .expect()
+ .statusCode(404)
+ .when()
+ .get(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID);
+
+ assertEquals(unwrapDto(response, ServiceError.class).getMessage(), errMessage);
+ }
+
+ @Test
+ public void shouldThrowNotFoundExceptionWhenUpdatingNonExistingUserDevfile() throws Exception {
+ // given
+ final UserDevfile userDevfile =
+ DtoConverter.asDto(TestObjectGenerator.createUserDevfile("devfile-name"));
+
+ doThrow(new NotFoundException(format("User devfile with id %s is not found.", USER_DEVFILE_ID)))
+ .when(userDevfileManager)
+ .updateUserDevfile(any(UserDevfile.class));
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType(APPLICATION_JSON)
+ .body(DtoFactory.getInstance().toJson(userDevfile))
+ .when()
+ .put(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID);
+ // then
+ assertEquals(response.getStatusCode(), 404);
+ assertEquals(
+ unwrapDto(response, ServiceError.class).getMessage(),
+ format("User devfile with id %s is not found.", USER_DEVFILE_ID));
+ }
+
+ @Test
+ public void shouldBeAbleToUpdateUserDevfile() throws Exception {
+ // given
+ final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto();
+ final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT);
+ when(userDevfileManager.updateUserDevfile(any(UserDevfile.class))).thenReturn(userDevfileImpl);
+
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType(APPLICATION_JSON)
+ .body(DtoFactory.getInstance().toJson(devfileDto))
+ .when()
+ .put(SECURE_PATH + "/devfile/" + devfileDto.getId());
+ // then
+ assertEquals(response.getStatusCode(), 200);
+ assertEquals(
+ new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT),
+ userDevfileImpl);
+ verify(userDevfileManager).updateUserDevfile(devfileDto);
+ verify(linksInjector).injectLinks(any(), any());
+ }
+
+ @Test(dataProvider = "invalidUserDevfiles")
+ public void shouldFailToUpdateWithInvalidUserDevfile(
+ UserDevfileDto userDevfileDto, String expectedErrorMessage) throws Exception {
+ // given
+ userDevfileDto = userDevfileDto.withId("id-123");
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType(APPLICATION_JSON)
+ .body(DtoFactory.getInstance().toJson(userDevfileDto))
+ .when()
+ .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId());
+ // then
+ assertEquals(response.getStatusCode(), 400);
+ ServiceError error = unwrapDto(response, ServiceError.class);
+ assertNotNull(error);
+ assertEquals(error.getMessage(), expectedErrorMessage);
+ verifyZeroInteractions(userDevfileManager);
+ verifyZeroInteractions(linksInjector);
+ }
+
+ @Test
+ public void shouldOverrideIdOnUpdateUserDevfile() throws Exception {
+ // given
+ final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto();
+ final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT);
+
+ final String newID = NameGenerator.generate("id", 24);
+ final UserDevfileImpl expectedUserDevfileImpl =
+ new UserDevfileImpl(newID, TEST_ACCOUNT, userDevfileImpl);
+ final UserDevfileDto expectedDto =
+ org.eclipse.che.api.devfile.server.DtoConverter.asDto(expectedUserDevfileImpl);
+ when(userDevfileManager.updateUserDevfile(any(UserDevfile.class)))
+ .thenReturn(expectedUserDevfileImpl);
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType(APPLICATION_JSON)
+ .body(DtoFactory.getInstance().toJson(devfileDto))
+ .when()
+ .put(SECURE_PATH + "/devfile/" + newID);
+ // then
+ assertEquals(response.getStatusCode(), 200);
+ assertEquals(
+ new UserDevfileImpl(unwrapDto(response, UserDevfileDto.class), TEST_ACCOUNT),
+ expectedUserDevfileImpl);
+ verify(userDevfileManager).updateUserDevfile(expectedDto);
+ verify(linksInjector).injectLinks(any(), any());
+ }
+
+ @Test
+ public void shouldRemoveUserDevfileByGivenIdentifier() throws Exception {
+ // given
+ // when
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .expect()
+ .statusCode(204)
+ .when()
+ .delete(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID);
+ // then
+ verify(userDevfileManager).removeUserDevfile(USER_DEVFILE_ID);
+ }
+
+ @Test
+ public void shouldNotThrowAnyExceptionWhenRemovingNonExistingUserDevfile() throws Exception {
+ // given
+ Mockito.doNothing().when(userDevfileManager).removeUserDevfile(anyString());
+ // when
+ Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .when()
+ .delete(SECURE_PATH + "/devfile/" + USER_DEVFILE_ID);
+ // then
+ assertEquals(response.getStatusCode(), 204);
+ }
+
+ @Test
+ public void shouldGetUserDevfilesAvailableToUser() throws Exception {
+ // given
+ final UserDevfileDto devfileDto = TestObjectGenerator.createUserDevfileDto();
+ final UserDevfileImpl userDevfileImpl = new UserDevfileImpl(devfileDto, TEST_ACCOUNT);
+ doReturn(new Page<>(ImmutableList.of(userDevfileImpl), 0, 1, 1))
+ .when(userDevfileManager)
+ .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList());
+
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .when()
+ .expect()
+ .statusCode(200)
+ .get(SECURE_PATH + "/devfile/search");
+ // then
+ final List res = unwrapDtoList(response, UserDevfileDto.class);
+ assertEquals(res.size(), 1);
+ assertEquals(res.get(0).withLinks(emptyList()), devfileDto);
+ verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), anyList(), anyList());
+ }
+
+ @Test
+ public void shouldBeAbleToSetLimitAndOffsetOnUserDevfileSearch() throws Exception {
+ // given
+ doReturn(new Page<>(Collections.emptyList(), 0, 1, 0))
+ .when(userDevfileManager)
+ .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList());
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .queryParam("maxItems", 5)
+ .queryParam("skipCount", 52)
+ .when()
+ .expect()
+ .statusCode(200)
+ .get(SECURE_PATH + "/devfile/search");
+ // then
+ verify(userDevfileManager).getUserDevfiles(eq(5), eq(52), anyList(), anyList());
+ }
+
+ @Test
+ public void shouldBeAbleToSetFiltertOnUserDevfileSearch() throws Exception {
+ // given
+ doReturn(new Page<>(Collections.emptyList(), 0, 1, 0))
+ .when(userDevfileManager)
+ .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList());
+ Map parameters =
+ ImmutableMap.of("id", "sdfsdf5", "devfile.meta.name", "like:%dfdf");
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .queryParameter("id", "sdfsdf5")
+ .queryParameter("devfile.meta.name", "like:%dfdf")
+ .when()
+ .expect()
+ .statusCode(200)
+ .get(SECURE_PATH + "/devfile/search");
+ // then
+ Class>> listClass =
+ (Class>>) (Class) ArrayList.class;
+ ArgumentCaptor>> filterCaptor = ArgumentCaptor.forClass(listClass);
+ verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), filterCaptor.capture(), anyList());
+ assertEquals(
+ filterCaptor.getValue(),
+ ImmutableList.of(new Pair("devfile.meta.name", "like:%dfdf"), new Pair("id", "sdfsdf5")));
+ }
+
+ @Test
+ public void shouldBeAbleToSetOrderOnUserDevfileSearch() throws Exception {
+ // given
+ doReturn(new Page<>(Collections.emptyList(), 0, 1, 0))
+ .when(userDevfileManager)
+ .getUserDevfiles(anyInt(), anyInt(), anyList(), anyList());
+ // when
+ final Response response =
+ given()
+ .auth()
+ .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
+ .contentType("application/json")
+ .queryParameter("order", "id:asc,name:desc")
+ .when()
+ .expect()
+ .statusCode(200)
+ .get(SECURE_PATH + "/devfile/search");
+ // then
+ Class>> listClass =
+ (Class>>) (Class) ArrayList.class;
+ ArgumentCaptor>> orderCaptor = ArgumentCaptor.forClass(listClass);
+ verify(userDevfileManager).getUserDevfiles(eq(30), eq(0), anyList(), orderCaptor.capture());
+ assertEquals(
+ orderCaptor.getValue(), ImmutableList.of(new Pair("id", "asc"), new Pair("name", "desc")));
+ }
+
+ @DataProvider
+ public Object[][] validUserDevfiles() {
+ return new Object[][] {
+ {
+ newDto(UserDevfileDto.class)
+ .withName("My devfile")
+ .withDescription("Devfile description")
+ .withDevfile(
+ newDto(DevfileDto.class)
+ .withApiVersion("1.0.0")
+ .withMetadata(newDto(MetadataDto.class).withName("name")))
+ },
+ {
+ newDto(UserDevfileDto.class)
+ .withName(null)
+ .withDescription("Devfile description")
+ .withDevfile(
+ newDto(DevfileDto.class)
+ .withApiVersion("1.0.0")
+ .withMetadata(newDto(MetadataDto.class).withName("name")))
+ },
+ {
+ newDto(UserDevfileDto.class)
+ .withName("My devfile")
+ .withDevfile(
+ newDto(DevfileDto.class)
+ .withApiVersion("1.0.0")
+ .withMetadata(newDto(MetadataDto.class).withName("name")))
+ },
+ {
+ newDto(UserDevfileDto.class)
+ .withName("My devfile")
+ .withDescription("Devfile description")
+ .withDevfile(
+ newDto(DevfileDto.class)
+ .withApiVersion("1.0.0")
+ .withMetadata(newDto(MetadataDto.class).withGenerateName("gen-")))
+ },
+ {DtoConverter.asDto(TestObjectGenerator.createUserDevfile())}
+ };
+ }
+
+ @DataProvider
+ public Object[][] invalidUserDevfiles() {
+ return new Object[][] {
+ {
+ newDto(UserDevfileDto.class)
+ .withName("My devfile")
+ .withDescription("Devfile description")
+ .withDevfile(null),
+ "Mandatory field `devfile` is not defined."
+ },
+ {
+ newDto(UserDevfileDto.class)
+ .withName("My devfile")
+ .withDescription("Devfile description")
+ .withDevfile(
+ newDto(DevfileDto.class)
+ .withApiVersion(null)
+ .withMetadata(newDto(MetadataDto.class).withName("name"))),
+ "Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\"."
+ }
+ };
+ }
+
+ private static T unwrapDto(Response response, Class dtoClass) {
+ return DtoFactory.getInstance().createDtoFromJson(response.asString(), dtoClass);
+ }
+
+ private static List unwrapDtoList(Response response, Class dtoClass)
+ throws IOException {
+ return new ArrayList<>(
+ DtoFactory.getInstance().createListDtoFromJson(response.body().asInputStream(), dtoClass));
+ }
+
+ @Filter
+ public static class EnvironmentFilter implements RequestFilter {
+
+ public void doFilter(GenericContainerRequest request) {
+ EnvironmentContext.getCurrent().setSubject(TEST_SUBJECT);
+ }
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java
new file mode 100644
index 0000000000..0971126025
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/TestObjectGenerator.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableMap;
+import org.eclipse.che.account.shared.model.Account;
+import org.eclipse.che.account.spi.AccountImpl;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl;
+import org.eclipse.che.commons.lang.NameGenerator;
+import org.eclipse.che.commons.subject.Subject;
+import org.eclipse.che.commons.subject.SubjectImpl;
+
+public class TestObjectGenerator {
+
+ public static final String TEST_CHE_NAMESPACE = "user";
+ public static final String CURRENT_USER_ID = NameGenerator.generate("usrid", 6);
+ public static final Subject TEST_SUBJECT =
+ new SubjectImpl(TEST_CHE_NAMESPACE, CURRENT_USER_ID, "token", false);
+ public static final String USER_DEVFILE_ID = NameGenerator.generate("usrd", 16);
+ public static final AccountImpl TEST_ACCOUNT =
+ new AccountImpl("acc-id042u3ui3oi", TEST_CHE_NAMESPACE, "test");
+
+ public static UserDevfileDto createUserDevfileDto() {
+ return DtoConverter.asDto(createUserDevfile(NameGenerator.generate("name", 6)));
+ }
+
+ public static UserDevfileImpl createUserDevfile() {
+ return createUserDevfile(NameGenerator.generate("name", 6));
+ }
+
+ public static UserDevfileImpl createUserDevfile(String name) {
+ return createUserDevfile(NameGenerator.generate("id", 6), name);
+ }
+
+ public static UserDevfileImpl createUserDevfile(String id, String name) {
+ return new UserDevfileImpl(id, TEST_ACCOUNT, name, "devfile description", createDevfile(name));
+ }
+
+ public static UserDevfileImpl createUserDevfile(String id, Account account, String name) {
+ return new UserDevfileImpl(id, account, name, "devfile description", createDevfile(name));
+ }
+
+ public static UserDevfileImpl createUserDevfile(Account account) {
+ return createUserDevfile(
+ NameGenerator.generate("id", 6), account, NameGenerator.generate("name", 6));
+ }
+
+ public static DevfileImpl createDevfile(String generatedName) {
+ return createDevfile(null, generatedName);
+ }
+
+ public static DevfileImpl createDevfileWithName(String name) {
+ return createDevfile(name, null);
+ }
+
+ private static DevfileImpl createDevfile(String name, String generatedName) {
+ String effectiveName = MoreObjects.firstNonNull(name, generatedName);
+ SourceImpl source1 =
+ new SourceImpl(
+ "type1",
+ "http://location",
+ "branch1",
+ "point1",
+ "tag1",
+ "commit1",
+ "sparseCheckoutDir1");
+ ProjectImpl project1 = new ProjectImpl("project1", source1, "path1");
+
+ SourceImpl source2 =
+ new SourceImpl(
+ "type2",
+ "http://location",
+ "branch2",
+ "point2",
+ "tag2",
+ "commit2",
+ "sparseCheckoutDir2");
+ ProjectImpl project2 = new ProjectImpl("project2", source2, "path2");
+
+ ActionImpl action1 =
+ new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null);
+ ActionImpl action2 =
+ new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null);
+
+ CommandImpl command1 =
+ new CommandImpl(
+ effectiveName + "-1", singletonList(action1), singletonMap("attr1", "value1"), null);
+ CommandImpl command2 =
+ new CommandImpl(
+ effectiveName + "-2", singletonList(action2), singletonMap("attr2", "value2"), null);
+
+ EntrypointImpl entrypoint1 =
+ new EntrypointImpl(
+ "parentName1",
+ singletonMap("parent1", "selector1"),
+ "containerName1",
+ asList("command1", "command2"),
+ asList("arg1", "arg2"));
+
+ EntrypointImpl entrypoint2 =
+ new EntrypointImpl(
+ "parentName2",
+ singletonMap("parent2", "selector2"),
+ "containerName2",
+ asList("command3", "command4"),
+ asList("arg3", "arg4"));
+
+ org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 =
+ new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1");
+
+ org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 =
+ new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2");
+
+ EnvImpl env1 = new EnvImpl("name1", "value1");
+ EnvImpl env2 = new EnvImpl("name2", "value2");
+
+ EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1"));
+ EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2"));
+
+ ComponentImpl component1 =
+ new ComponentImpl(
+ "kubernetes",
+ "component1",
+ null,
+ null,
+ null,
+ null,
+ "refcontent1",
+ ImmutableMap.of("app.kubernetes.io/component", "db"),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ false,
+ false,
+ null,
+ null,
+ null,
+ asList(env1, env2),
+ null);
+ component1.setSelector(singletonMap("key1", "value1"));
+
+ ComponentImpl component2 =
+ new ComponentImpl(
+ "dockerimage",
+ "component2",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "image",
+ "256G",
+ null,
+ "3",
+ "180m",
+ false,
+ false,
+ singletonList("command"),
+ singletonList("arg"),
+ asList(volume1, volume2),
+ asList(env1, env2),
+ asList(endpoint1, endpoint2));
+ ComponentImpl component3 =
+ new ComponentImpl(
+ "chePlugin",
+ "check/terminal-sample/0.0.1",
+ ImmutableMap.of(
+ "java.home",
+ "/home/user/jdk11aertwertert",
+ "java.boolean",
+ "true",
+ "java.long",
+ "123444L"));
+ MetadataImpl metadata = new MetadataImpl(name);
+ metadata.setGenerateName(generatedName);
+ DevfileImpl devfile =
+ new DevfileImpl(
+ "1.0.0",
+ asList(project1, project2),
+ asList(component1, component2, component3),
+ asList(command1, command2),
+ singletonMap("attribute1", "value1"),
+ metadata);
+
+ return devfile;
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java
new file mode 100644
index 0000000000..5a95f5c322
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.util.Arrays.asList;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_ACCOUNT;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.TEST_CHE_NAMESPACE;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.createUserDevfile;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+
+import java.util.Collections;
+import java.util.Optional;
+import org.eclipse.che.account.api.AccountManager;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.core.Page;
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent;
+import org.eclipse.che.api.devfile.shared.event.DevfileDeletedEvent;
+import org.eclipse.che.api.devfile.shared.event.DevfileUpdatedEvent;
+import org.eclipse.che.commons.env.EnvironmentContext;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(value = MockitoTestNGListener.class)
+public class UserDevfileManagerTest {
+ @Mock UserDevfileDao userDevfileDao;
+ @Mock EventService eventService;
+ @Mock AccountManager accountManager;
+ @InjectMocks UserDevfileManager userDevfileManager;
+
+ @Captor private ArgumentCaptor userDevfileArgumentCaptor;
+ @Captor private ArgumentCaptor devfileCreatedEventCaptor;
+ @Captor private ArgumentCaptor devfileDeletedEventCaptor;
+ @Captor private ArgumentCaptor devfileUpdatedEventCaptor;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ EnvironmentContext.getCurrent().setSubject(TestObjectGenerator.TEST_SUBJECT);
+ lenient().doReturn(TEST_ACCOUNT).when(accountManager).getByName(eq(TEST_CHE_NAMESPACE));
+ }
+
+ @Test
+ public void shouldGenerateUserDevfileIdOnCreation() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile =
+ new UserDevfileImpl(null, TEST_ACCOUNT, createUserDevfile());
+ when(userDevfileDao.create(any(UserDevfileImpl.class)))
+ .thenAnswer(invocationOnMock -> invocationOnMock.getArguments()[0]);
+ // when
+ UserDevfile actual = userDevfileManager.createDevfile(userDevfile);
+ // then
+ verify(userDevfileDao).create(userDevfileArgumentCaptor.capture());
+ assertFalse(isNullOrEmpty(userDevfileArgumentCaptor.getValue().getId()));
+ assertEquals(new UserDevfileImpl(null, TEST_ACCOUNT, actual), userDevfile);
+ }
+
+ @Test
+ public void shouldSendDevfileCreatedEventOnCreation() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile =
+ new UserDevfileImpl(null, TEST_ACCOUNT, createUserDevfile());
+ when(userDevfileDao.create(any(UserDevfileImpl.class)))
+ .thenAnswer(invocationOnMock -> invocationOnMock.getArguments()[0]);
+ // when
+ UserDevfile expected = userDevfileManager.createDevfile(userDevfile);
+ // then
+ verify(eventService).publish(devfileCreatedEventCaptor.capture());
+ assertEquals(expected, devfileCreatedEventCaptor.getValue().getUserDevfile());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenGettingUserDevfileByNullId() throws Exception {
+ userDevfileManager.getById(null);
+ }
+
+ @Test
+ public void shouldGetUserDevfileById() throws Exception {
+ // given
+ final Optional toFetch = Optional.of(createUserDevfile());
+ when(userDevfileDao.getById(eq("id123"))).thenReturn(toFetch);
+
+ // when
+ final UserDevfile fetched = userDevfileManager.getById("id123");
+ // then
+ assertEquals(fetched, toFetch.get());
+ verify(userDevfileDao).getById("id123");
+ }
+
+ @Test(
+ expectedExceptions = NotFoundException.class,
+ expectedExceptionsMessageRegExp = "Devfile with id 'id123' doesn't exist")
+ public void shouldThrowNotFoundExceptionOnGetUserDevfileByIdIfNotFound() throws Exception {
+ // given
+ doReturn(Optional.empty()).when(userDevfileDao).getById(eq("id123"));
+ // when
+ userDevfileManager.getById("id123");
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenUpdatingUserDevfileByNullId() throws Exception {
+ userDevfileManager.updateUserDevfile(null);
+ }
+
+ @Test
+ public void shouldUpdateUserDevfile() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile = createUserDevfile();
+ when(userDevfileDao.update(any(UserDevfileImpl.class)))
+ .thenAnswer(invocationOnMock -> Optional.of(invocationOnMock.getArguments()[0]));
+ // when
+ userDevfileManager.updateUserDevfile(userDevfile);
+ // then
+ verify(userDevfileDao).update(eq(userDevfile));
+ }
+
+ @Test(expectedExceptions = NotFoundException.class)
+ public void shouldThrowNotFoundIfUserDevfileIsNotFoundOnUpdate() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile = createUserDevfile();
+ Mockito.doReturn(Optional.empty()).when(userDevfileDao).update(any(UserDevfileImpl.class));
+ // when
+ userDevfileManager.updateUserDevfile(userDevfile);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhendeleteUserDevfileByNullId() throws Exception {
+ userDevfileManager.removeUserDevfile(null);
+ }
+
+ @Test
+ public void shouldRemoveUserDevfile() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile = createUserDevfile();
+ // when
+ userDevfileManager.removeUserDevfile(userDevfile.getId());
+ // then
+ verify(userDevfileDao).remove(userDevfile.getId());
+ }
+
+ @Test
+ public void shouldSendDevfileUpdatedEventOnUpdateDevfile() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile = createUserDevfile();
+ when(userDevfileDao.update(any(UserDevfileImpl.class)))
+ .thenAnswer(invocationOnMock -> Optional.of(invocationOnMock.getArguments()[0]));
+ // when
+ userDevfileManager.updateUserDevfile(userDevfile);
+ // then
+ verify(eventService).publish(devfileUpdatedEventCaptor.capture());
+ assertEquals(userDevfile, devfileUpdatedEventCaptor.getValue().getUserDevfile());
+ }
+
+ @Test
+ public void shouldBeAbleToGetUserDevfilesAvailableToUser() throws ServerException {
+ // given
+ final UserDevfileImpl userDevfile = createUserDevfile();
+ final UserDevfileImpl userDevfile2 = createUserDevfile();
+ when(userDevfileDao.getDevfiles(2, 30, Collections.emptyList(), Collections.emptyList()))
+ .thenReturn(new Page<>(asList(userDevfile, userDevfile2), 0, 2, 2));
+ // when
+ Page actual =
+ userDevfileManager.getUserDevfiles(2, 30, Collections.emptyList(), Collections.emptyList());
+ // then
+ assertEquals(actual.getItems().size(), 2);
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java
new file mode 100644
index 0000000000..02ea85656a
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/jpa/UserDevfileTckModule.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.jpa;
+
+import com.google.inject.TypeLiteral;
+import org.eclipse.che.account.spi.AccountDao;
+import org.eclipse.che.account.spi.AccountImpl;
+import org.eclipse.che.account.spi.jpa.JpaAccountDao;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+import org.eclipse.che.api.user.server.model.impl.UserImpl;
+import org.eclipse.che.api.workspace.server.devfile.SerializableConverter;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl;
+import org.eclipse.che.commons.test.db.H2DBTestServer;
+import org.eclipse.che.commons.test.db.H2JpaCleaner;
+import org.eclipse.che.commons.test.db.PersistTestModuleBuilder;
+import org.eclipse.che.commons.test.tck.TckModule;
+import org.eclipse.che.commons.test.tck.TckResourcesCleaner;
+import org.eclipse.che.commons.test.tck.repository.JpaTckRepository;
+import org.eclipse.che.commons.test.tck.repository.TckRepository;
+import org.eclipse.che.core.db.DBInitializer;
+import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler;
+import org.eclipse.che.core.db.schema.SchemaInitializer;
+import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer;
+import org.h2.Driver;
+
+/** Tck module for UserDevfile test. */
+public class UserDevfileTckModule extends TckModule {
+
+ @Override
+ protected void configure() {
+ H2DBTestServer server = H2DBTestServer.startDefault();
+ install(
+ new PersistTestModuleBuilder()
+ .setDriver(Driver.class)
+ .runningOn(server)
+ .addEntityClasses(
+ UserImpl.class,
+ AccountImpl.class,
+ UserDevfileImpl.class,
+ DevfileImpl.class,
+ ActionImpl.class,
+ CommandImpl.class,
+ ComponentImpl.class,
+ DevfileImpl.class,
+ EndpointImpl.class,
+ EntrypointImpl.class,
+ EnvImpl.class,
+ ProjectImpl.class,
+ SourceImpl.class,
+ VolumeImpl.class)
+ .addClass(SerializableConverter.class)
+ .setExceptionHandler(H2ExceptionHandler.class)
+ .setProperty("eclipselink.logging.level", "OFF")
+ .build());
+ bind(DBInitializer.class).asEagerSingleton();
+ bind(SchemaInitializer.class)
+ .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema"));
+ bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server));
+
+ bind(UserDevfileDao.class).to(JpaUserDevfileDao.class);
+ bind(AccountDao.class).to(JpaAccountDao.class);
+
+ bind(new TypeLiteral>() {})
+ .toInstance(new JpaTckRepository<>(UserDevfileImpl.class));
+ bind(new TypeLiteral>() {})
+ .toInstance(new JpaTckRepository<>(UserImpl.class));
+ bind(new TypeLiteral>() {})
+ .toInstance(new JpaTckRepository<>(AccountImpl.class));
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java
new file mode 100644
index 0000000000..486f944fa4
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2012-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.devfile.server.spi.tck;
+
+import static java.lang.Math.min;
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.eclipse.che.api.devfile.server.TestObjectGenerator.createUserDevfile;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.persistence.EntityManager;
+import org.eclipse.che.account.spi.AccountImpl;
+import org.eclipse.che.api.core.ConflictException;
+import org.eclipse.che.api.core.NotFoundException;
+import org.eclipse.che.api.core.Page;
+import org.eclipse.che.api.core.ServerException;
+import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile;
+import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.devfile.server.TestObjectGenerator;
+import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent;
+import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao;
+import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl;
+import org.eclipse.che.api.devfile.server.spi.UserDevfileDao;
+import org.eclipse.che.api.user.server.model.impl.UserImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl;
+import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl;
+import org.eclipse.che.commons.lang.NameGenerator;
+import org.eclipse.che.commons.lang.Pair;
+import org.eclipse.che.commons.test.tck.TckListener;
+import org.eclipse.che.commons.test.tck.repository.TckRepository;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+@Listeners(TckListener.class)
+@Test(suiteName = UserDevfileDaoTest.SUITE_NAME)
+public class UserDevfileDaoTest {
+
+ public static final String SUITE_NAME = "DevfileDaoTck";
+ private static final int ENTRY_COUNT = 10;
+ private static final int COUNT_OF_ACCOUNTS = 6;
+
+ private UserDevfileImpl[] devfiles;
+ private AccountImpl[] accounts;
+
+ @Inject private EventService eventService;
+
+ @Inject private UserDevfileDao userDevfileDaoDao;
+
+ @Inject private TckRepository devfileTckRepository;
+
+ @Inject private TckRepository userTckRepository;
+
+ @Inject private TckRepository accountRepo;
+
+ @Inject private Provider entityManagerProvider;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ accounts = new AccountImpl[COUNT_OF_ACCOUNTS];
+ for (int i = 0; i < COUNT_OF_ACCOUNTS; i++) {
+ accounts[i] = new AccountImpl("accountId" + i, "accountName" + i, "test");
+ }
+
+ devfiles = new UserDevfileImpl[ENTRY_COUNT];
+ for (int i = 0; i < ENTRY_COUNT; i++) {
+ AccountImpl account = accounts[i / 2];
+ devfiles[i] =
+ createUserDevfile(
+ NameGenerator.generate("id-" + i + "-", 6),
+ account,
+ NameGenerator.generate("devfileName-" + i, 6));
+ }
+ accountRepo.createAll(Stream.of(accounts).map(AccountImpl::new).collect(toList()));
+ devfileTckRepository.createAll(Stream.of(devfiles).map(UserDevfileImpl::new).collect(toList()));
+ }
+
+ @AfterMethod
+ public void cleanUp() throws Exception {
+ devfileTckRepository.removeAll();
+ accountRepo.removeAll();
+ }
+
+ @Test
+ public void shouldGetUserDevfileById() throws Exception {
+ final UserDevfileImpl devfile = devfiles[0];
+
+ assertEquals(userDevfileDaoDao.getById(devfile.getId()), Optional.of(devfile));
+ }
+
+ @Test(dependsOnMethods = "shouldGetUserDevfileById")
+ public void shouldCreateUserDevfile() throws Exception {
+ // given
+ final UserDevfileImpl devfile = createUserDevfile(accounts[0]);
+ // when
+ userDevfileDaoDao.create(devfile);
+
+ assertEquals(
+ userDevfileDaoDao.getById(devfile.getId()), Optional.of(new UserDevfileImpl(devfile)));
+ }
+
+ @Test
+ public void shouldCreateUserDevfileWithNullDescription() throws Exception {
+ // given
+ final UserDevfileImpl devfile = createUserDevfile(accounts[0]);
+ devfile.setDescription(null);
+ // when
+ userDevfileDaoDao.create(devfile);
+
+ Optional devfileOptional = userDevfileDaoDao.getById(devfile.getId());
+ assertTrue(devfileOptional.isPresent());
+ assertNull(devfileOptional.get().getDescription());
+ assertEquals(devfileOptional, Optional.of(new UserDevfileImpl(devfile)));
+ }
+
+ @Test
+ public void shouldCreateUserDevfileWithEmptyMataName() throws Exception {
+ // given
+ final UserDevfileImpl devfile = createUserDevfile(accounts[0]);
+ DevfileImpl newDevfile = new DevfileImpl(devfile.getDevfile());
+ MetadataImpl newMeta = new MetadataImpl();
+ newMeta.setGenerateName("gener-");
+ newDevfile.setMetadata(newMeta);
+ devfile.setDevfile(newDevfile);
+ // when
+ userDevfileDaoDao.create(devfile);
+
+ Optional devfileOptional = userDevfileDaoDao.getById(devfile.getId());
+ assertTrue(devfileOptional.isPresent());
+ UserDevfile actual = devfileOptional.get();
+ assertNull(actual.getDevfile().getMetadata().getName());
+ assertNotNull(actual.getDevfile().getMetadata().getGenerateName());
+ assertEquals(devfileOptional, Optional.of(new UserDevfileImpl(devfile)));
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenCreateNullDevfile() throws Exception {
+ userDevfileDaoDao.create(null);
+ }
+
+ @Test(expectedExceptions = ConflictException.class)
+ public void shouldThrowConflictExceptionWhenCreatingUserDevfileWithExistingId() throws Exception {
+ // given
+ final UserDevfileImpl devfile = createUserDevfile(accounts[0]);
+ final UserDevfileImpl existing = devfiles[0];
+ devfile.setId(existing.getId());
+ // when
+ userDevfileDaoDao.create(devfile);
+ // then
+ }
+
+ @Test
+ public void shouldUpdateUserDevfile() throws Exception {
+ // given
+
+ DevfileImpl newDevfile = TestObjectGenerator.createDevfile("newUpdate");
+ newDevfile.setApiVersion("V15.0");
+ newDevfile.setProjects(
+ ImmutableList.of(
+ new ProjectImpl(
+ "projectUp2",
+ new SourceImpl(
+ "typeUp2",
+ "http://location",
+ "branch2",
+ "point2",
+ "tag2",
+ "commit2",
+ "sparseCheckoutDir2"),
+ "path2")));
+ newDevfile.setComponents(ImmutableList.of(new ComponentImpl("type3", "id54")));
+ newDevfile.setCommands(
+ ImmutableList.of(
+ new CommandImpl(
+ new CommandImpl(
+ "cmd1",
+ Collections.singletonList(
+ new ActionImpl(
+ "exe44", "compo2nent2", "run.sh", "/home/user/2", null, null)),
+ Collections.singletonMap("attr1", "value1"),
+ null))));
+ newDevfile.setAttributes(ImmutableMap.of("key2", "val34"));
+ newDevfile.setMetadata(new MetadataImpl("myNewName"));
+ final UserDevfileImpl update = devfiles[0];
+ update.setDevfile(newDevfile);
+ // when
+ userDevfileDaoDao.update(update);
+ // then
+ assertEquals(userDevfileDaoDao.getById(update.getId()), Optional.of(update));
+ }
+
+ @Test
+ public void shouldNotUpdateWorkspaceWhichDoesNotExist() throws Exception {
+ // given
+ final UserDevfileImpl userDevfile = devfiles[0];
+ userDevfile.setId("non-existing-devfile");
+ // when
+ Optional result = userDevfileDaoDao.update(userDevfile);
+ // then
+ assertFalse(result.isPresent());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenUpdatingNull() throws Exception {
+ userDevfileDaoDao.update(null);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenGetByIdNull() throws Exception {
+ userDevfileDaoDao.getById(null);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenDeleteNull() throws Exception {
+ userDevfileDaoDao.getById(null);
+ }
+
+ @Test(dependsOnMethods = "shouldGetUserDevfileById")
+ public void shouldRemoveDevfile() throws Exception {
+ final String userDevfileId = devfiles[0].getId();
+ userDevfileDaoDao.remove(userDevfileId);
+ Optional result = userDevfileDaoDao.getById(userDevfileId);
+
+ assertFalse(result.isPresent());
+ }
+
+ @Test
+ public void shouldDoNothingWhenRemovingNonExistingUserDevfile() throws Exception {
+ userDevfileDaoDao.remove("non-existing");
+ }
+
+ @Test
+ public void shouldBeAbleToGetAvailableToUserDevfiles() throws ServerException {
+ // given
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(30, 0, Collections.emptyList(), Collections.emptyList());
+ // then
+ assertEquals(new HashSet<>(result.getItems()), new HashSet<>(asList(devfiles)));
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void shouldThrowIllegalStateExceptionOnNegativeLimit() throws Exception {
+ userDevfileDaoDao.getDevfiles(0, -2, Collections.emptyList(), Collections.emptyList());
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void shouldThrowIllegalStateExceptionOnNegativeSkipCount() throws Exception {
+ userDevfileDaoDao.getDevfiles(-2, 0, Collections.emptyList(), Collections.emptyList());
+ }
+
+ @Test
+ public void shouldBeAbleToGetAvailableToUserDevfilesWithFilter() throws ServerException {
+ // given
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ 30,
+ 0,
+ ImmutableList.of(new Pair<>("name", "like:devfileName%")),
+ Collections.emptyList());
+ // then
+ assertEquals(new HashSet<>(result.getItems()), new HashSet<>(asList(devfiles)));
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void shouldNotAllowSearchWithInvalidFilter() throws ServerException {
+ // given
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ 30,
+ 0,
+ ImmutableList.of(
+ new Pair<>("id", "like:dev%"),
+ new Pair<>("devfile.metadata.something", "like:devfileName%")),
+ Collections.emptyList());
+ // then
+ }
+
+ @Test
+ public void shouldBeAbleToGetAvailableToUserDevfilesWithFilter2()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ final UserDevfileImpl update = devfiles[0];
+ update.setName("New345Name");
+ userDevfileDaoDao.update(update);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ 30, 0, ImmutableList.of(new Pair<>("name", "like:%w345N%")), Collections.emptyList());
+ // then
+ assertEquals(new HashSet<>(result.getItems()), ImmutableSet.of(update));
+ }
+
+ @Test
+ public void shouldBeAbleToGetAvailableToUserDevfilesWithFilterAndLimit()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ final UserDevfileImpl update = devfiles[0];
+ update.setName("New345Name");
+ userDevfileDaoDao.update(update);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ 12,
+ 0,
+ ImmutableList.of(new Pair<>("name", "like:devfileName%")),
+ Collections.emptyList());
+ // then
+ assertEquals(result.getItems().size(), 9);
+ }
+
+ @Test
+ public void shouldBeAbleToGetDevfilesSortedById()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ UserDevfileImpl[] expected =
+ Arrays.stream(devfiles)
+ .sorted(Comparator.comparing(UserDevfileImpl::getId))
+ .toArray(UserDevfileImpl[]::new);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ devfiles.length, 0, Collections.emptyList(), ImmutableList.of(new Pair<>("id", "asc")));
+ // then
+ assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected);
+ }
+
+ @Test
+ public void shouldBeAbleToGetDevfilesSortedByIdReverse()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ UserDevfileImpl[] expected =
+ Arrays.stream(devfiles)
+ .sorted(Comparator.comparing(UserDevfileImpl::getId).reversed())
+ .toArray(UserDevfileImpl[]::new);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ devfiles.length,
+ 0,
+ Collections.emptyList(),
+ ImmutableList.of(new Pair<>("id", "desc")));
+ // then
+ assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected);
+ }
+
+ @Test
+ public void shouldBeAbleToGetDevfilesSortedByName()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ UserDevfileImpl[] expected =
+ Arrays.stream(devfiles)
+ .sorted(Comparator.comparing(UserDevfileImpl::getName))
+ .toArray(UserDevfileImpl[]::new);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ devfiles.length,
+ 0,
+ Collections.emptyList(),
+ ImmutableList.of(new Pair<>("name", "asc")));
+ // then
+ assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected);
+ }
+
+ @Test
+ public void shouldSendDevfileDeletedEventOnRemoveUserDevfile() throws Exception {
+ // given
+ final String userDevfileId = devfiles[0].getId();
+ final boolean[] isNotified = new boolean[] {false};
+ eventService.subscribe(event -> isNotified[0] = true, BeforeDevfileRemovedEvent.class);
+ // when
+ userDevfileDaoDao.remove(userDevfileId);
+ // then
+ assertTrue(isNotified[0], "Event subscriber notified");
+ }
+
+ @Test(dataProvider = "boundsdataprovider")
+ public void shouldBeAbleToGetDevfilesSortedByNameWithSpecificMaxItemsAndSkipCount(
+ int maxitems, int skipCount) throws ServerException, NotFoundException, ConflictException {
+ // given
+ UserDevfileImpl[] expected =
+ Arrays.stream(
+ Arrays.copyOfRange(devfiles, skipCount, min(devfiles.length, skipCount + maxitems)))
+ .sorted(Comparator.comparing(UserDevfileImpl::getId))
+ .toArray(UserDevfileImpl[]::new);
+ // when
+ final Page result =
+ userDevfileDaoDao.getDevfiles(
+ maxitems,
+ skipCount,
+ Collections.emptyList(),
+ ImmutableList.of(new Pair<>("id", "asc")));
+ // then
+ assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected);
+ }
+
+ @DataProvider
+ public Object[][] boundsdataprovider() {
+ return new Object[][] {
+ {1, 1},
+ {1, 4},
+ {4, 5},
+ {6, 8},
+ {1, ENTRY_COUNT},
+ {ENTRY_COUNT, ENTRY_COUNT},
+ {ENTRY_COUNT, 1},
+ {ENTRY_COUNT, 8}
+ };
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "The number of items has to be positive.")
+ public void shouldNotAllowZeroMaxItemsToSearch()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ // when
+ userDevfileDaoDao.getDevfiles(0, 0, Collections.emptyList(), Collections.emptyList());
+ // then
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "The number of items has to be positive.")
+ public void shouldNotAllowNegativeMaxItemsToSearch()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ // when
+ userDevfileDaoDao.getDevfiles(-5, 0, Collections.emptyList(), Collections.emptyList());
+ // then
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp =
+ "The number of items to skip can't be negative or greater than 2147483647")
+ public void shouldNotAllowNegativeItemsToSkipToSearch()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ // when
+ userDevfileDaoDao.getDevfiles(5, -1, Collections.emptyList(), Collections.emptyList());
+ // then
+ }
+
+ @Test(
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp =
+ "Invalid sort order direction\\. Possible values are 'asc' or 'desc'"
+ + " but got: \\[\\{first=name, second=ddd}, \\{first=id, second=bla}]")
+ public void shouldFailOnInvalidSortOrder()
+ throws ServerException, NotFoundException, ConflictException {
+ // given
+ // when
+ userDevfileDaoDao.getDevfiles(
+ 5,
+ 4,
+ Collections.emptyList(),
+ ImmutableList.of(
+ new Pair<>("id", "asc"),
+ new Pair<>("name", "ddd"),
+ new Pair<>("name", "DesC"),
+ new Pair<>("id", "bla")));
+ // then
+ }
+
+ @Test
+ public void shouldGetDevfilesByNamespace() throws Exception {
+ final UserDevfileImpl devfile1 = devfiles[0];
+ final UserDevfileImpl devfile2 = devfiles[1];
+ assertEquals(devfile1.getNamespace(), devfile2.getNamespace(), "Namespaces must be the same");
+
+ final Page found = userDevfileDaoDao.getByNamespace(devfile1.getNamespace(), 6, 0);
+
+ assertEquals(found.getTotalItemsCount(), 2);
+ assertEquals(found.getItemsCount(), 2);
+ assertEquals(new HashSet<>(found.getItems()), new HashSet<>(asList(devfile1, devfile2)));
+ }
+
+ @Test
+ public void emptyListShouldBeReturnedWhenThereAreNoDevfilesInGivenNamespace() throws Exception {
+ assertTrue(userDevfileDaoDao.getByNamespace("non-existing-namespace", 30, 0).isEmpty());
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void shouldThrowNpeWhenGettingDevfilesByNullNamespace() throws Exception {
+ userDevfileDaoDao.getByNamespace(null, 30, 0);
+ }
+
+ @Test(dataProvider = "validOrderFiled")
+ public void shouldTestValidOrderFileds(String filed) {
+ JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder =
+ new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null);
+ try {
+ queryBuilder.withOrder(ImmutableList.of(new Pair<>(filed, "blah")));
+ } catch (IllegalArgumentException e) {
+ Assert.fail(filed + " is valid but failed");
+ }
+ }
+
+ @Test(
+ dataProvider = "invalidOrderField",
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "Order allowed only by \\[id, name\\] but got: .*")
+ public void shouldTestInvalidOrderFileds(String filed) {
+ JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder =
+ new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null);
+ queryBuilder.withOrder(ImmutableList.of(new Pair<>(filed, "blah")));
+ }
+
+ @Test(dataProvider = "validSearchFiled")
+ public void shouldTestValidSearchFileds(String filed) {
+ JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder =
+ new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null);
+ try {
+ queryBuilder.withFilter(ImmutableList.of(new Pair<>(filed, "blah")));
+ } catch (IllegalArgumentException e) {
+ Assert.fail(filed + " is valid but failed");
+ }
+ }
+
+ @Test(
+ dataProvider = "invalidSearchField",
+ expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "Filtering allowed only by \\[name\\] but got: .*")
+ public void shouldTestInvalidSearchFileds(String filed) {
+ JpaUserDevfileDao.UserDevfileSearchQueryBuilder queryBuilder =
+ new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(null);
+ queryBuilder.withFilter(ImmutableList.of(new Pair<>(filed, "blah")));
+ }
+
+ @DataProvider
+ public Object[][] validOrderFiled() {
+ return new Object[][] {{"id"}, {"Id"}, {"name"}, {"nAmE"}};
+ }
+
+ @DataProvider
+ public Object[][] invalidOrderField() {
+ return new Object[][] {{"devfile"}, {"meta_name"}, {"description"}, {"meta_generated_name"}};
+ }
+
+ @DataProvider
+ public Object[][] validSearchFiled() {
+ return new Object[][] {
+ {"name"}, {"NaMe"},
+ };
+ }
+
+ @DataProvider
+ public Object[][] invalidSearchField() {
+ return new Object[][] {
+ {"id"}, {"devfile"}, {"ID"}, {"meta_name"}, {"description"}, {"meta_generated_name"}
+ };
+ }
+}
diff --git a/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule
new file mode 100644
index 0000000000..7e85050a9b
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule
@@ -0,0 +1 @@
+org.eclipse.che.api.devfile.server.jpa.UserDevfileTckModule
diff --git a/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml b/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml
new file mode 100644
index 0000000000..2250aaa5aa
--- /dev/null
+++ b/wsmaster/che-core-api-devfile/src/test/resources/logback-test.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n
+
+
+
+
+
+
+
diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java
index b5ee0d066b..d9c0ed08ec 100644
--- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java
+++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/jpa/JpaFactoryDao.java
@@ -64,7 +64,7 @@ public class JpaFactoryDao implements FactoryDao {
format("Factory with name '%s' already exists for current user", factory.getName()));
} catch (IntegrityConstraintViolationException ex) {
throw new ConflictException(
- "Could not create factory with creator that refers on non-existent user");
+ "Could not create factory with creator that refers to a non-existent user");
} catch (RuntimeException ex) {
throw new ServerException(ex.getLocalizedMessage(), ex);
}
diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java
index 11d6121adb..5f18aa257b 100644
--- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java
+++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java
@@ -28,7 +28,7 @@ import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
import org.eclipse.che.api.workspace.server.DtoConverter;
-import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
@@ -53,18 +53,18 @@ public class URLFactoryBuilder {
private final String defaultChePlugins;
private final URLFetcher urlFetcher;
- private final DevfileManager devfileManager;
+ private final DevfileParser devfileParser;
@Inject
public URLFactoryBuilder(
@Named("che.factory.default_editor") String defaultCheEditor,
@Named("che.factory.default_plugins") String defaultChePlugins,
URLFetcher urlFetcher,
- DevfileManager devfileManager) {
+ DevfileParser devfileParser) {
this.defaultCheEditor = defaultCheEditor;
this.defaultChePlugins = defaultChePlugins;
this.urlFetcher = urlFetcher;
- this.devfileManager = devfileManager;
+ this.devfileParser = devfileParser;
}
/**
@@ -115,8 +115,8 @@ public class URLFactoryBuilder {
continue;
}
try {
- DevfileImpl devfile = devfileManager.parseYaml(devfileYamlContent, overrideProperties);
- devfileManager.resolveReference(devfile, fileContentProvider);
+ DevfileImpl devfile = devfileParser.parseYaml(devfileYamlContent, overrideProperties);
+ devfileParser.resolveReference(devfile, fileContentProvider);
devfile = ensureToUseGenerateName(devfile);
FactoryDto factoryDto =
diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java
index d232984983..cb9b1e70a4 100644
--- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java
+++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/DefaultFactoryParameterResolverTest.java
@@ -31,7 +31,7 @@ import java.util.Map;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl;
import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder;
-import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
@@ -76,10 +76,10 @@ public class DefaultFactoryParameterResolverTest {
DevfileIntegrityValidator integrityValidator = new DevfileIntegrityValidator(validators);
- DevfileManager devfileManager = new DevfileManager(validator, integrityValidator);
+ DevfileParser devfileParser = new DevfileParser(validator, integrityValidator);
URLFactoryBuilder factoryBuilder =
- new URLFactoryBuilder("editor", "plugin", urlFetcher, devfileManager);
+ new URLFactoryBuilder("editor", "plugin", urlFetcher, devfileParser);
DefaultFactoryParameterResolver res =
new DefaultFactoryParameterResolver(factoryBuilder, urlFetcher);
diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java
index 5061cf95ab..654723a7bc 100644
--- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java
+++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java
@@ -35,7 +35,7 @@ import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation;
import org.eclipse.che.api.factory.shared.dto.FactoryDto;
-import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.FileContentProvider;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException;
@@ -68,7 +68,7 @@ public class URLFactoryBuilderTest {
/** Grab content of URLs */
@Mock private URLFetcher urlFetcher;
- @Mock private DevfileManager devfileManager;
+ @Mock private DevfileParser devfileParser;
/** Tested instance. */
private URLFactoryBuilder urlFactoryBuilder;
@@ -76,7 +76,7 @@ public class URLFactoryBuilderTest {
@BeforeClass
public void setUp() {
this.urlFactoryBuilder =
- new URLFactoryBuilder(defaultEditor, defaultPlugin, urlFetcher, devfileManager);
+ new URLFactoryBuilder(defaultEditor, defaultPlugin, urlFetcher, devfileParser);
}
@Test
@@ -132,7 +132,7 @@ public class URLFactoryBuilderTest {
workspaceConfigImpl.setDefaultEnv("name");
when(urlFetcher.fetchSafely(anyString())).thenReturn("random_content");
- when(devfileManager.parseYaml(anyString(), anyMap())).thenReturn(devfile);
+ when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile);
FactoryDto factory =
urlFactoryBuilder
@@ -192,7 +192,7 @@ public class URLFactoryBuilderTest {
return "http://foo.bar/anything";
}
}));
- when(devfileManager.parseYaml(anyString(), anyMap())).thenReturn(devfile);
+ when(devfileParser.parseYaml(anyString(), anyMap())).thenReturn(devfile);
when(urlFetcher.fetchSafely(anyString())).thenReturn("anything");
FactoryDto factory =
urlFactoryBuilder
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java
index 335a2f1cdb..ec577fc641 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProvider.java
@@ -35,7 +35,7 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
-import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
@@ -54,12 +54,12 @@ import org.eclipse.che.dto.server.DtoFactory;
public class WorkspaceEntityProvider
implements MessageBodyReader, MessageBodyWriter {
- private DevfileManager devfileManager;
+ private DevfileParser devfileParser;
private ObjectMapper mapper = new ObjectMapper();
@Inject
- public WorkspaceEntityProvider(DevfileManager devfileManager) {
- this.devfileManager = devfileManager;
+ public WorkspaceEntityProvider(DevfileParser devfileParser) {
+ this.devfileParser = devfileParser;
}
@Override
@@ -81,7 +81,7 @@ public class WorkspaceEntityProvider
JsonNode wsNode = mapper.readTree(entityStream);
JsonNode devfileNode = wsNode.path("devfile");
if (!devfileNode.isNull() && !devfileNode.isMissingNode()) {
- devfileManager.parseJson(devfileNode.toString());
+ devfileParser.parseJson(devfileNode.toString());
}
return DtoFactory.getInstance().createDtoFromJson(wsNode.toString(), WorkspaceDto.class);
} catch (DevfileFormatException e) {
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java
index 282c376b8c..73ebd7e00b 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProvider.java
@@ -55,11 +55,11 @@ import org.eclipse.che.dto.server.DtoFactory;
public class DevfileEntityProvider
implements MessageBodyReader, MessageBodyWriter {
- private DevfileManager devfileManager;
+ private DevfileParser devfileParser;
@Inject
- public DevfileEntityProvider(DevfileManager devfileManager) {
- this.devfileManager = devfileManager;
+ public DevfileEntityProvider(DevfileParser devfileParser) {
+ this.devfileParser = devfileParser;
}
@Override
@@ -81,13 +81,13 @@ public class DevfileEntityProvider
try {
if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
return asDto(
- devfileManager.parseJson(
+ devfileParser.parseJson(
CharStreams.toString(
new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType)))));
} else if (mediaType.isCompatible(MediaType.valueOf("text/yaml"))
|| mediaType.isCompatible(MediaType.valueOf("text/x-yaml"))) {
return asDto(
- devfileManager.parseYaml(
+ devfileParser.parseYaml(
CharStreams.toString(
new InputStreamReader(entityStream, getCharsetOrUtf8(mediaType)))));
}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java
index ab49aff0bc..8155c83c33 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileModule.java
@@ -24,7 +24,6 @@ public class DevfileModule extends AbstractModule {
@Override
protected void configure() {
- bind(DevfileService.class);
bind(DevfileEntityProvider.class);
DevfileBindings.onWorkspaceApplierBinder(
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java
similarity index 99%
rename from wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java
rename to wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java
index 49c893aa0f..dc731cb830 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileManager.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileParser.java
@@ -45,7 +45,7 @@ import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
*/
@Beta
@Singleton
-public class DevfileManager {
+public class DevfileParser {
private ObjectMapper yamlMapper;
private ObjectMapper jsonMapper;
@@ -54,7 +54,7 @@ public class DevfileManager {
private final OverridePropertiesApplier overridePropertiesApplier;
@Inject
- public DevfileManager(
+ public DevfileParser(
DevfileSchemaValidator schemaValidator, DevfileIntegrityValidator integrityValidator) {
this(
schemaValidator,
@@ -64,7 +64,7 @@ public class DevfileManager {
}
@VisibleForTesting
- DevfileManager(
+ DevfileParser(
DevfileSchemaValidator schemaValidator,
DevfileIntegrityValidator integrityValidator,
ObjectMapper yamlMapper,
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java
deleted file mode 100644
index 35c5782432..0000000000
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2012-2018 Red Hat, Inc.
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * Red Hat, Inc. - initial API and implementation
- */
-package org.eclipse.che.api.workspace.server.devfile;
-
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
-import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
-
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
-import java.io.IOException;
-import javax.inject.Inject;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Response;
-import org.eclipse.che.api.core.ServerException;
-import org.eclipse.che.api.core.rest.Service;
-import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
-
-@Api(value = "/devfile", description = "Devfile REST API")
-@Path("/devfile")
-public class DevfileService extends Service {
-
- private DevfileSchemaProvider schemaCachedProvider;
-
- @Inject
- public DevfileService(DevfileSchemaProvider schemaCachedProvider) {
- this.schemaCachedProvider = schemaCachedProvider;
- }
-
- /**
- * Retrieves the json schema.
- *
- * @return json schema
- */
- @GET
- @Produces(APPLICATION_JSON)
- @ApiOperation(value = "Retrieves current version of devfile JSON schema")
- @ApiResponses({
- @ApiResponse(code = 200, message = "The schema successfully retrieved"),
- @ApiResponse(code = 500, message = "Internal server error occurred")
- })
- public Response getSchema() throws ServerException {
- try {
- return Response.ok(schemaCachedProvider.getSchemaContent(CURRENT_API_VERSION)).build();
- } catch (IOException e) {
- throw new ServerException(e);
- }
- }
-}
diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java
index 78b035abab..45f5ef51ea 100644
--- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java
+++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/devfile/MetadataImpl.java
@@ -35,6 +35,11 @@ public class MetadataImpl implements Metadata {
this.name = name;
}
+ public MetadataImpl(String name, String generateName) {
+ this.name = name;
+ this.generateName = generateName;
+ }
+
public MetadataImpl(Metadata metadata) {
this.name = metadata.getName();
this.generateName = metadata.getGenerateName();
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java
index 66dfdee584..d2006f8060 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceEntityProviderTest.java
@@ -20,7 +20,7 @@ import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
-import org.eclipse.che.api.workspace.server.devfile.DevfileManager;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl;
import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto;
@@ -34,14 +34,14 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class WorkspaceEntityProviderTest {
- @Mock private DevfileManager devfileManager;
+ @Mock private DevfileParser devfileParser;
@InjectMocks private WorkspaceEntityProvider workspaceEntityProvider;
@Test
public void shouldBuildDtoFromValidJson() throws Exception {
- when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl());
+ when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl());
WorkspaceDto actual = newDto(WorkspaceDto.class).withDevfile(newDto(DevfileDto.class));
@@ -54,6 +54,6 @@ public class WorkspaceEntityProviderTest {
new ByteArrayInputStream(
DtoFactory.getInstance().toJson(actual).getBytes(StandardCharsets.UTF_8)));
- verify(devfileManager).parseJson(anyString());
+ verify(devfileParser).parseJson(anyString());
}
}
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
index ef58bf575b..ed1a6efd82 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceServiceTest.java
@@ -69,7 +69,12 @@ import org.eclipse.che.api.core.model.workspace.runtime.ServerStatus;
import org.eclipse.che.api.core.rest.ApiExceptionMapper;
import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.shared.dto.ServiceError;
+import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider;
+import org.eclipse.che.api.workspace.server.devfile.DevfileParser;
import org.eclipse.che.api.workspace.server.devfile.URLFetcher;
+import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
+import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator;
+import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator;
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
@@ -139,6 +144,13 @@ public class WorkspaceServiceTest {
@SuppressWarnings("unused") // is declared for deploying by everrest-assured
private CheJsonProvider jsonProvider = new CheJsonProvider(Collections.emptySet());
+ @SuppressWarnings("unused") // is declared for deploying by everrest-assured
+ private DevfileEntityProvider devfileEntityProvider =
+ new DevfileEntityProvider(
+ new DevfileParser(
+ new DevfileSchemaValidator(new DevfileSchemaProvider()),
+ new DevfileIntegrityValidator(Collections.emptyMap())));
+
@Mock private WorkspaceManager wsManager;
@Mock private MachineTokenProvider machineTokenProvider;
@Mock private WorkspaceLinksGenerator linksGenerator;
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java
index d4fde146f3..5a8982dd3a 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileEntityProviderTest.java
@@ -29,14 +29,14 @@ import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
public class DevfileEntityProviderTest {
- @Mock private DevfileManager devfileManager;
+ @Mock private DevfileParser devfileParser;
@InjectMocks private DevfileEntityProvider devfileEntityProvider;
@Test
public void shouldBuildDtoFromValidYaml() throws Exception {
- when(devfileManager.parseYaml(anyString())).thenReturn(new DevfileImpl());
+ when(devfileParser.parseYaml(anyString())).thenReturn(new DevfileImpl());
devfileEntityProvider.readFrom(
DevfileDto.class,
@@ -46,13 +46,13 @@ public class DevfileEntityProviderTest {
new MultivaluedHashMap<>(),
getClass().getClassLoader().getResourceAsStream("devfile/devfile.yaml"));
- verify(devfileManager).parseYaml(anyString());
+ verify(devfileParser).parseYaml(anyString());
}
@Test
public void shouldBuildDtoFromValidJson() throws Exception {
- when(devfileManager.parseJson(anyString())).thenReturn(new DevfileImpl());
+ when(devfileParser.parseJson(anyString())).thenReturn(new DevfileImpl());
devfileEntityProvider.readFrom(
DevfileDto.class,
@@ -62,7 +62,7 @@ public class DevfileEntityProviderTest {
new MultivaluedHashMap<>(),
getClass().getClassLoader().getResourceAsStream("devfile/devfile.json"));
- verify(devfileManager).parseJson(anyString());
+ verify(devfileParser).parseJson(anyString());
}
@Test(
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java
similarity index 91%
rename from wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java
rename to wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java
index 72349535f0..4afa2eeed4 100644
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileManagerTest.java
+++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileParserTest.java
@@ -43,7 +43,7 @@ import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
@Listeners(MockitoTestNGListener.class)
-public class DevfileManagerTest {
+public class DevfileParserTest {
private static final String DEVFILE_YAML_CONTENT = "devfile yaml stub";
@@ -56,13 +56,12 @@ public class DevfileManagerTest {
@Mock private JsonNode devfileJsonNode;
private DevfileImpl devfile;
- private DevfileManager devfileManager;
+ private DevfileParser devfileParser;
@BeforeMethod
public void setUp() throws Exception {
devfile = new DevfileImpl();
- devfileManager =
- new DevfileManager(schemaValidator, integrityValidator, yamlMapper, jsonMapper);
+ devfileParser = new DevfileParser(schemaValidator, integrityValidator, yamlMapper, jsonMapper);
lenient().when(jsonMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile);
lenient().when(yamlMapper.treeToValue(any(), eq(DevfileImpl.class))).thenReturn(devfile);
@@ -72,7 +71,7 @@ public class DevfileManagerTest {
@Test
public void testValidateAndParse() throws Exception {
// when
- DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
+ DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT);
// then
assertEquals(parsed, devfile);
@@ -93,7 +92,7 @@ public class DevfileManagerTest {
devfile.getComponents().add(component);
// when
- DevfileImpl parsed = devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
+ DevfileImpl parsed = devfileParser.parseYaml(DEVFILE_YAML_CONTENT);
// then
assertNotNull(parsed.getCommands().get(0).getAttributes());
@@ -113,7 +112,7 @@ public class DevfileManagerTest {
devfile.getComponents().add(component);
// when
- devfileManager.resolveReference(devfile, contentProvider);
+ devfileParser.resolveReference(devfile, contentProvider);
// then
verify(contentProvider).fetchContent(eq("myfile.yaml"));
@@ -125,7 +124,7 @@ public class DevfileManagerTest {
expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty")
public void shouldThrowDevfileExceptionWhenEmptyObjectProvided() throws Exception {
// when
- devfileManager.parseJson("{}");
+ devfileParser.parseJson("{}");
}
@Test(
@@ -133,7 +132,7 @@ public class DevfileManagerTest {
expectedExceptionsMessageRegExp = "Unable to parse Devfile - provided source is empty")
public void shouldThrowDevfileExceptionWhenEmptySourceProvided() throws Exception {
// when
- devfileManager.parseJson("");
+ devfileParser.parseJson("");
}
@Test(
@@ -150,7 +149,7 @@ public class DevfileManagerTest {
devfile.getComponents().add(component);
// when
- devfileManager.resolveReference(devfile, contentProvider);
+ devfileParser.resolveReference(devfile, contentProvider);
// then exception is thrown
}
@@ -163,7 +162,7 @@ public class DevfileManagerTest {
doThrow(new DevfileFormatException("non valid")).when(schemaValidator).validate(any());
// when
- devfileManager.parseYaml(DEVFILE_YAML_CONTENT);
+ devfileParser.parseYaml(DEVFILE_YAML_CONTENT);
}
@Test(
@@ -177,6 +176,6 @@ public class DevfileManagerTest {
doThrow(jsonException).when(jsonMapper).treeToValue(any(), any());
// when
- devfileManager.parseJson(DEVFILE_YAML_CONTENT);
+ devfileParser.parseJson(DEVFILE_YAML_CONTENT);
}
}
diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java
deleted file mode 100644
index e8512ea736..0000000000
--- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2012-2018 Red Hat, Inc.
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- * Red Hat, Inc. - initial API and implementation
- */
-package org.eclipse.che.api.workspace.server.devfile;
-
-import static com.jayway.restassured.RestAssured.given;
-import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
-import static org.everrest.assured.JettyHttpServer.*;
-import static org.testng.Assert.assertEquals;
-
-import com.jayway.restassured.response.Response;
-import org.eclipse.che.api.core.rest.ApiExceptionMapper;
-import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
-import org.eclipse.che.commons.subject.Subject;
-import org.eclipse.che.commons.subject.SubjectImpl;
-import org.everrest.assured.EverrestJetty;
-import org.mockito.testng.MockitoTestNGListener;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Listeners;
-import org.testng.annotations.Test;
-
-@Listeners({EverrestJetty.class, MockitoTestNGListener.class})
-public class DevfileServiceTest {
-
- @SuppressWarnings("unused") // is declared for deploying by everrest-assured
- private ApiExceptionMapper exceptionMapper;
-
- private DevfileSchemaProvider schemaProvider = new DevfileSchemaProvider();
-
- private static final Subject SUBJECT = new SubjectImpl("user", "user123", "token", false);
-
- @SuppressWarnings("unused")
- private DevfileService devFileService;
-
- @BeforeMethod
- public void initService() {
- this.devFileService = new DevfileService(schemaProvider);
- }
-
- @Test
- public void shouldRetrieveSchema() throws Exception {
- final Response response =
- given()
- .auth()
- .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD)
- .when()
- .get(SECURE_PATH + "/devfile");
-
- assertEquals(response.getStatusCode(), 200);
- assertEquals(
- response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION));
- }
-}
diff --git a/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql
new file mode 100644
index 0000000000..2ea95dbc5e
--- /dev/null
+++ b/wsmaster/che-core-sql-schema/src/main/resources/che-schema/7.20.0/1__userdevfile.sql
@@ -0,0 +1,29 @@
+--
+-- Copyright (c) 2012-2020 Red Hat, Inc.
+-- This program and the accompanying materials are made
+-- available under the terms of the Eclipse Public License 2.0
+-- which is available at https://www.eclipse.org/legal/epl-2.0/
+--
+-- SPDX-License-Identifier: EPL-2.0
+--
+-- Contributors:
+-- Red Hat, Inc. - initial API and implementation
+--
+
+
+-- add userdevfile table
+CREATE TABLE userdevfile (
+ id VARCHAR(255) NOT NULL UNIQUE,
+ accountid VARCHAR(255) NOT NULL,
+ devfile_id BIGINT NOT NULL UNIQUE,
+ meta_generated_name VARCHAR(255) ,
+ meta_name VARCHAR(255) ,
+ name VARCHAR(255) NOT NULL ,
+ description TEXT ,
+ PRIMARY KEY (id)
+);
+CREATE INDEX index_userdevfile_devfile_id ON userdevfile (devfile_id);
+CREATE INDEX index_userdevfile_name ON userdevfile(name);
+ALTER TABLE userdevfile ADD CONSTRAINT unq_userdevfile_0 UNIQUE (name, accountid);
+ALTER TABLE userdevfile ADD CONSTRAINT fx_userdevfile_accountid FOREIGN KEY (accountid) REFERENCES account (id);
+ALTER TABLE userdevfile ADD CONSTRAINT fk_userdevfile_devfile_id FOREIGN KEY (devfile_id) REFERENCES devfile (id);
diff --git a/wsmaster/integration-tests/mysql-tck/pom.xml b/wsmaster/integration-tests/mysql-tck/pom.xml
index 4936443381..10c85c19c9 100644
--- a/wsmaster/integration-tests/mysql-tck/pom.xml
+++ b/wsmaster/integration-tests/mysql-tck/pom.xml
@@ -90,6 +90,17 @@
che-core-api-account
test