Commit 0a7f58a4 authored by Matija Obreza's avatar Matija Obreza
Browse files

REST API for indicators

parent bba5dc48
......@@ -12,10 +12,13 @@ import javax.persistence.Table;
import org.genesys2.server.model.VersionedAuditedModel;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "className", discriminatorType = DiscriminatorType.STRING, length = 100)
@Table(name = "kpidimension")
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public abstract class Dimension<T> extends VersionedAuditedModel {
@Column(length = 100, nullable = false)
......
......@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
......@@ -20,10 +21,13 @@ public class Execution extends VersionedAuditedModel {
@JoinColumn(name = "parameterId")
private KPIParameter parameter;
@OneToMany(orphanRemoval=true, fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
@OneToMany(orphanRemoval = true, fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
@JoinColumn(name = "executionId")
private List<ExecutionDimension> dimensions = new ArrayList<ExecutionDimension>();
@Column(length = 100)
private String title;
public void setParameter(KPIParameter parameter) {
this.parameter = parameter;
}
......@@ -92,4 +96,12 @@ public class Execution extends VersionedAuditedModel {
return null;
return dimensions.get(depth).getDimension();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
......@@ -5,6 +5,8 @@ import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
public class JpaDimension extends Dimension<Object> {
......@@ -12,7 +14,10 @@ public class JpaDimension extends Dimension<Object> {
private String entity;
@Column(length = 50)
private String field;
@Column(name = "`condition`", length = 100)
private String condition;
@JsonIgnore
@Override
public Set<Object> getValues() {
throw new RuntimeException("JpaDimensions must be evaluated in KPIService");
......@@ -34,4 +39,12 @@ public class JpaDimension extends Dimension<Object> {
this.field = field;
}
public void setCondition(String condition) {
this.condition = condition;
}
public String getCondition() {
return condition;
}
}
......@@ -21,7 +21,7 @@ public class KPIParameter extends VersionedAuditedModel {
@Column(length = 100, nullable = false)
private String entity;
@Column(name = "`condition`", length = 100)
@Column(name = "`condition`", length = 300)
private String condition;
@Lob
......
......@@ -25,15 +25,22 @@ public class NumericListDimension extends FixedListDimension<Number> {
private Set<Double> values;
public void setValues(Set<Number> list) {
if (list == null) {
this.values = null;
} else {
Set<Double> doubles = new HashSet<Double>();
for (Number n : list) {
doubles.add(n.doubleValue());
}
this.values = doubles;
}
}
@Override
public Set<Number> getValues() {
if (values == null) {
return null;
}
Set<Number> numbers = new HashSet<Number>();
for (double d : values) {
numbers.add(toType(d));
......
......@@ -18,6 +18,8 @@ import javax.persistence.TemporalType;
import org.genesys2.server.model.BusinessModel;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "kpiobservation")
public class Observation extends BusinessModel {
......@@ -29,12 +31,13 @@ public class Observation extends BusinessModel {
@Column(name = "`value`")
private long value;
@JsonIgnore
@ManyToOne(cascade = {}, fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "parameterId")
private KPIParameter parameter;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "kpiobservationdimension", joinColumns = @JoinColumn(name = "observationId"), inverseJoinColumns = @JoinColumn(name = "dimensionId"))
@JoinTable(name = "kpiobservationdimension", joinColumns = @JoinColumn(name = "observationId"), inverseJoinColumns = @JoinColumn(name = "dimensionKeyId"))
private Set<DimensionKey> dimensions = new HashSet<DimensionKey>();
@Column
......
package org.genesys2.server.service;
import java.util.List;
import java.util.Set;
import org.genesys2.server.model.kpi.Dimension;
import org.genesys2.server.model.kpi.Execution;
......@@ -24,7 +25,7 @@ public interface KPIService {
Dimension<?> getDimension(long id);
List<?> getValues(JpaDimension loadedJpa);
Set<?> getValues(JpaDimension loadedJpa);
Execution save(Execution execution);
......@@ -37,4 +38,10 @@ public interface KPIService {
void delete(List<Observation> observations);
List<KPIParameter> listParameters();
List<Dimension<?>> listDimensions();
List<Execution> listExecutions();
}
......@@ -9,6 +9,7 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.kpi.Dimension;
......@@ -20,10 +21,11 @@ import org.genesys2.server.model.kpi.Observation;
import org.genesys2.server.persistence.domain.kpi.DimensionKeyRepository;
import org.genesys2.server.persistence.domain.kpi.DimensionRepository;
import org.genesys2.server.persistence.domain.kpi.ExecutionRepository;
import org.genesys2.server.persistence.domain.kpi.ObservationRepository;
import org.genesys2.server.persistence.domain.kpi.KPIParameterRepository;
import org.genesys2.server.persistence.domain.kpi.ObservationRepository;
import org.genesys2.server.service.KPIService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -66,6 +68,11 @@ public class KPIServiceImpl implements KPIService {
return parameterRepository.findByName(name);
}
@Override
public List<KPIParameter> listParameters() {
return parameterRepository.findAll(new Sort("name"));
}
@Override
@Transactional
public void delete(KPIParameter parameter) {
......@@ -87,6 +94,11 @@ public class KPIServiceImpl implements KPIService {
return dim;
}
@Override
public List<Dimension<?>> listDimensions() {
return dimensionRepository.findAll(new Sort("name"));
}
@Override
public Execution getExecution(long id) {
return executionRepository.findOne(id);
......@@ -98,6 +110,11 @@ public class KPIServiceImpl implements KPIService {
return executionRepository.save(execution);
}
@Override
public List<Execution> listExecutions() {
return executionRepository.findAll();
}
@Override
@Transactional
public void delete(Execution execution) {
......@@ -105,11 +122,14 @@ public class KPIServiceImpl implements KPIService {
}
@Override
public List<?> getValues(JpaDimension jpaDimension) {
public Set<?> getValues(JpaDimension jpaDimension) {
StringBuilder paQuery = new StringBuilder();
paQuery.append("select distinct a.").append(jpaDimension.getField()).append(" from ");
paQuery.append(jpaDimension.getEntity()).append(" a");
if (StringUtils.isNotBlank(jpaDimension.getCondition())) {
paQuery.append(" where ").append(jpaDimension.getCondition());
}
LOG.debug(paQuery);
Query q = entityManager.createQuery(paQuery.toString());
......@@ -119,7 +139,7 @@ public class KPIServiceImpl implements KPIService {
// q.setParameter(i + 1, params[i]);
// }
return q.getResultList();
return new HashSet<Object>(q.getResultList());
}
@Override
......@@ -179,7 +199,12 @@ public class KPIServiceImpl implements KPIService {
printRes(res, paramExec, params.toArray(), results);
} else {
// Recurse
for (Object val : dim.getValues()) {
Set<?> values = null;
if (dim instanceof JpaDimension) {
values = getValues((JpaDimension) dim);
} else
values = dim.getValues();
for (Object val : values) {
params.add(val);
internalExecute(paQuery, paramExec, results, depth + 1, params);
params.remove(depth);
......
/**
* 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.servlet.controller.rest;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.ValidationException;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import org.genesys2.server.exception.AuthorizationException;
import org.genesys2.server.model.kpi.BooleanDimension;
import org.genesys2.server.model.kpi.Dimension;
import org.genesys2.server.model.kpi.Execution;
import org.genesys2.server.model.kpi.ExecutionDimension;
import org.genesys2.server.model.kpi.KPIParameter;
import org.genesys2.server.model.kpi.Observation;
import org.genesys2.server.service.KPIService;
import org.genesys2.server.servlet.controller.rest.model.ExecutionDimensionJson;
import org.genesys2.server.servlet.controller.rest.model.ExecutionJson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
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.ResponseBody;
@Controller
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = { "/api/v0/kpi", "/json/v0/kpi" })
public class KPIController extends RestController {
@Autowired
private KPIService kpiService;
/**
* List parameters
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/parameter/list", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
Map<String, String> listParameters() {
LOG.info("Listing KPI parameters");
HashMap<String, String> m = new HashMap<>();
for (KPIParameter kpip : kpiService.listParameters()) {
m.put(kpip.getName(), kpip.getTitle() + "\n" + kpip.getDescription());
}
return m;
}
/**
* Get parameter
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/parameter/{name:.+}", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
KPIParameter getParameter(@PathVariable("name") String name) {
return kpiService.getParameter(name);
}
/**
* Update parameter
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/parameter", method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
KPIParameter createParameter(@RequestBody KPIParameter parameter) throws ValidationException {
LOG.info("Updating parameter: " + parameter.getName());
final Validator validator = new Validator();
final List<ConstraintViolation> violations = validator.validate(parameter);
if (violations.size() > 0) {
// TODO We could do better messages on validation error
throw new ModelValidationException("Validation failed", violations);
}
return kpiService.save(parameter);
}
/**
* List dimensions
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/dimension/list", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
HashMap<Long, String> listDimensions() {
LOG.info("Listing KPI dimensions");
HashMap<Long, String> m = new HashMap<>();
for (Dimension<?> dim : kpiService.listDimensions()) {
m.put(dim.getId(), dim.getName() + " " + dim.getTitle());
}
return m;
}
/**
* Get parameter
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/dimension/{id}", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
Dimension<?> getDimension(@PathVariable("id") long id) {
return kpiService.getDimension(id);
}
/**
* Update {@link BooleanDimension}
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/dimension", method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
Dimension<?> updateBooleanDimension(@RequestBody Dimension<?> dimension) throws ValidationException {
LOG.info("Updating dimension: " + dimension.getName());
final Validator validator = new Validator();
final List<ConstraintViolation> violations = validator.validate(dimension);
if (violations.size() > 0) {
// TODO We could do better messages on validation error
throw new ModelValidationException("Validation failed", violations);
}
return kpiService.save(dimension);
}
/**
* List dimensions
*
* @return
* @throws AuthorizationException
*/
@RequestMapping(value = "/execution/list", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
HashMap<Long, String> listExecution() {
LOG.info("Listing KPI executions");
HashMap<Long, String> m = new HashMap<>();
for (Execution exec : kpiService.listExecutions()) {
m.put(exec.getId(), exec.getTitle());
}
return m;
}
/**
* Get parameter
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/execution/{id}", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
ExecutionJson getExecution(@PathVariable("id") long id) {
Execution execution = kpiService.getExecution(id);
return ExecutionJson.from(execution);
}
/**
* Update {@link BooleanDimension}
*
* @return
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/execution/{id}/execute", method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
List<Observation> execute(@PathVariable("id") long id) {
Execution execution = kpiService.getExecution(id);
List<Observation> res = kpiService.execute(execution);
return kpiService.save(res);
}
/**
* Update {@link BooleanDimension}
*
* @return
* @throws ValidationException
*/
@RequestMapping(value = "/execution", method = { RequestMethod.PUT, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
ExecutionJson updateExecution(@RequestBody ExecutionJson ej) throws ValidationException {
LOG.info("Updating execution: " + ej.title);
final Validator validator = new Validator();
final List<ConstraintViolation> violations = validator.validate(ej);
if (violations.size() > 0) {
// TODO We could do better messages on validation error
throw new ModelValidationException("Validation failed", violations);
}
Execution execution = null;
if (ej.id != null)
execution = kpiService.getExecution(ej.id);
if (execution == null)
execution = new Execution();
execution.setTitle(ej.title);
execution.setParameter(kpiService.getParameter(ej.parameter));
execution.setVersion(ej.version);
// Update executionDimensions
for (int i = execution.getExecutionDimensions().size() - 1; i >= 0; i--) {
ExecutionDimension ed = execution.getExecutionDimensions().get(i);
if (!contains(ed, ej.dimensions))
execution.getExecutionDimensions().remove(i);
}
for (ExecutionDimensionJson edj : ej.dimensions) {
updateOrInsert(edj, execution.getExecutionDimensions());
}
return ExecutionJson.from(kpiService.save(execution));
}
private void updateOrInsert(ExecutionDimensionJson edj, List<ExecutionDimension> executionDimensions) {
for (ExecutionDimension ed : executionDimensions) {
if (ed.getId() != null && ed.getId().equals(edj.id)) {
ed.setDimension(kpiService.getDimension(edj.dimensionId));
ed.setField(edj.field);
ed.setLink(edj.link);
return;
}
}
ExecutionDimension ed = new ExecutionDimension();
ed.setDimension(kpiService.getDimension(edj.dimensionId));
ed.setField(edj.field);
ed.setLink(edj.link);
executionDimensions.add(ed);
}
private boolean contains(ExecutionDimension ed, Collection<ExecutionDimensionJson> dimensions) {
for (ExecutionDimensionJson edj : dimensions) {
if (edj.id != null && edj.id.equals(ed.getId()))
return true;
}
return false;
}
}
package org.genesys2.server.servlet.controller.rest.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.genesys2.server.model.kpi.ExecutionDimension;
public class ExecutionDimensionJson {
public Long id;
public long dimensionId;
public String link;
public String field;
public static ExecutionDimensionJson from(ExecutionDimension executionDimension) {
if (executionDimension == null)
return null;
ExecutionDimensionJson ej = new ExecutionDimensionJson();
ej.id = executionDimension.getId();
ej.dimensionId = executionDimension.getDimension().getId();
ej.link = executionDimension.getLink();
ej.field = executionDimension.getField();
return ej;
}
public static Collection<ExecutionDimensionJson> from(List<ExecutionDimension> executionDimension) {
if (executionDimension == null) {
return null;
}
List<ExecutionDimensionJson> list = new ArrayList<ExecutionDimensionJson>(executionDimension.size());
for (ExecutionDimension e : executionDimension) {
list.add(from(e));
}
return list;
}
}
\ No newline at end of file
package org.genesys2.server.servlet.controller.rest.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.genesys2.server.model.kpi.Execution;
public class ExecutionJson {
public Long id;
public long version = 0;
public String parameter;
public String title;
public Collection<ExecutionDimensionJson> dimensions;
public static ExecutionJson from(Execution execution) {
if (execution == null)
return null;
ExecutionJson ej = new ExecutionJson();
ej.id = execution