Skip to content

Commit a9618e5

Browse files
authored
Merge branch 'main' into main_marcxml21
2 parents 1774f7c + 8531448 commit a9618e5

11 files changed

Lines changed: 212 additions & 11 deletions

File tree

src/main/java/org/tdl/vireo/config/AppWebSecurityConfig.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
44

5+
import java.nio.charset.StandardCharsets;
6+
import java.util.regex.Pattern;
7+
8+
import org.springframework.context.annotation.Bean;
59
import org.springframework.context.annotation.Configuration;
610
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
711
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
812
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
13+
import org.springframework.security.web.firewall.HttpFirewall;
14+
import org.springframework.security.web.firewall.StrictHttpFirewall;
915
import org.tdl.vireo.auth.service.VireoUserDetailsService;
1016
import org.tdl.vireo.model.Role;
1117
import org.tdl.vireo.model.User;
@@ -18,6 +24,17 @@
1824
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
1925
public class AppWebSecurityConfig extends AuthWebSecurityConfig<User, UserRepo, VireoUserDetailsService> {
2026

27+
@Bean
28+
public HttpFirewall httpFirewall() {
29+
StrictHttpFirewall firewall = new StrictHttpFirewall();
30+
Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
31+
firewall.setAllowedHeaderValues(header -> {
32+
String parsed = new String(header.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
33+
return allowed.matcher(parsed).matches();
34+
});
35+
return firewall;
36+
}
37+
2138
@Override
2239
protected void configure(HttpSecurity http) throws Exception {
2340
// @formatter:off

src/main/java/org/tdl/vireo/controller/SubmissionController.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ public class SubmissionController {
193193
@Value("${app.documentType.rename:}")
194194
private String documentTypesToRename;
195195

196+
@Value("${app.secondaryDelimiter:|}")
197+
private String secondaryDelimiter;
198+
196199
@RequestMapping("/all")
197200
@PreAuthorize("hasRole('ADMIN')")
198201
public ApiResponse getAll() {
@@ -597,7 +600,7 @@ private void processBatchExport(
597600

598601
// Stream data rows
599602
for (Submission submission : submissions) {
600-
ExportPackage exportPackage = packagerUtility.packageExport(packager, submission, columns);
603+
ExportPackage exportPackage = packagerUtility.packageExport(packager, submission, columns, secondaryDelimiter);
601604
if (exportPackage.isMap()) {
602605
Map<String, String> rowData = (Map<String, String>) exportPackage.getPayload();
603606
Row row = worksheet.createRow(rowCount++);

src/main/java/org/tdl/vireo/model/CustomActionDefinition.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ public CustomActionDefinition(String label, Boolean isStudentVisible) {
3434
isStudentVisible(isStudentVisible);
3535
}
3636

37+
/**
38+
* @return the position
39+
*/
40+
@Override
41+
@JsonView(Views.SubmissionList.class)
42+
public Long getPosition() {
43+
return super.getPosition();
44+
}
45+
3746
/**
3847
* @return the label
3948
*/

src/main/java/org/tdl/vireo/model/packager/AbstractPackager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public EP packageExport(Submission submission, List<SubmissionListColumn> column
6767
throw new UnsupportedFormatterException("Exporter does not support submission list columns!");
6868
}
6969

70+
public EP packageExport(Submission submission, List<SubmissionListColumn> columns, String delimiter) throws UnsupportedFormatterException {
71+
throw new UnsupportedFormatterException("Exporter does not support submission list columns and delimiter!");
72+
}
73+
7074
public EP packageExport(Submission submission, Map<String, String> ds_docs) throws UnsupportedFormatterException {
7175
throw new UnsupportedFormatterException("Exporter does not support multiple docs !");
7276
}

src/main/java/org/tdl/vireo/model/packager/ExcelPackager.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,21 @@ public ExcelExportPackage packageExport(Submission submission, String manifest)
6565

6666
@Override
6767
public ExcelExportPackage packageExport(Submission submission, List<SubmissionListColumn> columns) {
68+
return new ExcelExportPackage(submission, "Excel", buildRow(submission, columns, ", "));
69+
}
70+
71+
@Override
72+
public ExcelExportPackage packageExport(Submission submission, List<SubmissionListColumn> columns, String delimiter) {
73+
return new ExcelExportPackage(submission, "Excel", buildRow(submission, columns, delimiter));
74+
}
75+
76+
private Map<String, String> buildRow(Submission submission, List<SubmissionListColumn> columns, String fieldValueDelimiter) {
6877
Map<String, String> row = new HashMap<String, String>();
6978
columns.forEach(column -> {
7079
Optional<String> predicate = Optional.ofNullable(column.getPredicate());
7180
if (predicate.isPresent()) {
72-
List<String> fieldValues = new ArrayList<String>();
73-
for (FieldValue fieldValue : submission.getFieldValues()) {
74-
if (fieldValue.getFieldPredicate().getValue().equals(predicate.get().trim())) {
75-
fieldValues.add(fieldValue.getValue());
76-
row.put(column.getTitle(), String.join(", ", fieldValues));
77-
} else {
78-
row.put(column.getTitle(), String.join(", ", fieldValues)); }
79-
}
81+
List<String> fieldValues = getFieldValues(submission, predicate.get().trim());
82+
row.put(column.getTitle(), String.join(fieldValueDelimiter, fieldValues));
8083
} else {
8184
if (column.getValuePath().size() > 0) {
8285
String[] valuePath = column.getValuePath().toArray(new String[column.getValuePath().size()]);
@@ -142,7 +145,22 @@ public ExcelExportPackage packageExport(Submission submission, List<SubmissionLi
142145
}
143146
}
144147
});
145-
return new ExcelExportPackage(submission, "Excel", row);
148+
return row;
149+
}
150+
151+
private List<String> getFieldValues(Submission submission, String predicate) {
152+
List<String> fieldValues = new ArrayList<String>();
153+
for (FieldValue fieldValue : submission.getFieldValues()) {
154+
if (matchesPredicate(fieldValue, predicate)) {
155+
fieldValues.add(fieldValue.getValue());
156+
}
157+
}
158+
return fieldValues;
159+
}
160+
161+
private boolean matchesPredicate(FieldValue fieldValue, String predicate) {
162+
String fieldPredicate = fieldValue.getFieldPredicate().getValue();
163+
return fieldPredicate.equals(predicate) || fieldPredicate.startsWith(predicate + ".");
146164
}
147165

148166
}

src/main/java/org/tdl/vireo/model/packager/Packager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public interface Packager<EP extends ExportPackage> {
2626

2727
public EP packageExport(Submission submission, List<SubmissionListColumn> columns);
2828

29+
public EP packageExport(Submission submission, List<SubmissionListColumn> columns, String delimiter);
30+
2931
public EP packageExport(Submission submission, Map<String,String> dsDocs);
3032

3133
}

src/main/java/org/tdl/vireo/utility/PackagerUtility.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public ExportPackage packageExport(Packager<?> packager, Submission submission,
4545
return packager.packageExport(submission, columns);
4646
}
4747

48+
public ExportPackage packageExport(Packager<?> packager, Submission submission, List<SubmissionListColumn> columns, String delimiter) {
49+
return packager.packageExport(submission, columns, delimiter);
50+
}
51+
4852
public ExportPackage packageExport(Packager<?> packager, Submission submission, Map<String, String> dsDocs) {
4953
return packager.packageExport(submission, dsDocs);
5054
}

src/main/resources/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ app:
137137
# edu.tamu.weaver.auth.service.UserCredentialsService
138138
authority.admins: admin@tdl.org,aggieJack@tamu.edu
139139

140+
# ExcelExport secondary delimiter for keywords within CSV, otherwise multiple keywords appear as separate columns
141+
secondaryDelimiter: '|'
140142

141143
security:
142144
# edu.tamu.weaver.auth.service.CryptoService

src/main/webapp/app/views/admin/list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ <h1>List ETDs</h1>
3636
<span ng-class="{'glyphicon glyphicon-remove-circle' : $first}" ng-click="addRowFilter($parent.$index, row)"></span>
3737
<span ng-if="col.title() !== 'Custom Actions'">{{displaySubmissionProperty(row, col)}}</span>
3838
<span ng-if="col.title() === 'Custom Actions'">
39-
<span ng-repeat="ca in row.customActionValues">
39+
<span ng-repeat="ca in row.customActionValues | orderBy:'definition.position'">
4040
<input ng-checked="{{ca.value}}" type="checkbox" disabled> {{getCustomActionLabelById(ca.definition.id)}}</input>
4141
<br/>
4242
</span>

src/test/java/org/tdl/vireo/model/CustomActionDefinitionTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
package org.tdl.vireo.model;
22

3+
import static org.junit.jupiter.api.Assertions.assertNotNull;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
36
import java.util.stream.Stream;
7+
8+
import org.junit.jupiter.api.Test;
49
import org.junit.jupiter.params.provider.Arguments;
510
import org.mockito.InjectMocks;
11+
import org.tdl.vireo.model.response.Views;
12+
13+
import com.fasterxml.jackson.databind.MapperFeature;
14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import com.fasterxml.jackson.databind.json.JsonMapper;
16+
import com.fasterxml.jackson.databind.node.ObjectNode;
617

718
public class CustomActionDefinitionTest extends AbstractModelCustomMethodTest<CustomActionDefinition> {
819

@@ -43,4 +54,62 @@ private static Stream<Arguments> getParameterMethodStream() {
4354
);
4455
}
4556

57+
/**
58+
* Regression test for GitHub issue #2104.
59+
*
60+
* Verifies that {@code CustomActionDefinition.position} is included in the
61+
* JSON output when serialized under {@link Views.SubmissionList} (and its
62+
* sub-views such as {@link Views.SubmissionIndividualActionLogs}).
63+
*
64+
* The application sets {@code jackson.mapper.default-view-inclusion: false},
65+
* so any field without a matching {@code @JsonView} annotation is silently
66+
* excluded when a view is active. The fix overrides {@code getPosition()} in
67+
* {@code CustomActionDefinition} with {@code @JsonView(Views.SubmissionList.class)}.
68+
* Removing that annotation will cause this test to fail.
69+
*/
70+
@Test
71+
public void testPositionSerializedInSubmissionListView() throws Exception {
72+
// Configure ObjectMapper to match application.yml: default-view-inclusion: false
73+
ObjectMapper mapper = JsonMapper.builder()
74+
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
75+
.build();
76+
77+
CustomActionDefinition definition = new CustomActionDefinition("Test action", true);
78+
definition.setPosition(3L);
79+
80+
// Serialize under Views.SubmissionList — the view used by /submission/query
81+
String json = mapper.writerWithView(Views.SubmissionList.class).writeValueAsString(definition);
82+
ObjectNode node = (ObjectNode) mapper.readTree(json);
83+
84+
assertNotNull(node.get("position"),
85+
"position must be present in SubmissionList view JSON; " +
86+
"ensure getPosition() in CustomActionDefinition is annotated with @JsonView(Views.SubmissionList.class)");
87+
assertTrue(node.get("position").isNumber(),
88+
"position must be a numeric value in SubmissionList view JSON");
89+
}
90+
91+
/**
92+
* Verifies that {@code position} is also included when serialized under the
93+
* {@link Views.SubmissionIndividualActionLogs} view used by the admin
94+
* {@code GET /submission/get-one/{id}} endpoint, since that view extends
95+
* {@link Views.SubmissionList}.
96+
*/
97+
@Test
98+
public void testPositionSerializedInSubmissionIndividualActionLogsView() throws Exception {
99+
ObjectMapper mapper = JsonMapper.builder()
100+
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
101+
.build();
102+
103+
CustomActionDefinition definition = new CustomActionDefinition("Test action", true);
104+
definition.setPosition(5L);
105+
106+
String json = mapper.writerWithView(Views.SubmissionIndividualActionLogs.class).writeValueAsString(definition);
107+
ObjectNode node = (ObjectNode) mapper.readTree(json);
108+
109+
assertNotNull(node.get("position"),
110+
"position must be present in SubmissionIndividualActionLogs view JSON");
111+
assertTrue(node.get("position").isNumber(),
112+
"position must be a numeric value in SubmissionIndividualActionLogs view JSON");
113+
}
114+
46115
}

0 commit comments

Comments
 (0)