org.molgenis.data.rest.v2.RestControllerV2 Maven / Gradle / Ivy
package org.molgenis.data.rest.v2;
import static com.google.common.collect.Lists.transform;
import static java.util.Objects.requireNonNull;
import static org.molgenis.data.rest.v2.RestControllerV2.BASE_URI;
import static org.molgenis.util.MolgenisDateFormat.getDateFormat;
import static org.molgenis.util.MolgenisDateFormat.getDateTimeFormat;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.DELETE;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.molgenis.MolgenisFieldTypes.FieldTypeEnum;
import org.molgenis.data.AggregateQuery;
import org.molgenis.data.AggregateResult;
import org.molgenis.data.AttributeMetaData;
import org.molgenis.data.DataService;
import org.molgenis.data.DuplicateEntityException;
import org.molgenis.data.Entity;
import org.molgenis.data.EntityMetaData;
import org.molgenis.data.Fetch;
import org.molgenis.data.MolgenisDataAccessException;
import org.molgenis.data.MolgenisDataException;
import org.molgenis.data.MolgenisQueryException;
import org.molgenis.data.MolgenisRepositoryCapabilitiesException;
import org.molgenis.data.Query;
import org.molgenis.data.Repository;
import org.molgenis.data.RepositoryCapability;
import org.molgenis.data.UnknownAttributeException;
import org.molgenis.data.UnknownEntityException;
import org.molgenis.data.i18n.LanguageService;
import org.molgenis.data.meta.MetaValidationUtils;
import org.molgenis.data.rest.EntityPager;
import org.molgenis.data.rest.Href;
import org.molgenis.data.rest.service.RestService;
import org.molgenis.data.support.EntityMetaDataUtils;
import org.molgenis.data.support.QueryImpl;
import org.molgenis.security.core.MolgenisPermissionService;
import org.molgenis.security.core.Permission;
import org.molgenis.security.core.runas.RunAsSystemProxy;
import org.molgenis.security.permission.PermissionSystemService;
import org.molgenis.util.ErrorMessageResponse;
import org.molgenis.util.ErrorMessageResponse.ErrorMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.util.UriComponentsBuilder;
@Controller
@RequestMapping(BASE_URI)
class RestControllerV2
{
private static final Logger LOG = LoggerFactory.getLogger(RestControllerV2.class);
static final int MAX_ENTITIES = 1000;
public static final String BASE_URI = "/api/v2";
private final DataService dataService;
private final RestService restService;
private final MolgenisPermissionService permissionService;
private final PermissionSystemService permissionSystemService;
private final LanguageService languageService;
static UnknownEntityException createUnknownEntityException(String entityName)
{
return new UnknownEntityException("Operation failed. Unknown entity: '" + entityName + "'");
}
static MolgenisDataAccessException createNoReadPermissionOnEntityException(String entityName)
{
return new MolgenisDataAccessException("No read permission on entity " + entityName);
}
static MolgenisDataException createNoWriteCapabilitiesOnEntityException(String entityName)
{
return new MolgenisRepositoryCapabilitiesException("No write capabilities for entity " + entityName);
}
static DuplicateEntityException createDuplicateEntityException(String entityName)
{
return new DuplicateEntityException("Operation failed. Duplicate entity: '" + entityName + "'");
}
static UnknownAttributeException createUnknownAttributeException(String entityName, String attributeName)
{
return new UnknownAttributeException("Operation failed. Unknown attribute: '" + attributeName
+ "', of entity: '" + entityName + "'");
}
static MolgenisDataAccessException createMolgenisDataAccessExceptionReadOnlyAttribute(String entityName,
String attributeName)
{
return new MolgenisDataAccessException("Operation failed. Attribute '" + attributeName + "' of entity '"
+ entityName + "' is readonly");
}
static MolgenisDataException createMolgenisDataExceptionUnknownIdentifier(int count)
{
return new MolgenisDataException("Operation failed. Unknown identifier on index " + count);
}
static MolgenisDataException createMolgenisDataExceptionIdentifierAndValue()
{
return new MolgenisDataException("Operation failed. Entities must provide only an identifier and a value");
}
static UnknownEntityException createUnknownEntityExceptionNotValidId(String id)
{
return new UnknownEntityException("The entity you are trying to update [" + id + "] does not exist.");
}
@Autowired
public RestControllerV2(DataService dataService, MolgenisPermissionService permissionService,
RestService restService, LanguageService languageService, PermissionSystemService permissionSystemService)
{
this.dataService = requireNonNull(dataService);
this.permissionService = requireNonNull(permissionService);
this.restService = requireNonNull(restService);
this.languageService = requireNonNull(languageService);
this.permissionSystemService = requireNonNull(permissionSystemService);
}
@Autowired
@RequestMapping(value = "/version", method = GET)
@ResponseBody
public Map getVersion(@Value("${molgenis.version:@null}") String molgenisVersion,
@Value("${molgenis.build.date:@null}") String molgenisBuildDate)
{
if (molgenisVersion == null) throw new IllegalArgumentException("molgenisVersion is null");
if (molgenisBuildDate == null) throw new IllegalArgumentException("molgenisBuildDate is null");
molgenisBuildDate = molgenisBuildDate.equals("${maven.build.timestamp}") ? new SimpleDateFormat(
"yyyy-MM-dd HH:mm").format(new java.util.Date()) + " by Eclipse" : molgenisBuildDate;
Map result = new HashMap<>();
result.put("molgenisVersion", molgenisVersion);
result.put("buildDate", molgenisBuildDate);
return result;
}
/**
* Retrieve an entity instance by id, optionally specify which attributes to include in the response.
*
* @param entityName
* @param id
* @param attributeFilter
* @return
*/
@RequestMapping(value = "/{entityName}/{id:.+}", method = GET)
@ResponseBody
public Map retrieveEntity(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id,
@RequestParam(value = "attrs", required = false) AttributeFilter attributeFilter)
{
EntityMetaData entityMeta = dataService.getEntityMetaData(entityName);
Fetch fetch = AttributeFilterToFetchConverter.convert(attributeFilter, entityMeta,
languageService.getCurrentUserLanguageCode());
Entity entity = dataService.findOne(entityName, id, fetch);
if (entity == null)
{
throw new UnknownEntityException(entityName + " [" + id + "] not found");
}
return createEntityResponse(entity, fetch, true);
}
@RequestMapping(value = "/{entityName}/{id:.+}", method = POST, params = "_method=GET")
@ResponseBody
public Map retrieveEntityPost(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id,
@RequestParam(value = "attrs", required = false) AttributeFilter attributeFilter)
{
EntityMetaData entityMeta = dataService.getEntityMetaData(entityName);
Fetch fetch = AttributeFilterToFetchConverter.convert(attributeFilter, entityMeta,
languageService.getCurrentUserLanguageCode());
Entity entity = dataService.findOne(entityName, id, fetch);
if (entity == null)
{
throw new UnknownEntityException(entityName + " [" + id + "] not found");
}
return createEntityResponse(entity, fetch, true);
}
@RequestMapping(value = "/{entityName}/{id:.+}", method = DELETE)
@ResponseStatus(NO_CONTENT)
public void deleteEntity(@PathVariable("entityName") String entityName, @PathVariable("id") Object id)
{
dataService.delete(entityName, id);
}
/**
* Retrieve an entity collection, optionally specify which attributes to include in the response.
*
* @param entityName
* @param request
* @param httpRequest
* @return
*/
@RequestMapping(value = "/{entityName}", method = GET)
@ResponseBody
public EntityCollectionResponseV2 retrieveEntityCollection(@PathVariable("entityName") String entityName,
@Valid EntityCollectionRequestV2 request, HttpServletRequest httpRequest)
{
return createEntityCollectionResponse(entityName, request, httpRequest);
}
@RequestMapping(value = "/{entityName}", method = POST, params = "_method=GET")
@ResponseBody
public EntityCollectionResponseV2 retrieveEntityCollectionPost(@PathVariable("entityName") String entityName,
@Valid EntityCollectionRequestV2 request, HttpServletRequest httpRequest)
{
return createEntityCollectionResponse(entityName, request, httpRequest);
}
/**
* Retrieve attribute meta data
*
* @param entityName
* @param attributeName
* @return
*/
@RequestMapping(value = "/{entityName}/meta/{attributeName}", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttributeMetaDataResponseV2 retrieveEntityAttributeMeta(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName)
{
return createAttributeMetaDataResponse(entityName, attributeName);
}
@RequestMapping(value = "/{entityName}/meta/{attributeName}", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttributeMetaDataResponseV2 retrieveEntityAttributeMetaPost(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName)
{
return createAttributeMetaDataResponse(entityName, attributeName);
}
/**
* Try to create multiple entities in one transaction. If one fails all fails.
*
* @param entityName
* name of the entity where the entities are going to be added.
* @param request
* EntityCollectionCreateRequestV2
* @param response
* HttpServletResponse
* @return EntityCollectionCreateResponseBodyV2
* @throws Exception
*/
@RequestMapping(value = "/{entityName}", method = POST, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public EntityCollectionBatchCreateResponseBodyV2 createEntities(@PathVariable("entityName") String entityName,
@RequestBody @Valid EntityCollectionBatchRequestV2 request, HttpServletResponse response) throws Exception
{
final EntityMetaData meta = dataService.getEntityMetaData(entityName);
if (meta == null)
{
throw createUnknownEntityException(entityName);
}
try
{
final List entities = request.getEntities().stream().map(e -> this.restService.toEntity(meta, e))
.collect(Collectors.toList());
final EntityCollectionBatchCreateResponseBodyV2 responseBody = new EntityCollectionBatchCreateResponseBodyV2();
final List ids = new ArrayList();
// Add all entities
this.dataService.add(entityName, entities.stream());
entities.forEach(entity -> {
String id = entity.getIdValue().toString();
ids.add(id.toString());
responseBody.getResources().add(
new AutoValue_ResourcesResponseV2(Href.concatEntityHref(RestControllerV2.BASE_URI, entityName,
id)));
});
responseBody.setLocation(Href.concatEntityCollectionHref(RestControllerV2.BASE_URI, entityName, meta
.getIdAttribute().getName(), ids));
response.setStatus(HttpServletResponse.SC_CREATED);
return responseBody;
}
catch (Exception e)
{
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
throw e;
}
}
/**
* Copy an entity.
*
* @param entityName
* name of the entity that will be copied.
* @param request
* CopyEntityRequestV2
* @param response
* HttpServletResponse
* @return String name of the new entity
* @throws Exception
*/
@RequestMapping(value = "copy/{entityName}", method = POST, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public String copyEntity(@PathVariable("entityName") String entityName,
@RequestBody @Valid CopyEntityRequestV2 request, HttpServletResponse response) throws Exception
{
// No repo
if (!dataService.hasRepository(entityName)) throw createUnknownEntityException(entityName);
Repository repositoryToCopyFrom = dataService.getRepository(entityName);
// Check if the entity already exists
String newFullName = EntityMetaDataUtils.buildFullName(repositoryToCopyFrom.getEntityMetaData().getPackage(),
request.getNewEntityName());
if (dataService.hasRepository(newFullName)) throw createDuplicateEntityException(newFullName);
// Validate the new name
MetaValidationUtils.validateName(request.getNewEntityName());
// Permission
boolean readPermission = permissionService.hasPermissionOnEntity(repositoryToCopyFrom.getName(),
Permission.READ);
if (!readPermission) throw createNoReadPermissionOnEntityException(entityName);
// Capabilities
boolean writableCapabilities = dataService.getCapabilities(repositoryToCopyFrom.getName()).contains(
RepositoryCapability.WRITABLE);
if (!writableCapabilities) throw createNoWriteCapabilitiesOnEntityException(entityName);
// Copy
this.copyRepositoryRunAsSystem(repositoryToCopyFrom, request.getNewEntityName(), request.getNewEntityName());
// Retrieve new repo
Repository repository = dataService.getRepository(newFullName);
permissionSystemService.giveUserEntityPermissions(SecurityContextHolder.getContext(),
Collections.singletonList(repository.getName()));
response.addHeader("Location", Href.concatMetaEntityHrefV2(RestControllerV2.BASE_URI, repository.getName()));
response.setStatus(HttpServletResponse.SC_CREATED);
return repository.getName();
}
private void copyRepositoryRunAsSystem(Repository repositoryToCopyFrom, String newRepositoryId,
String newRepositoryLabel)
{
RunAsSystemProxy.runAsSystem(() -> dataService.copyRepository(repositoryToCopyFrom, newRepositoryId,
newRepositoryLabel));
}
/**
* Try to update multiple entities in one transaction. If one fails all fails.
*
* @param entityName
* name of the entity where the entities are going to be added.
* @param request
* EntityCollectionCreateRequestV2
* @param response
* HttpServletResponse
* @throws Exception
*/
@RequestMapping(value = "/{entityName}", method = PUT)
public synchronized void updateEntities(@PathVariable("entityName") String entityName,
@RequestBody @Valid EntityCollectionBatchRequestV2 request, HttpServletResponse response) throws Exception
{
final EntityMetaData meta = dataService.getEntityMetaData(entityName);
if (meta == null)
{
throw createUnknownEntityException(entityName);
}
try
{
final Stream entities = request.getEntities().stream().map(e -> this.restService.toEntity(meta, e));
// update all entities
this.dataService.update(entityName, entities);
response.setStatus(HttpServletResponse.SC_OK);
}
catch (Exception e)
{
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
throw e;
}
}
/**
*
* @param entityName
* The name of the entity to update
* @param attributeName
* The name of the attribute to update
* @param request
* EntityCollectionBatchRequestV2
* @param response
* HttpServletResponse
* @throws Exception
*/
@RequestMapping(value = "/{entityName}/{attributeName}", method = PUT)
@ResponseStatus(OK)
public synchronized void updateAttribute(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName,
@RequestBody @Valid EntityCollectionBatchRequestV2 request, HttpServletResponse response) throws Exception
{
final EntityMetaData meta = dataService.getEntityMetaData(entityName);
if (meta == null)
{
throw createUnknownEntityException(entityName);
}
try
{
AttributeMetaData attr = meta.getAttribute(attributeName);
if (attr == null)
{
throw createUnknownAttributeException(entityName, attributeName);
}
if (attr.isReadonly())
{
throw createMolgenisDataAccessExceptionReadOnlyAttribute(entityName, attributeName);
}
final List entities = request.getEntities().stream().filter(e -> e.size() == 2)
.map(e -> this.restService.toEntity(meta, e)).collect(Collectors.toList());
if (entities.size() != request.getEntities().size())
{
throw createMolgenisDataExceptionIdentifierAndValue();
}
final List updatedEntities = new ArrayList();
int count = 0;
for (Entity entity : entities)
{
String id = checkForEntityId(entity, count);
Entity originalEntity = dataService.findOne(entityName, id);
if (originalEntity == null)
{
throw createUnknownEntityExceptionNotValidId(id);
}
Object value = this.restService.toEntityValue(attr, entity.get(attributeName));
originalEntity.set(attributeName, value);
updatedEntities.add(originalEntity);
count++;
}
// update all entities
this.dataService.update(entityName, updatedEntities.stream());
response.setStatus(HttpServletResponse.SC_OK);
}
catch (Exception e)
{
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
throw e;
}
}
/**
* Get the i18n resource strings in the language of the current user
*/
@RequestMapping(value = "/i18n", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public Map getI18nStrings()
{
Map translations = new HashMap<>();
ResourceBundle bundle = languageService.getBundle();
for (String key : bundle.keySet())
{
translations.put(key, bundle.getString(key));
}
return translations;
}
/**
* Get entity id and perform a check, throwing an MolgenisDataException when necessary
*
* @param entity
* @param count
* @return
*/
private String checkForEntityId(Entity entity, int count)
{
Object id = entity.getIdValue();
if (null == id)
{
throw createMolgenisDataExceptionUnknownIdentifier(count);
}
return id.toString();
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(BAD_REQUEST)
public @ResponseBody ErrorMessageResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException exception)
{
LOG.info("Invalid method arguments.", exception);
return new ErrorMessageResponse(transform(exception.getBindingResult().getFieldErrors(),
error -> new ErrorMessage(error.getDefaultMessage())));
}
@ExceptionHandler(MolgenisDataException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleMolgenisDataException(MolgenisDataException e)
{
LOG.info("Operation failed.", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(MolgenisDataAccessException.class)
@ResponseStatus(UNAUTHORIZED)
@ResponseBody
public ErrorMessageResponse handleMolgenisDataAccessException(MolgenisDataAccessException e)
{
LOG.debug("Data access exception occurred.", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorMessageResponse handleRuntimeException(RuntimeException e)
{
LOG.error("Runtime exception occurred.", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(ConversionFailedException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleConversionFailedException(ConversionFailedException e)
{
LOG.info("ConversionFailedException occurred", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
private AttributeMetaDataResponseV2 createAttributeMetaDataResponse(String entityName, String attributeName)
{
EntityMetaData entity = dataService.getEntityMetaData(entityName);
if (entity == null)
{
throw new UnknownEntityException(entityName + " not found");
}
AttributeMetaData attribute = entity.getAttribute(attributeName);
if (attribute == null)
{
throw new RuntimeException("attribute : " + attributeName + " does not exist!");
}
return new AttributeMetaDataResponseV2(entityName, entity, attribute, null, permissionService, dataService,
languageService);
}
private EntityCollectionResponseV2 createEntityCollectionResponse(String entityName,
EntityCollectionRequestV2 request, HttpServletRequest httpRequest)
{
EntityMetaData meta = dataService.getEntityMetaData(entityName);
Query q = request.getQ() != null ? request.getQ().createQuery(meta) : new QueryImpl();
q.pageSize(request.getNum()).offset(request.getStart()).sort(request.getSort());
Fetch fetch = AttributeFilterToFetchConverter.convert(request.getAttrs(), meta,
languageService.getCurrentUserLanguageCode());
if (fetch != null)
{
q.fetch(fetch);
}
if (request.getAggs() != null)
{
// return aggregates for aggregate query
AggregateQuery aggsQ = request.getAggs().createAggregateQuery(meta, q);
AttributeMetaData xAttr = aggsQ.getAttributeX();
AttributeMetaData yAttr = aggsQ.getAttributeY();
if (xAttr == null && yAttr == null)
{
throw new MolgenisQueryException("Aggregate query is missing 'x' or 'y' attribute");
}
AggregateResult aggs = dataService.aggregate(entityName, aggsQ);
AttributeMetaDataResponseV2 xAttrResponse = xAttr != null ? new AttributeMetaDataResponseV2(entityName,
meta, xAttr, fetch, permissionService, dataService, languageService) : null;
AttributeMetaDataResponseV2 yAttrResponse = yAttr != null ? new AttributeMetaDataResponseV2(entityName,
meta, yAttr, fetch, permissionService, dataService, languageService) : null;
return new EntityAggregatesResponse(aggs, xAttrResponse, yAttrResponse, BASE_URI + '/' + entityName);
}
else
{
Long count = dataService.count(entityName, q);
Iterable it;
if (count > 0)
{
it = new Iterable()
{
@Override
public Iterator iterator()
{
return dataService.findAll(entityName, q).iterator();
}
};
}
else
{
it = Collections.emptyList();
}
EntityPager pager = new EntityPager(request.getStart(), request.getNum(), count, it);
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy