CollectionServiceImpl.java

package org.xandercat.pmdb.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.xandercat.pmdb.dao.CollectionDao;
import org.xandercat.pmdb.dao.KeyGenerator;
import org.xandercat.pmdb.dao.MovieDao;
import org.xandercat.pmdb.dao.repository.DynamoCollectionRepository;
import org.xandercat.pmdb.dao.repository.DynamoMovieRepository;
import org.xandercat.pmdb.dto.CollectionPermission;
import org.xandercat.pmdb.dto.Movie;
import org.xandercat.pmdb.dto.MovieCollection;
import org.xandercat.pmdb.exception.WebServicesException;
import org.xandercat.pmdb.exception.CollectionSharingException;
import org.xandercat.pmdb.util.ApplicationProperties;
import org.xandercat.pmdb.util.ExcelPorter;

@Component
public class CollectionServiceImpl implements CollectionService {

	private static final Logger LOGGER = LogManager.getLogger(CollectionServiceImpl.class);
	
	@Autowired
	private CollectionDao collectionDao;

	@Autowired
	private DynamoCollectionRepository dynamoCollectionRepository;
	
	@Autowired
	private MovieDao movieDao;
	
	@Autowired
	private DynamoMovieRepository dynamoMovieRepository;
	
	@Autowired
	private KeyGenerator keyGenerator;
	
	@Autowired
	private ApplicationProperties applicationProperties;
	
	private void assertCloudReady(MovieCollection movieCollection) throws WebServicesException {
		if (!applicationProperties.isAwsEnabled() && movieCollection.isCloud()) {
			throw new WebServicesException("Cloud services are disabled.");
		}
		return;
	}
	
	@Override
	public Optional<MovieCollection> getDefaultMovieCollection(String username) {
		Optional<String> collectionId = collectionDao.getDefaultCollectionId(username);
		return collectionId.isPresent()? collectionDao.getViewableMovieCollection(collectionId.get(), username) : Optional.empty();
	}

	@Override
	public void setDefaultMovieCollection(String collectionId, String callingUsername) throws CollectionSharingException {
		assertCollectionViewable(collectionId, callingUsername);		
		collectionDao.setDefaultCollection(callingUsername, collectionId);
		
	}

	@Override
	public List<MovieCollection> getViewableMovieCollections(String username) {
		return collectionDao.getViewableMovieCollections(username);
	}

	@Override
	public List<MovieCollection> getShareOfferMovieCollections(String username) {
		return collectionDao.getShareOfferMovieCollections(username);
	}

