Skip to content

Commit cdd9599

Browse files
committed
Merge branch 'hotfix-1.4.6'
2 parents 160f510 + f3982a9 commit cdd9599

29 files changed

+439
-211
lines changed

docs/release-notes.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Release Notes
22

3+
## 1.4.6
4+
5+
This release has a few fixes that provide better user feedback during registration and while updating the user profile.
6+
7+
Fix deletion of user in the administrative section. Its associated tokens were somehow set to `NULL` by Hibernate before
8+
being deleted.
9+
10+
Hide research focus in the user profile if nothing has been entered.
11+
312
## 1.4.5
413

514
This release resolves an issue with slow SMTP servers causing a proxy timeout by sending emails asynchronously.

mkdocs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ nav:
1212
plugins:
1313
- macros
1414
extra:
15-
rdp_version: 1.4.3
15+
rdp_version: 1.4.6

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>ubc.pavlab</groupId>
77
<artifactId>rdp</artifactId>
8-
<version>1.4.5</version>
8+
<version>1.4.6</version>
99

1010
<developers>
1111
<developer>
@@ -175,7 +175,7 @@
175175

176176
<properties>
177177
<java.version>1.8</java.version>
178-
<tomcat.version>8.5.60</tomcat.version>
178+
<tomcat.version>8.5.71</tomcat.version>
179179
</properties>
180180

181181
<build>

