/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.workspace.server.jpa;
import com.google.inject.persist.Transactional;
import org.eclipse.che.account.event.BeforeAccountRemovedEvent;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.workspace.server.WorkspaceManager;
import org.eclipse.che.api.workspace.server.event.BeforeWorkspaceRemovedEvent;
import org.eclipse.che.api.workspace.server.event.WorkspaceRemovedEvent;
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
import org.eclipse.che.api.workspace.server.spi.WorkspaceDao;
import org.eclipse.che.core.db.cascade.CascadeEventSubscriber;
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
/**
* JPA based implementation of {@link WorkspaceDao}.
*
* @author Yevhenii Voevodin
*/
@Singleton
public class JpaWorkspaceDao implements WorkspaceDao {
@Inject
private EventService eventService;
@Inject
private Provider<EntityManager> managerProvider;
@Override
public WorkspaceImpl create(WorkspaceImpl workspace) throws ConflictException, ServerException {
requireNonNull(workspace, "Required non-null workspace");
try {
doCreate(workspace);
} catch (DuplicateKeyException dkEx) {
throw new ConflictException(format("Workspace with id '%s' or name '%s' in namespace '%s' already exists",
workspace.getId(),
workspace.getConfig().getName(),
workspace.getNamespace()));
} catch (RuntimeException x) {
throw new ServerException(x.getMessage(), x);
}
return new WorkspaceImpl(workspace);
}
@Override
public WorkspaceImpl update(WorkspaceImpl update) throws NotFoundException, ConflictException, ServerException {
requireNonNull(update, "Required non-null update");
try {
return new WorkspaceImpl(doUpdate(update));
} catch (DuplicateKeyException dkEx) {
throw new ConflictException(format("Workspace with name '%s' in namespace '%s' already exists",
update.getConfig().getName(),
update.getNamespace()));
} catch (RuntimeException x) {
throw new ServerException(x.getMessage(), x);
}
}
@Override
public void remove(String id) throws ServerException {
requireNonNull(id, "Required non-null id");
try {
Optional<WorkspaceImpl> workspaceOpt = doRemove(id);
workspaceOpt.ifPresent(workspace -> eventService.publish(new WorkspaceRemovedEvent(workspace)));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public WorkspaceImpl get(String id) throws NotFoundException, ServerException {
requireNonNull(id, "Required non-null id");
try {
final WorkspaceImpl workspace = managerProvider.get().find(WorkspaceImpl.class, id);
if (workspace == null) {
throw new NotFoundException(format("Workspace with id '%s' doesn't exist", id));
}
return new WorkspaceImpl(workspace);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public WorkspaceImpl get(String name, String namespace) throws NotFoundException, ServerException {
requireNonNull(name, "Required non-null name");
requireNonNull(namespace, "Required non-null namespace");
try {
return new WorkspaceImpl(managerProvider.get()
.createNamedQuery("Workspace.getByName", WorkspaceImpl.class)
.setParameter("namespace", namespace)
.setParameter("name", name)
.getSingleResult());
} catch (NoResultException noResEx) {
throw new NotFoundException(format("Workspace with name '%s' in namespace '%s' doesn't exist",
name,
namespace));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public List<WorkspaceImpl> getByNamespace(String namespace) throws ServerException {
requireNonNull(namespace, "Required non-null namespace");
try {
return managerProvider.get()
.createNamedQuery("Workspace.getByNamespace", WorkspaceImpl.class)
.setParameter("namespace", namespace)
.getResultList()
.stream()
.map(WorkspaceImpl::new)
.collect(Collectors.toList());
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public List<WorkspaceImpl> getWorkspaces(String userId) throws ServerException {
try {
return managerProvider.get()
.createNamedQuery("Workspace.getAll", WorkspaceImpl.class)
.getResultList()
.stream()
.map(WorkspaceImpl::new)
.collect(Collectors.toList());
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public List<WorkspaceImpl> getWorkspaces(boolean isTemporary, int skipCount, int maxItems) throws ServerException {
checkArgument(maxItems >= 0, "The number of items to return can't be negative.");
checkArgument(skipCount >= 0, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
return managerProvider.get()
.createNamedQuery("Workspace.getByTemporary", WorkspaceImpl.class)
.setParameter("temporary", isTemporary)
.setMaxResults(maxItems)
.setFirstResult(skipCount)
.getResultList()
.stream()
.map(WorkspaceImpl::new)
.collect(toList());
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Transactional
protected void doCreate(WorkspaceImpl workspace) {
if (workspace.getConfig() != null) {
workspace.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes);
}
EntityManager manager = managerProvider.get();
manager.persist(workspace);
manager.flush();
}
@Transactional(rollbackOn = {RuntimeException.class, ServerException.class})
protected Optional<WorkspaceImpl> doRemove(String id) throws ServerException {
final WorkspaceImpl workspace = managerProvider.get().find(WorkspaceImpl.class, id);
if (workspace == null) {
return Optional.empty();
}
final EntityManager manager = managerProvider.get();
eventService.publish(new BeforeWorkspaceRemovedEvent(new WorkspaceImpl(workspace))).propagateException();
manager.remove(workspace);
manager.flush();
return Optional.of(workspace);
}
@Transactional
protected WorkspaceImpl doUpdate(WorkspaceImpl update) throws NotFoundException {
EntityManager manager = managerProvider.get();
if (manager.find(WorkspaceImpl.class, update.getId()) == null) {
throw new NotFoundException(format("Workspace with id '%s' doesn't exist", update.getId()));
}
if (update.getConfig() != null) {
update.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes);
}
WorkspaceImpl merged = manager.merge(update);
manager.flush();
return merged;
}
@Singleton
public static class RemoveWorkspaceBeforeAccountRemovedEventSubscriber
extends CascadeEventSubscriber<BeforeAccountRemovedEvent> {
@Inject
private EventService eventService;
@Inject
private WorkspaceManager workspaceManager;
@PostConstruct
public void subscribe() {
eventService.subscribe(this, BeforeAccountRemovedEvent.class);
}
@PreDestroy
public void unsubscribe() {
eventService.unsubscribe(this, BeforeAccountRemovedEvent.class);
}
@Override
public void onCascadeEvent(BeforeAccountRemovedEvent event) throws Exception {
for (WorkspaceImpl workspace : workspaceManager.getByNamespace(event.getAccount().getName(), false)) {
workspaceManager.removeWorkspace(workspace.getId());
}
}
}
@Singleton
public static class RemoveSnapshotsBeforeWorkspaceRemovedEventSubscriber
extends CascadeEventSubscriber<BeforeWorkspaceRemovedEvent> {
@Inject
private EventService eventService;
@Inject
private WorkspaceManager workspaceManager;
@PostConstruct
public void subscribe() {
eventService.subscribe(this, BeforeWorkspaceRemovedEvent.class);
}
@PreDestroy
public void unsubscribe() {
eventService.unsubscribe(this, BeforeWorkspaceRemovedEvent.class);
}
@Override
public void onCascadeEvent(BeforeWorkspaceRemovedEvent event) throws Exception {
workspaceManager.removeSnapshots(event.getWorkspace().getId());
}
}
}