diff --git a/pom.xml b/pom.xml index 0f927f0..cb1cee9 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,11 @@ org.springframework.boot spring-boot-starter-data-mongodb + + com.bol + spring-data-mongodb-encrypt + 2.2 + org.springframework.boot spring-boot-starter-data-redis diff --git a/src/main/java/com/vinodh/config/MongoConfig.java b/src/main/java/com/vinodh/config/MongoConfig.java new file mode 100644 index 0000000..ba04560 --- /dev/null +++ b/src/main/java/com/vinodh/config/MongoConfig.java @@ -0,0 +1,86 @@ +package com.vinodh.config; + +import com.bol.crypt.CryptVault; +import com.bol.secure.CachedEncryptionEventListener; +import com.bol.secure.ReflectionEncryptionEventListener; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientURI; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.mongodb.config.AbstractMongoConfiguration; +import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +import java.util.Base64; + +/** + * @author thimmv + * @createdAt 23-06-2019 22:00 + */ +@Configuration +@EnableMongoRepositories(basePackages = "com.vinodh.repository") +@PropertySource("classpath:application.yml") +public class MongoConfig extends AbstractMongoConfiguration { + + // normally you would use @Value to wire a property here + private static final byte[] secretKey = Base64.getDecoder().decode("hqHKBLV83LpCqzKpf8OvutbCs+O5wX5BPu3btWpEvXA="); + private static final byte[] oldKey = Base64.getDecoder().decode("cUzurmCcL+K252XDJhhWI/A/+wxYXLgIm678bwsE2QM="); + +// @Value("${spring.data.mongodb.secretKey}") +// private String secretKey; +// +// @Value("${spring.data.mongodb.oldKey}") +// private String oldKey; + + @Value("${spring.data.mongodb.database}") + private String database; + + @Value("${spring.data.mongodb.uri}") + private String uri; + + @Override + public MongoClient mongoClient() { + return new MongoClient(new MongoClientURI(uri)); + } + + @Override + @Bean + public MappingMongoConverter mappingMongoConverter() throws Exception { + MappingMongoConverter converter = super.mappingMongoConverter(); + // NB: without overriding defaultMongoTypeMapper, an _class field is put in every document + // since we know exactly which java class a specific document maps to, this is surplus + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + return converter; + } + + @Override + protected String getDatabaseName() { + return database; + } + + @Bean + public CryptVault cryptVault() { + return new CryptVault() + .with256BitAesCbcPkcs5PaddingAnd128BitSaltKey(0, oldKey) + .with256BitAesCbcPkcs5PaddingAnd128BitSaltKey(1, secretKey) + // can be omitted if it's the highest version + .withDefaultKeyVersion(1); + } + + @Bean + @Primary + public ReflectionEncryptionEventListener encryptionEventListener(CryptVault cryptVault) { + return new ReflectionEncryptionEventListener(cryptVault); + } + +// @Bean +// public MongoEncryptionListener encryptionEventListener() { +// return new MongoEncryptionListener(); +// } + + +} diff --git a/src/main/java/com/vinodh/config/MongoEncryptionListener.java b/src/main/java/com/vinodh/config/MongoEncryptionListener.java new file mode 100644 index 0000000..7e1996f --- /dev/null +++ b/src/main/java/com/vinodh/config/MongoEncryptionListener.java @@ -0,0 +1,76 @@ +//package com.vinodh.config; +// +//import com.bol.crypt.CryptVault; +//import com.bol.crypt.FieldCryptException; +//import com.bol.secure.AbstractEncryptionEventListener; +//import com.bol.secure.Encrypted; +//import org.bson.Document; +//import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent; +//import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; +//import org.springframework.stereotype.Component; +//import org.springframework.util.ReflectionUtils; +// +//import java.lang.reflect.Field; +//import java.util.Map; +// +///** +// * @author thimmv +// * @createdAt 23-06-2019 22:40 +// */ +////@Component +//public class MongoEncryptionListener extends AbstractEncryptionEventListener { +// +// public MongoEncryptionListener(CryptVault cryptVault) { +// super(cryptVault); +// } +// +// @Override +// public void onBeforeConvert(BeforeConvertEvent event) { +// Class targetClass = event.getCollectionName().getClass(); +// Document targetDocument = event.getDocument(); +// for (Map.Entry fieldsMap : targetDocument.entrySet()) { +// String fieldName = fieldsMap.getKey(); +// if (fieldName.equals("_class")) continue; +// Field field = ReflectionUtils.findField(targetClass, fieldName); +// field.setAccessible(true); +// if (field == null) continue; +// +// Object fieldValue = fieldsMap.getValue(); +// if (field.isAnnotationPresent(Encrypted.class)) { +// // direct encryption +// try { +// targetDocument.put(fieldName, cryptVault.encrypt(fieldValue.toString().getBytes())); +// } catch (Exception e) { +// throw new FieldCryptException(fieldName, e); +// } +// +// } +// field.setAccessible(true); +// } +// } +// +// @Override +// public void onAfterConvert(AfterConvertEvent event) { +// Class targetClass = event.getCollectionName().getClass(); +// Document targetDocument = event.getDocument(); +// for (Map.Entry fieldsMap : targetDocument.entrySet()) { +// String fieldName = fieldsMap.getKey(); +// if (fieldName.equals("_class")) continue; +// Field field = ReflectionUtils.findField(targetClass, fieldName); +// field.setAccessible(true); +// if (field == null) continue; +// +// Object fieldValue = fieldsMap.getValue(); +// if (field.isAnnotationPresent(Encrypted.class)) { +// // direct encryption +// try { +// targetDocument.put(fieldName, cryptVault.decrypt(fieldValue.toString().getBytes())); +// } catch (Exception e) { +// throw new FieldCryptException(fieldName, e); +// } +// +// } +// field.setAccessible(true); +// } +// } +//} diff --git a/src/main/java/com/vinodh/controller/MovieController.java b/src/main/java/com/vinodh/controller/MovieController.java new file mode 100644 index 0000000..35eea95 --- /dev/null +++ b/src/main/java/com/vinodh/controller/MovieController.java @@ -0,0 +1,87 @@ +package com.vinodh.controller; + +import com.bol.crypt.CryptVault; +import com.vinodh.documents.Movie; +import com.vinodh.service.MovieService; +import com.vinodh.service.NextSequenceService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:41 + */ +@RestController +@RequestMapping("/movie") +@Api(value = "Set of endpoints for Creating, Retrieving, Updating and Deleting of Movie information.") +@Slf4j +public class MovieController { + + + @Autowired + private MovieService movieService; + + @Autowired + private CryptVault cryptVault; + + @Autowired + private NextSequenceService nextSequenceService; + + @GetMapping("/findAll") + @ApiOperation(value = "Find All available movies") + public ResponseEntity> getAllMovies() { + return ResponseEntity.ok(movieService.findAll()); + } + + @GetMapping("/findById") + @ApiOperation(value = "Find Movie based on ID") + public ResponseEntity findById(@ApiParam(value = "Find Movie based on ID.", required = true) @RequestParam Long id) { + return ResponseEntity.ok(movieService.findById(id)); + } + + @GetMapping("/findByTitle") + @ApiOperation(value = "Find Movie based on NAME") + public ResponseEntity> findByTitle(@ApiParam(value = "Find Movie based on Name.", required = true) @RequestParam String title) { + return ResponseEntity.ok(movieService.findByTitle(title)); + } + + @PostMapping("/save") + @ApiOperation(value = "Save new Movie details") + public ResponseEntity save(@ApiParam(value = "Movie information to be created.", required = true) @RequestBody Movie movie) { + if (movie.getId() == null) movie.setId(nextSequenceService.getNextSequence()); + return ResponseEntity.ok(movieService.save(movie)); + } + + @PutMapping("/update") + @ApiOperation(value = "Update Movie details") + public ResponseEntity update(@ApiParam(value = "Movie information to be updated.", required = true) @RequestBody Movie movie) { + return ResponseEntity.ok(movieService.update(movie)); + } + + @DeleteMapping("/delete") + @ApiOperation(value = "Delete Movie details") + public void deleteUserByID(@ApiParam(value = "Delete Movie based on ID.", required = true) @RequestParam Long id) { + movieService.deleteById(id); + } + + @DeleteMapping("/deleteAll") + @ApiOperation(value = "Delete All Movie details") + public void deleteAll() { + movieService.deleteAll(); + } + +} diff --git a/src/main/java/com/vinodh/documents/CustomSequences.java b/src/main/java/com/vinodh/documents/CustomSequences.java new file mode 100644 index 0000000..d95127c --- /dev/null +++ b/src/main/java/com/vinodh/documents/CustomSequences.java @@ -0,0 +1,17 @@ +package com.vinodh.documents; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@NoArgsConstructor +@Setter +@Getter +@Document(collection = "customSequences") +public class CustomSequences { + @Id + private String id; + private Long seq; +} \ No newline at end of file diff --git a/src/main/java/com/vinodh/documents/ImdbRating.java b/src/main/java/com/vinodh/documents/ImdbRating.java new file mode 100644 index 0000000..9b54a98 --- /dev/null +++ b/src/main/java/com/vinodh/documents/ImdbRating.java @@ -0,0 +1,18 @@ +package com.vinodh.documents; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:49 + */ +@NoArgsConstructor +@Setter +@Getter +public class ImdbRating { + private Long id; + private double rating; + private long votes; +} diff --git a/src/main/java/com/vinodh/documents/Movie.java b/src/main/java/com/vinodh/documents/Movie.java new file mode 100644 index 0000000..e6938df --- /dev/null +++ b/src/main/java/com/vinodh/documents/Movie.java @@ -0,0 +1,44 @@ +package com.vinodh.documents; + +import com.bol.secure.Encrypted; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:42 + */ +@Document("movies") +@NoArgsConstructor +@Setter +@Getter +public class Movie { + + @Id + private Long id; + + @Encrypted + private String title; + + private int year, runtime; + + private LocalDateTime released; + + @Encrypted + private String poster, plot; + + private Set cast = new HashSet<>(); + private Set directors = new HashSet<>(); + private Set countries = new HashSet<>(); + private Set genres = new HashSet<>(); + + private ImdbRating imdb; + +} diff --git a/src/main/java/com/vinodh/repository/MovieRepository.java b/src/main/java/com/vinodh/repository/MovieRepository.java new file mode 100644 index 0000000..91e9661 --- /dev/null +++ b/src/main/java/com/vinodh/repository/MovieRepository.java @@ -0,0 +1,17 @@ +package com.vinodh.repository; + +import com.vinodh.documents.Movie; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:43 + */ +@Repository +public interface MovieRepository extends MongoRepository { + List findByTitle(String name); + +} diff --git a/src/main/java/com/vinodh/service/EmployeeServiceImpl.java b/src/main/java/com/vinodh/service/EmployeeServiceImpl.java index c0d0fc2..eb69435 100644 --- a/src/main/java/com/vinodh/service/EmployeeServiceImpl.java +++ b/src/main/java/com/vinodh/service/EmployeeServiceImpl.java @@ -47,6 +47,7 @@ public List findByName(String name) { } @Override + @CachePut(value = "Employee_Cache", key = "#employee.id") public Employee save(Employee employee) { return employeeRepository.save(employee); } diff --git a/src/main/java/com/vinodh/service/MovieService.java b/src/main/java/com/vinodh/service/MovieService.java new file mode 100644 index 0000000..15a4060 --- /dev/null +++ b/src/main/java/com/vinodh/service/MovieService.java @@ -0,0 +1,26 @@ +package com.vinodh.service; + +import com.vinodh.documents.Movie; + +import java.util.List; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:42 + */ +public interface MovieService { + + List findAll(); + + Movie findById(Long id); + + List findByTitle(String name); + + Movie save(Movie movie); + + Movie update(Movie movie); + + void deleteById(Long id); + + void deleteAll(); +} diff --git a/src/main/java/com/vinodh/service/MovieServiceImpl.java b/src/main/java/com/vinodh/service/MovieServiceImpl.java new file mode 100644 index 0000000..cc2b932 --- /dev/null +++ b/src/main/java/com/vinodh/service/MovieServiceImpl.java @@ -0,0 +1,81 @@ +package com.vinodh.service; + +import com.bol.crypt.CryptVault; +import com.vinodh.documents.Movie; +import com.vinodh.repository.MovieRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author thimmv + * @createdAt 23-06-2019 15:43 + */ +@Service +@Slf4j +public class MovieServiceImpl implements MovieService { + + @Autowired + private MovieRepository movieRepository; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private CryptVault cryptVault; + + @Override + public List findAll() { + return movieRepository.findAll(); + } + + @Override + @Cacheable(value = "Movie_Cache", key = "#id") + public Movie findById(Long id) { + return movieRepository.findById(id).get(); + } + + @Override + @CachePut(value = "Movie_Cache", key = "#title") + public List findByTitle(String title) { + byte[] encrypt = cryptVault.encrypt(title.getBytes()); + List movies = mongoTemplate.find(query(where("title : " + encrypt)), Movie.class); + return movieRepository.findByTitle(title); + } + + @Override + @CachePut(value = "Movie_Cache", key = "#movie.id") + public Movie save(Movie movie) { + return movieRepository.save(movie); + } + + @Override + @CachePut(value = "Movie_Cache", key = "#movie.id") + public Movie update(Movie movie) { + return movieRepository.save(movie); + } + + @Override + @CacheEvict(value = "Movie_Cache", key = "#movie.id") + public void deleteById(Long id) { + movieRepository.deleteById(id); + } + + @Override + @CacheEvict(value = "Movie_Cache", allEntries = false) + public void deleteAll() { + movieRepository.deleteAll(); + } +} diff --git a/src/main/java/com/vinodh/service/NextSequenceService.java b/src/main/java/com/vinodh/service/NextSequenceService.java new file mode 100644 index 0000000..ff6c985 --- /dev/null +++ b/src/main/java/com/vinodh/service/NextSequenceService.java @@ -0,0 +1,27 @@ +package com.vinodh.service; + +import com.vinodh.documents.CustomSequences; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; +import static org.springframework.data.mongodb.core.FindAndModifyOptions.options; + +@Service +public class NextSequenceService { + + @Autowired + private MongoOperations mongoOperations; + + public long getNextSequence() { + CustomSequences counter = mongoOperations.findAndModify( + query(where("_id").is("customSequences")), + new Update().inc("seq", 1), + options().returnNew(true).upsert(true), + CustomSequences.class); + return counter.getSeq(); + } +} \ No newline at end of file