Commit a47c4042 authored by Matija Obreza's avatar Matija Obreza

FIX #11 Improved material request handling

parent 2eb4c341
......@@ -98,18 +98,18 @@
<name>Releases</name>
<url>https://oss.sonatype.org/content/repositories/releases</url>
</repository>
<repository>
<id>sonatype mirror</id>
<url>http://search.maven.org/remotecontent?filepath=</url>
</repository>
<repository>
<id>sonatype mirror</id>
<url>http://search.maven.org/remotecontent?filepath=</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>sonatype mirror</id>
<url>http://search.maven.org/remotecontent?filepath=</url>
</pluginRepository>
</pluginRepositories>
<pluginRepositories>
<pluginRepository>
<id>sonatype mirror</id>
<url>http://search.maven.org/remotecontent?filepath=</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<!--Test dependencies -->
......@@ -479,14 +479,14 @@
<artifactId>hazelcast-hibernate4</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.apache.jena</groupId>
<artifactId>apache-jena-libs</artifactId>
<type>pom</type>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.bioversityinternational</groupId>
<artifactId>org.bioversityinternational.ontology</artifactId>
......@@ -544,7 +544,7 @@
<plugin>
<inherited>true</inherited>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<version>3.1</version>
<configuration>
<source>${jdk.source}</source>
<target>${jdk.target}</target>
......@@ -553,6 +553,16 @@
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.17</version>
<configuration>
<forkMode>once</forkMode>
<argLine>-Xms512m -Xmx1024m</argLine>
<testFailureIgnore>false</testFailureIgnore>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
......
......@@ -31,8 +31,14 @@ import org.genesys2.server.model.VersionedAuditedModel;
@Entity
@Table(name = "request")
public class MaterialRequest extends VersionedAuditedModel {
public static final int NOTVALIDATED = 0;
public static final int VALIDATED = 1;
public static final int DISPATCHED = 2;
@Column(length = 36, unique=true)
@Column
private int state = NOTVALIDATED;
@Column(length = 36, unique = true)
private String uuid;
@Column(length = 200, nullable = false)
......@@ -42,7 +48,7 @@ public class MaterialRequest extends VersionedAuditedModel {
@Lob
private String body;
@Column(length = 32, nullable = false)
@Column(length = 32, nullable = true)
private String pid;
@PrePersist
......@@ -55,6 +61,14 @@ public class MaterialRequest extends VersionedAuditedModel {
public MaterialRequest() {
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getUuid() {
return uuid;
}
......@@ -85,7 +99,8 @@ public class MaterialRequest extends VersionedAuditedModel {
}
/**
* @param pid EasySMTA PID
* @param pid
* EasySMTA PID
*/
public void setPid(String pid) {
this.pid = pid;
......
/**
* Copyright 2014 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.model.genesys;
import java.text.MessageFormat;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import org.apache.commons.lang.math.RandomUtils;
import org.genesys2.server.model.VersionedAuditedModel;
/**
* {@link MaterialRequest} is broken down into individual requests to
* institutes.
*
* @author matijaobreza
*
*/
@Entity
@Table(name = "requestsub")
public class MaterialSubRequest extends VersionedAuditedModel {
public static final int NOTCONFIRMED = 0;
public static final int CONFIRMED = 1;
@Column
private int state = NOTCONFIRMED;
@Column(length = 36, unique = true)
private String uuid;
@Column(length = 8, nullable = false)
private String instCode;
@Column(length = 200, nullable = true)
private String instEmail;
@Column(length = 100000)
@Lob
private String body;
@ManyToOne(cascade = {}, optional = false)
private MaterialRequest sourceRequest;
@PrePersist
void prepersist() {
if (this.uuid == null)
this.uuid = UUID.nameUUIDFromBytes(
("genesys:request:" + sourceRequest.getUuid() + ":" + instCode + ":" + System.currentTimeMillis() + ":" + RandomUtils.nextInt(100))
.getBytes()).toString();
}
public MaterialSubRequest() {
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getInstCode() {
return instCode;
}
public void setInstCode(String instCode) {
this.instCode = instCode;
}
public String getInstEmail() {
return instEmail;
}
public void setInstEmail(String instEmail) {
this.instEmail = instEmail;
}
public MaterialRequest getSourceRequest() {
return sourceRequest;
}
public void setSourceRequest(MaterialRequest sourceRequest) {
this.sourceRequest = sourceRequest;
}
@Override
public String toString() {
return MessageFormat.format("SubRequest uuid={0} inst={1} body={2}", uuid, instCode, body);
}
}
......@@ -107,6 +107,10 @@ public interface AccessionRepository extends JpaRepository<Accession, Long> {
@Query("select count(a.id) from Accession a where a.id in ( ?1 ) and a.availability = true")
long countAvailable(Set<Long> accessionIds);
@Query("select a.id from Accession a where a.id in ( ?1 ) and a.availability = true")
Set<Long> filterAvailable(Set<Long> accessionIds);
@Modifying
@Query("update Accession a set a.inSvalbard = true where a in ?1")
......@@ -114,4 +118,10 @@ public interface AccessionRepository extends JpaRepository<Accession, Long> {
Page<Accession> findByInstituteAndTaxSpecies(FaoInstitute institute, long taxSpecies, Pageable pageable);
@Query("select distinct a.institute from Accession a where a.id in ( ?1 )")
List<FaoInstitute> findDistinctInstitutesFor(Set<Long> accessionIds);
@Query("select distinct a.id from Accession a where a.institute = ?1 and a.id in ( ?2 )")
Set<Long> findByInstituteAndIds(FaoInstitute institute, Set<Long> accessionIds);
}
/**
* Copyright 2014 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.persistence.domain;
import org.genesys2.server.model.genesys.MaterialSubRequest;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MaterialSubRequestRepository extends JpaRepository<MaterialSubRequest, Long> {
MaterialSubRequest findByUuid(String uuid);
}
......@@ -123,7 +123,8 @@ public interface GenesysService {
void saveSvalbards(List<SvalbardData> svalbards);
long countAvailable(Set<Long> accessionIds);
Set<Long> filterAvailable(Set<Long> accessionIds);
void saveAccession(Accession... accession);
void updateAccessionCount(FaoInstitute institute);
......@@ -171,4 +172,8 @@ public interface GenesysService {
*/
Accession getAccession(String instCode, String acceNumb);
List<FaoInstitute> findHoldingInstitutes(Set<Long> accessionIds);
Set<Long> listAccessions(FaoInstitute holdingInstitute, Set<Long> accessionIds);
}
......@@ -16,17 +16,81 @@
package org.genesys2.server.service;
import java.io.IOException;
import java.util.Set;
import org.genesys2.server.model.genesys.MaterialRequest;
import org.genesys2.server.service.impl.EasySMTAConnector.EasySMTAUserData;
import org.genesys2.server.model.genesys.MaterialSubRequest;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
public interface RequestService {
MaterialRequest sendRequest(EasySMTAUserData pid, Set<Long> accessionIds);
MaterialRequest sendRequest(RequestInfo requestInfo, Set<Long> accessionIds) throws RequestException;
MaterialRequest createRequest(EasySMTAUserData pid, Set<Long> accessionIds);
MaterialRequest createRequest(RequestInfo requestInfo, EasySMTA.EasySMTAUserData pid, Set<Long> accessionIds) throws RequestException;
MaterialRequest getRequest(String uuid);
MaterialRequest validateClientRequest(String tokenUuid, String key) throws RequestException, NoSuchVerificationTokenException;
void relayRequest(MaterialSubRequest materialSubRequest);
MaterialSubRequest validateReceipt(String tokenUuid, String key) throws NoSuchVerificationTokenException;
static class RequestException extends Exception {
public RequestException(String message) {
super(message);
}
public RequestException(String message, IOException e) {
super(message, e);
}
}
static class NoPidException extends RuntimeException {
public NoPidException(String message) {
super(message);
}
}
public static class RequestInfo {
private String email;
private int purposeType;
private boolean preacceptSMTA;
private String notes;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getPurposeType() {
return purposeType;
}
public void setPurposeType(int purposeType) {
this.purposeType = purposeType;
}
public boolean isPreacceptSMTA() {
return preacceptSMTA;
}
public void setPreacceptSMTA(boolean preacceptSMTA) {
this.preacceptSMTA = preacceptSMTA;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
}
}
......@@ -24,6 +24,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
......@@ -197,6 +198,16 @@ public class GenesysServiceImpl implements GenesysService, TraitService, Dataset
return accessionRepository.findByInstitute(faoInstitute, pageable);
}
@Override
public List<FaoInstitute> findHoldingInstitutes(Set<Long> accessionIds) {
return accessionRepository.findDistinctInstitutesFor(accessionIds);
}
@Override
public Set<Long> listAccessions(FaoInstitute institute, Set<Long> accessionIds) {
return accessionRepository.findByInstituteAndIds(institute, accessionIds);
}
@Override
public Page<Accession> listAccessions(Collection<Long> accessionIds, Pageable pageable) {
if (accessionIds == null || accessionIds.size() == 0) {
......@@ -579,6 +590,13 @@ public class GenesysServiceImpl implements GenesysService, TraitService, Dataset
return 0;
return accessionRepository.countAvailable(accessionIds);
}
@Override
public Set<Long> filterAvailable(Set<Long> accessionIds) {
if (accessionIds == null || accessionIds.size() == 0)
return Collections.emptySet();
return accessionRepository.filterAvailable(accessionIds);
}
/**
* Returns datasets to which current user has 'WRITE'
......
......@@ -16,25 +16,38 @@
package org.genesys2.server.service.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.MaterialRequest;
import org.genesys2.server.model.genesys.MaterialSubRequest;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.impl.FaoInstituteSetting;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.persistence.domain.MaterialRequestRepository;
import org.genesys2.server.persistence.domain.MaterialSubRequestRepository;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.EMailService;
import org.genesys2.server.service.EasySMTA;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.RequestService;
import org.genesys2.server.service.impl.EasySMTAConnector.EasySMTAUserData;
import org.genesys2.server.service.TokenVerificationService;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
......@@ -44,10 +57,19 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
@Transactional(readOnly=true)
@Transactional(readOnly = true)
public class RequestServiceImpl implements RequestService {
private static final String REQUEST_TOKENTYPE = "confirm-request";
private static final String RECEIPT_TOKENTYPE = "confirm-receipt";
private static final Log LOG = LogFactory.getLog(RequestServiceImpl.class);
@Autowired
private EasySMTA pidChecker;
@Autowired
private TokenVerificationService tokenVerificationService;
@Autowired
private EMailService emailService;
......@@ -57,9 +79,15 @@ public class RequestServiceImpl implements RequestService {
@Autowired
private GenesysService genesysService;
@Autowired
private InstituteService instituteService;
@Autowired
private MaterialRequestRepository requestRepository;
@Autowired
private MaterialSubRequestRepository subRequestRepository;
@Value("${base.url}")
private String baseUrl;
......@@ -70,38 +98,74 @@ public class RequestServiceImpl implements RequestService {
@Override
@Transactional
public MaterialRequest sendRequest(EasySMTAUserData pid, Set<Long> accessionIds) {
MaterialRequest request = createRequest(pid, accessionIds);
public MaterialRequest sendRequest(RequestInfo requestInfo, Set<Long> accessionIds) throws RequestException {
Set<Long> availableAccessionIds = genesysService.filterAvailable(accessionIds);
Locale locale = LocaleContextHolder.getLocale();
System.err.println("Current locale: " + locale);
if (availableAccessionIds == null || availableAccessionIds.size() == 0) {
throw new RequestException("None of the selected accessions are available for distribution");
}
Article article = contentService.getGlobalArticle("smtp.material-request", Locale.ENGLISH);
// Check Easy-SMTA for PID
EasySMTA.EasySMTAUserData pid = pidChecker.getUserData(requestInfo.getEmail());
String mailSubject = "[" + request.getUuid() + "] " + article.getTitle();
Article article;
if (pid == null) {
LOG.warn("No such user in ITPGRFA system");
article = contentService.getGlobalArticle("smtp.material-confirm-no-pid", Locale.ENGLISH);
} else {
article = contentService.getGlobalArticle("smtp.material-confirm", Locale.ENGLISH);
}
MaterialRequest request = createRequest(requestInfo, pid, availableAccessionIds);
Page<Accession> accessions = genesysService.listAccessions(accessionIds, new PageRequest(0, Integer.MAX_VALUE));
// Generate verification token+key
VerificationToken verificationToken = tokenVerificationService.generateToken(REQUEST_TOKENTYPE, request.getUuid());
// Send email to the user with verification
Page<Accession> accessions = genesysService.listAccessions(availableAccessionIds, new PageRequest(0, Integer.MAX_VALUE));
// Create the root hash
Map<String, Object> root = new HashMap<String, Object>();
root.put("baseUrl", baseUrl);
root.put("verificationToken", verificationToken);
root.put("pid", pid);
root.put("accessions", accessions.getContent());
String mailBody = contentService.processTemplate(article.getBody(), root);
String mailSubject = "[" + request.getUuid() + "] " + article.getTitle();
LOG.info(">>>" + mailBody);
emailService.sendMail(requestsEmail, null, mailSubject, mailBody);
// send to user
emailService.sendMail(request.getEmail(), null, mailSubject, mailBody);
return request;
}
@Override
@Transactional(readOnly = false)
public MaterialRequest createRequest(EasySMTAUserData pid, Set<Long> accessionIds) {
public MaterialRequest createRequest(RequestInfo requestInfo, EasySMTA.EasySMTAUserData pid, Set<Long> accessionIds) throws RequestException {
Set<Long> availableAccessionIds = genesysService.filterAvailable(accessionIds);
if (availableAccessionIds == null || availableAccessionIds.size() == 0) {
throw new RequestException("None of the selected accessions are available for distribution");
}
MaterialRequest request = new MaterialRequest();
request.setEmail(pid.getEmail());
request.setPid(pid.getPid());
request.setEmail(requestInfo.getEmail());
if (pid != null)
request.setPid(pid.getPid());
RequestBody rb = new RequestBody(pid, accessionIds);
RequestBody rb = new RequestBody(requestInfo, pid, availableAccessionIds);
request.setBody(serialize(rb));
request = requestRepository.save(request);
LOG.info("Persisted new material request: " + request);
return request;
}
private String serialize(RequestBody rb) {
String x = null;
try {
x = mapper.writeValueAsString(rb);
......@@ -109,19 +173,167 @@ public class RequestServiceImpl implements RequestService {
} catch (JsonProcessingException e) {
e.printStackTrace();
}
request.setBody(x);
return x;
}
request = requestRepository.save(request);
LOG.info("Persisted new material request: " + request);
return request;
// Rollback for any exception
@Override
@Transactional
public MaterialRequest validateClientRequest(String tokenUuid, String key) throws NoSuchVerificationTokenException {