/* * Copyright 2015 fdefalco. * * 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.ohdsi.webapi.service; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import javax.transaction.Transactional; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.DELETE; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.ohdsi.webapi.conceptset.ConceptSet; import org.ohdsi.webapi.conceptset.ConceptSetExport; import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfo; import org.ohdsi.webapi.conceptset.ConceptSetGenerationInfoRepository; import org.ohdsi.webapi.conceptset.ConceptSetItem; import org.ohdsi.webapi.conceptset.ExportUtil; import org.ohdsi.webapi.evidence.NegativeControlRepository; import org.ohdsi.webapi.source.SourceInfo; import org.ohdsi.webapi.vocabulary.Concept; import org.ohdsi.webapi.vocabulary.ConceptSetExpression; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Component; /** * * @author fdefalco */ @Path("/conceptset/") @Component public class ConceptSetService extends AbstractDaoService { @Autowired private ConceptSetGenerationInfoRepository conceptSetGenerationInfoRepository; @Autowired private NegativeControlRepository negativeControlRepository; @Autowired private VocabularyService vocabService; @Autowired private SourceService sourceService; @Path("{id}") @GET @Produces(MediaType.APPLICATION_JSON) public ConceptSet getConceptSet(@PathParam("id") final int id) { return getConceptSetRepository().findById(id); } @GET @Path("/") @Produces(MediaType.APPLICATION_JSON) public Iterable<ConceptSet> getConceptSets() { return getConceptSetRepository().findAll(); } @GET @Path("{id}/items") @Produces(MediaType.APPLICATION_JSON) public Iterable<ConceptSetItem> getConceptSetItems(@PathParam("id") final int id) { return getConceptSetItemRepository().findAllByConceptSetId(id); } @GET @Path("{id}/expression") @Produces(MediaType.APPLICATION_JSON) public ConceptSetExpression getConceptSetExpression(@PathParam("id") final int id) { HashMap<Long, ConceptSetItem> map = new HashMap<>(); // collect the concept set items so we can lookup their properties later for (ConceptSetItem csi : getConceptSetItems(id)) { map.put(csi.getConceptId(), csi); } // create our expression to return ConceptSetExpression expression = new ConceptSetExpression(); // expression.items = new ConceptSetExpression.ConceptSetItem[map.size()]; ArrayList<ConceptSetExpression.ConceptSetItem> expressionItems = new ArrayList<>(); // lookup the concepts we need information for long[] identifiers = new long[map.size()]; int identifierIndex = 0; for (Long identifier : map.keySet()) { identifiers[identifierIndex] = identifier; identifierIndex++; } // assume we want to resolve using the priority vocabulary provider SourceInfo vocabSourceInfo = sourceService.getPriorityVocabularySourceInfo(); Collection<Concept> concepts = vocabService.executeIdentifierLookup(vocabSourceInfo.sourceKey, identifiers); // put the concept information into the expression along with the concept set item information for (Concept concept : concepts) { ConceptSetExpression.ConceptSetItem currentItem = new ConceptSetExpression.ConceptSetItem(); currentItem.concept = concept; ConceptSetItem csi = map.get(concept.conceptId); currentItem.includeDescendants = (csi.getIncludeDescendants() == 1); currentItem.includeMapped = (csi.getIncludeMapped() == 1); currentItem.isExcluded = (csi.getIsExcluded() == 1); expressionItems.add(currentItem); } expression.items = expressionItems.toArray(new ConceptSetExpression.ConceptSetItem[0]); return expression; } @GET @Path("{id}/{name}/exists") @Produces(MediaType.APPLICATION_JSON) public Collection<ConceptSet> getConceptSetExists(@PathParam("id") final int id, @PathParam("name") String name) { return getConceptSetRepository().conceptSetExists(id, name); } @PUT @Path("{id}/items") @Produces(MediaType.APPLICATION_JSON) @Transactional public boolean saveConceptSetItems(@PathParam("id") final int id, ConceptSetItem[] items) { getConceptSetItemRepository().deleteByConceptSetId(id); for (ConceptSetItem csi : items) { csi.setConceptSetId(id); getConceptSetItemRepository().save(csi); } return true; } @GET @Path("/exportlist") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response exportConceptSetList(@QueryParam("conceptsets") final String conceptSetList) throws Exception { ArrayList<Integer> conceptSetIds = new ArrayList<>(); try { String[] conceptSetItems = conceptSetList.split("\\+"); for(String csi : conceptSetItems) { conceptSetIds.add(Integer.valueOf(csi)); } if (conceptSetIds.size() <= 0) { throw new IllegalArgumentException("You must supply a querystring value for conceptsets that is of the form: ?conceptset=<concept_set_id_1>+<concept_set_id_2>+<concept_set_id_n>"); } } catch (Exception e) { throw e; } ByteArrayOutputStream baos; SourceInfo sourceInfo = sourceService.getPriorityVocabularySourceInfo(); ArrayList<ConceptSetExport> cs = new ArrayList<>(); Response response = null; try { // Load all of the concept sets requested for (int i = 0; i < conceptSetIds.size(); i++) { // Get the concept set information cs.add(getConceptSetForExport(conceptSetIds.get(i), sourceInfo)); } // Write Concept Set Expression to a CSV baos = ExportUtil.writeConceptSetExportToCSVAndZip(cs); response = Response .ok(baos) .type(MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=\"conceptSetExport.zip\"") .build(); } catch (Exception ex) { throw ex; } return response; } @GET @Path("{id}/export") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response exportConceptSetToCSV(@PathParam("id") final String id) throws Exception { return this.exportConceptSetList(id); } @Path("/") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public ConceptSet createConceptSet(ConceptSet conceptSet) { ConceptSet updated = new ConceptSet(); return updateConceptSet(updated, conceptSet); } @Path("/{id}") @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public ConceptSet updateConceptSet(@PathParam("id") final int id, ConceptSet conceptSet) throws Exception { ConceptSet updated = this.getConceptSet(id); if (updated == null) { throw new Exception("Concept Set does not exist."); } return updateConceptSet(updated, conceptSet); } private ConceptSet updateConceptSet(ConceptSet dst, ConceptSet src) { dst.setName(src.getName()); dst = this.getConceptSetRepository().save(dst); return dst; } private ConceptSetExport getConceptSetForExport(int conceptSetId, SourceInfo vocabSource) { ConceptSetExport cs = new ConceptSetExport(); // Set the concept set id cs.ConceptSetId = conceptSetId; // Get the concept set information cs.ConceptSetName = this.getConceptSet(conceptSetId).getName(); // Get the concept set expression cs.csExpression = this.getConceptSetExpression(conceptSetId); // Lookup the identifiers cs.identifierConcepts = vocabService.executeIncludedConceptLookup(vocabSource.sourceKey, cs.csExpression); //vocabService.executeIdentifierLookup(vocabSource.sourceKey, conceptIds); // Lookup the mapped items cs.mappedConcepts = vocabService.executeMappedLookup(vocabSource.sourceKey, cs.csExpression); return cs; } @GET @Path("{id}/generationinfo") @Produces(MediaType.APPLICATION_JSON) public Collection<ConceptSetGenerationInfo> getConceptSetGenerationInfo(@PathParam("id") final int id) { return this.conceptSetGenerationInfoRepository.findAllByConceptSetId(id); } @DELETE @Transactional(rollbackOn = Exception.class, dontRollbackOn = EmptyResultDataAccessException.class) @Path("{id}") public void deleteConceptSet(@PathParam("id") final int id) throws Exception { // Remove any evidence try { this.negativeControlRepository.deleteAllByConceptSetId(id); } catch (EmptyResultDataAccessException e) { // Ignore - there may be no data log.debug(e.getMessage()); } catch (Exception e) { throw e; } // Remove any generation info try { this.conceptSetGenerationInfoRepository.deleteByConceptSetId(id); } catch (EmptyResultDataAccessException e) { // Ignore - there may be no data log.debug(e.getMessage()); } catch (Exception e) { throw e; } // Remove the concept set items try { getConceptSetItemRepository().deleteByConceptSetId(id); } catch (EmptyResultDataAccessException e) { // Ignore - there may be no data log.debug(e.getMessage()); } catch (Exception e) { throw e; } // Remove the concept set try { getConceptSetRepository().delete(id); } catch (EmptyResultDataAccessException e) { // Ignore - there may be no data log.debug(e.getMessage()); } catch (Exception e) { throw e; } } }