	@Override
	public MovieCollection getViewableMovieCollection(String collectionId, String callingUsername)	throws CollectionSharingException {
		Optional<MovieCollection> movieCollection = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollection.isPresent()) {
			throw new CollectionSharingException("User does not have permission to view collection.");
		}
		return movieCollection.get();
	}

	@Override
	public void addMovieCollection(MovieCollection movieCollection, String callingUsername) throws WebServicesException {
		movieCollection.setOwnerAndOwned(callingUsername, callingUsername); // enforce that movie collection owner is the calling username
		assertCloudReady(movieCollection);
		//TODO: Need to consider error control, especially considering the mirroring of movie collections (check other methods too)
		collectionDao.addMovieCollection(movieCollection);
		if (movieCollection.isCloud()) {
			try {
				dynamoCollectionRepository.save(movieCollection);
			} catch (Exception e) {
				throw new WebServicesException(e);
			}
		}
	}

	@Override
	public void updateMovieCollection(MovieCollection movieCollection, String callingUsername) throws CollectionSharingException, WebServicesException {
		Optional<MovieCollection> viewableMovieCollectionOptional = collectionDao.getViewableMovieCollection(movieCollection.getId(), callingUsername);
		if (!viewableMovieCollectionOptional.isPresent() || !viewableMovieCollectionOptional.get().isEditable()) { // collection must be editable
			throw new CollectionSharingException("User does not have required permission to update collection.");
		}
		MovieCollection editableMovieCollection = viewableMovieCollectionOptional.get();
		assertCloudReady(editableMovieCollection);
		editableMovieCollection.setName(movieCollection.getName());
		collectionDao.updateMovieCollection(editableMovieCollection);
		if (editableMovieCollection.isCloud()) {
			try {
				dynamoCollectionRepository.save(editableMovieCollection);
			} catch (Exception e) {
				throw new WebServicesException(e);
			}
		}
	}

	@Override
	public void deleteMovieCollection(String collectionId, String callingUsername) throws CollectionSharingException, WebServicesException {
		Optional<MovieCollection> movieCollectionOptional = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollectionOptional.isPresent() || !movieCollectionOptional.get().getOwner().equals(callingUsername)) { // only owner can delete collection
			throw new CollectionSharingException("User does not have required permission to delete collection.");
		}
		MovieCollection movieCollection = movieCollectionOptional.get();
		assertCloudReady(movieCollection);
		if (movieCollection.isCloud()) {
			try {
				dynamoMovieRepository.deleteByCollectionId(collectionId);
				dynamoCollectionRepository.deleteById(collectionId);
			} catch (Exception e) {
				throw new WebServicesException(e);
			}
		} else {
			movieDao.deleteMoviesForCollection(collectionId);
		}
		collectionDao.deleteMovieCollection(collectionId);
	}

	@Override
	public void shareMovieCollection(String collectionId, String shareWithUsername, boolean editable, String callingUsername) throws CollectionSharingException {
		assertCollectionEditable(collectionId, callingUsername);
		collectionDao.shareCollection(collectionId, shareWithUsername, editable);
	}

	@Override
	public void unshareMovieCollection(String collectionId, String unshareWithUsername, String callingUsername) throws CollectionSharingException {
		MovieCollection movieCollection = null;
		if (callingUsername.equals(unshareWithUsername)) {
			movieCollection = assertCollectionViewable(collectionId, callingUsername);
		} else {
			movieCollection = assertCollectionEditable(collectionId, callingUsername);
		}
		if (movieCollection.getOwner().equals(unshareWithUsername)) {
			throw new CollectionSharingException("Collection owner permissions cannot be revoked.");
		}
		collectionDao.unshareCollection(collectionId, unshareWithUsername);
	}

	@Override
	public void updateEditable(String collectionId, String updateUsername, boolean editable, String callingUsername) throws CollectionSharingException {
		MovieCollection movieCollection = assertCollectionEditable(collectionId, callingUsername);
		if (movieCollection.getOwner().equals(updateUsername)) {
			throw new CollectionSharingException("Collection owner permissions cannot be changed.");
		}
		collectionDao.updateEditable(collectionId, updateUsername, editable);
		
	}

	@Override
	public void acceptShareOffer(String collectionId, String callingUsername) throws CollectionSharingException {
		if (!collectionDao.acceptShareOffer(collectionId, callingUsername)) {
			throw new CollectionSharingException("Unable to accept share offer for collection " + collectionId + " user " + callingUsername);
		}
	}

	@Override
	public void declineShareOffer(String collectionId, String callingUsername) throws CollectionSharingException {
		if (!collectionDao.unshareCollection(collectionId, callingUsername)) {
			throw new CollectionSharingException("Unable to decline share offer for collection " + collectionId + " user " + callingUsername);
		}
	}
	
	@Override
	public MovieCollection assertCollectionViewable(String collectionId, String callingUsername) throws CollectionSharingException {
		Optional<MovieCollection> movieCollection = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollection.isPresent()) { // collection must be viewable
			throw new CollectionSharingException("User does not have required permission to update share permission.");
		}
		return movieCollection.get();
	}

	@Override
	public MovieCollection assertCollectionEditable(String collectionId, String callingUsername) throws CollectionSharingException {
		Optional<MovieCollection> movieCollection = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollection.isPresent() || !movieCollection.get().isEditable()) { // collection must be editable
			throw new CollectionSharingException("User does not have required permission to update share permission.");
		}
		return movieCollection.get();
	}

	@Override
	public List<CollectionPermission> getCollectionPermissions(String collectionId, String callingUsername) throws CollectionSharingException {
		Optional<MovieCollection> movieCollection = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollection.isPresent() || !callingUsername.equals(movieCollection.get().getOwner())) {
			throw new CollectionSharingException("User can only view sharing permissions of collections they are the owner of.");
		}
		return collectionDao.getCollectionPermissions(collectionId);
	}

	@Override
	public Optional<CollectionPermission> getCollectionPermission(String collectionId, String username, String callingUsername) throws CollectionSharingException {
		Optional<MovieCollection> movieCollection = collectionDao.getViewableMovieCollection(collectionId, callingUsername);
		if (!movieCollection.isPresent() || !callingUsername.equals(movieCollection.get().getOwner())) {
			throw new CollectionSharingException("User can only view sharing permissions of collections they are the owner of.");
		}
		return collectionDao.getCollectionPermission(collectionId, username);
	}

	@Override
	public void importCollection(MultipartFile mFile, String collectionName, boolean cloud, List<String> sheetNames, List<String> columnNames, String callingUsername) throws IOException {
		long startTime = System.currentTimeMillis();
		ExcelPorter excelImporter = new ExcelPorter(mFile.getInputStream(), mFile.getOriginalFilename());
		List<Movie> movies = new ArrayList<Movie>();
		for (String sheetName : sheetNames) {
			movies.addAll(excelImporter.getMoviesForSheet(sheetName, columnNames));
		}
		long parseTime = System.currentTimeMillis();
		MovieCollection movieCollection = new MovieCollection();
		movieCollection.setName(collectionName);
		movieCollection.setOwnerAndOwned(callingUsername, callingUsername);
		movieCollection.setEditable(true);
		movieCollection.setCloud(cloud);
		collectionDao.addMovieCollection(movieCollection);
		if (cloud) {
			dynamoCollectionRepository.save(movieCollection); // key will have already been set by mirrored movie collection save
		}
		movies.forEach(movie -> movie.setCollectionId(movieCollection.getId()));
		if (cloud) {
			movies.forEach(movie -> movie.setId(keyGenerator.getKey())); // TODO: Could redefine the DynamoDB table to have auto-generated keys
			dynamoMovieRepository.saveAll(movies);
		} else {
			movieDao.addMovies(movies);
		}
		long storeTime = System.currentTimeMillis();
		LOGGER.info("Time to parse: " + String.valueOf(parseTime - startTime) + "ms; Time to store: " + String.valueOf(storeTime - parseTime) + " ms");
	}
}