org.molgenis.data.rest.RestController Maven / Gradle / Ivy
package org.molgenis.data.rest;
import static java.util.Objects.requireNonNull;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.CATEGORICAL;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.CATEGORICAL_MREF;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.COMPOUND;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.DATE;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.DATE_TIME;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.FILE;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.MREF;
import static org.molgenis.MolgenisFieldTypes.FieldTypeEnum.XREF;
import static org.molgenis.data.rest.RestController.BASE_URI;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_FOUND;
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.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.molgenis.MolgenisFieldTypes.FieldTypeEnum;
import org.molgenis.auth.MolgenisUser;
import org.molgenis.data.AttributeMetaData;
import org.molgenis.data.DataService;
import org.molgenis.data.Entity;
import org.molgenis.data.EntityCollection;
import org.molgenis.data.EntityMetaData;
import org.molgenis.data.MolgenisDataAccessException;
import org.molgenis.data.MolgenisDataException;
import org.molgenis.data.MolgenisReferencedEntityException;
import org.molgenis.data.Query;
import org.molgenis.data.QueryRule;
import org.molgenis.data.Repository;
import org.molgenis.data.Sort;
import org.molgenis.data.UnknownAttributeException;
import org.molgenis.data.UnknownEntityException;
import org.molgenis.data.i18n.LanguageService;
import org.molgenis.data.rest.service.RestService;
import org.molgenis.data.rsql.MolgenisRSQL;
import org.molgenis.data.support.DefaultEntityCollection;
import org.molgenis.data.support.QueryImpl;
import org.molgenis.data.validation.ConstraintViolation;
import org.molgenis.data.validation.MolgenisValidationException;
import org.molgenis.security.core.MolgenisPermissionService;
import org.molgenis.security.core.runas.RunAsSystem;
import org.molgenis.security.core.token.TokenService;
import org.molgenis.security.core.token.UnknownTokenException;
import org.molgenis.security.token.TokenExtractor;
import org.molgenis.util.ErrorMessageResponse;
import org.molgenis.util.ErrorMessageResponse.ErrorMessage;
import org.molgenis.util.MolgenisDateFormat;
import org.molgenis.util.ResourceFingerprintRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Controller;
import org.springframework.validation.ObjectError;
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.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import cz.jirutka.rsql.parser.RSQLParserException;
/**
* Rest endpoint for the DataService
*
* Query, create, update and delete entities.
*
* If a repository isn't capable of doing the requested operation an error is thrown.
*
* Response is json.
*
* @author erwin
*/
@Controller
@RequestMapping(BASE_URI)
public class RestController
{
private static final Logger LOG = LoggerFactory.getLogger(RestController.class);
public static final String BASE_URI = "/api/v1";
private static final Pattern PATTERN_EXPANDS = Pattern.compile("([^\\[^\\]]+)(?:\\[(.+)\\])?");
private final DataService dataService;
private final TokenService tokenService;
private final AuthenticationManager authenticationManager;
private final MolgenisPermissionService molgenisPermissionService;
private final MolgenisRSQL molgenisRSQL;
private final RestService restService;
private final LanguageService languageService;
@Autowired
public RestController(DataService dataService, TokenService tokenService,
AuthenticationManager authenticationManager, MolgenisPermissionService molgenisPermissionService,
ResourceFingerprintRegistry resourceFingerprintRegistry, MolgenisRSQL molgenisRSQL,
RestService restService, LanguageService languageService)
{
this.dataService = requireNonNull(dataService);
this.tokenService = requireNonNull(tokenService);
this.authenticationManager = requireNonNull(authenticationManager);
this.molgenisPermissionService = requireNonNull(molgenisPermissionService);
this.molgenisRSQL = requireNonNull(molgenisRSQL);
this.restService = requireNonNull(restService);
this.languageService = requireNonNull(languageService);
}
/**
* Checks if an entity exists.
*/
@RequestMapping(value = "/{entityName}/exist", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public boolean entityExists(@PathVariable("entityName") String entityName)
{
try
{
dataService.getRepository(entityName);
return true;
}
catch (UnknownEntityException e)
{
return false;
}
}
/**
* Gets the metadata for an entity
*
* Example url: /api/v1/person/meta
*
* @param entityName
* @return EntityMetaData
*/
@RequestMapping(value = "/{entityName}/meta", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public EntityMetaDataResponse retrieveEntityMeta(@PathVariable("entityName") String entityName,
@RequestParam(value = "attributes", required = false) String[] attributes,
@RequestParam(value = "expand", required = false) String[] attributeExpands)
{
Set attributeSet = toAttributeSet(attributes);
Map> attributeExpandSet = toExpandMap(attributeExpands);
EntityMetaData meta = dataService.getEntityMetaData(entityName);
return new EntityMetaDataResponse(meta, attributeSet, attributeExpandSet, molgenisPermissionService,
dataService, languageService);
}
/**
* Same as retrieveEntityMeta (GET) only tunneled through POST.
*
* Example url: /api/v1/person/meta?_method=GET
*
* @param entityName
* @return EntityMetaData
*/
@RequestMapping(value = "/{entityName}/meta", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public EntityMetaDataResponse retrieveEntityMetaPost(@PathVariable("entityName") String entityName,
@Valid @RequestBody EntityMetaRequest request)
{
Set attributesSet = toAttributeSet(request != null ? request.getAttributes() : null);
Map> attributeExpandSet = toExpandMap(request != null ? request.getExpand() : null);
EntityMetaData meta = dataService.getEntityMetaData(entityName);
return new EntityMetaDataResponse(meta, attributesSet, attributeExpandSet, molgenisPermissionService,
dataService, languageService);
}
/**
* Example url: /api/v1/person/meta/emailaddresses
*
* @param entityName
* @return EntityMetaData
*/
@RequestMapping(value = "/{entityName}/meta/{attributeName}", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttributeMetaDataResponse retrieveEntityAttributeMeta(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName,
@RequestParam(value = "attributes", required = false) String[] attributes,
@RequestParam(value = "expand", required = false) String[] attributeExpands)
{
Set attributeSet = toAttributeSet(attributes);
Map> attributeExpandSet = toExpandMap(attributeExpands);
return getAttributeMetaDataPostInternal(entityName, attributeName, attributeSet, attributeExpandSet);
}
/**
* Same as retrieveEntityAttributeMeta (GET) only tunneled through POST.
*
* @param entityName
* @return EntityMetaData
*/
@RequestMapping(value = "/{entityName}/meta/{attributeName}", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttributeMetaDataResponse retrieveEntityAttributeMetaPost(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName, @Valid @RequestBody EntityMetaRequest request)
{
Set attributeSet = toAttributeSet(request != null ? request.getAttributes() : null);
Map> attributeExpandSet = toExpandMap(request != null ? request.getExpand() : null);
return getAttributeMetaDataPostInternal(entityName, attributeName, attributeSet, attributeExpandSet);
}
/**
* Get's an entity by it's id
*
* Examples:
*
* /api/v1/person/99 Retrieves a person with id 99
*
* @param entityName
* @param id
* @param attributeExpands
* @return
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}/{id:.+}", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public Map retrieveEntity(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id, @RequestParam(value = "attributes", required = false) String[] attributes,
@RequestParam(value = "expand", required = false) String[] attributeExpands)
{
Set attributesSet = toAttributeSet(attributes);
Map> attributeExpandSet = toExpandMap(attributeExpands);
EntityMetaData meta = dataService.getEntityMetaData(entityName);
Entity entity = dataService.findOne(entityName, id);
if (entity == null)
{
throw new UnknownEntityException(entityName + " " + id + " not found");
}
return getEntityAsMap(entity, meta, attributesSet, attributeExpandSet);
}
/**
* Same as retrieveEntity (GET) only tunneled through POST.
*
* @param entityName
* @param id
* @param request
* @return
*/
@RequestMapping(value = "/{entityName}/{id:.+}", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public Map retrieveEntity(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id, @Valid @RequestBody EntityMetaRequest request)
{
Set attributesSet = toAttributeSet(request != null ? request.getAttributes() : null);
Map> attributeExpandSet = toExpandMap(request != null ? request.getExpand() : null);
EntityMetaData meta = dataService.getEntityMetaData(entityName);
Entity entity = dataService.findOne(entityName, id);
if (entity == null)
{
throw new UnknownEntityException(entityName + " " + id + " not found");
}
return getEntityAsMap(entity, meta, attributesSet, attributeExpandSet);
}
/**
* Get's an XREF entity or a list of MREF entities
*
* Example:
*
* /api/v1/person/99/address
*
* @param entityName
* @param id
* @param refAttributeName
* @param request
* @param attributeExpands
* @return
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}/{id}/{refAttributeName}", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public Object retrieveEntityAttribute(@PathVariable("entityName") String entityName, @PathVariable("id") Object id,
@PathVariable("refAttributeName") String refAttributeName, @Valid EntityCollectionRequest request,
@RequestParam(value = "attributes", required = false) String[] attributes,
@RequestParam(value = "expand", required = false) String[] attributeExpands)
{
Set attributesSet = toAttributeSet(attributes);
Map> attributeExpandSet = toExpandMap(attributeExpands);
return retrieveEntityAttributeInternal(entityName, id, refAttributeName, request, attributesSet,
attributeExpandSet);
}
/**
* Get's an XREF entity or a list of MREF entities
*
* Example:
*
* /api/v1/person/99/address
*
* @param entityName
* @param id
* @param refAttributeName
* @param request
* @return
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}/{id}/{refAttributeName}", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public Object retrieveEntityAttributePost(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id, @PathVariable("refAttributeName") String refAttributeName,
@Valid @RequestBody EntityCollectionRequest request)
{
Set attributesSet = toAttributeSet(request != null ? request.getAttributes() : null);
Map> attributeExpandSet = toExpandMap(request != null ? request.getExpand() : null);
return retrieveEntityAttributeInternal(entityName, id, refAttributeName, request, attributesSet,
attributeExpandSet);
}
/**
* Do a query
*
* Returns json
*
* @param entityName
* @param request
* @param attributeExpands
* @return
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}", method = GET, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public EntityCollectionResponse retrieveEntityCollection(@PathVariable("entityName") String entityName,
@Valid EntityCollectionRequest request,
@RequestParam(value = "attributes", required = false) String[] attributes,
@RequestParam(value = "expand", required = false) String[] attributeExpands)
{
Set attributesSet = toAttributeSet(attributes);
Map> attributeExpandSet = toExpandMap(attributeExpands);
return retrieveEntityCollectionInternal(entityName, request, attributesSet, attributeExpandSet);
}
/**
* Same as retrieveEntityCollection (GET) only tunneled through POST.
*
* Example url: /api/v1/person?_method=GET
*
* Returns json
*
* @param request
* @param entityName
* @return
*/
@RequestMapping(value = "/{entityName}", method = POST, params = "_method=GET", produces = APPLICATION_JSON_VALUE)
@ResponseBody
public EntityCollectionResponse retrieveEntityCollectionPost(@PathVariable("entityName") String entityName,
@Valid @RequestBody EntityCollectionRequest request)
{
Set attributesSet = toAttributeSet(request != null ? request.getAttributes() : null);
Map> attributeExpandSet = toExpandMap(request != null ? request.getExpand() : null);
request = request != null ? request : new EntityCollectionRequest();
return retrieveEntityCollectionInternal(entityName, request, attributesSet, attributeExpandSet);
}
/**
* Does a rsql/fiql query, returns the result as csv
*
* Parameters:
*
* q: the query
*
* attributes: the attributes to return, if not specified returns all attributes
*
* start: the index of the first row, default 0
*
* num: the number of results to return, default 100, max 10000
*
*
* Example: /api/v1/csv/person?q=firstName==Piet&attributes=firstName,lastName&start=10&num=100
*
* @param entityName
* @param attributes
* @param req
* @param resp
* @return
* @throws IOException
*/
@RequestMapping(value = "/csv/{entityName}", method = GET, produces = "text/csv")
@ResponseBody
public EntityCollection retrieveEntityCollection(@PathVariable("entityName") String entityName,
@RequestParam(value = "attributes", required = false) String[] attributes, HttpServletRequest req,
HttpServletResponse resp) throws IOException
{
final Set attributesSet = toAttributeSet(attributes);
EntityMetaData meta;
Iterable entities;
try
{
meta = dataService.getEntityMetaData(entityName);
Query q = new QueryStringParser(meta, molgenisRSQL).parseQueryString(req.getParameterMap());
String[] sortAttributeArray = req.getParameterMap().get("sortColumn");
if (sortAttributeArray != null && sortAttributeArray.length == 1
&& StringUtils.isNotEmpty(sortAttributeArray[0]))
{
String sortAttribute = sortAttributeArray[0];
String sortOrderArray[] = req.getParameterMap().get("sortOrder");
Sort.Direction order = Sort.Direction.ASC;
if (sortOrderArray != null && sortOrderArray.length == 1 && StringUtils.isNotEmpty(sortOrderArray[0]))
{
String sortOrder = sortOrderArray[0];
if (sortOrder.equals("ASC"))
{
order = Sort.Direction.ASC;
}
else if (sortOrder.equals("DESC"))
{
order = Sort.Direction.DESC;
}
else
{
throw new RuntimeException("unknown sort order");
}
}
q.sort().on(sortAttribute, order);
}
if (q.getPageSize() == 0)
{
q.pageSize(EntityCollectionRequest.DEFAULT_ROW_COUNT);
}
if (q.getPageSize() > EntityCollectionRequest.MAX_ROWS)
{
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Num exceeded the maximum of "
+ EntityCollectionRequest.MAX_ROWS + " rows");
return null;
}
entities = new Iterable()
{
@Override
public Iterator iterator()
{
return dataService.findAll(entityName, q).iterator();
}
};
}
catch (ConversionFailedException | RSQLParserException | UnknownAttributeException | IllegalArgumentException
| UnsupportedOperationException | UnknownEntityException e)
{
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
return null;
}
catch (MolgenisDataAccessException e)
{
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return null;
}
// Check attribute names
Iterable attributesIterable = Iterables.transform(meta.getAtomicAttributes(),
attributeMetaData -> attributeMetaData.getName().toLowerCase());
if (attributesSet != null)
{
SetView diff = Sets.difference(attributesSet, Sets.newHashSet(attributesIterable));
if (!diff.isEmpty())
{
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown attributes " + diff);
return null;
}
}
attributesIterable = Iterables.transform(meta.getAtomicAttributes(), AttributeMetaData::getName);
if (attributesSet != null)
{
attributesIterable = Iterables.filter(attributesIterable,
attribute -> attributesSet.contains(attribute.toLowerCase()));
}
return new DefaultEntityCollection(entities, attributesIterable);
}
/**
* Creates a new entity from a html form post.
*
* @param entityName
* @param request
* @param response
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}", method = POST, headers = "Content-Type=application/x-www-form-urlencoded")
public void createFromFormPost(@PathVariable("entityName") String entityName, HttpServletRequest request,
HttpServletResponse response)
{
Map paramMap = new HashMap();
for (String param : request.getParameterMap().keySet())
{
String[] values = request.getParameterValues(param);
String value = values != null ? StringUtils.join(values, ',') : null;
if (StringUtils.isNotBlank(value))
{
paramMap.put(param, value);
}
}
createInternal(entityName, paramMap, response);
}
/**
* Creates a new entity from a html form post.
*
* @param entityName
* @param request
* @param response
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}", method = POST, headers = "Content-Type=multipart/form-data")
public void createFromFormPostMultiPart(@PathVariable("entityName") String entityName,
MultipartHttpServletRequest request, HttpServletResponse response)
{
Map paramMap = new HashMap();
for (String param : request.getParameterMap().keySet())
{
String[] values = request.getParameterValues(param);
String value = values != null ? StringUtils.join(values, ',') : null;
if (StringUtils.isNotBlank(value))
{
paramMap.put(param, value);
}
}
// add files to param map
for (Entry> entry : request.getMultiFileMap().entrySet())
{
String param = entry.getKey();
List files = entry.getValue();
if (files != null && files.size() > 1)
{
throw new IllegalArgumentException("Multiple file input not supported");
}
paramMap.put(param, files != null && !files.isEmpty() ? files.get(0) : null);
}
createInternal(entityName, paramMap, response);
}
@RequestMapping(value = "/{entityName}", method = POST)
public void create(@PathVariable("entityName") String entityName, @RequestBody Map entityMap,
HttpServletResponse response)
{
if (entityMap == null)
{
throw new UnknownEntityException("Missing entity in body");
}
createInternal(entityName, entityMap, response);
}
/**
* Updates an entity using PUT
*
* Example url: /api/v1/person/99
*
* @param entityName
* @param id
* @param entityMap
*/
@RequestMapping(value = "/{entityName}/{id}", method = PUT)
@ResponseStatus(OK)
public void update(@PathVariable("entityName") String entityName, @PathVariable("id") Object id,
@RequestBody Map entityMap)
{
updateInternal(entityName, id, entityMap);
}
/**
* Updates an entity by tunneling PUT through POST
*
* Example url: /api/v1/person/99?_method=PUT
*
* @param entityName
* @param id
* @param entityMap
*/
@RequestMapping(value = "/{entityName}/{id}", method = POST, params = "_method=PUT")
@ResponseStatus(OK)
public void updatePost(@PathVariable("entityName") String entityName, @PathVariable("id") Object id,
@RequestBody Map entityMap)
{
updateInternal(entityName, id, entityMap);
}
@RequestMapping(value = "/{entityName}/{id}/{attributeName}", method = PUT)
@ResponseStatus(OK)
public void updateAttributePUT(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName, @PathVariable("id") Object id,
@RequestBody Object paramValue)
{
updateAttribute(entityName, attributeName, id, paramValue);
}
// TODO alternative for synchronization, for example by adding updatAttribute methods to the REST api
@RequestMapping(value = "/{entityName}/{id}/{attributeName}", method = POST, params = "_method=PUT")
@ResponseStatus(OK)
public synchronized void updateAttribute(@PathVariable("entityName") String entityName,
@PathVariable("attributeName") String attributeName, @PathVariable("id") Object id,
@RequestBody Object paramValue)
{
Entity entity = dataService.findOne(entityName, id);
if (entity == null)
{
throw new UnknownEntityException("Entity of type " + entityName + " with id " + id + " not found");
}
EntityMetaData entityMetaData = dataService.getEntityMetaData(entityName);
AttributeMetaData attr = entityMetaData.getAttribute(attributeName);
if (attr == null)
{
throw new UnknownAttributeException("Attribute '" + attributeName + "' of entity '" + entityName
+ "' does not exist");
}
if (attr.isReadonly())
{
throw new MolgenisDataAccessException("Attribute '" + attributeName + "' of entity '" + entityName
+ "' is readonly");
}
Object value = this.restService.toEntityValue(attr, paramValue);
entity.set(attributeName, value);
dataService.update(entityName, entity);
}
/**
* Updates an entity from a html form post.
*
* Tunnels PUT through POST
*
* Example url: /api/v1/person/99?_method=PUT
*
* @param entityName
* @param id
* @param request
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}/{id}", method = POST, params = "_method=PUT", headers = "Content-Type=multipart/form-data")
@ResponseStatus(NO_CONTENT)
public void updateFromFormPostMultiPart(@PathVariable("entityName") String entityName,
@PathVariable("id") Object id, MultipartHttpServletRequest request)
{
Object typedId = dataService.getRepository(entityName).getEntityMetaData().getIdAttribute().getDataType()
.convert(id);
Map paramMap = new HashMap();
for (String param : request.getParameterMap().keySet())
{
String[] values = request.getParameterValues(param);
String value = values != null ? StringUtils.join(values, ',') : null;
paramMap.put(param, value);
}
// add files to param map
for (Entry> entry : request.getMultiFileMap().entrySet())
{
String param = entry.getKey();
List files = entry.getValue();
if (files != null && files.size() > 1)
{
throw new IllegalArgumentException("Multiple file input not supported");
}
paramMap.put(param, files != null && !files.isEmpty() ? files.get(0) : null);
}
updateInternal(entityName, typedId, paramMap);
}
/**
* Updates an entity from a html form post.
*
* Tunnels PUT through POST
*
* Example url: /api/v1/person/99?_method=PUT
*
* @param entityName
* @param id
* @param request
* @throws UnknownEntityException
*/
@RequestMapping(value = "/{entityName}/{id}", method = POST, params = "_method=PUT", headers = "Content-Type=application/x-www-form-urlencoded")
@ResponseStatus(NO_CONTENT)
public void updateFromFormPost(@PathVariable("entityName") String entityName, @PathVariable("id") Object id,
HttpServletRequest request)
{
Object typedId = dataService.getRepository(entityName).getEntityMetaData().getIdAttribute().getDataType()
.convert(id);
Map paramMap = new HashMap();
for (String param : request.getParameterMap().keySet())
{
String[] values = request.getParameterValues(param);
String value = values != null ? StringUtils.join(values, ',') : null;
paramMap.put(param, value);
}
updateInternal(entityName, typedId, paramMap);
}
/**
* Deletes an entity by it's id
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}/{id}", method = DELETE)
@ResponseStatus(NO_CONTENT)
public void delete(@PathVariable("entityName") String entityName, @PathVariable Object id)
{
Object typedId = dataService.getRepository(entityName).getEntityMetaData().getIdAttribute().getDataType()
.convert(id);
dataService.delete(entityName, typedId);
}
/**
* Deletes an entity by it's id but tunnels DELETE through POST
*
* Example url: /api/v1/person/99?_method=DELETE
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}/{id}", method = POST, params = "_method=DELETE")
@ResponseStatus(NO_CONTENT)
public void deletePost(@PathVariable("entityName") String entityName, @PathVariable Object id)
{
delete(entityName, id);
}
/**
* Deletes all entities for the given entity name
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}", method = DELETE)
@ResponseStatus(NO_CONTENT)
public void deleteAll(@PathVariable("entityName") String entityName)
{
dataService.deleteAll(entityName);
}
/**
* Deletes all entities for the given entity name but tunnels DELETE through POST
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}", method = POST, params = "_method=DELETE")
@ResponseStatus(NO_CONTENT)
public void deleteAllPost(@PathVariable("entityName") String entityName)
{
dataService.deleteAll(entityName);
}
/**
* Deletes all entities and entity meta data for the given entity name
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}/meta", method = DELETE)
@ResponseStatus(NO_CONTENT)
public void deleteMeta(@PathVariable("entityName") String entityName)
{
deleteMetaInternal(entityName);
}
/**
* Deletes all entities and entity meta data for the given entity name but tunnels DELETE through POST
*
* @param entityName
* @param id
*/
@RequestMapping(value = "/{entityName}/meta", method = POST, params = "_method=DELETE")
@ResponseStatus(NO_CONTENT)
public void deleteMetaPost(@PathVariable("entityName") String entityName)
{
deleteMetaInternal(entityName);
}
private void deleteMetaInternal(String entityName)
{
dataService.getMeta().deleteEntityMeta(entityName);
}
/**
* Login to the api.
*
* Returns a json object with a token on correct login else throws an AuthenticationException. Clients can use this
* token when calling the api.
*
* Example:
*
* Request: {username:admin,password:xxx}
*
* Response: {token: b4fd94dc-eae6-4d9a-a1b7-dd4525f2f75d}
*
* @param login
* @param request
* @return
*/
@RequestMapping(value = "/login", method = POST, produces = APPLICATION_JSON_VALUE)
@ResponseBody
@RunAsSystem
public LoginResponse login(@Valid @RequestBody LoginRequest login, HttpServletRequest request)
{
if (login == null)
{
throw new HttpMessageNotReadableException("Missing login");
}
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(login.getUsername(),
login.getPassword());
authToken.setDetails(new WebAuthenticationDetails(request));
// Authenticate the login
Authentication authentication = authenticationManager.authenticate(authToken);
if (!authentication.isAuthenticated())
{
throw new BadCredentialsException("Unknown username or password");
}
MolgenisUser user = dataService.findOne(MolgenisUser.ENTITY_NAME,
new QueryImpl().eq(MolgenisUser.USERNAME, authentication.getName()), MolgenisUser.class);
// User authenticated, log the user in
SecurityContextHolder.getContext().setAuthentication(authentication);
// Generate a new token for the user
String token = tokenService.generateAndStoreToken(authentication.getName(), "Rest api login");
return new LoginResponse(token, user.getUsername(), user.getFirstName(), user.getLastName());
}
@RequestMapping("/logout")
@ResponseStatus(OK)
public void logout(HttpServletRequest request)
{
String token = TokenExtractor.getToken(request);
if (token == null)
{
throw new HttpMessageNotReadableException("Missing token in header");
}
tokenService.removeToken(token);
SecurityContextHolder.getContext().setAuthentication(null);
if (request.getSession(false) != null)
{
request.getSession().invalidate();
}
}
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e)
{
LOG.error("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(UnknownTokenException.class)
@ResponseStatus(NOT_FOUND)
@ResponseBody
public ErrorMessageResponse handleUnknownTokenException(UnknownTokenException e)
{
LOG.debug("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(UnknownEntityException.class)
@ResponseStatus(NOT_FOUND)
@ResponseBody
public ErrorMessageResponse handleUnknownEntityException(UnknownEntityException e)
{
LOG.debug("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(UnknownAttributeException.class)
@ResponseStatus(NOT_FOUND)
@ResponseBody
public ErrorMessageResponse handleUnknownAttributeException(UnknownAttributeException e)
{
LOG.debug("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
LOG.debug("", e);
List messages = Lists.newArrayList();
for (ObjectError error : e.getBindingResult().getAllErrors())
{
messages.add(new ErrorMessage(error.getDefaultMessage()));
}
return new ErrorMessageResponse(messages);
}
@ExceptionHandler(MolgenisValidationException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleMolgenisValidationException(MolgenisValidationException e)
{
LOG.info("", e);
List messages = Lists.newArrayList();
for (ConstraintViolation violation : e.getViolations())
{
messages.add(new ErrorMessage(violation.getMessage()));
}
return new ErrorMessageResponse(messages);
}
@ExceptionHandler(ConversionException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleConversionException(ConversionException e)
{
LOG.info("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(MolgenisDataException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
public ErrorMessageResponse handleMolgenisDataException(MolgenisDataException e)
{
LOG.error("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(UNAUTHORIZED)
@ResponseBody
public ErrorMessageResponse handleAuthenticationException(AuthenticationException e)
{
LOG.info("", e);
// workaround for https://github.com/molgenis/molgenis/issues/4441
String message = e.getMessage();
String messagePrefix = "org.springframework.security.core.userdetails.UsernameNotFoundException: ";
if (message.startsWith(messagePrefix))
{
message = message.substring(messagePrefix.length());
}
return new ErrorMessageResponse(new ErrorMessage(message));
}
@ExceptionHandler(MolgenisDataAccessException.class)
@ResponseStatus(UNAUTHORIZED)
@ResponseBody
public ErrorMessageResponse handleMolgenisDataAccessException(MolgenisDataAccessException e)
{
LOG.info("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorMessageResponse handleRuntimeException(RuntimeException e)
{
LOG.error("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
@ExceptionHandler(MolgenisReferencedEntityException.class)
@ResponseStatus(INTERNAL_SERVER_ERROR)
@ResponseBody
public ErrorMessageResponse handleMolgenisReferencingEntityException(MolgenisReferencedEntityException e)
{
LOG.error("", e);
return new ErrorMessageResponse(new ErrorMessage(e.getMessage()));
}
private void updateInternal(String entityName, Object id, Map entityMap)
{
EntityMetaData meta = dataService.getEntityMetaData(entityName);
if (meta.getIdAttribute() == null)
{
throw new IllegalArgumentException(entityName + " does not have an id attribute");
}
Entity existing = dataService.findOne(entityName, id);
if (existing == null)
{
throw new UnknownEntityException("Entity of type " + entityName + " with id " + id + " not found");
}
Entity entity = this.restService.toEntity(meta, entityMap);
entity.set(meta.getIdAttribute().getName(), existing.getIdValue());
dataService.update(entityName, entity);
}
private void createInternal(String entityName, Map entityMap, HttpServletResponse response)
{
EntityMetaData meta = dataService.getEntityMetaData(entityName);
Entity entity = this.restService.toEntity(meta, entityMap);
dataService.add(entityName, entity);
Object id = entity.getIdValue();
if (id != null)
{
response.addHeader("Location", Href.concatEntityHref(RestController.BASE_URI, entityName, id));
}
response.setStatus(HttpServletResponse.SC_CREATED);
}
private AttributeMetaDataResponse getAttributeMetaDataPostInternal(String entityName, String attributeName,
Set attributeSet, Map> attributeExpandSet)
{
EntityMetaData meta = dataService.getEntityMetaData(entityName);
AttributeMetaData attributeMetaData = meta.getAttribute(attributeName);
if (attributeMetaData != null)
{
return new AttributeMetaDataResponse(entityName, meta, attributeMetaData, attributeSet, attributeExpandSet,
molgenisPermissionService, dataService, languageService);
}
else
{
throw new UnknownAttributeException(attributeName);
}
}
private Object retrieveEntityAttributeInternal(String entityName, Object id, String refAttributeName,
EntityCollectionRequest request, Set attributesSet, Map> attributeExpandSet)
{
EntityMetaData meta = dataService.getEntityMetaData(entityName);
// Check if the entity has an attribute with name refAttributeName
AttributeMetaData attr = meta.getAttribute(refAttributeName);
if (attr == null)
{
throw new UnknownAttributeException(entityName + " does not have an attribute named " + refAttributeName);
}
// Get the entity
Entity entity = dataService.findOne(entityName, id);
if (entity == null)
{
throw new UnknownEntityException(entityName + " " + id + " not found");
}
String attrHref = Href.concatAttributeHref(RestController.BASE_URI, meta.getName(), entity.getIdValue(),
refAttributeName);
switch (attr.getDataType().getEnumType())
{
case COMPOUND:
Map entityHasAttributeMap = new LinkedHashMap();
entityHasAttributeMap.put("href", attrHref);
@SuppressWarnings("unchecked")
Iterable attributeParts = (Iterable) entity.get(refAttributeName);
for (AttributeMetaData attributeMetaData : attributeParts)
{
String attrName = attributeMetaData.getName();
entityHasAttributeMap.put(attrName, entity.get(attrName));
}
return entityHasAttributeMap;
case CATEGORICAL_MREF:
case MREF:
List mrefEntities = new ArrayList();
for (Entity e : entity.getEntities((attr.getName())))
mrefEntities.add(e);
int count = mrefEntities.size();
int toIndex = request.getStart() + request.getNum();
mrefEntities = mrefEntities.subList(request.getStart(), toIndex > count ? count : toIndex);
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy