/*******************************************************************************
* 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.user.server.jpa;
import com.google.inject.persist.Transactional;
import org.eclipse.che.api.core.ApiException;
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.user.server.model.impl.UserImpl;
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
import org.eclipse.che.security.PasswordEncryptor;
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 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 UserDao}.
*
* @author Yevhenii Voevodin
* @author Anton Korneta
* @author Igor Vinokur
*/
@Singleton
public class JpaUserDao implements UserDao {
@Inject
protected Provider<EntityManager> managerProvider;
@Inject
private PasswordEncryptor encryptor;
@Override
@Transactional
public UserImpl getByAliasAndPassword(String emailOrName, String password) throws NotFoundException, ServerException {
requireNonNull(emailOrName, "Required non-null email or name");
requireNonNull(password, "Required non-null password");
try {
final UserImpl user = managerProvider.get()
.createNamedQuery("User.getByAliasAndPassword", UserImpl.class)
.setParameter("alias", emailOrName)
.getSingleResult();
if (!encryptor.test(password, user.getPassword())) {
throw new NotFoundException(format("User with email or name '%s' and given password doesn't exist", emailOrName));
}
return erasePassword(user);
} catch (NoResultException x) {
throw new NotFoundException(format("User with email or name '%s' and given password doesn't exist", emailOrName));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
public void create(UserImpl user) throws ConflictException, ServerException {
requireNonNull(user, "Required non-null user");
try {
if (user.getPassword() != null) {
user.setPassword(encryptor.encrypt(user.getPassword()));
}
doCreate(user);
} catch (DuplicateKeyException x) {
// TODO make more concrete
throw new ConflictException("User with such id/name/email/alias already exists");
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
public void update(UserImpl update) throws NotFoundException, ServerException, ConflictException {
requireNonNull(update, "Required non-null update");
try {
doUpdate(update);
} catch (DuplicateKeyException x) {
// TODO make more concrete
throw new ConflictException("User with such name/email/alias already exists");
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
public void remove(String id) throws ServerException {
requireNonNull(id, "Required non-null id");
try {
doRemove(id);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
public UserImpl getByAlias(String alias) throws NotFoundException, ServerException {
requireNonNull(alias, "Required non-null alias");
try {
return erasePassword(managerProvider.get()
.createNamedQuery("User.getByAlias", UserImpl.class)
.setParameter("alias", alias)
.getSingleResult());
} catch (NoResultException x) {
throw new NotFoundException(format("User with alias '%s' doesn't exist", alias));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public UserImpl getById(String id) throws NotFoundException, ServerException {
requireNonNull(id, "Required non-null id");
try {
final UserImpl user = managerProvider.get().find(UserImpl.class, id);
if (user == null) {
throw new NotFoundException(format("User with id '%s' doesn't exist", id));
}
return erasePassword(user);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public UserImpl getByName(String name) throws NotFoundException, ServerException {
requireNonNull(name, "Required non-null name");
try {
return erasePassword(managerProvider.get()
.createNamedQuery("User.getByName", UserImpl.class)
.setParameter("name", name)
.getSingleResult());
} catch (NoResultException x) {
throw new NotFoundException(format("User with name '%s' doesn't exist", name));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public UserImpl getByEmail(String email) throws NotFoundException, ServerException {
requireNonNull(email, "Required non-null email");
try {
return erasePassword(managerProvider.get()
.createNamedQuery("User.getByEmail", UserImpl.class)
.setParameter("email", email)
.getSingleResult());
} catch (NoResultException x) {
throw new NotFoundException(format("User with email '%s' doesn't exist", email));
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public Page<UserImpl> getAll(int maxItems, long skipCount) throws ServerException {
// TODO need to ensure that 'getAll' query works with same data as 'getTotalCount'
checkArgument(maxItems >= 0, "The number of items to return can't be negative.");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
final List<UserImpl> list = managerProvider.get()
.createNamedQuery("User.getAll", UserImpl.class)
.setMaxResults(maxItems)
.setFirstResult((int)skipCount)
.getResultList()
.stream()
.map(JpaUserDao::erasePassword)
.collect(toList());
return new Page<>(list, skipCount, maxItems, getTotalCount());
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public Page<UserImpl> getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException {
requireNonNull(namePart, "Required non-null name part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
final List<UserImpl> list = managerProvider.get()
.createNamedQuery("User.getByNamePart", UserImpl.class)
.setParameter("name", namePart.toLowerCase())
.setMaxResults(maxItems)
.setFirstResult((int)skipCount)
.getResultList()
.stream()
.map(JpaUserDao::erasePassword)
.collect(toList());
final long count = managerProvider.get()
.createNamedQuery("User.getByNamePartCount", Long.class)
.setParameter("name", namePart.toLowerCase())
.getSingleResult();
return new Page<>(list, skipCount, maxItems, count);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public Page<UserImpl> getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException {
requireNonNull(emailPart, "Required non-null email part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
final List<UserImpl> list = managerProvider.get()
.createNamedQuery("User.getByEmailPart", UserImpl.class)
.setParameter("email", emailPart.toLowerCase())
.setMaxResults(maxItems)
.setFirstResult((int)skipCount)
.getResultList()
.stream()
.map(JpaUserDao::erasePassword)
.collect(toList());
final long count = managerProvider.get()
.createNamedQuery("User.getByEmailPartCount", Long.class)
.setParameter("email", emailPart.toLowerCase())
.getSingleResult();
return new Page<>(list, skipCount, maxItems, count);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Override
@Transactional
public long getTotalCount() throws ServerException {
try {
return managerProvider.get().createNamedQuery("User.getTotalCount", Long.class).getSingleResult();
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}
@Transactional(rollbackOn = {RuntimeException.class, ApiException.class})
protected void doCreate(UserImpl user) throws ConflictException, ServerException {
EntityManager manage = managerProvider.get();
manage.persist(user);
manage.flush();
}
@Transactional
protected void doUpdate(UserImpl update) throws NotFoundException {
final EntityManager manager = managerProvider.get();
final UserImpl user = manager.find(UserImpl.class, update.getId());
if (user == null) {
throw new NotFoundException(format("Couldn't update user with id '%s' because it doesn't exist", update.getId()));
}
final String password = update.getPassword();
if (password != null) {
update.setPassword(encryptor.encrypt(password));
} else {
update.setPassword(user.getPassword());
}
manager.merge(update);
manager.flush();
}
@Transactional(rollbackOn = {RuntimeException.class, ServerException.class})
protected void doRemove(String id) {
final EntityManager manager = managerProvider.get();
final UserImpl user = manager.find(UserImpl.class, id);
if (user != null) {
manager.remove(user);
manager.flush();
}
}
// Returns user instance copy without password
private static UserImpl erasePassword(UserImpl source) {
return new UserImpl(source.getId(),
source.getEmail(),
source.getName(),
null,
source.getAliases());
}
}