-
Notifications
You must be signed in to change notification settings - Fork 2
ME-32: Support IDGEN Domains (identifier sources & auto generation options) #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| /* | ||
| * 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/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.module.metadataexport.domain.idgen; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.apache.commons.lang3.BooleanUtils; | ||
| import org.openmrs.OpenmrsObject; | ||
| import org.openmrs.PatientIdentifierType; | ||
| import org.openmrs.annotation.OpenmrsProfile; | ||
| import org.openmrs.api.context.Context; | ||
| import org.openmrs.api.db.hibernate.HibernateUtil; | ||
| import org.openmrs.module.idgen.AutoGenerationOption; | ||
| import org.openmrs.module.idgen.IdentifierPool; | ||
| import org.openmrs.module.idgen.IdentifierSource; | ||
| import org.openmrs.module.idgen.RemoteIdentifierSource; | ||
| import org.openmrs.module.idgen.SequentialIdentifierGenerator; | ||
| import org.openmrs.module.idgen.service.IdentifierSourceService; | ||
| import org.openmrs.module.initializer.Domain; | ||
| import org.openmrs.module.metadataexport.export.BaseLineExporter; | ||
| import org.openmrs.module.metadataexport.export.CsvDomainExporter; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collection; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @OpenmrsProfile(modules = { "idgen:4.6.* - 9.*" }) | ||
| public class AutoGenerationOptionDomainExporter extends CsvDomainExporter<AutoGenerationOption> { | ||
|
|
||
| @Override | ||
| protected List<BaseLineExporter<AutoGenerationOption>> chain() { | ||
| return Collections.singletonList(new AutoGenerationOptionLineExporter()); | ||
| } | ||
|
|
||
| @Override | ||
| protected String fileName() { | ||
| return "autoGenerationOptions.csv"; | ||
| } | ||
|
|
||
| @Override | ||
| public Domain getDomain() { | ||
| return Domain.AUTO_GENERATION_OPTIONS; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handles(OpenmrsObject instance) { | ||
| return instance instanceof AutoGenerationOption; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<AutoGenerationOption> getAllInstances() { | ||
| IdentifierSourceService service = Context.getService(IdentifierSourceService.class); | ||
| List<AutoGenerationOption> options = new ArrayList<>(); | ||
| for (PatientIdentifierType type : Context.getPatientService().getAllPatientIdentifierTypes(true)) { | ||
| List<AutoGenerationOption> forType = service.getAutoGenerationOptions(type); | ||
| if (forType != null) { | ||
| options.addAll(forType); | ||
| } | ||
| } | ||
| return exportable(options); | ||
| } | ||
|
|
||
| /** The subset of options that can round-trip through Iniz, in a stable order. */ | ||
| static List<AutoGenerationOption> exportable(List<AutoGenerationOption> options) { | ||
| List<AutoGenerationOption> result = new ArrayList<>(); | ||
| for (AutoGenerationOption option : options) { | ||
| if (BooleanUtils.isTrue(option.getRetired())) { | ||
| continue; | ||
| } | ||
| IdentifierSource source = HibernateUtil.getRealObjectFromProxy(option.getSource()); | ||
| if (source != null && !(source instanceof SequentialIdentifierGenerator) | ||
| && !(source instanceof RemoteIdentifierSource) && !(source instanceof IdentifierPool)) { | ||
|
Comment on lines
+82
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that If merged as-is, a server with an auto generation option on such a pool gets an Asking the idgen exporter whether it will really export the source, instead of repeating a type test here, would stop the two sides drifting again. It would also make the README's "auto generation options pointing at them" line true for the pool cases it now lists. |
||
| log.warn("Idgen: skipping auto generation option {} whose source {} has unsupported type {}", | ||
| option.getUuid(), source.getUuid(), source.getClass().getName()); | ||
| continue; | ||
| } | ||
| result.add(option); | ||
| } | ||
| result.sort(Comparator | ||
| .comparing( | ||
| (AutoGenerationOption o) -> o.getIdentifierType() == null || o.getIdentifierType().getName() == null ? "" | ||
| : o.getIdentifierType().getName()) | ||
| .thenComparing(o -> o.getLocation() == null ? "" : o.getLocation().getName(), | ||
| Comparator.nullsFirst(Comparator.naturalOrder())) | ||
| .thenComparing(AutoGenerationOption::getUuid, Comparator.nullsFirst(Comparator.naturalOrder()))); | ||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<? extends OpenmrsObject> getDependencies(AutoGenerationOption instance) { | ||
| List<OpenmrsObject> dependencies = new ArrayList<>(); | ||
| if (instance.getIdentifierType() != null) { | ||
| dependencies.add(instance.getIdentifierType()); | ||
| } | ||
| if (instance.getSource() != null) { | ||
| dependencies.add(instance.getSource()); | ||
| } | ||
| if (instance.getLocation() != null) { | ||
| dependencies.add(instance.getLocation()); | ||
| } | ||
| return dependencies; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * 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/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.module.metadataexport.domain.idgen; | ||
|
|
||
| import org.openmrs.module.idgen.AutoGenerationOption; | ||
| import org.openmrs.module.initializer.api.BaseLineProcessor; | ||
| import org.openmrs.module.initializer.api.idgen.autogen.AutoGenerationOptionLineProcessor; | ||
| import org.openmrs.module.metadataexport.export.BaseLineExporter; | ||
| import org.openmrs.module.metadataexport.export.ExportLine; | ||
|
|
||
| /** | ||
| * Iniz ignores {@code void/retire} for this domain, so a full row is always written (retired | ||
| * options are filtered out by the domain exporter instead). The boolean columns are always emitted | ||
| * — an absent cell becomes a null that NPEs unboxing into idgen's primitive-boolean setters. | ||
| */ | ||
| public class AutoGenerationOptionLineExporter extends BaseLineExporter<AutoGenerationOption> { | ||
|
|
||
| @Override | ||
| public void export(AutoGenerationOption option, ExportLine line) { | ||
| line.put(BaseLineProcessor.HEADER_UUID, option.getUuid()); | ||
| if (option.getIdentifierType() != null) { | ||
| line.put(AutoGenerationOptionLineProcessor.IDENTIFIER_TYPE, option.getIdentifierType().getUuid()); | ||
| } | ||
| if (option.getLocation() != null) { | ||
| line.put(AutoGenerationOptionLineProcessor.LOCATION, option.getLocation().getUuid()); | ||
| } | ||
| if (option.getSource() != null) { | ||
| line.put(AutoGenerationOptionLineProcessor.IDENTIFIER_SOURCE, option.getSource().getUuid()); | ||
| } | ||
| line.put(AutoGenerationOptionLineProcessor.MANUAL_ENTRY_ENABLED, Boolean.toString(option.isManualEntryEnabled())); | ||
| line.put(AutoGenerationOptionLineProcessor.AUTO_GEN_ENABLED, | ||
| Boolean.toString(option.isAutomaticGenerationEnabled())); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| * 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/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.module.metadataexport.domain.idgen; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.openmrs.api.db.hibernate.HibernateUtil; | ||
| import org.openmrs.module.idgen.IdentifierPool; | ||
| import org.openmrs.module.idgen.IdentifierSource; | ||
| import org.openmrs.module.metadataexport.export.BaseLineExporter; | ||
| import org.openmrs.module.metadataexport.export.ExportLine; | ||
|
|
||
| /** | ||
| * Columns specific to {@link IdentifierPool} sources. The pooled identifiers themselves are runtime | ||
| * data and are not exported. The boolean columns are always emitted — an absent cell becomes a null | ||
| * that NPEs when Iniz assigns it into idgen's primitive-backed fields. | ||
| */ | ||
| @Slf4j | ||
| public class IdentifierPoolLineExporter extends BaseLineExporter<IdentifierSource> { | ||
|
|
||
| @Override | ||
| public void export(IdentifierSource source, ExportLine line) { | ||
| source = HibernateUtil.getRealObjectFromProxy(source); | ||
| if (!(source instanceof IdentifierPool)) { | ||
| return; | ||
| } | ||
|
|
||
| IdentifierPool pool = (IdentifierPool) source; | ||
| if (pool.getSource() == null) { | ||
| log.warn("Idgen: identifier pool {} has no backing source; Iniz requires one on import", pool.getUuid()); | ||
| } else { | ||
| line.put(IdentifierSourceLineExporter.HEADER_POOL_IDENTIFIER_SOURCE, pool.getSource().getUuid()); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch logs that Iniz will reject the row, then writes the row anyway, and I think it has to skip the pool instead. A pool exported without a The same gap sits one level out in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
| line.put(IdentifierSourceLineExporter.HEADER_POOL_BATCH_SIZE, String.valueOf(pool.getBatchSize())); | ||
| line.put(IdentifierSourceLineExporter.HEADER_POOL_MINIMUM_SIZE, String.valueOf(pool.getMinPoolSize())); | ||
| line.put(IdentifierSourceLineExporter.HEADER_POOL_REFILL_WITH_TASK, | ||
| Boolean.toString(pool.isRefillWithScheduledTask())); | ||
| line.put(IdentifierSourceLineExporter.HEADER_POOL_SEQUENTIAL_ALLOCATION, Boolean.toString(pool.isSequential())); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| /* | ||
| * 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/. OpenMRS is also distributed under | ||
| * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. | ||
| * | ||
| * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS | ||
| * graphic logo is a trademark of OpenMRS Inc. | ||
| */ | ||
| package org.openmrs.module.metadataexport.domain.idgen; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.openmrs.OpenmrsObject; | ||
| import org.openmrs.annotation.OpenmrsProfile; | ||
| import org.openmrs.api.context.Context; | ||
| import org.openmrs.api.db.hibernate.HibernateUtil; | ||
| import org.openmrs.module.idgen.IdentifierPool; | ||
| import org.openmrs.module.idgen.IdentifierSource; | ||
| import org.openmrs.module.idgen.RemoteIdentifierSource; | ||
| import org.openmrs.module.idgen.SequentialIdentifierGenerator; | ||
| import org.openmrs.module.idgen.service.IdentifierSourceService; | ||
| import org.openmrs.module.initializer.Domain; | ||
| import org.openmrs.module.metadataexport.export.BaseLineExporter; | ||
| import org.openmrs.module.metadataexport.export.CsvDomainExporter; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Identifier sources are written as one file per source type, mirroring Iniz's own fixture layout, | ||
| * so each file carries an {@code _order:} header — pools reference their backing source by uuid, so | ||
| * the pool file must load last. Custom {@link IdentifierSource} subclasses have no Iniz | ||
| * representation and are skipped with a warning. | ||
| */ | ||
| @Slf4j | ||
| @Component | ||
| @OpenmrsProfile(modules = { "idgen:4.6.* - 9.*" }) | ||
| public class IdentifierSourceDomainExporter extends CsvDomainExporter<IdentifierSource> { | ||
|
|
||
| public static final String FILE_SEQUENTIAL = "idgen_sequential.csv"; | ||
|
|
||
| public static final String FILE_REMOTE = "idgen_remote.csv"; | ||
|
|
||
| public static final String FILE_POOL = "idgen_pool.csv"; | ||
|
|
||
| @Override | ||
| protected List<BaseLineExporter<IdentifierSource>> chain() { | ||
| return Arrays.asList(new IdentifierSourceLineExporter(), new SequentialIdentifierGeneratorLineExporter(), | ||
| new RemoteIdentifierSourceLineExporter(), new IdentifierPoolLineExporter()); | ||
| } | ||
|
|
||
| @Override | ||
| protected String fileName() { | ||
| throw new UnsupportedOperationException("idgen writes one file per source type; see partition()"); | ||
| } | ||
|
|
||
| @Override | ||
| protected Map<String, Collection<IdentifierSource>> partition(Collection<IdentifierSource> instances) { | ||
| Map<String, Collection<IdentifierSource>> files = new LinkedHashMap<>(); | ||
| for (IdentifierSource instance : instances) { | ||
| IdentifierSource real = HibernateUtil.getRealObjectFromProxy(instance); | ||
| if (real instanceof IdentifierPool) { | ||
| files.computeIfAbsent(FILE_POOL, f -> new ArrayList<>()).add(instance); | ||
| } else if (real instanceof SequentialIdentifierGenerator) { | ||
| files.computeIfAbsent(FILE_SEQUENTIAL, f -> new ArrayList<>()).add(instance); | ||
| } else if (real instanceof RemoteIdentifierSource) { | ||
| files.computeIfAbsent(FILE_REMOTE, f -> new ArrayList<>()).add(instance); | ||
| } else { | ||
| log.warn("Idgen: skipping identifier source {} of unsupported type {}", real.getUuid(), | ||
| real.getClass().getName()); | ||
| } | ||
| } | ||
| return files; | ||
| } | ||
|
|
||
| @Override | ||
| protected Integer order(String fileName) { | ||
| // pools must load after the sources they reference | ||
| switch (fileName) { | ||
| case FILE_SEQUENTIAL: | ||
| return 1000; | ||
| case FILE_REMOTE: | ||
| return 2000; | ||
| case FILE_POOL: | ||
| return 3000; | ||
| default: | ||
| throw new IllegalArgumentException("Not an idgen export file: " + fileName); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public Domain getDomain() { | ||
| return Domain.IDGEN; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean handles(OpenmrsObject instance) { | ||
| return instance instanceof SequentialIdentifierGenerator || instance instanceof RemoteIdentifierSource | ||
| || instance instanceof IdentifierPool; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<IdentifierSource> getAllInstances() { | ||
| List<IdentifierSource> sources = new ArrayList<>(); | ||
| for (IdentifierSource source : Context.getService(IdentifierSourceService.class).getAllIdentifierSources(true)) { | ||
| IdentifierSource real = HibernateUtil.getRealObjectFromProxy(source); | ||
| if (handles(real)) { | ||
| sources.add(source); | ||
| } else { | ||
| log.warn("Idgen: skipping identifier source {} of unsupported type {} — no Iniz representation", | ||
| real.getUuid(), real.getClass().getName()); | ||
| } | ||
| } | ||
| return sources; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<? extends OpenmrsObject> getDependencies(IdentifierSource instance) { | ||
| List<OpenmrsObject> dependencies = new ArrayList<>(); | ||
| if (instance.getIdentifierType() != null) { | ||
| dependencies.add(instance.getIdentifierType()); | ||
| } | ||
| IdentifierSource real = HibernateUtil.getRealObjectFromProxy(instance); | ||
| if (real instanceof IdentifierPool && ((IdentifierPool) real).getSource() != null) { | ||
| dependencies.add(((IdentifierPool) real).getSource()); | ||
| } | ||
| return dependencies; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AutoGenerationOptionhas no retired column. idgen maps only id, uuid, identifier_type, location, source, manual_entry_enabled and automatic_generation_enabled (IdentifierSource.hbm.xml, and the liquibase changesets add nothing else), so a persisted option always reports the in-memoryBaseOpenmrsMetadatadefault and this branch never fires.exportableFiltersRetiredOptionspasses only because it retires an option that never came from the database.Small thing, but together with the "retired options are filtered out by the domain exporter instead" note in
AutoGenerationOptionLineExporterit reads as though retirement were supported for this domain, whichAutoGenerationOptionsCsvParser.setRetiredexplicitly refuses on the import side.