package com.nicusa.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nicusa.assembler.DrugAssembler;
import com.nicusa.converter.DrugResourceToDomainConverter;
import com.nicusa.domain.Drug;
import com.nicusa.resource.DrugResource;
import com.nicusa.resource.UserProfileResource;
import com.nicusa.util.*;
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.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.util.UriComponentsBuilder;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
@RestController
public class DrugController {
private static final Logger log = LoggerFactory.getLogger(DrugController.class);
HttpRestClient rest = new HttpRestClient();
HttpSlurper slurp = new HttpSlurper();
FdaSearchTermUtil fixTerm = new FdaSearchTermUtil();
@Autowired
ApiKey apiKey;
@Autowired
@Value("${fda.drug.label.url:https://api.fda.gov/drug/label.json}")
String fdaDrugLabelUrl;
@Autowired
@Value("${nlm.dailymed.autocomplete.url:https://dailymed.nlm.nih.gov/dailymed/autocomplete.cfm}")
private String nlmDailymedAutocompleteUrl;
@Autowired
@Value("${nlm.rxnav.url:http://rxnav.nlm.nih.gov/REST/rxcui}")
String nlmRxnavUrl;
@PersistenceContext
private EntityManager entityManager;
@Autowired
private DrugAssembler drugAssembler;
@Autowired
private DrugResourceToDomainConverter drugResourceToDomainConverter;
@Autowired
private SecurityController securityController;
@Transactional
@ResponseBody
@RequestMapping(value = "/api/drug", method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<?> create(@RequestBody DrugResource drugResource) {
Long loggedInUserProfileId = securityController.getAuthenticatedUserProfileId();
if (loggedInUserProfileId != null && loggedInUserProfileId != UserProfileResource.ANONYMOUS_USER_PROFILE_ID) {
Drug drug = drugResourceToDomainConverter.convert(drugResource);
entityManager.persist(drug);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(linkTo(methodOn(DrugController.class).get(drug.getId())).toUri());
return new ResponseEntity<>(httpHeaders, HttpStatus.CREATED);
} else {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
@Transactional
@ResponseBody
@RequestMapping(value = "/drug/{id}", method = RequestMethod.DELETE, consumes = "application/json")
public ResponseEntity<?> delete(@PathVariable("id") Long id) {
Long loggedInUserProfileId = securityController.getAuthenticatedUserProfileId();
if(loggedInUserProfileId != null && loggedInUserProfileId != UserProfileResource.ANONYMOUS_USER_PROFILE_ID) {
Drug drug = entityManager.find(Drug.class, id);
entityManager.remove(drug);
return new ResponseEntity<>(HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
@ResponseBody
@RequestMapping(value = "/drug/{id}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<DrugResource> get(@PathVariable("id") Long id) {
Drug drug = entityManager.find(Drug.class, id);
if (drug == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(drugAssembler.toResource(drug), HttpStatus.OK);
}
public Set<String> getUniisByName ( String name ) throws IOException {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(
this.fdaDrugLabelUrl )
.queryParam( "search", "(openfda.brand_name:" +
fixTerm.makeFdaReady( name ) + ")" )
.queryParam( "count", "openfda.unii" );
this.apiKey.addToUriComponentsBuilder( builder );
try {
String result = rest.getForObject( builder.build().toUri(), String.class );
FieldFinder finder = new FieldFinder( "term" );
return finder.find( result );
} catch ( HttpClientErrorException notFound ) {
if( notFound.getStatusCode() == HttpStatus.NOT_FOUND ){
// server reported 404, handle it by returning no results
log.warn( "[getUniisByName] No uniis with name " + name);
return Collections.emptySet();
}
throw notFound;
}
}
public Map<String,Set<String>> getBrandNamesByNameAndUniis (
String name,
Set<String> uniis ) throws IOException {
Map<String,Set<String>> rv = new TreeMap<String,Set<String>>();
for ( String unii : uniis ) {
rv.put( unii, this.getBrandNamesByNameAndUnii( name, unii ));
}
return rv;
}
public Set<String> getBrandNamesByNameAndUnii (
String name,
String unii ) throws IOException {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(
this.fdaDrugLabelUrl )
.queryParam( "search", "(openfda.unii:" +
fixTerm.makeFdaSafe( unii ) +
")+AND+(openfda.brand_name:" +
fixTerm.makeFdaReady( fixTerm.makeFdaSafe( name )) +
")")
.queryParam( "count", "openfda.brand_name.exact" );
this.apiKey.addToUriComponentsBuilder( builder );
try {
String result = rest.getForObject( builder.build().toUri(), String.class );
FieldFinder finder = new FieldFinder( "term" );
return finder.find( result );
} catch ( HttpClientErrorException notFound ) {
if( notFound.getStatusCode() == HttpStatus.NOT_FOUND ){
// server reported 404, handle it by returning no results
log.warn( "No brand name data found for search by name: " +
name + " and unii " + unii );
return Collections.emptySet();
}
throw notFound;
}
}
public String getGenericNameByRxcui ( Long rxcui ) throws IOException {
if ( rxcui == null ) {
return "";
}
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(
this.nlmRxnavUrl + "/" + rxcui + "/allProperties.json" )
.queryParam( "prop", "names" );
String result = rest.getForObject( builder.build().toUri(), String.class );
FieldFinder finder = new FieldFinder( "propValue" );
Set<String> generics = finder.find( result );
if ( generics.isEmpty() ) {
return "";
} else {
return generics.iterator().next();
}
}
public Long getRxcuiByUnii ( String unii ) throws IOException {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(
this.nlmRxnavUrl + ".json" )
.queryParam( "idtype", "UNII_CODE" )
.queryParam( "id",
URLEncoder.encode( unii, StandardCharsets.UTF_8.name() ));
String result = rest.getForObject( builder.build().toUri(), String.class );
FieldFinder finder = new FieldFinder( "rxnormId" );
for ( String id : finder.find( result )) {
try {
return Long.parseLong( id );
} catch ( NumberFormatException nfe ) {
// ignore invalid ids
log.warn( "Got invalid rxnormId from Rxnav query on unii" +
unii );
}
}
return null;
}
@RequestMapping("/drug")
public String search(
@RequestParam(value = "name", defaultValue = "") String name,
@RequestParam(value = "limit", defaultValue = "10") int limit,
@RequestParam(value = "skip", defaultValue = "0") int skip) throws IOException {
name = fixTerm.makeFdaSafe( name );
if ( name.length() == 0 ) {
return "[]";
}
List<DrugSearchResult> rv = new LinkedList<DrugSearchResult>();
Set<String> uniis = this.getUniisByName( name );
Map<String,Set<String>> brandNames = this.getBrandNamesByNameAndUniis(
name,
uniis );
// create full list of drug search results
for ( String unii : uniis ) {
for ( String brandName : brandNames.get( unii )) {
DrugSearchResult res = new DrugSearchResult();
res.setUnii( unii );
res.setBrandName( brandName );
rv.add( res );
}
}
// sort the list of drug search results by custom comparator
Collections.sort( rv, new DrugSearchComparator( name ));
// implement skip/limit
if ( rv.size() > 0 ) {
rv = rv.subList( skip, Math.min( rv.size(), skip+limit ));
}
// fill in details for all the results we're returning
for ( DrugSearchResult res : rv ) {
res.setRxcui( this.getRxcuiByUnii( res.getUnii() ));
res.setGenericName( this.getGenericNameByRxcui( res.getRxcui() ));
if ( res.getActiveIngredients().isEmpty() &&
res.getGenericName() != null ) {
res.setActiveIngredients(
new TreeSet<String>( Arrays.asList(
res.getGenericName().split( ", " ))));
}
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString( rv );
}
@RequestMapping("/autocomplete")
//TODO - Move this to the client side.
public String autocomplete(
@RequestParam(value = "name", defaultValue = "") String name) throws IOException {
if (name == null) {
name = "";
}
String result = "[{\"value\":\"" + name + "\"}]";
if (name.length() >= 2) {
ObjectMapper mapper = new ObjectMapper();
String query = this.nlmDailymedAutocompleteUrl + "?key=search&returntype=json&term="+name;
JsonNode node = mapper.readTree(
rest.getForObject(query, String.class));
AutocompleteFilter myFilter = new AutocompleteFilter();
List<String> values = myFilter.filter(node);
result = mapper.writeValueAsString(values);
}
return result;
}
}