Work on custom search params
This commit is contained in:
parent
d4dda1dace
commit
73924199a9
|
@ -27,6 +27,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.persistence.Query;
|
||||
import javax.persistence.Tuple;
|
||||
|
@ -64,15 +65,17 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirSystemDao.class);
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTxManager;
|
||||
|
||||
@Autowired
|
||||
private IForcedIdDao myForcedIdDao;
|
||||
|
||||
private ReentrantLock myReindexLock = new ReentrantLock(false);
|
||||
|
||||
@Autowired
|
||||
private ITermConceptDao myTermConceptDao;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTxManager;
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
@Override
|
||||
public void deleteAllTagsOnServer(RequestDetails theRequestDetails) {
|
||||
|
@ -205,8 +208,14 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
@Transactional()
|
||||
@Override
|
||||
public int markAllResourcesForReindexing() {
|
||||
|
||||
ourLog.info("Marking all resources as needing reindexing");
|
||||
int retVal = myEntityManager.createQuery("UPDATE " + ResourceTable.class.getSimpleName() + " t SET t.myIndexStatus = null").executeUpdate();
|
||||
|
||||
ourLog.info("Marking all concepts as needing reindexing");
|
||||
retVal += myTermConceptDao.markAllForReindexing();
|
||||
|
||||
ourLog.info("Done marking reindexing");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -266,16 +275,21 @@ public abstract class BaseHapiFhirSystemDao<T, MT> extends BaseHapiFhirDao<IBase
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public int performReindexingPass(final Integer theCount) {
|
||||
if (!myReindexLock.tryLock()) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return doPerformReindexingPass(theCount);
|
||||
} catch (ReindexFailureException e) {
|
||||
ourLog.warn("Reindexing failed for resource {}", e.getResourceId());
|
||||
markResourceAsIndexingFailed(e.getResourceId());
|
||||
return -1;
|
||||
} finally {
|
||||
myReindexLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem;
|
||||
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
|
@ -187,6 +188,9 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ITermCodeSystemVersionDao myCsvDao;
|
||||
|
||||
@Override
|
||||
protected ResourceTable updateEntity(IBaseResource theResource, ResourceTable theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
|
||||
boolean theUpdateVersion, Date theUpdateTime) {
|
||||
|
@ -198,12 +202,22 @@ public class FhirResourceDaoCodeSystemDstu3 extends FhirResourceDaoDstu3<CodeSys
|
|||
String codeSystemUrl = cs.getUrl();
|
||||
if (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == null) {
|
||||
ourLog.info("CodeSystem {} has a status of {}, going to store concepts in terminology tables", retVal.getIdDt().getValue(), cs.getContentElement().getValueAsString());
|
||||
TermCodeSystemVersion persCs = new TermCodeSystemVersion();
|
||||
persCs.setResource(retVal);
|
||||
persCs.setResourceVersionId(retVal.getVersion());
|
||||
persCs.getConcepts().addAll(toPersistedConcepts(cs.getConcept(), persCs));
|
||||
|
||||
Long codeSystemResourcePid = retVal.getId();
|
||||
TermCodeSystemVersion persCs = myCsvDao.findByCodeSystemResourceAndVersion(codeSystemResourcePid, retVal.getVersion());
|
||||
if (persCs != null) {
|
||||
ourLog.info("Code system version already exists in database");
|
||||
} else {
|
||||
|
||||
persCs = new TermCodeSystemVersion();
|
||||
persCs.setResource(retVal);
|
||||
persCs.setResourceVersionId(retVal.getVersion());
|
||||
persCs.getConcepts().addAll(toPersistedConcepts(cs.getConcept(), persCs));
|
||||
ourLog.info("Code system has {} concepts", persCs.getConcepts().size());
|
||||
myTerminologySvc.storeNewCodeSystemVersion(codeSystemResourcePid, codeSystemUrl, persCs);
|
||||
|
||||
}
|
||||
|
||||
myTerminologySvc.storeNewCodeSystemVersion(retVal.getId(), codeSystemUrl, persCs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Stopwatch;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -66,6 +67,7 @@ import ca.uhn.fhir.util.ObjectUtil;
|
|||
import ca.uhn.fhir.util.ValidateUtil;
|
||||
|
||||
public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
||||
private static boolean ourForceSaveDeferredAlwaysForUnitTest;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiTerminologySvc.class);
|
||||
private static final Object PLACEHOLDER_OBJECT = new Object();
|
||||
|
||||
|
@ -94,8 +96,11 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
protected EntityManager myEntityManager;
|
||||
|
||||
private boolean myProcessDeferred = true;
|
||||
private long myNextReindexPass;
|
||||
private boolean myProcessDeferred = true;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTransactionMgr;
|
||||
|
||||
private boolean addToSet(Set<TermConcept> theSetToPopulate, TermConcept theConcept) {
|
||||
boolean retVal = theSetToPopulate.add(theConcept);
|
||||
|
@ -108,6 +113,25 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private int ensureParentsSaved(Collection<TermConceptParentChildLink> theParents) {
|
||||
ourLog.trace("Checking {} parents", theParents.size());
|
||||
int retVal = 0;
|
||||
|
||||
for (TermConceptParentChildLink nextLink : theParents) {
|
||||
if (nextLink.getRelationshipType() == RelationshipTypeEnum.ISA) {
|
||||
TermConcept nextParent = nextLink.getParent();
|
||||
retVal += ensureParentsSaved(nextParent.getParents());
|
||||
if (nextParent.getId() == null) {
|
||||
myConceptDao.saveAndFlush(nextParent);
|
||||
retVal++;
|
||||
ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void fetchChildren(TermConcept theConcept, Set<TermConcept> theSetToPopulate) {
|
||||
for (TermConceptParentChildLink nextChildLink : theConcept.getChildren()) {
|
||||
TermConcept nextChild = nextChildLink.getChild();
|
||||
|
@ -175,6 +199,15 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override
|
||||
* @param theSystem The code system
|
||||
* @param theCode The code
|
||||
*/
|
||||
protected List<VersionIndependentConcept> findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
@Override
|
||||
public Set<TermConcept> findCodesBelow(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) {
|
||||
|
@ -206,7 +239,6 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
ArrayList<VersionIndependentConcept> retVal = toVersionIndependentConcepts(theSystem, codes);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override
|
||||
* @param theSystem The code system
|
||||
|
@ -215,16 +247,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
protected List<VersionIndependentConcept> findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override
|
||||
* @param theSystem The code system
|
||||
* @param theCode The code
|
||||
*/
|
||||
protected List<VersionIndependentConcept> findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
private TermCodeSystemVersion findCurrentCodeSystemVersionForSystem(String theCodeSystem) {
|
||||
TermCodeSystem cs = getCodeSystem(theCodeSystem);
|
||||
if (cs == null || cs.getCurrentVersion() == null) {
|
||||
|
@ -233,11 +256,12 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
TermCodeSystemVersion csv = cs.getCurrentVersion();
|
||||
return csv;
|
||||
}
|
||||
|
||||
private TermCodeSystem getCodeSystem(String theSystem) {
|
||||
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theSystem);
|
||||
return cs;
|
||||
}
|
||||
|
||||
|
||||
private void persistChildren(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack, int theTotalConcepts) {
|
||||
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
|
||||
return;
|
||||
|
@ -271,10 +295,47 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
|
||||
}
|
||||
|
||||
private void saveConceptLink(TermConceptParentChildLink next) {
|
||||
if (next.getId() == null) {
|
||||
myConceptParentChildLinkDao.save(next);
|
||||
private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) {
|
||||
if (theNext.getCodeSystem() != null) {
|
||||
return;
|
||||
}
|
||||
theNext.setCodeSystem(theCodeSystemVersion);
|
||||
for (TermConceptParentChildLink next : theNext.getChildren()) {
|
||||
populateVersion(next.getChild(), theCodeSystemVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private void processReindexing() {
|
||||
if (System.currentTimeMillis() < myNextReindexPass && !ourForceSaveDeferredAlwaysForUnitTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionTemplate tt = new TransactionTemplate(myTransactionMgr);
|
||||
tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
int maxResult = 1000;
|
||||
Page<TermConcept> resources = myConceptDao.findResourcesRequiringReindexing(new PageRequest(0, maxResult));
|
||||
if (resources.hasContent() == false) {
|
||||
myNextReindexPass = System.currentTimeMillis() + DateUtils.MILLIS_PER_MINUTE;
|
||||
return;
|
||||
}
|
||||
|
||||
ourLog.info("Indexing {} / {} concepts", resources.getContent().size(), resources.getTotalElements());
|
||||
|
||||
int count = 0;
|
||||
StopWatch stopwatch = new StopWatch();
|
||||
|
||||
for (TermConcept resourceTable : resources) {
|
||||
saveConcept(resourceTable);
|
||||
count++;
|
||||
}
|
||||
|
||||
ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", new Object[] { count, resources.getContent().size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(count) });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private int saveConcept(TermConcept theConcept) {
|
||||
|
@ -289,33 +350,10 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
ourLog.trace("Saved {} and got PID {}", theConcept.getCode(), theConcept.getId());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int ensureParentsSaved(Collection<TermConceptParentChildLink> theParents) {
|
||||
ourLog.trace("Checking {} parents", theParents.size());
|
||||
int retVal = 0;
|
||||
|
||||
for (TermConceptParentChildLink nextLink : theParents) {
|
||||
if (nextLink.getRelationshipType() == RelationshipTypeEnum.ISA) {
|
||||
TermConcept nextParent = nextLink.getParent();
|
||||
retVal += ensureParentsSaved(nextParent.getParents());
|
||||
if (nextParent.getId() == null) {
|
||||
myConceptDao.saveAndFlush(nextParent);
|
||||
retVal++;
|
||||
ourLog.debug("Saved parent code {} and got id {}", nextParent.getCode(), nextParent.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void populateVersion(TermConcept theNext, TermCodeSystemVersion theCodeSystemVersion) {
|
||||
if (theNext.getCodeSystem() != null) {
|
||||
return;
|
||||
}
|
||||
theNext.setCodeSystem(theCodeSystemVersion);
|
||||
for (TermConceptParentChildLink next : theNext.getChildren()) {
|
||||
populateVersion(next.getChild(), theCodeSystemVersion);
|
||||
|
||||
private void saveConceptLink(TermConceptParentChildLink next) {
|
||||
if (next.getId() == null) {
|
||||
myConceptParentChildLinkDao.save(next);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,43 +406,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
ourLog.info("All deferred concepts and relationships have now been synchronized to the database");
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTransactionMgr;
|
||||
|
||||
private void processReindexing() {
|
||||
if (System.currentTimeMillis() < myNextReindexPass) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionTemplate tt = new TransactionTemplate(myTransactionMgr);
|
||||
tt.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
||||
tt.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
int maxResult = 1000;
|
||||
Page<TermConcept> resources = myConceptDao.findResourcesRequiringReindexing(new PageRequest(0, maxResult));
|
||||
if (resources.hasContent() == false) {
|
||||
myNextReindexPass = System.currentTimeMillis() + DateUtils.MILLIS_PER_MINUTE;
|
||||
return;
|
||||
}
|
||||
|
||||
ourLog.info("Indexing {} / {} concepts", resources.getContent().size(), resources.getTotalElements());
|
||||
|
||||
int count = 0;
|
||||
StopWatch stopwatch = new StopWatch();
|
||||
|
||||
for (TermConcept resourceTable : resources) {
|
||||
saveConcept(resourceTable);
|
||||
count++;
|
||||
}
|
||||
|
||||
ourLog.info("Indexed {} / {} concepts in {}ms - Avg {}ms / resource", new Object[] { count, resources.getContent().size(), stopwatch.getMillis(), stopwatch.getMillisPerOperation(count) });
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProcessDeferred(boolean theProcessDeferred) {
|
||||
myProcessDeferred = theProcessDeferred;
|
||||
|
@ -470,7 +472,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
totalCodeCount += validateConceptForStorage(next, theCodeSystemVersion, conceptsStack, allConcepts);
|
||||
}
|
||||
|
||||
ourLog.info("Saving version");
|
||||
ourLog.info("Saving version containing {} concepts", totalCodeCount);
|
||||
|
||||
TermCodeSystemVersion codeSystemVersion = myCodeSystemVersionDao.saveAndFlush(theCodeSystemVersion);
|
||||
|
||||
|
@ -503,13 +505,13 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
ourLog.info("Note that some concept saving was deferred - still have {} concepts and {} relationships", myConceptsToSaveLater.size(), myConceptLinksToSaveLater.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsSystem(String theSystem) {
|
||||
TermCodeSystem cs = getCodeSystem(theSystem);
|
||||
return cs != null;
|
||||
}
|
||||
|
||||
|
||||
private ArrayList<VersionIndependentConcept> toVersionIndependentConcepts(String theSystem, Set<TermConcept> codes) {
|
||||
ArrayList<VersionIndependentConcept> retVal = new ArrayList<VersionIndependentConcept>(codes.size());
|
||||
for (TermConcept next : codes) {
|
||||
|
@ -547,4 +549,12 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is present only for unit tests, do not call from client code
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void setForceSaveDeferredAlwaysForUnitTest(boolean theForceSaveDeferredAlwaysForUnitTest) {
|
||||
ourForceSaveDeferredAlwaysForUnitTest = theForceSaveDeferredAlwaysForUnitTest;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvc;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class FhirResourceDaoDstu3CodeSystemTest extends BaseJpaDstu3Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3CodeSystemTest.class);
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(false);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIndexContained() throws Exception {
|
||||
BaseHapiTerminologySvc.setForceSaveDeferredAlwaysForUnitTest(true);
|
||||
|
||||
String input = IOUtils.toString(getClass().getResource("/dstu3_codesystem_complete.json"), StandardCharsets.UTF_8);
|
||||
CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input);
|
||||
myCodeSystemDao.create(cs, mySrd);
|
||||
|
||||
|
||||
mySystemDao.markAllResourcesForReindexing();
|
||||
|
||||
int outcome = mySystemDao.performReindexingPass(100);
|
||||
assertNotEquals(-1, outcome); // -1 means there was a failure
|
||||
|
||||
myTermSvc.saveDeferred();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
|
@ -11,9 +8,6 @@ import org.junit.AfterClass;
|
|||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class FhirResourceDaoDstu3ContainedTest extends BaseJpaDstu3Test {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"resourceType": "CodeSystem",
|
||||
"id": "22472",
|
||||
"meta": {
|
||||
"versionId": "3",
|
||||
"lastUpdated": "2017-01-27T10:53:39.457-05:00"
|
||||
},
|
||||
"url": "xvalue://dedalus.eu/mci/CodeSystem/AddressUse",
|
||||
"name": "AddressUse",
|
||||
"status": "active",
|
||||
"publisher": "x1v1-mci",
|
||||
"date": "2016-04-07",
|
||||
"description": "AddressUse",
|
||||
"caseSensitive": true,
|
||||
"compositional": false,
|
||||
"versionNeeded": false,
|
||||
"content": "complete",
|
||||
"concept": {
|
||||
"code": "work",
|
||||
"display": "Work",
|
||||
"definition": "An office address. First choice for business related contacts during business hours.",
|
||||
"designation": {
|
||||
"language": "IT",
|
||||
"value": "Lavoro"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue