Java tutorial
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.jembi.rhea.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jembi.rhea.TerminologyService; import com.apelon.apelonserver.client.ServerConnection; import com.apelon.apelonserver.client.ServerConnectionSocket; import com.apelon.dts.client.DTSException; import com.apelon.dts.client.association.AssociationQuery; import com.apelon.dts.client.association.AssociationType; import com.apelon.dts.client.association.ConceptAssociation; import com.apelon.dts.client.association.Synonym; import com.apelon.dts.client.attribute.DTSProperty; import com.apelon.dts.client.attribute.DTSPropertyType; import com.apelon.dts.client.concept.ConceptAttributeSetDescriptor; import com.apelon.dts.client.concept.ConceptChild; import com.apelon.dts.client.concept.ConceptParent; import com.apelon.dts.client.concept.DTSConcept; import com.apelon.dts.client.concept.DTSConceptQuery; import com.apelon.dts.client.concept.DTSSearchOptions; import com.apelon.dts.client.concept.NavChildContext; import com.apelon.dts.client.concept.NavParentContext; import com.apelon.dts.client.concept.NavQuery; import com.apelon.dts.client.concept.OntylogConcept; import com.apelon.dts.client.concept.OntylogConceptQuery; import com.apelon.dts.client.concept.SearchQuery; import com.apelon.dts.client.concept.ThesaurusConceptQuery; import com.apelon.dts.client.namespace.Namespace; import com.apelon.dts.client.namespace.NamespaceQuery; import com.apelon.dts.client.namespace.NamespaceType; /** * An Apelon DTS implementation for the terminology service * * @author Jembi Health Systems */ public class ApelonServiceImpl implements TerminologyService { protected final Log log = LogFactory.getLog(getClass()); public class ApelonNamespace implements TerminologyService.TSNamespace { protected Namespace ns; private int id; private String code; private String name; private ApelonNamespace(Namespace ns) { this.ns = ns; id = ns.getId(); code = ns.getCode(); name = ns.getName(); } @Override public int getId() { return id; } @Override public void setId(int id) { this.id = id; } @Override public String getCode() { return code; } @Override public void setCode(String code) { this.code = code; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public List<TSTerm> getRoots() { try { if (ns.getNamespaceType().equals(NamespaceType.ONTYLOG_EXTENSION)) { List res = new ArrayList(1); res.add(new PseudoRoot(ns)); return res; } NavQuery navQry = NavQuery.createInstance(getConn()); ConceptChild[] roots = navQry.getConceptChildRoots(asd, id); List<TSTerm> res = new ArrayList<TSTerm>(roots.length); for (ConceptChild c : roots) res.add(new ApelonTerm(c)); return res; } catch (Exception ex) { throw new RuntimeException(ex); } } } public class ApelonProperty implements TerminologyService.TSProperty { private String name, value; protected DTSPropertyType _propTypeRef; private ApelonProperty(String name, String value) { this.name = name; this.value = value; } private ApelonProperty(DTSPropertyType type) { this(type.getName(), null); _propTypeRef = type; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public String getValue() { return value; } @Override public void setValue(String value) { this.value = value; } } public class ApelonTerm implements TerminologyService.TSTerm { private DTSConcept concept; private String code; private String name; private TSNamespace namespace; private String preferredTerm; private List<TSTerm> synonyms; private List<TSProperty> properties; private List<TSTerm> superConcepts = null; private List<TSTerm> subConcepts = null; private Boolean hasSubConcepts = null; private ApelonTerm() { } private ApelonTerm(DTSConcept concept) throws TerminologyService.TSException { this.concept = concept; code = concept.getCode(); name = concept.getName(); namespace = lookupNamespace(concept.getNamespaceId()); Synonym pref = concept.getFetchedPreferredTerm(); if (pref != null) preferredTerm = pref.getConcept().getName(); else preferredTerm = name; synonyms = new ArrayList<TSTerm>(); for (Synonym s : concept.getFetchedSynonyms()) { synonyms.add(new ApelonTerm(s.getConcept())); } properties = new ArrayList<TSProperty>(); for (DTSProperty prop : concept.getFetchedProperties()) { properties.add(new ApelonProperty(prop.getName(), prop.getValue())); } } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public TSNamespace getNamespace() { return namespace; } public void setNamespace(TSNamespace namespace) { this.namespace = namespace; } public String getPreferredTerm() { return preferredTerm; } public void setPreferredTerm(String preferredTerm) { this.preferredTerm = preferredTerm; } public List<TSTerm> getSynonyms() { return synonyms; } public void setSynonyms(List<TSTerm> synonyms) { this.synonyms = synonyms; } public List<TSProperty> getProperties() { return properties; } public void setProperties(List<TSProperty> properties) { this.properties = properties; } public void setSuperConcepts(List<TSTerm> superConcepts) { this.superConcepts = superConcepts; } public void setSubConcepts(List<TSTerm> subConcepts) { this.subConcepts = subConcepts; hasSubConcepts = (subConcepts != null) ? subConcepts.size() > 0 : false; } public List<TSTerm> getSuperConcepts() { try { if (superConcepts == null) { NavQuery navQry = NavQuery.createInstance(getConn()); NavParentContext ctx = navQry.getNavParentContext((OntylogConcept) concept, asd); superConcepts = new ArrayList<TSTerm>(); if (ctx.getParents() == null || ctx.getParents().length == 0) { loadSuperconceptsThroughAssociations(); } else { for (ConceptParent cp : ctx.getParents()) { superConcepts.add(new ApelonTerm(cp)); } } Collections.sort(superConcepts, new Comparator<TSTerm>() { public int compare(TSTerm o1, TSTerm o2) { if (o1 == o2) return 0; return o1.getName().compareToIgnoreCase(o2.getName()); } }); } } catch (Exception ex) { throw new RuntimeException(ex); } return superConcepts; } public List<TSTerm> getSubConcepts() { try { if (subConcepts == null) { NavQuery navQry = NavQuery.createInstance(getConn()); NavChildContext ctx = navQry.getNavChildContext((OntylogConcept) concept, asd); subConcepts = new ArrayList<TSTerm>(); if (ctx.getChildren() == null || ctx.getChildren().length == 0) { loadSubconceptsThroughAssociations(); } else { for (ConceptChild cp : ctx.getChildren()) { ApelonTerm term = new ApelonTerm(cp); term.hasSubConcepts = cp.getHasChildren(); subConcepts.add(term); } } Collections.sort(subConcepts, new Comparator<TSTerm>() { public int compare(TSTerm o1, TSTerm o2) { if (o1 == o2) return 0; return o1.getName().compareToIgnoreCase(o2.getName()); } }); } } catch (Exception ex) { throw new RuntimeException(ex); } return subConcepts; } public boolean getHasSubConcepts() { try { if (hasSubConcepts == null) { hasSubConcepts = getSubConcepts().size() > 0; } } catch (Exception ex) { throw new RuntimeException(ex); } return hasSubConcepts.booleanValue(); } public String getHasSubConceptsAsString() { return getHasSubConcepts() ? "true" : "false"; } private void loadSuperconceptsThroughAssociations() throws TerminologyService.TSException, DTSException { for (ConceptAssociation cca : fetchInverseConceptAssociations(concept.getNamespaceId(), code, "Parent Of")) { ApelonTerm term = new ApelonTerm(cca.getFromConcept()); term.hasSubConcepts = true; superConcepts.add(term); } } private void loadSubconceptsThroughAssociations() throws TerminologyService.TSException, DTSException { for (ConceptAssociation cca : fetchConceptAssociations(concept.getNamespaceId(), code, "Parent Of")) { ApelonTerm term = new ApelonTerm(cca.getToConcept()); term.hasSubConcepts = fetchConceptAssociations(concept.getNamespaceId(), cca.getToConcept().getCode(), "Parent Of").length > 0; subConcepts.add(term); } } } /** * If the namespace is an ontylog extension, we return a non-existent "pseudo-root". * This can then be used by the getRoots and getTerm methods to add handling for ontylog extensions. * * TODO I'm sure there's a better way than using fake roots */ private class PseudoRoot extends ApelonTerm { public static final String CODE = "Ontylog Extension Pseudo Root"; private PseudoRoot(Namespace ns) { this(new ApelonNamespace(ns)); } private PseudoRoot(TerminologyService.TSNamespace ns) { setCode(CODE); setNamespace(ns); } @Override public List<TSTerm> getSubConcepts() { try { List<TSTerm> res = new ArrayList<TSTerm>(); ApelonNamespace lns = (ApelonNamespace) lookupNamespace( ((ApelonNamespace) getNamespace()).ns.getLinkedNamespaceId()); res.addAll(lns.getRoots()); //TODO this currently works for our Rwanda Extension, but isn't generic res.add(getTerm("C1", getNamespace().getId())); return res; } catch (Exception ex) { throw new RuntimeException(ex); } } @Override public boolean getHasSubConcepts() { return true; } } private ServerConnection _conn; private ConceptAttributeSetDescriptor asd; public ApelonServiceImpl() { asd = new ConceptAttributeSetDescriptor("allProps"); asd.setAllPropertyTypes(true); log.info("Instantiated new Apelon terminology service instance"); } private synchronized ServerConnection getConn() throws TerminologyService.TSException { try { if (_conn == null) _conn = new ServerConnectionSocket("localhost", 6666); return _conn; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } /* Browsing */ public TSTerm getTerm(String code, int namespaceId) throws TerminologyService.TSException { try { if (code.equals(PseudoRoot.CODE)) return new PseudoRoot(lookupNamespace(namespaceId)); DTSConcept c = ThesaurusConceptQuery.createInstance(getConn()).findConceptByCode(code, namespaceId, asd); if (c == null) { ApelonNamespace ns = (ApelonNamespace) lookupNamespace(namespaceId); if (ns.ns.getNamespaceType().equals(NamespaceType.ONTYLOG_EXTENSION)) c = ThesaurusConceptQuery.createInstance(getConn()).findConceptByCode(code, ns.ns.getLinkedNamespaceId(), asd); } return c != null ? new ApelonTerm(c) : null; } catch (DTSException ex) { throw new TerminologyService.TSException(ex); } } public List<TSTerm> getRootTerms(int namespaceId) throws TerminologyService.TSException { try { OntylogConcept[] cs = NavQuery.createInstance(getConn()).getConceptChildRoots(asd, namespaceId); List<TSTerm> res = new ArrayList<TSTerm>(); for (OntylogConcept c : cs) res.add(new ApelonTerm(c)); return res; } catch (DTSException ex) { throw new TerminologyService.TSException(ex); } } private ConceptAssociation[] fetchConceptAssociations(int namespaceId, String code, String name) throws TerminologyService.TSException, DTSException { ConceptAttributeSetDescriptor ca = new ConceptAttributeSetDescriptor(name + " asd"); ca.addConceptAssociationType( AssociationQuery.createInstance(getConn()).findAssociationTypeByName(name, namespaceId)); OntylogConcept c = (OntylogConcept) OntylogConceptQuery.createInstance(getConn()).findConceptByCode(code, namespaceId, ca); ConceptAssociation[] res = c.getFetchedConceptAssociations(); return res != null ? res : new ConceptAssociation[0]; } private ConceptAssociation[] fetchInverseConceptAssociations(int namespaceId, String code, String name) throws TerminologyService.TSException, DTSException { ConceptAttributeSetDescriptor ca = new ConceptAttributeSetDescriptor(name + " asd"); ca.addInverseConceptAssociationType( AssociationQuery.createInstance(getConn()).findAssociationTypeByName(name, namespaceId)); OntylogConcept c = (OntylogConcept) OntylogConceptQuery.createInstance(getConn()).findConceptByCode(code, namespaceId, ca); ConceptAssociation[] res = c.getFetchedInverseConceptAssociations(); return res != null ? res : new ConceptAssociation[0]; } public boolean validateTerm(String conceptCode, String namespaceCode) throws TerminologyService.TSException { try { ServerConnection conn = getConn(); NamespaceQuery qry = NamespaceQuery.createInstance(conn); Namespace ns = qry.findNamespaceByCode(namespaceCode); if (ns == null) return false; DTSConcept c = ThesaurusConceptQuery.createInstance(conn).findConceptByCode(conceptCode, ns.getId(), ConceptAttributeSetDescriptor.NO_ATTRIBUTES); if (c == null && ns.getNamespaceType().equals(NamespaceType.ONTYLOG_EXTENSION)) c = ThesaurusConceptQuery.createInstance(conn).findConceptByCode(conceptCode, ns.getLinkedNamespaceId(), ConceptAttributeSetDescriptor.NO_ATTRIBUTES); return c != null; } catch (DTSException ex) { throw new TerminologyService.TSException(ex); } } public String validateTerm_ReturnAs1or0String(String code, String namespaceCode) throws TerminologyService.TSException { return validateTerm(code, namespaceCode) ? "1" : "0"; } /* Searching */ public List<TSTerm> search(String query) throws TerminologyService.TSException { return search(query, false); } public List<TSTerm> search(String query, boolean exact) throws TerminologyService.TSException { return search(null, query, exact); } public List<TSTerm> search(List<Integer> namespaceIds, String query, boolean exact) throws TerminologyService.TSException { return search(namespaceIds, query, exact, -1); } public List<TSTerm> search(List<Integer> namespaceIds, String query, boolean exact, int maxResultsPerNamespace) throws TerminologyService.TSException { if (namespaceIds == null || namespaceIds.isEmpty()) return search((Integer) null, query, exact, maxResultsPerNamespace); List<TSTerm> res = new LinkedList<TerminologyService.TSTerm>(); for (Integer id : namespaceIds) { res.addAll(search(id, query, exact, maxResultsPerNamespace)); } return res; } private List<TSTerm> search(Integer namespaceId, String query, boolean exact, int maxResultsPerNamespace) throws TerminologyService.TSException { try { DTSSearchOptions searchOpts = new DTSSearchOptions(); if (namespaceId != null) searchOpts.setNamespaceId(namespaceId); if (maxResultsPerNamespace > 0) searchOpts.setLimit(maxResultsPerNamespace); searchOpts.setAttributeSetDescriptor(asd); SearchQuery searchQry = SearchQuery.createInstance(getConn()); String theQuery = buildQueryString(query, exact); if (theQuery.length() < 3 || (theQuery.charAt(0) == '*' && theQuery.length() < 5)) return Collections.emptyList(); DTSConcept[] cons = searchQry.findConceptsWithNameMatching(theQuery, searchOpts, true); //TODO Lookup by code //if (namespaceId!=null && (cons==null || cons.length==0)) { // //look for code // DTSPropertyType type = getCodeInSourcePropertyType(namespaceId); // if (type!=null) { // cons = searchQry.findConceptsWithPropertyMatching(type, theQuery, searchOpts); // } //} List<TSTerm> res = new LinkedList<TSTerm>(); for (DTSConcept c : cons) res.add(new ApelonTerm(c)); return res; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } private DTSPropertyType getCodeInSourcePropertyType(int namespaceId) throws TerminologyService.TSException { for (TSProperty prop : getAllPropertyTypes(namespaceId)) { if ("Code in Source".equalsIgnoreCase(prop.getName())) return ((ApelonProperty) prop)._propTypeRef; } return null; } private static String buildQueryString(String query, boolean exact) { StringBuilder scrubbed = new StringBuilder(); for (int i = 0; i < query.length(); i++) { if (Character.isLetterOrDigit(query.charAt(i)) || Character.isWhitespace(query.charAt(i))) scrubbed.append(query.charAt(i)); } if (exact) return scrubbed.toString(); StringBuilder res = new StringBuilder(); res.append("*"); for (String s : scrubbed.toString().split("\\s+")) res.append(s + "*"); return res.toString(); } /* Namespaces */ public TSNamespace lookupNamespace(int id) throws TerminologyService.TSException { try { NamespaceQuery qry = NamespaceQuery.createInstance(getConn()); Namespace ns = qry.findNamespaceById(id); return ns != null ? new ApelonNamespace(ns) : null; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } public TSNamespace lookupNamespace(String code) throws TerminologyService.TSException { try { NamespaceQuery qry = NamespaceQuery.createInstance(getConn()); Namespace ns = qry.findNamespaceByCode(code); return ns != null ? new ApelonNamespace(ns) : null; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } public List<TSNamespace> getAllNamespaces() throws TerminologyService.TSException { try { NamespaceQuery qry = NamespaceQuery.createInstance(getConn()); List<TSNamespace> res = new ArrayList<TSNamespace>(); for (Namespace ns : qry.getNamespaces()) res.add(new ApelonNamespace(ns)); return res; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } public List<TSProperty> getAllPropertyTypes(int namespaceId) throws TSException { try { DTSPropertyType types[] = DTSConceptQuery.createInstance(getConn()) .getConceptPropertyTypes(namespaceId); if (types == null || types.length == 0) return Collections.emptyList(); List<TSProperty> res = new ArrayList<TerminologyService.TSProperty>(types.length); for (DTSPropertyType type : types) { res.add(new ApelonProperty(type)); } return res; } catch (Exception ex) { throw new TerminologyService.TSException(ex); } } /* Exporting */ private static class _TSTreeNode { List<TSTerm> breadth; int i; public _TSTreeNode(List<TSTerm> breadth, int i) { this.breadth = breadth; this.i = i; } } /** * Export a specified namespace as a CSV string. Traversal is done depth-first. */ public String exportNamespace(int namespaceId) throws TerminologyService.TSException { StringBuilder res = new StringBuilder(); try { LinkedList<_TSTreeNode> cStack = new LinkedList<_TSTreeNode>(); List<TSTerm> breadth = getRootTerms(namespaceId); List<TSProperty> props = getAllPropertyTypes(namespaceId); int i = 0; ThesaurusConceptQuery cQuery = ThesaurusConceptQuery.createInstance(getConn()); res.append("\"Code\",\"Name\""); for (TSProperty prop : props) res.append(",\"" + prop.getName() + "\""); res.append("\n"); while (i < breadth.size()) { ApelonTerm term = (ApelonTerm) breadth.get(i); //properties aren't being fetched for sub-concepts, so look up the term to fetch it's properties DTSProperty[] termProps = cQuery.findConceptById(term.concept.getId(), namespaceId, asd) .getFetchedProperties(); res.append("\"" + term.getCode() + "\",\"" + term.getName() + "\""); for (TSProperty prop : props) { boolean addedProp = false; for (DTSProperty termProp : termProps) { if (termProp.getName().equals(prop.getName())) { res.append(",\"" + termProp.getValue() + "\""); addedProp = true; } } if (!addedProp) res.append(",\"\""); } res.append("\n"); if (term.getHasSubConcepts()) { cStack.push(new _TSTreeNode(breadth, i)); breadth = term.getSubConcepts(); i = 0; continue; } while (i + 1 == breadth.size() && !cStack.isEmpty()) { _TSTreeNode node = cStack.pop(); breadth = node.breadth; i = node.i; } i++; } } catch (Exception ex) { throw new TerminologyService.TSException(ex); } return res.toString(); } public String exportTerm(String code, int namespaceId) throws TerminologyService.TSException { StringBuilder res = new StringBuilder(); ApelonTerm term = (ApelonTerm) getTerm(code, namespaceId); res.append("\"Code\",\"Name\""); for (TSProperty prop : term.properties) res.append(",\"" + prop.getName() + "\""); res.append("\n"); res.append("\"" + term.getCode() + "\",\"" + term.getName() + "\""); for (TSProperty prop : term.getProperties()) res.append(",\"" + prop.getValue() + "\""); res.append("\n"); return res.toString(); } }