/* (c) 2015-2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.geofence.rest; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.geoserver.geofence.core.model.Rule; import org.geoserver.geofence.rest.xml.JaxbRule; import org.geoserver.geofence.rest.xml.JaxbRuleList; import org.geoserver.geofence.services.RuleAdminService; import org.geoserver.geofence.services.dto.RuleFilter; import org.geoserver.geofence.services.dto.RuleFilter.IdNameFilter; import org.geoserver.geofence.services.dto.RuleFilter.SpecialFilterType; import org.geoserver.geofence.services.dto.RuleFilter.TextFilter; import org.geoserver.geofence.services.dto.ShortRule; import org.geoserver.geofence.services.exception.NotFoundServiceEx; import org.springframework.dao.DuplicateKeyException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; @Controller public class RulesRestController { private RuleAdminService adminService; public RulesRestController(RuleAdminService adminService) { this.adminService = adminService; } @ExceptionHandler(NotFoundServiceEx.class) public void ruleNotFound(NotFoundServiceEx exception, HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(404, exception.getMessage()); } @ExceptionHandler(DuplicateKeyException.class) public void rule(DuplicateKeyException exception, HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(409, exception.getMessage()); } @RequestMapping(value = "/rest/rules", method = RequestMethod.GET, produces={"application/xml", "application/json"}) public @ResponseBody JaxbRuleList get( @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "entries", required = false) Integer entries, @RequestParam(value = "full", required = false, defaultValue = "false") boolean full, @RequestParam(value = "userName", required = false) String userName, @RequestParam(value = "userAny", required = false) Boolean userDefault, @RequestParam(value = "roleName", required = false) String roleName, @RequestParam(value = "roleAny", required = false) Boolean roleDefault, @RequestParam(value = "instanceId", required = false) Long instanceId, @RequestParam(value = "instanceName", required = false) String instanceName, @RequestParam(value = "instanceAny", required = false) Boolean instanceDefault, @RequestParam(value = "service", required = false) String serviceName, @RequestParam(value = "serviceAny", required = false) Boolean serviceDefault, @RequestParam(value = "request", required = false) String requestName, @RequestParam(value = "requestAny", required = false) Boolean requestDefault, @RequestParam(value = "workspace", required = false) String workspace, @RequestParam(value = "workspaceAny", required = false) Boolean workspaceDefault, @RequestParam(value = "layer", required = false) String layer, @RequestParam(value = "layerAny", required = false) Boolean layerDefault ) { RuleFilter filter = buildFilter( userName, userDefault, roleName, roleDefault, instanceId, instanceName, instanceDefault, serviceName, serviceDefault, requestName, requestDefault, workspace, workspaceDefault, layer, layerDefault); return new JaxbRuleList(adminService.getListFull(filter, page, entries)); } @RequestMapping(value = "/rest/rules/id/{id}", method = RequestMethod.GET, produces={"application/xml", "application/json"}) public @ResponseBody JaxbRule get(@PathVariable ("id") Long id) { return new JaxbRule(adminService.get(id)); } @RequestMapping(value = "/rest/rules/count", method = RequestMethod.GET, produces={"application/xml", "application/json"}) public @ResponseBody JaxbRuleList count( @RequestParam(value = "userName", required = false) String userName, @RequestParam(value = "userAny", required = false) Boolean userDefault, @RequestParam(value = "roleName", required = false) String roleName, @RequestParam(value = "roleAny", required = false) Boolean roleDefault, @RequestParam(value = "instanceId", required = false) Long instanceId, @RequestParam(value = "instanceName", required = false) String instanceName, @RequestParam(value = "instanceAny", required = false) Boolean instanceDefault, @RequestParam(value = "service", required = false) String serviceName, @RequestParam(value = "serviceAny", required = false) Boolean serviceDefault, @RequestParam(value = "request", required = false) String requestName, @RequestParam(value = "requestAny", required = false) Boolean requestDefault, @RequestParam(value = "workspace", required = false) String workspace, @RequestParam(value = "workspaceAny", required = false) Boolean workspaceDefault, @RequestParam(value = "layer", required = false) String layer, @RequestParam(value = "layerAny", required = false) Boolean layerDefault ) { RuleFilter filter = buildFilter( userName, userDefault, roleName, roleDefault, instanceId, instanceName, instanceDefault, serviceName, serviceDefault, requestName, requestDefault, workspace, workspaceDefault, layer, layerDefault); return new JaxbRuleList(adminService.count(filter)); } @RequestMapping(value = "/rest/rules", method = RequestMethod.POST) public ResponseEntity<Long> insert(@RequestBody JaxbRule rule) { long priority = rule.getPriority() == null ? 0 : rule.getPriority().longValue(); if (adminService.getRuleByPriority(priority) != null) { adminService.shift(priority, 1); } return new ResponseEntity<Long>(adminService.insert(rule.toRule()), HttpStatus.CREATED); } @RequestMapping(value = "/rest/rules/id/{id}", method = RequestMethod.POST) public @ResponseStatus(HttpStatus.OK) void update(@PathVariable ("id") Long id, @RequestBody JaxbRule rule) { if (rule.getPriority() != null) { ShortRule priorityRule = adminService.getRuleByPriority(rule.getPriority().longValue()); if (priorityRule != null && priorityRule.getId() != id) { adminService.shift(rule.getPriority().longValue(), 1); } } adminService.update(rule.toRule(adminService.get(id))); } @RequestMapping(value = "/rest/rules/id/{id}", method = RequestMethod.DELETE) public @ResponseStatus(HttpStatus.OK) void delete(@PathVariable("id") Long id) { adminService.delete(id); } protected RuleFilter buildFilter( String userName, Boolean userDefault, String roleName, Boolean groupDefault, Long instanceId, String instanceName, Boolean instanceDefault, String serviceName, Boolean serviceDefault, String requestName, Boolean requestDefault, String workspace, Boolean workspaceDefault, String layer, Boolean layerDefault) { RuleFilter filter = new RuleFilter(SpecialFilterType.ANY, true); setFilter(filter.getUser(), userName, userDefault); setFilter(filter.getRole(), roleName, groupDefault); setFilter(filter.getInstance(), instanceId, instanceName, instanceDefault); setFilter(filter.getService(), serviceName, serviceDefault); setFilter(filter.getRequest(), requestName, requestDefault); setFilter(filter.getWorkspace(), workspace, workspaceDefault); setFilter(filter.getLayer(), layer, layerDefault); return filter; } private void setFilter(IdNameFilter filter, Long id, String name, Boolean includeDefault) { if (id != null && name != null) { throw new IllegalArgumentException("Id and name can't be both defined (id:" + id + " name:" + name + ")"); } if (id != null) { filter.setId(id); if (includeDefault != null) { filter.setIncludeDefault(includeDefault); } } else if (name != null) { filter.setName(name); if (includeDefault != null) { filter.setIncludeDefault(includeDefault); } } else { if (includeDefault != null && includeDefault) { filter.setType(SpecialFilterType.DEFAULT); } else { filter.setType(SpecialFilterType.ANY); } } } private void setFilter(TextFilter filter, String name, Boolean includeDefault) { if (name != null) { filter.setText(name); if (includeDefault != null) { filter.setIncludeDefault(includeDefault); } } else { if (includeDefault != null && includeDefault) { filter.setType(SpecialFilterType.DEFAULT); } else { filter.setType(SpecialFilterType.ANY); } } } /** * Move the provided rules to the target priority. Rules will be sorted by their priority, * first rule will be updated with a priority equal to the target priority and the next ones * will get an incremented priority value. */ @RequestMapping(value = "/rest/rules/move", method = RequestMethod.GET, produces = {"application/xml", "application/json"}) public @ResponseBody ResponseEntity<JaxbRuleList> move( @RequestParam(value = "targetPriority", required = true) int targetPriority, @RequestParam(value = "rulesIds", required = true) String rulesIds ) { // let's find the rules that need to be moved List<Rule> rules = findRules(rulesIds); if (rules.isEmpty()) { return ResponseEntity.ok(null); } // shift priorities of rules with a priority equal or lower than the target priority adminService.shift(targetPriority, rules.size()); // update moved rules priority long priority = targetPriority; for (Rule rule : rules) { rule.setPriority(priority); adminService.update(rule); priority++; } // return moved rules with their priority updated return ResponseEntity.ok(new JaxbRuleList(rules)); } /** * Helper method that will parse and retrieve the provided rules sorted by their priority. */ private List<Rule> findRules(String rulesIds) { return Arrays.stream(rulesIds.split(",")).map(ruleId -> { try { // parsing the rule id return Long.parseLong(ruleId); } catch (NumberFormatException exception) { // error parsing the rule id throw new InvalidRulesIds(); } }).map(ruleId -> { // search the rule by id return adminService.get(ruleId); }).filter(rule -> rule != null) .sorted((ruleA, ruleB) -> Long.compare(ruleA.getPriority(), ruleB.getPriority())) .collect(Collectors.toList()); } @ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Invalid rules ids") private class InvalidRulesIds extends RuntimeException { } }