src/main/java/ubc/pavlab/rdp/controllers/LoginController.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public ModelAndView createNewUser( @Validated(User.ValidationUserAccount.class)
6767
RedirectAttributes redirectAttributes,
6868
Locale locale ) {
6969
ModelAndView modelAndView = new ModelAndView( "registration" );
70-
User userExists = userService.findUserByEmailNoAuth( user.getEmail() );
70+
User existingUser = userService.findUserByEmailNoAuth( user.getEmail() );
7171

7272
user.setEnabled( false );
7373

@@ -78,9 +78,15 @@ public ModelAndView createNewUser( @Validated(User.ValidationUserAccount.class)
7878
userProfile.setHideGenelist( false );
7979
userProfile.setContactEmailVerified( false );
8080

81-
if ( userExists != null ) {
82-
bindingResult.rejectValue( "email", "error.user", "There is already a user registered this email." );
83-
log.warn( "Trying to register an already registered email." );
81+
if ( existingUser != null ) {
82+
if ( existingUser.isEnabled() ) {
83+
bindingResult.rejectValue( "email", "error.user", "There is already a user registered this email." );
84+
} else {
85+
// maybe the user is attempting to re-register, unaware that the confirmation hasn't been processed
86+
userService.createVerificationTokenForUser( existingUser, locale );
87+
bindingResult.rejectValue( "email", "error.user", "You have already registered an account with this email. We just sent you a new confirmation email." );
88+
}
89+
log.warn( "Trying to register an already registered email: " + user.getEmail() + "." );
8490
}
8591

8692
if ( bindingResult.hasErrors() ) {

src/main/java/ubc/pavlab/rdp/controllers/UserController.java

+49-7
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
import lombok.extern.apachecommons.CommonsLog;
66
import org.hibernate.validator.constraints.NotEmpty;
77
import org.springframework.beans.factory.annotation.Autowired;
8-
import org.springframework.context.ApplicationEventPublisher;
8+
import org.springframework.context.MessageSource;
99
import org.springframework.http.HttpStatus;
1010
import org.springframework.http.MediaType;
1111
import org.springframework.http.ResponseEntity;
1212
import org.springframework.security.access.annotation.Secured;
1313
import org.springframework.security.authentication.BadCredentialsException;
1414
import org.springframework.stereotype.Controller;
1515
import org.springframework.validation.BindingResult;
16+
import org.springframework.validation.FieldError;
1617
import org.springframework.web.bind.annotation.*;
1718
import org.springframework.web.multipart.MultipartFile;
1819
import org.springframework.web.servlet.ModelAndView;
@@ -57,6 +58,9 @@ public class UserController {
5758
@Autowired
5859
private ApplicationSettings applicationSettings;
5960

61+
@Autowired
62+
private MessageSource messageSource;
63+
6064
@GetMapping(value = { "/user/home" })
6165
public ModelAndView userHome() {
6266
ModelAndView modelAndView = new ModelAndView( "user/home" );
@@ -243,16 +247,54 @@ public String verifyContactEmail( @RequestParam String token, RedirectAttributes
243247
@Data
244248
static class ProfileWithOrganUberonIds {
245249
@Valid
246-
private Profile profile;
247-
private Set<String> organUberonIds;
250+
private final Profile profile;
251+
private final Set<String> organUberonIds;
252+
}
253+
254+
@Data
255+
static class ProfileSavedModel {
256+
private final String message;
257+
private final boolean contactEmailVerified;
258+
}
259+
260+
@Data
261+
static class FieldErrorModel {
262+
private final String field;
263+
private final String message;
264+
private final Object rejectedValue;
265+
266+
public static FieldErrorModel fromFieldError( FieldError fieldError ) {
267+
return new FieldErrorModel( fieldError.getField(), fieldError.getDefaultMessage(), fieldError.getRejectedValue() );
268+
}
269+
}
270+
271+
@Data
272+
static class BindingResultModel {
273+
private final List<FieldErrorModel> fieldErrors;
274+
275+
public static BindingResultModel fromBindingResult( BindingResult bindingResult ) {
276+
return new BindingResultModel( bindingResult.getFieldErrors().stream().map( FieldErrorModel::fromFieldError ).collect( Collectors.toList() ) );
277+
}
248278
}
249279

250280
@ResponseBody
251-
@PostMapping(value = "/user/profile", produces = MediaType.TEXT_PLAIN_VALUE)
252-
public String saveProfile( @RequestBody ProfileWithOrganUberonIds profileWithOrganUberonIds, Locale locale ) {
281+
@PostMapping(value = "/user/profile", produces = MediaType.APPLICATION_JSON_VALUE)
282+
public ResponseEntity<?> saveProfile( @Valid @RequestBody ProfileWithOrganUberonIds profileWithOrganUberonIds, BindingResult bindingResult, Locale locale ) {
253283
User user = userService.findCurrentUser();
254-
userService.updateUserProfileAndPublicationsAndOrgans( user, profileWithOrganUberonIds.profile, profileWithOrganUberonIds.profile.getPublications(), profileWithOrganUberonIds.organUberonIds, locale );
255-
return "Saved.";
284+
if ( bindingResult.hasErrors() ) {
285+
return ResponseEntity.badRequest()
286+
.body( BindingResultModel.fromBindingResult( bindingResult ) );
287+
} else {
288+
String previousContactEmail = user.getProfile().getContactEmail();
289+
user = userService.updateUserProfileAndPublicationsAndOrgans( user, profileWithOrganUberonIds.profile, profileWithOrganUberonIds.profile.getPublications(), profileWithOrganUberonIds.organUberonIds, locale );
290+
String message = messageSource.getMessage( "UserController.profileSaved", new Object[]{ user.getProfile().getContactEmail() }, locale );
291+
if ( user.getProfile().getContactEmail() != null &&
292+
!user.getProfile().getContactEmail().equals( previousContactEmail ) &&
293+
!user.getProfile().isContactEmailVerified() ) {
294+
message = messageSource.getMessage( "UserController.profileSavedAndContactEmailUpdated", new String[]{ user.getProfile().getContactEmail() }, locale );
295+
}
296+
return ResponseEntity.ok( new ProfileSavedModel( message, user.getProfile().isContactEmailVerified() ) );
297+
}
256298
}
257299

258300
@ResponseBody

src/main/java/ubc/pavlab/rdp/model/AccessToken.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public class AccessToken extends Token implements UserContent {
2222
@GeneratedValue(strategy = GenerationType.AUTO)
2323
private Integer id;
2424

25-
@ManyToOne(fetch = FetchType.EAGER)
26-
@JoinColumn(name = "user_id", nullable = false)
25+
@ManyToOne(optional = false)
26+
@JoinColumn(name = "user_id")
2727
private User user;
2828

2929
@Override

src/main/java/ubc/pavlab/rdp/model/PasswordResetToken.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public class PasswordResetToken extends Token implements UserContent {
2626
@GeneratedValue(strategy = GenerationType.AUTO)
2727
private Integer id;
2828

29-
@OneToOne(fetch = FetchType.EAGER)
30-
@JoinColumn(nullable = false, name = "user_id")
29+
@ManyToOne(optional = false)
30+
@JoinColumn(name = "user_id")
3131
private User user;
3232

3333
@Override

src/main/java/ubc/pavlab/rdp/model/User.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,17 @@ public interface ValidationServiceAccount {
7676
@JsonIgnore
7777
private final Set<Role> roles = new HashSet<>();
7878

79-
@OneToMany(cascade = CascadeType.ALL)
79+
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
8080
@JoinColumn(name = "user_id")
8181
@JsonIgnore
8282
private final Set<AccessToken> accessTokens = new HashSet<>();
8383

84-
@OneToMany(cascade = CascadeType.ALL)
84+
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
8585
@JoinColumn(name = "user_id")
8686
@JsonIgnore
8787
private final Set<VerificationToken> verificationTokens = new HashSet<>();
8888

89-
@OneToMany(cascade = CascadeType.ALL)
89+
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
9090
@JoinColumn(name = "user_id")
9191
@JsonIgnore
9292
private final Set<PasswordResetToken> passwordResetTokens = new HashSet<>();

src/main/java/ubc/pavlab/rdp/model/VerificationToken.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ public class VerificationToken extends Token implements UserContent {
2727
@GeneratedValue(strategy = GenerationType.AUTO)
2828
private Integer id;
2929

30-
@OneToOne(fetch = FetchType.EAGER)
31-
@JoinColumn(nullable = false, name = "user_id")
30+
@ManyToOne(optional = false)
31+
@JoinColumn(name = "user_id")
3232
private User user;
3333

3434
@Email

src/main/java/ubc/pavlab/rdp/services/RemoteResourceService.java

+34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ubc.pavlab.rdp.services;
22

3+
import ubc.pavlab.rdp.controllers.ApiController;
34
import ubc.pavlab.rdp.exception.RemoteException;
45
import ubc.pavlab.rdp.model.Taxon;
56
import ubc.pavlab.rdp.model.User;
@@ -10,6 +11,7 @@
1011

1112
import java.net.URI;
1213
import java.util.Collection;
14+
import java.util.Locale;
1315
import java.util.Set;
1416
import java.util.UUID;
1517

@@ -19,15 +21,47 @@
1921
*/
2022
public interface RemoteResourceService {
2123

24+
/**
25+
* Get the version of the remote API by reading its OpenAPI specification.
26+
*
27+
* @param remoteHost from which only the authority is used with {@link URI#getAuthority()}
28+
* @return the API version
29+
* @throws RemoteException if any error occured while retrieving the API version
30+
*/
2231
String getApiVersion( URI remoteHost ) throws RemoteException;
2332

33+
/**
34+
* Find users by name among all partner registries.
35+
*
36+
* @see ApiController#searchUsersByName(String, Boolean, Set, Set, Set, String, String, Locale)
37+
*/
2438
Collection<User> findUsersByLikeName( String nameLike, Boolean prefix, Set<ResearcherPosition> researcherPositions, Collection<ResearcherCategory> researcherTypes, Collection<String> organUberonIds );
2539

40+
/**
41+
* Find users by description among all partner registries.
42+
*
43+
* @see ApiController#searchUsersByDescription(String, Set, Set, Set, String, String, Locale)
44+
*/
2645
Collection<User> findUsersByDescription( String descriptionLike, Set<ResearcherPosition> researcherPositions, Collection<ResearcherCategory> researcherTypes, Collection<String> organUberonIds );
2746

47+
/**
48+
* Find genes by symbol among all partner registries.
49+
*
50+
* @see ApiController#searchUsersByGeneSymbol(String, Integer, Set, Integer, Set, Set, Set, String, String, Locale)
51+
*/
2852
Collection<UserGene> findGenesBySymbol( String symbol, Taxon taxon, Set<TierType> tier, Integer orthologTaxonId, Set<ResearcherPosition> researcherPositions, Set<ResearcherCategory> researcherTypes, Set<String> organUberonIds );
2953

54+
/**
55+
* Retrieve a user from a specific registry.
56+
*
57+
* @see ApiController#getUserById(Integer, String, String, Locale)
58+
*/
3059
User getRemoteUser( Integer userId, URI remoteHost ) throws RemoteException;
3160

61+
/**
62+
* Retrieve an anonymized user from a specific registry.
63+
*
64+
* @see ApiController#getUserByAnonymousId(UUID, String, String, Locale)
65+
*/
3266
User getAnonymizedUser( UUID anonymousId, URI remoteHost ) throws RemoteException;
3367
}

src/main/java/ubc/pavlab/rdp/services/RemoteResourceServiceImpl.java

+8-12
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
import org.springframework.stereotype.Service;
1212
import org.springframework.util.LinkedMultiValueMap;
1313
import org.springframework.util.MultiValueMap;
14-
import org.springframework.util.concurrent.ListenableFuture;
15-
import org.springframework.util.concurrent.ListenableFutureCallback;
1614
import org.springframework.web.client.AsyncRestTemplate;
17-
import org.springframework.web.client.RestClientException;
1815
import org.springframework.web.util.UriComponentsBuilder;
1916
import ubc.pavlab.rdp.exception.RemoteException;
2017
import ubc.pavlab.rdp.model.Taxon;
@@ -69,9 +66,11 @@ public String getApiVersion( URI remoteHost ) throws RemoteException {
6966
.toUri();
7067
try {
7168
OpenAPI openAPI = asyncRestTemplate.getForEntity( uri, OpenAPI.class ).get().getBody();
72-
// OpenAPI specification was introduced in 1.4, so we assume 1.0.0 for previous versions
69+
// The OpenAPI specification was introduced in 1.4, so we assume 1.0.0 for previous versions
7370
if ( openAPI.getInfo() == null ) {
7471
return "1.0.0";
72+
} else if ( openAPI.getInfo().getVersion().equals( "v0" ) ) {
73+
return "1.4.0"; // the version number was missing in early 1.4
7574
} else {
7675
return openAPI.getInfo().getVersion();
7776
}
@@ -146,14 +145,7 @@ public User getRemoteUser( Integer userId, URI remoteHost ) throws RemoteExcepti
146145
.buildAndExpand( Collections.singletonMap( "userId", userId ) )
147146
.toUri();
148147

149-
try {
150-
ResponseEntity<User> responseEntity = asyncRestTemplate.getForEntity( uri, User.class ).get();
151-
User user = responseEntity.getBody();
152-
initUser( user );
153-
return user;
154-
} catch ( InterruptedException | ExecutionException e ) {
155-
throw new RemoteException( MessageFormat.format( "Unsuccessful response received for {0}.", uri ), e );
156-
}
148+
return getUserByUri( uri );
157149
}
158150

159151
@Override
@@ -174,6 +166,10 @@ public User getAnonymizedUser( UUID anonymousId, URI remoteHost ) throws RemoteE
174166
.buildAndExpand( Collections.singletonMap( "anonymousId", anonymousId ) )
175167
.toUri();
176168

169+
return getUserByUri( uri );
170+
}
171+
172+
private User getUserByUri( URI uri ) throws RemoteException {
177173
try {
178174
ResponseEntity<User> responseEntity = asyncRestTemplate.getForEntity( uri, User.class ).get();
179175
User user = responseEntity.getBody();

src/main/java/ubc/pavlab/rdp/util/VersionUtils.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package ubc.pavlab.rdp.util;
22

3+
import lombok.extern.apachecommons.CommonsLog;
4+
5+
@CommonsLog
36
public class VersionUtils {
47

58
private static final int[] FACTORS = new int[]{ 99 * 99 * 99, 99 * 99, 99 };
69

7-
private static int parseVersion( String version ) {
10+
private static int parseVersion( String version ) throws IllegalArgumentException {
811
int i = 0;
912
int v = 0;
1013
String[] components = version.split( "\\." );
1114
for ( String c : components ) {
1215
int ci = Integer.parseInt( c );
1316
if ( ci < 0 || ci > 99 ) {
14-
throw new RuntimeException( "Version component must be within 0 and 99." );
17+
throw new IllegalArgumentException( "Version component must be within 0 and 99." );
1518
}
1619
v += FACTORS[i++] * ci;
1720
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- I'm not exactly sure why this is necessary, but Hibernate set this column to NULL before removing it
2+
alter table access_token modify column user_id integer null;
3+
alter table password_reset_token modify column user_id integer null;
4+
alter table verification_token modify column user_id integer null;

src/main/resources/messages.properties

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ TierType.TIER1=Tier 1
3636
TierType.TIER2=Tier 2
3737
TierType.TIER3=Tier 3
3838

39-
AbstractUserDetailsAuthenticationProvider.badCredentials=Username/Password is incorrect.
39+
AbstractUserDetailsAuthenticationProvider.badCredentials=Your username or password is incorrect.
4040
AbstractUserDetailsAuthenticationProvider.disabled=Your account is disabled, please confirm your email. <a href="resendConfirmation">Click here</a> to resend confirmation.
4141
AbstractUserDetailsAuthenticationProvider.expired=User account has expired.
4242
AbstractUserDetailsAuthenticationProvider.locked=User account is locked.
@@ -55,6 +55,11 @@ SearchController.errorNoOrthologs=No orthologs of <strong>{0}</strong> found in
5555
# {1} contains the taxon scientific name
5656
SearchController.errorNoGene=Unknown gene <strong>{0}</strong> in taxon <em>{1}</em>.
5757

58+
UserController.profileSaved=Your profile has been saved.
59+
# {0} contains the contact email
60+
UserController.profileSavedAndContactEmailUpdated=Your profile has been saved. Your contact email was updated and an \
61+
email with a verification link has been sent to {0}.
62+
5863
# {0} contains the site shortname
5964
ApiConfig.title={0} RESTful API
6065
# {0} contains the site shortname

0 commit comments

Comments
 (0)