Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bxc 4429 update view setting endpoint #1686

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ public class ViewSettingRequest {
public enum ViewBehavior {
INDIVIDUALS,
PAGED,
CONTINUOUS
CONTINUOUS;

public String getString() {
return this.name().toLowerCase();
}

public static ViewBehavior caseInsensitiveValueOf(String behavior) {
return valueOf(behavior.toUpperCase());
}
}

public String getObjectPidString() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package edu.unc.lib.boxc.operations.jms.viewSettings;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import edu.unc.lib.boxc.operations.jms.MessageSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
* Service for sending requests to update view settings of a work object
*/
public class ViewSettingRequestSender extends MessageSender {
private static final Logger log = LoggerFactory.getLogger(ViewSettingRequestSender.class);
private static final ObjectWriter MAPPER = new ObjectMapper().writerFor(ViewSettingRequest.class);

public void sendToQueue(ViewSettingRequest request) throws IOException {
String messageBody = MAPPER.writeValueAsString(request);
sendMessage(messageBody);
log.info("Job to update view setting has been queued for work {} by {}",
request.getObjectPidString(), request.getAgent().getUsername());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public void process(Exchange exchange) throws Exception {
if (behavior == null) {
repositoryObjectFactory.deleteProperty(repositoryObject, CdrView.viewBehavior);
} else {
repositoryObjectFactory.createExclusiveRelationship(repositoryObject, CdrView.viewBehavior, behavior);
repositoryObjectFactory.createExclusiveRelationship(repositoryObject,
CdrView.viewBehavior, behavior.getString());
}
}
// TODO BXC-4428 send message to update solr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
<property name="repositoryObjectFactory" ref="repositoryObjectFactory" />
</bean>

<bean id="viewBehaviorRequestProcessor" class="edu.unc.lib.boxc.services.camel.viewSettings.ViewSettingRequestProcessor">
<bean id="viewSettingRequestProcessor" class="edu.unc.lib.boxc.services.camel.viewSettings.ViewSettingRequestProcessor">
<property name="accessControlService" ref="aclService" />
<property name="repositoryObjectLoader" ref="repositoryObjectLoader" />
<property name="repositoryObjectFactory" ref="repositoryObjectFactory" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public void testUpdateViewBehaviorSuccess() throws Exception {
processor.process(exchange);

verify(repositoryObjectFactory).createExclusiveRelationship(
eq(workObject), eq(CdrView.viewBehavior), eq(ViewSettingRequest.ViewBehavior.PAGED));
eq(workObject), eq(CdrView.viewBehavior),
eq(ViewSettingRequest.ViewBehavior.PAGED.getString()));
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion static/js/admin/src/ViewSettingsForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ define('ViewSettingsForm', [ 'jquery', 'jquery-ui', 'underscore', 'RemoteStateCh
ViewSettingsForm.prototype.preprocessForm = function() {
let newViewSetting = $('#view_settings_change', this.$form).val();
let pids = $('#view_settings_targets', this.$form).val();
this.action_url = `/services/api/edit/view_settings?targets=${encodeURIComponent(pids)}&view_setting=${encodeURIComponent(newViewSetting)}`;
this.action_url = `/services/api/edit/viewSettings?targets=${encodeURIComponent(pids)}&behavior=${encodeURIComponent(newViewSetting)}`;
};

ViewSettingsForm.prototype.validationErrors = function(resultObject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ define('UpdateViewSettingsBatchAction', [ 'jquery', 'AbstractBatchAction', "tpl!
let pids = $('#view_settings_targets', self.$form).val();

$.ajax({
url: `/services/api/edit/view_settings?targets=${encodeURIComponent(pids)}&view_setting=${encodeURIComponent(newViewSetting)}`,
url: `/services/api/edit/viewSettings?targets=${encodeURIComponent(pids)}&behavior=${encodeURIComponent(newViewSetting)}`,
type: 'PUT',
contentType: 'application/json; charset=utf-8',
dataType: 'json'
Expand Down
2 changes: 1 addition & 1 deletion static/templates/admin/viewSettingsForm.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="form_field">
<label for="view_settings_change">Page Display:</label>
<select id="view_settings_change">
<option value="individual" <%= (metadata.view_settings === undefined || metadata.view_settings === 'individual') ? "selected" : "" %>>Individual</option>
<option value="individuals" <%= (metadata.view_settings === undefined || metadata.view_settings === 'individuals') ? "selected" : "" %>>Individuals</option>
<option value="paged" <%= (metadata.view_settings == 'paged') ? "selected" : "" %>>Paged</option>
</select>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package edu.unc.lib.boxc.web.services.rest.modify;

import com.apicatalog.jsonld.StringUtils;
import edu.unc.lib.boxc.auth.api.Permission;
import edu.unc.lib.boxc.auth.api.models.AccessGroupSet;
import edu.unc.lib.boxc.auth.api.models.AgentPrincipals;
import edu.unc.lib.boxc.auth.api.services.AccessControlService;
import edu.unc.lib.boxc.model.api.exceptions.InvalidOperationForObjectType;
import edu.unc.lib.boxc.model.api.ids.PID;
import edu.unc.lib.boxc.model.api.objects.RepositoryObjectLoader;
import edu.unc.lib.boxc.model.api.objects.WorkObject;
import edu.unc.lib.boxc.model.api.rdf.CdrView;
import edu.unc.lib.boxc.model.fcrepo.ids.PIDs;
import edu.unc.lib.boxc.operations.jms.viewSettings.ViewSettingRequest;
import edu.unc.lib.boxc.operations.jms.viewSettings.ViewSettingRequestSender;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.Resource;
import org.slf4j.Logger;
Expand All @@ -19,24 +23,30 @@
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static edu.unc.lib.boxc.auth.fcrepo.services.GroupsThreadStore.getAgentPrincipals;
import static edu.unc.lib.boxc.operations.jms.viewSettings.ViewSettingRequest.ViewBehavior.caseInsensitiveValueOf;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
* Controller for handling view setting requests. This may include things like setting a viewBehavior.
*/
@Controller
public class ViewSettingController {
private static final Logger LOG = LoggerFactory.getLogger(ViewSettingController.class);
private static final Logger log = LoggerFactory.getLogger(ViewSettingController.class);
@Autowired
private AccessControlService accessControlService;
@Autowired
private RepositoryObjectLoader repositoryObjectLoader;
@Autowired
private ViewSettingRequestSender viewSettingRequestSender;

/**
* This endpoint gets the view settings of the object
Expand Down Expand Up @@ -66,9 +76,70 @@ public ResponseEntity<Object> getViewSetting(@PathVariable("id") String id) {
return new ResponseEntity<>(result, HttpStatus.OK);
}

@PutMapping(value = "/edit/viewSettings", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<Object> updateViewSetting(@RequestParam Map<String,String> allParams) {
Map<String, Object> result = new HashMap<>();

if (hasBadParams(allParams)) {
result.put("error", "Request must include ids and view settings");
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}

// see if view behavior is valid
var viewBehavior = caseInsensitiveValueOf(allParams.get("behavior"));

var ids = allParams.get("targets").split(",");
var agent = getAgentPrincipals();
AccessGroupSet principals = agent.getPrincipals();
for (String id : ids) {
var pid = PIDs.get(id);
// check permissions for object first
accessControlService.assertHasAccess("Insufficient permissions to edit view settings for " + id,
pid, principals, Permission.editViewSettings);

//check if object is a work
var repositoryObject = repositoryObjectLoader.getRepositoryObject(pid);
if (!(repositoryObject instanceof WorkObject)) {
throw new InvalidOperationForObjectType("Cannot update View Settings of object " +
id + ", as only WorkObjects have View Settings");
}
}

// now build and send requests
for (String id : ids) {
var request = buildRequest(id, viewBehavior, agent);
try {
viewSettingRequestSender.sendToQueue(request);
} catch (IOException e) {
log.error("Error updating view setting for {}", request.getObjectPidString(), e);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

}

result.put("ids", ids);
result.put("status", "Submitted view setting updates for " + ids.length + " object(s)");
result.put("timestamp", System.currentTimeMillis());
return new ResponseEntity<>(result, HttpStatus.OK);
}

private String getValue(Resource resource, Property property) {
var propValue = resource.getProperty(property);
return propValue == null ? null : propValue.getString();
}

private ViewSettingRequest buildRequest(String id,
ViewSettingRequest.ViewBehavior viewBehavior,
AgentPrincipals agent) {
var request = new ViewSettingRequest();
request.setObjectPidString(id);
request.setViewBehavior(viewBehavior);
request.setAgent(agent);
return request;
}

private boolean hasBadParams(Map<String,String> params) {
return params.isEmpty() || StringUtils.isBlank(params.get("targets"));
}
}
10 changes: 10 additions & 0 deletions web-services-app/src/main/webapp/WEB-INF/service-context.xml
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,16 @@
<property name="jmsTemplate" ref="thumbnailRequestJmsTemplate"/>
</bean>

<bean id="viewSettingsRequestSender" class="edu.unc.lib.boxc.operations.jms.viewSettings.ViewSettingRequestSender">
<property name="jmsTemplate" ref="viewSettingRequestJmsTemplate"/>
</bean>

<bean id="viewSettingRequestJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory" />
<property name="defaultDestinationName" value="${cdr.viewsetting.stream}" />
<property name="pubSubDomain" value="false" />
</bean>

<bean id="imageServerV2Service" class="edu.unc.lib.boxc.web.services.processing.ImageServerV2Service">
<property name="imageServerProxyBasePath" value="${iiif.v2.base.url}"/>
<property name="basePath" value="${services.api.url}"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import edu.unc.lib.boxc.model.api.objects.WorkObject;
import edu.unc.lib.boxc.model.api.rdf.CdrView;
import edu.unc.lib.boxc.model.fcrepo.ids.PIDs;
import edu.unc.lib.boxc.operations.jms.viewSettings.ViewSettingRequestSender;
import edu.unc.lib.boxc.web.services.rest.MvcTestHelpers;
import edu.unc.lib.boxc.web.services.rest.exceptions.RestResponseEntityExceptionHandler;
import org.apache.jena.rdf.model.Resource;
Expand All @@ -36,6 +37,7 @@
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class ViewSettingIT {
Expand All @@ -46,19 +48,23 @@ public class ViewSettingIT {
@Mock
private RepositoryObjectLoader repositoryObjectLoader;
@Mock
private ViewSettingRequestSender viewSettingRequestSender;
@Mock
private FileObject fileObject;
@Mock
private WorkObject workObject;
@Mock
private WorkObject workObject2;
@Mock
private Resource resource;
@Mock
private Statement stmt;
private MockMvc mockMvc;
private AutoCloseable closeable;
private final static String USERNAME = "test_user";
private final static AccessGroupSet GROUPS = new AccessGroupSetImpl("adminGroup");
private static final String OBJECT_ID = "f277bb38-272c-471c-a28a-9887a1328a1f";
private static final PID OBJECT_PID = PIDs.get(OBJECT_ID);
private static final String WORK_ID = "f277bb38-272c-471c-a28a-9887a1328a1f";
private static final PID WORK_PID = PIDs.get(WORK_ID);

@BeforeEach
public void setup() {
Expand All @@ -68,7 +74,7 @@ public void setup() {
.build();
GroupsThreadStore.storeUsername(USERNAME);
GroupsThreadStore.storeGroups(GROUPS);
when(repositoryObjectLoader.getRepositoryObject(eq(PIDs.get(OBJECT_ID)))).thenReturn(workObject);
when(repositoryObjectLoader.getRepositoryObject(eq(PIDs.get(WORK_ID)))).thenReturn(workObject);
when(workObject.getResource()).thenReturn(resource);
}

Expand All @@ -83,7 +89,7 @@ public void testGetViewSetting() throws Exception {
when(resource.getProperty(eq(CdrView.viewBehavior))).thenReturn(stmt);
when(stmt.getString()).thenReturn(paged);

var result = mockMvc.perform(get("/edit/viewSettings/" + OBJECT_ID)
var result = mockMvc.perform(get("/edit/viewSettings/" + WORK_ID)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful())
.andReturn();
Expand All @@ -95,16 +101,16 @@ public void testGetViewSetting() throws Exception {
@Test
public void testGetViewSettingNoPermission() throws Exception {
doThrow(new AccessRestrictionException()).when(accessControlService)
.assertHasAccess(anyString(), eq(OBJECT_PID), any(), eq(Permission.viewHidden));
.assertHasAccess(anyString(), eq(WORK_PID), any(), eq(Permission.viewHidden));

mockMvc.perform(get("/edit/viewSettings/" + OBJECT_ID)
mockMvc.perform(get("/edit/viewSettings/" + WORK_ID)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isForbidden());
}

@Test
public void testGetViewSettingWithNullViewBehavior() throws Exception {
var result = mockMvc.perform(get("/edit/viewSettings/" + OBJECT_ID)
var result = mockMvc.perform(get("/edit/viewSettings/" + WORK_ID)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful())
.andReturn();
Expand All @@ -122,4 +128,60 @@ public void testGetViewSettingNotAWork() throws Exception {
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}

@Test
public void testUpdateViewSettingSingleObject() throws Exception {
var result = mockMvc.perform(put("/edit/viewSettings?targets=" +
WORK_ID + "&behavior=continuous"))
.andExpect(status().is2xxSuccessful())
.andReturn();

Map<String, Object> respMap = MvcTestHelpers.getMapFromResponse(result);
assertEquals("Submitted view setting updates for 1 object(s)", respMap.get("status"));
}

@Test
public void testUpdateViewSettingMultipleObjects() throws Exception {
var workId2 = "ba70a1ee-fa7c-437f-a979-cc8b16599652";
when(repositoryObjectLoader.getRepositoryObject(eq(PIDs.get(workId2)))).thenReturn(workObject2);
var result = mockMvc.perform(put("/edit/viewSettings?targets=" + WORK_ID + "," +
workId2 + "&behavior=continuous"))
.andExpect(status().is2xxSuccessful())
.andReturn();

Map<String, Object> respMap = MvcTestHelpers.getMapFromResponse(result);
assertEquals("Submitted view setting updates for 2 object(s)", respMap.get("status"));
}
@Test
public void testUpdateViewSettingNoPermission() throws Exception {
doThrow(new AccessRestrictionException()).when(accessControlService)
.assertHasAccess(anyString(), eq(WORK_PID), any(), eq(Permission.editViewSettings));
mockMvc.perform(put("/edit/viewSettings?targets=" + WORK_ID + "&behavior=continuous"))
.andExpect(status().isForbidden());
}
@Test
public void testUpdateViewSettingWithInvalidBehaviorValue() throws Exception {
mockMvc.perform(put("/edit/viewSettings?targets=" + WORK_ID + "&behavior=good"))
.andExpect(status().isBadRequest());
}
@Test
public void testUpdateViewSettingNotAWork() throws Exception {
var fileId = "ba70a1ee-fa7c-437f-a979-cc8b16599652";
when(repositoryObjectLoader.getRepositoryObject(eq(PIDs.get(fileId)))).thenReturn(fileObject);

mockMvc.perform(put("/edit/viewSettings?targets=" + fileId + "&behavior=continuous"))
.andExpect(status().isBadRequest());
}

@Test
public void testUpdateViewSettingTargetIsBlank() throws Exception {
mockMvc.perform(put("/edit/viewSettings?targets=&behavior=good"))
.andExpect(status().isBadRequest());
}

@Test
public void testUpdateViewSettingTargetNotProvided() throws Exception {
mockMvc.perform(put("/edit/viewSettings?behavior=good"))
.andExpect(status().isBadRequest());
}
}
Loading