diff --git a/.github/workflows/angular_build.yml b/.github/workflows/angular_build.yml
index 5d86ccedd97..3c618189ea8 100644
--- a/.github/workflows/angular_build.yml
+++ b/.github/workflows/angular_build.yml
@@ -26,7 +26,7 @@ jobs:
uses: actions/setup-node@v5
with:
# https://github.com/actions/setup-node
- node-version: 20
+ node-version: 24
cache: npm
cache-dependency-path: matchbox-frontend/package-lock.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1f086d8564a..449c0d6f269 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -105,6 +105,15 @@
"vmArgs": "-Dspring.config.additional-location=file:with-ch/application.yaml",
"cwd": "${workspaceFolder}/matchbox-server"
},
+ {
+ "type": "java",
+ "name": "Launch Matchbox-Server (ans)",
+ "request": "launch",
+ "mainClass": "ca.uhn.fhir.jpa.starter.Application",
+ "projectName": "matchbox-server",
+ "vmArgs": "-Dspring.config.additional-location=file:with-ans/application.yaml",
+ "cwd": "${workspaceFolder}/matchbox-server"
+ },
{
"type": "java",
"name": "Launch Matchbox-Server (gazelle)",
diff --git a/Claude.md b/Claude.md
new file mode 100644
index 00000000000..e3ae7c2e94d
--- /dev/null
+++ b/Claude.md
@@ -0,0 +1,560 @@
+# Matchbox Project Overview
+
+## What is Matchbox?
+
+Matchbox validation and mapping platform that provides:
+- **FHIR validation** against profiles and implementation guides
+- **FHIR mapping language** transformations (StructureMap)
+- **CDA to FHIR** transformation support
+- **Questionnaire** form filling capabilities
+- **Terminology services** integration
+- **AI-powered validation** assistance
+
+## Project Structure
+
+The project consists of three main components:
+
+```
+matchbox/
+├── matchbox-engine/ # Core FHIR validation and transformation library
+├── matchbox-server/ # Full FHIR server with REST API
+├── matchbox-frontend/ # Angular web UI
+└── docs/ # MkDocs documentation
+```
+
+## Component Details
+
+### 1. matchbox-engine (Core Library)
+
+**Purpose**: Reusable Java library for FHIR validation and transformation
+
+**Technology Stack**:
+- Java 21
+- Spring Framework 6.1
+- HAPI FHIR 8.0.0
+- HL7 FHIR Core 6.7.10 (official FHIR validator)
+
+**Key Capabilities**:
+- FHIR resource validation (R4, R5, R4B)
+- StructureMap-based transformations
+- CDA to FHIR conversion
+- FHIR package loading and management
+
+
+**Key Classes**:
+```
+ch.ahdis.matchbox.engine/
+└── MatchboxEngine # Core engine class
+```
+
+**Testing**:
+- JUnit 5 for unit tests
+- XMLUnit for XML comparisons
+- Comprehensive validation test suites for R4/R5
+
+**Build**:
+```bash
+cd matchbox-engine
+mvn clean install
+```
+
+### 2. matchbox-server (FHIR Server)
+
+**Purpose**: Production-ready FHIR API support
+
+**Technology Stack**:
+- Java 21
+- Spring Boot 3.3.11
+- HAPI FHIR JPA Server 8.0.0
+- Embedded Tomcat 10.1.48
+- H2 (default) / PostgreSQL (production)
+
+**Key Features**:
+- Full FHIR REST API (R4, R5, R4B)
+- `$validate` operation with detailed outcomes
+- `$transform` operation for StructureMap transformations
+- `$convert` operation for CDA conversion
+- `$snapshot` operation for profile expansion
+- AI-powered validation (LangChain4j integration)
+- Model Context Protocol (MCP) server support
+
+**Configuration**:
+- Context path: `/matchbox` or `/matchboxv3`
+- Default port: 8080
+- Config file: `application.yaml`
+- Environment variables supported
+- Static files: `/static/` (Angular frontend)
+
+**Main Entry Point**:
+- `ca.uhn.fhir.jpa.starter.Application`
+
+**Key Packages**:
+```
+ch.ahdis.matchbox/
+├── config/ # Spring Boot configuration
+├── providers/ # FHIR operation providers
+│ ├── ValidationProvider # $validate operation
+│ ├── StructureMapProvider # $transform operation
+│ └── ConvertProvider # $convert operation
+├── interceptors/ # Request/response processing
+│ ├── MatchboxValidationInterceptor
+│ ├── MappingLanguageInterceptor
+│ └── HttpReadOnlyInterceptor
+├── terminology/ # Terminology services
+├── packages/ # FHIR package management
+├── mcp/ # Model Context Protocol
+└── util/ # Utilities
+```
+
+**Database Configuration**:
+
+*H2 (Default - Development)*:
+```yaml
+spring:
+ datasource:
+ url: jdbc:h2:file:./database/h2
+```
+
+*PostgreSQL (Production)*:
+```yaml
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/matchbox
+ username: matchbox
+ password: matchbox
+ driverClassName: org.postgresql.Driver
+ jpa:
+ properties:
+ hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect
+```
+
+**Build & Run**:
+```bash
+# Build with frontend
+cd matchbox-frontend
+npm install
+npm run build
+
+cd ../matchbox-server
+mvn clean install
+
+# Run
+java -jar target/matchbox.jar
+
+# Or with Maven
+mvn spring-boot:run
+```
+
+**Docker Deployment**:
+```bash
+# Basic deployment
+docker run -p 8080:8080 ghcr.io/ahdis/matchbox:latest
+
+# With PostgreSQL
+cd matchbox-server/with-postgres
+docker-compose up
+
+# With pre-loaded implementation guides
+cd matchbox-server/with-preload
+docker-compose up
+```
+
+**Available Docker Configurations**:
+- `with-postgres/` - PostgreSQL database
+- `with-preload/` - Pre-loaded Swiss IGs
+- `with-ch/` - Swiss EPR configuration
+- `with-ca/` - Canadian configuration
+- `with-ips/` - International Patient Summary
+- `with-cda/` - CDA transformation support
+- `with-gazelle/` - IHE Gazelle integration
+
+### 3. matchbox-frontend (Angular Web UI)
+
+**Purpose**: Modern web interface for FHIR validation and transformation
+
+**Technology Stack**:
+- Angular 19.0.0
+- TypeScript 5.6.3
+- Angular Material 19.0.0
+- fhir-kit-client 1.9.2
+- FHIRPath.js 3.15.2
+- Ace Editor 1.36.5 (code editing)
+- NGX-Translate 16.0.3 (i18n)
+
+**Key Features**:
+- Interactive FHIR validation with real-time feedback
+- StructureMap editor and transformation testing
+- Implementation guide browser
+- Resource upload and management
+- Questionnaire form filling
+- FHIRPath expression evaluation
+- Multi-language support (i18n)
+
+**Module Structure**:
+```
+src/app/
+├── validate/ # Validation UI
+├── transform/ # Transformation UI
+├── mapping-language/ # StructureMap editor
+├── igs/ # Implementation guides browser
+├── upload/ # Resource upload
+├── settings/ # Server settings
+├── capability-statement/ # Capability statement viewer
+├── shared/ # Shared components
+└── util/ # Utility services
+```
+
+**Development Server**:
+```bash
+cd matchbox-frontend
+npm install
+npm start
+# Opens on http://localhost:4200
+# Proxies API calls to http://localhost:8080
+```
+
+**Build for Production**:
+```bash
+npm run build
+# Output: ../matchbox-server/src/main/resources/static
+```
+
+**Testing**:
+```bash
+# Unit tests
+npm test
+
+# E2E tests
+npm run e2e
+
+# Linting
+npm run lint
+```
+
+**Configuration**:
+- `angular.json` - Angular CLI configuration
+- `src/proxy.conf.json` - Dev server proxy
+- `src/environments/` - Environment-specific settings
+- `tsconfig.json` - TypeScript compiler options
+
+## Development Workflow
+
+### Initial Setup
+
+```bash
+# Clone repository
+git clone https://github.com/ahdis/matchbox.git
+cd matchbox
+
+# Build engine
+cd matchbox-engine
+mvn clean install
+
+# Build and run server
+cd ../matchbox-server
+mvn spring-boot:run
+```
+
+### Frontend Development
+
+```bash
+# Terminal 1: Run backend
+cd matchbox-server
+mvn spring-boot:run
+
+# Terminal 2: Run frontend dev server
+cd matchbox-frontend
+npm install
+npm start
+# Visit http://localhost:4200
+```
+
+### Full Build (Backend + Frontend)
+
+```bash
+# Build frontend and copy to server resources
+cd matchbox-frontend
+npm install
+npm run build
+
+# Build server with embedded frontend
+cd ../matchbox-server
+mvn clean install
+
+# Run
+java -jar target/matchbox.jar
+# Visit http://localhost:8080/matchboxv3
+```
+
+## Testing
+
+### Backend Tests
+
+```bash
+# All tests
+cd matchbox-server
+mvn clean test
+
+# Specific test class
+mvn -Dtest=MatchboxApiR4Test test
+
+# Integration tests
+mvn verify
+```
+
+**Key Test Classes**:
+- `MatchboxApiR4Test` - R4 API tests
+- `MatchboxApiR5Test` - R5 API tests
+- `ValidationClient` - Validation testing utilities
+- `TransformTest` - StructureMap transformation tests
+- `GazelleApiR4Test` - IHE Gazelle integration tests
+
+## Configuration
+
+### Key Application Properties
+
+**application.yaml** (located in `matchbox-server/src/main/resources/`):
+
+```yaml
+server:
+ servlet:
+ context-path: /matchboxv3
+ port: 8080
+
+spring:
+ datasource:
+ url: jdbc:h2:file:./database/h2
+ username: sa
+ password: null
+ driverClassName: org.h2.Driver
+
+hapi:
+ fhir:
+ fhir_version: R4
+ server_address: http://localhost:8080/matchboxv3/fhir
+ allow_external_references: true
+ delete_expunge_enabled: true
+ openapi_enabled: true
+
+matchbox:
+ fhir:
+ context:
+ txServer: http://tx.fhir.org
+ igsPreloaded:
+ - hl7.fhir.r4.core#4.0.1
+```
+
+### Environment Variables
+
+```bash
+# Override server port
+SERVER_PORT=8888
+
+# PostgreSQL configuration
+SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/matchbox
+SPRING_DATASOURCE_USERNAME=matchbox
+SPRING_DATASOURCE_PASSWORD=matchbox
+
+# Terminology server
+MATCHBOX_FHIR_CONTEXT_TXSERVER=http://tx.fhir.org
+
+# AI/LLM configuration
+MATCHBOX_FHIR_CONTEXT_LLM_PROVIDER=openai
+MATCHBOX_FHIR_CONTEXT_LLM_MODELNAME=gpt-4
+MATCHBOX_FHIR_CONTEXT_LLM_APIKEY=sk-...
+```
+
+### Docker Volume Mounts
+
+```bash
+docker run -d \
+ -p 8080:8080 \
+ -v $(pwd)/config:/config \
+ -v $(pwd)/database:/database \
+ ghcr.io/ahdis/matchbox:latest
+```
+
+## API Endpoints
+
+### FHIR Operations
+
+- `POST /matchboxv3/fhir/StructureDefinition/$validate`
+ - Validate FHIR resources against profiles
+
+- `POST /matchboxv3/fhir/StructureMap/$transform`
+ - Transform resources using StructureMap
+
+- `POST /matchboxv3/fhir/StructureDefinition/$convert`
+ - Convert CDA documents to FHIR
+
+- `GET /matchboxv3/fhir/StructureDefinition/{id}/$snapshot`
+ - Generate snapshot from differential
+
+- `GET /matchboxv3/fhir/metadata`
+ - Server capability statement
+
+### OpenAPI Documentation
+
+- Swagger UI: `http://localhost:8080/matchboxv3/swagger-ui.html`
+- OpenAPI spec: `http://localhost:8080/matchboxv3/v3/api-docs`
+
+### Actuator Endpoints
+
+- Health: `/matchboxv3/actuator/health`
+- Metrics: `/matchboxv3/actuator/metrics`
+- Info: `/matchboxv3/actuator/info`
+
+## Important Files & Locations
+
+### Backend
+
+| File/Directory | Purpose |
+|----------------|---------|
+| `pom.xml` | Parent Maven configuration |
+| `matchbox-engine/pom.xml` | Engine build configuration |
+| `matchbox-server/pom.xml` | Server build configuration |
+| `matchbox-server/src/main/resources/application.yaml` | Main configuration |
+| `matchbox-server/src/main/resources/logback.xml` | Logging configuration |
+| `matchbox-server/src/main/java/ca/uhn/fhir/jpa/starter/Application.java` | Main entry point |
+| `matchbox-server/Dockerfile` | Docker image definition |
+| `matchbox-server/with-*/` | Docker Compose examples |
+
+### Frontend
+
+| File/Directory | Purpose |
+|----------------|---------|
+| `matchbox-frontend/package.json` | npm dependencies |
+| `matchbox-frontend/angular.json` | Angular CLI configuration |
+| `matchbox-frontend/tsconfig.json` | TypeScript configuration |
+| `matchbox-frontend/src/main.ts` | Frontend entry point |
+| `matchbox-frontend/src/app/app.module.ts` | Root Angular module |
+| `matchbox-frontend/src/proxy.conf.json` | Dev server proxy |
+| `matchbox-frontend/src/assets/` | Static assets, i18n |
+
+### Documentation
+
+| File/Directory | Purpose |
+|----------------|---------|
+| `README.md` | Project overview |
+| `docs/` | MkDocs documentation |
+| `mkdocs.yml` | Documentation configuration |
+| `docs/validation-tutorial.md` | Validation guide |
+| `docs/api.md` | API documentation |
+
+## CI/CD Pipeline
+
+GitHub Actions workflows (`.github/workflows/`):
+
+- **maven.yml** - Maven build and test
+- **integration_tests.yml** - Integration tests
+- **angular_build.yml** - Frontend build
+- **angular_test.yml** - Frontend tests
+- **documentation.yml** - Docs deployment
+- **googleregistry.yml** - Docker image publishing
+- **central_repository.yml** - Maven Central publishing
+
+## Release Process
+
+1. Update versions in `pom.xml`, `package.json`, and documentation
+2. Create PR and wait for tests to pass
+3. Merge PR to main
+4. Wait for Angular build workflow to complete
+5. Create GitHub release with tag (e.g., `v4.0.16`)
+6. Automated workflows publish:
+ - Docker image to Google Artifact Registry
+ - Maven artifacts to Maven Central
+
+## Dependencies Management
+
+### Major Dependencies
+
+**Backend**:
+- HAPI FHIR 8.0.0 - FHIR server framework
+- HL7 FHIR Core 6.7.10 - Official validator
+- Spring Boot 3.3.11 - Application framework
+- Jackson 2.17.1 - JSON processing
+- Hibernate - JPA/ORM
+- LangChain4j 1.0.0-beta1 - AI/LLM integration
+
+**Frontend**:
+- Angular 19.0.0 - Web framework
+- Angular Material 19.0.0 - UI components
+- fhir-kit-client 1.9.2 - FHIR client
+- FHIRPath.js 3.15.2 - FHIRPath evaluation
+- Ace Editor 1.36.5 - Code editor
+
+### Update Dependencies
+
+```bash
+# Backend
+mvn versions:display-dependency-updates
+
+# Frontend
+npm outdated
+```
+
+## Troubleshooting
+
+### Common Issues
+
+**Issue**: Port 8080 already in use
+```bash
+# Solution: Change port
+SERVER_PORT=8888 java -jar matchbox.jar
+```
+
+**Issue**: Out of memory errors
+```bash
+# Solution: Increase heap size
+java -Xmx4096M -jar matchbox.jar
+```
+
+**Issue**: Frontend build fails
+```bash
+# Solution: Clear node_modules and reinstall
+cd matchbox-frontend
+rm -rf node_modules package-lock.json
+npm install
+npm run build
+```
+
+**Issue**: Database connection errors
+```bash
+# Solution: Check database is running and config is correct
+# For H2, ensure database directory exists and is writable
+mkdir -p database
+```
+
+### Logging
+
+Enable debug logging:
+```yaml
+logging:
+ level:
+ ch.ahdis.matchbox: DEBUG
+ ca.uhn.fhir: DEBUG
+```
+
+Or via environment variable:
+```bash
+LOGGING_LEVEL_CH_AHDIS_MATCHBOX=DEBUG
+```
+
+## Resources
+
+- **GitHub Repository**: https://github.com/ahdis/matchbox
+- **Documentation**: https://ahdis.github.io/matchbox/
+- **Docker Images**: https://github.com/ahdis/matchbox/pkgs/container/matchbox
+- **Maven Central**: https://central.sonatype.com/artifact/ch.ahdis/matchbox-engine
+- **FHIR Specification**: https://hl7.org/fhir/
+- **HAPI FHIR**: https://hapifhir.io/
+
+## Support & Contributing
+
+- **Issues**: https://github.com/ahdis/matchbox/issues
+- **Discussions**: https://github.com/ahdis/matchbox/discussions
+- **Contributing Guide**: See `matchbox-frontend/CONTRIBUTING.md`
+
+## License
+
+Apache License 2.0
diff --git a/docs/changelog.md b/docs/changelog.md
index 7a795a0c10d..4025f63e08c 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,6 +1,9 @@
2025 Release 4.0.16
- adapt test and map for (440)
+- update org.hl7.fhir.core 6.7.10 (#448)
+- support validating CodeableConcept in internal tx (448)
+- FHIR R4 validation error with R5 extension (#424)
2025/11/03 Release 4.0.15
diff --git a/matchbox-engine/pom.xml b/matchbox-engine/pom.xml
index 18307308c11..54cae39eea6 100644
--- a/matchbox-engine/pom.xml
+++ b/matchbox-engine/pom.xml
@@ -6,7 +6,7 @@
matchbox
health.matchbox
- 4.0.15
+ 4.0.16
matchbox-engine
diff --git a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/MatchboxEngine.java b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/MatchboxEngine.java
index 73430036d27..de564c94b04 100644
--- a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/MatchboxEngine.java
+++ b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/MatchboxEngine.java
@@ -308,7 +308,7 @@ public MatchboxEngine getEngineR4() throws MatchboxEngineCreationException {
}
engine.getContext().setPackageTracker(engine);
engine.setPcm(this.getFilesystemPackageCacheManager());
- engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID));
+ engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID, null));
engine.setAllowExampleUrls(true);
log.info("engine R4 initialized");
return engine;
@@ -353,7 +353,7 @@ public MatchboxEngine getEngineR4B() throws MatchboxEngineCreationException {
}
engine.getContext().setPackageTracker(engine);
engine.setPcm(this.getFilesystemPackageCacheManager());
- engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID));
+ engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID, null));
engine.setAllowExampleUrls(true);
log.info("engine R4B initialized");
return engine;
@@ -396,8 +396,7 @@ public MatchboxEngine getEngineR5() throws MatchboxEngineCreationException {
throw new TerminologyServerException(e);
}
}
-
- engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID));
+ engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID, null));
engine.setAllowExampleUrls(true);
engine.getContext().setPackageTracker(engine);
@@ -434,7 +433,7 @@ public MatchboxEngine getEngine() throws MatchboxEngineCreationException {
}
engine.getContext().setPackageTracker(engine);
engine.setPcm(this.getFilesystemPackageCacheManager());
- engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID));
+ engine.setPolicyAdvisor(new ValidationPolicyAdvisor(ReferenceValidationPolicy.CHECK_VALID, null));
engine.setAllowExampleUrls(true);
return engine;
}
diff --git a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/ValidationPolicyAdvisor.java b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/ValidationPolicyAdvisor.java
index b55e6c1041b..288400440b7 100644
--- a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/ValidationPolicyAdvisor.java
+++ b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/ValidationPolicyAdvisor.java
@@ -22,8 +22,8 @@ public class ValidationPolicyAdvisor extends BasePolicyAdvisorForFullValidation
// This is a map of messageId to a list of regex paths that should be ignored
private final Map> messagesToIgnore = new HashMap<>();
- public ValidationPolicyAdvisor(ReferenceValidationPolicy refpol) {
- super(refpol);
+ public ValidationPolicyAdvisor(ReferenceValidationPolicy refpol, Set referencesTo) {
+ super(refpol, referencesTo);
}
@Override
diff --git a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxCli.java b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxCli.java
deleted file mode 100644
index 8ca4d53a605..00000000000
--- a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxCli.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package ch.ahdis.matchbox.engine.cli;
-
-import java.net.Authenticator;
-import java.net.PasswordAuthentication;
-import java.util.Locale;
-
-/*
- Copyright (c) 2011+, HL7, Inc.
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without modification,
- are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * Neither the name of HL7 nor the names of its contributors may be used to
- endorse or promote products derived from this software without specific
- prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- POSSIBILITY OF SUCH DAMAGE.
-
- */
-
-import org.hl7.fhir.r5.model.ImplementationGuide;
-import org.hl7.fhir.r5.model.StructureDefinition;
-import org.hl7.fhir.r5.terminologies.JurisdictionUtilities;
-import org.hl7.fhir.utilities.FileFormat;
-import org.hl7.fhir.utilities.TimeTracker;
-import org.hl7.fhir.utilities.Utilities;
-import org.hl7.fhir.utilities.VersionUtilities;
-import org.hl7.fhir.validation.Scanner;
-import org.hl7.fhir.validation.service.model.ValidationContext;
-import org.hl7.fhir.validation.service.utils.EngineMode;
-import org.hl7.fhir.validation.cli.Display;
-import org.hl7.fhir.validation.cli.param.Params;
-import org.hl7.fhir.validation.testexecutor.TestExecutor;
-import org.hl7.fhir.validation.testexecutor.TestExecutorParams;
-
-import ch.ahdis.matchbox.engine.MatchboxEngine;
-import lombok.extern.slf4j.Slf4j;
-
-
-/**
- * A executable class
- *
- * adapted from https://github.com/hapifhir/org.hl7.fhir.core/blob/master/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java
- *
- * @author Oliver Egger
- */
-@Slf4j
-public class MatchboxCli {
-
- public static final String HTTP_PROXY_HOST = "http.proxyHost";
- public static final String HTTP_PROXY_PORT = "http.proxyPort";
- public static final String HTTP_PROXY_USER = "http.proxyUser";
- public static final String HTTP_PROXY_PASS = "http.proxyPassword";
- public static final String JAVA_DISABLED_TUNNELING_SCHEMES = "jdk.http.auth.tunneling.disabledSchemes";
- public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes";
- public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies";
-
- private static MatchboxService matchboxService = new MatchboxService();
-
- public static void main(String[] args) throws Exception {
- TimeTracker tt = new TimeTracker();
- TimeTracker.Session tts = tt.start("Loading");
-
- System.out.println(VersionUtil.getPoweredBy());
- Display.displaySystemInfo(log);
-
- if (Params.hasParam(args, Params.PROXY)) {
- assert Params.getParam(args, Params.PROXY) != null : "PROXY arg passed in was NULL";
- String[] p = Params.getParam(args, Params.PROXY).split(":");
- System.setProperty(HTTP_PROXY_HOST, p[0]);
- System.setProperty(HTTP_PROXY_PORT, p[1]);
- }
-
- if (Params.hasParam(args, Params.PROXY_AUTH)) {
- assert Params.getParam(args, Params.PROXY) != null : "Cannot set PROXY_AUTH without setting PROXY...";
- assert Params.getParam(args, Params.PROXY_AUTH) != null : "PROXY_AUTH arg passed in was NULL...";
- String[] p = Params.getParam(args, Params.PROXY_AUTH).split(":");
- String authUser = p[0];
- String authPass = p[1];
-
- /*
- * For authentication, use java.net.Authenticator to set proxy's configuration and set the system properties
- * http.proxyUser and http.proxyPassword
- */
- Authenticator.setDefault(
- new Authenticator() {
- @Override
- public PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(authUser, authPass.toCharArray());
- }
- }
- );
-
- System.setProperty(HTTP_PROXY_USER, authUser);
- System.setProperty(HTTP_PROXY_PASS, authPass);
- System.setProperty(JAVA_USE_SYSTEM_PROXIES, "true");
-
- /*
- * For Java 1.8 and higher you must set
- * -Djdk.http.auth.tunneling.disabledSchemes=
- * to make proxies with Basic Authorization working with https along with Authenticator
- */
- System.setProperty(JAVA_DISABLED_TUNNELING_SCHEMES, "");
- System.setProperty(JAVA_DISABLED_PROXY_SCHEMES, "");
- }
-
- ValidationContext validationContext = Params.loadValidationContext(args);
-
- FileFormat.checkCharsetAndWarnIfNotUTF8(System.out);
-
- if (shouldDisplayHelpToUser(args)) {
- Display.displayHelpDetails(log, "help/help.txt");
- } else if (Params.hasParam(args, Params.TEST)) {
- parseTestParamsAndExecute(args);
- }
- else {
- Display.printCliParamsAndInfo(log,args);
- doValidation(tt, tts, validationContext);
- }
- }
-
- protected static void parseTestParamsAndExecute(String[] args) {
- final String testModuleParam = Params.getParam(args, Params.TEST_MODULES);
- final String testClassnameFilter = Params.getParam(args, Params.TEST_NAME_FILTER);
- final String testCasesDirectory = Params.getParam(args, Params.TEST);
- final String txCacheDirectory = Params.getParam(args, Params.TERMINOLOGY_CACHE);
- assert TestExecutorParams.isValidModuleParam(testModuleParam) : "Invalid test module param: " + testModuleParam;
- final String[] moduleNamesArg = TestExecutorParams.parseModuleParam(testModuleParam);
-
- assert TestExecutorParams.isValidClassnameFilterParam(testClassnameFilter) : "Invalid regex for test classname filter: " + testClassnameFilter;
-
- new TestExecutor(moduleNamesArg).executeTests(testClassnameFilter, txCacheDirectory, testCasesDirectory);
-
- System.exit(0);
- }
-
- private static boolean shouldDisplayHelpToUser(String[] args) {
- return (args.length == 0
- || Params.hasParam(args, Params.HELP)
- || Params.hasParam(args, "?")
- || Params.hasParam(args, "-?")
- || Params.hasParam(args, "/?"));
- }
-
- private static void doValidation(TimeTracker tt, TimeTracker.Session tts, ValidationContext cliContext) throws Exception {
- if (cliContext.getSv() == null) {
- cliContext.setSv(matchboxService.determineVersion(cliContext));
- }
- if (cliContext.getJurisdiction() == null) {
- System.out.println(" Jurisdiction: None specified (locale = "+Locale.getDefault().getCountry()+")");
- System.out.println(" Note that exceptions and validation failures may happen in the absense of a locale");
- } else {
- System.out.println(" Jurisdiction: "+JurisdictionUtilities.displayJurisdiction(cliContext.getJurisdiction()));
- }
-
- System.out.println("Loading");
- // Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long).
- // Version gets spit out a couple of lines later after we've loaded the context
- String definitions = "dev".equals(cliContext.getSv()) ? "hl7.fhir.r5.core#current" : VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());
-
- MatchboxEngine validator = matchboxService.initializeValidator(cliContext, definitions, tt);
- tts.end();
- switch (cliContext.getMode()) {
- case TRANSFORM:
- matchboxService.transform(cliContext, validator);
- break;
- case COMPILE:
- matchboxService.compile(cliContext, validator);
- break;
- case NARRATIVE:
- matchboxService.generateNarrative(cliContext, validator);
- break;
- case SNAPSHOT:
- matchboxService.generateSnapshot(cliContext, validator);
- break;
- case CONVERT:
- matchboxService.convertSources(cliContext, validator);
- break;
- case FHIRPATH:
- matchboxService.evaluateFhirpath(cliContext, validator);
- break;
- case VERSION:
- matchboxService.transformVersion(cliContext, validator);
- break;
- case VALIDATION:
- case SCAN:
- default:
- for (String s : cliContext.getProfiles()) {
- if (!validator.getContext().hasResource(StructureDefinition.class, s) && !validator.getContext().hasResource(ImplementationGuide.class, s)) {
- System.out.println(" Fetch Profile from " + s);
- validator.loadProfile(cliContext.getLocations().getOrDefault(s, s));
- }
- }
- System.out.println("Validating");
- if (cliContext.getMode() == EngineMode.SCAN) {
- Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(null), validator.getIgLoader(), validator.getFhirPathEngine());
- validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
- } else {
- matchboxService.validateSources(cliContext, validator);
- }
- break;
- }
- System.out.println("Done. " + tt.report()+". Max Memory = "+Utilities.describeSize(Runtime.getRuntime().maxMemory()));
- }
-}
diff --git a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxService.java b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxService.java
index 9f9768618b0..a1cb6a0438f 100644
--- a/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxService.java
+++ b/matchbox-engine/src/main/java/ch/ahdis/matchbox/engine/cli/MatchboxService.java
@@ -436,13 +436,14 @@ public String initializeValidator(ValidationContext validationContext, String de
} else {
fetcher.setReferencePolicy(ReferenceValidationPolicy.IGNORE);
}
+ fetcher.getCheckReferencesTo().addAll(validationContext.getCheckReferencesTo());
fetcher.setResolutionContext(validationContext.getResolutionContext());
} else {
DisabledValidationPolicyAdvisor fetcher = new DisabledValidationPolicyAdvisor();
validator.setPolicyAdvisor(fetcher);
refpol = ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
}
- validator.getPolicyAdvisor().setPolicyAdvisor(new ValidationPolicyAdvisor(validator.getPolicyAdvisor() == null ? refpol : validator.getPolicyAdvisor().getReferencePolicy()));
+ validator.getPolicyAdvisor().setPolicyAdvisor(new ValidationPolicyAdvisor(validator.getPolicyAdvisor() == null ? refpol : validator.getPolicyAdvisor().getReferencePolicy(), validationContext.getCheckReferencesTo()));
validator.getBundleValidationRules().addAll(validationContext.getBundleValidationRules());
validator.setJurisdiction(CodeSystemUtilities.readCoding(validationContext.getJurisdiction()));
diff --git a/matchbox-engine/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/matchbox-engine/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java
new file mode 100644
index 00000000000..28b71beb470
--- /dev/null
+++ b/matchbox-engine/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java
@@ -0,0 +1,5014 @@
+package org.hl7.fhir.r5.conformance.profile;
+
+/*
+ Copyright (c) 2011+, HL7, Inc.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of HL7 nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hl7.fhir.exceptions.DefinitionException;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.exceptions.FHIRFormatError;
+import org.hl7.fhir.r5.conformance.ElementRedirection;
+import org.hl7.fhir.r5.conformance.profile.MappingAssistant.MappingMergeModeOption;
+import org.hl7.fhir.r5.context.IWorkerContext;
+import org.hl7.fhir.r5.elementmodel.ObjectConverter;
+import org.hl7.fhir.r5.elementmodel.Property;
+import org.hl7.fhir.r5.extensions.ExtensionDefinitions;
+import org.hl7.fhir.r5.extensions.ExtensionUtilities;
+import org.hl7.fhir.r5.fhirpath.ExpressionNode;
+import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind;
+import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation;
+import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
+import org.hl7.fhir.r5.model.*;
+import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
+import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
+import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
+import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
+import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
+import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
+import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
+import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
+import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent;
+import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
+import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
+import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
+import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
+import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
+import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
+import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
+
+import org.hl7.fhir.r5.utils.UserDataNames;
+import org.hl7.fhir.r5.utils.xver.XVerExtensionManager;
+import org.hl7.fhir.r5.utils.xver.XVerExtensionManager.XVerExtensionStatus;
+import org.hl7.fhir.r5.utils.formats.CSVWriter;
+import org.hl7.fhir.r5.utils.xver.XVerExtensionManagerFactory;
+import org.hl7.fhir.utilities.*;
+import org.hl7.fhir.utilities.i18n.I18nConstants;
+import org.hl7.fhir.utilities.validation.ValidationMessage;
+import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
+import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
+import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
+import org.hl7.fhir.utilities.validation.ValidationOptions;
+import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
+import org.hl7.fhir.utilities.xml.SchematronWriter;
+import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
+import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
+import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
+
+
+/**
+ * This class provides a set of utility operations for working with Profiles.
+ * Key functionality:
+ * * getChildMap --?
+ * * getChildList
+ * * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
+ * * closeDifferential: fill out a differential by excluding anything not mentioned
+ * * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
+ * * generateTable: generate the HTML for a hierarchical table presentation of a structure
+ * * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
+ * * summarize: describe the contents of a profile
+ *
+ * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change
+ *
+ * @author Grahame
+ *
+ */
+@MarkedToMoveToAdjunctPackage
+@Slf4j
+public class ProfileUtilities {
+
+ private static boolean suppressIgnorableExceptions;
+
+
+ public class ElementDefinitionCounter {
+ int countMin = 0;
+ int countMax = 0;
+ int index = 0;
+ ElementDefinition focus;
+ Set names = new HashSet<>();
+
+ public ElementDefinitionCounter(ElementDefinition ed, int i) {
+ focus = ed;
+ index = i;
+ }
+
+ public int checkMin() {
+ if (countMin > focus.getMin()) {
+ return countMin;
+ } else {
+ return -1;
+ }
+ }
+
+ public int checkMax() {
+ if (countMax > max(focus.getMax())) {
+ return countMax;
+ } else {
+ return -1;
+ }
+ }
+
+ private int max(String max) {
+ if ("*".equals(max)) {
+ return Integer.MAX_VALUE;
+ } else {
+ return Integer.parseInt(max);
+ }
+ }
+
+ public boolean count(ElementDefinition ed, String name) {
+ countMin = countMin + ed.getMin();
+ if (countMax < Integer.MAX_VALUE) {
+ int m = max(ed.getMax());
+ if (m == Integer.MAX_VALUE) {
+ countMax = m;
+ } else {
+ countMax = countMax + m;
+ }
+ }
+ boolean ok = !names.contains(name);
+ names.add(name);
+ return ok;
+ }
+
+ public ElementDefinition getFocus() {
+ return focus;
+ }
+
+ public boolean checkMinMax() {
+ return countMin <= countMax;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ }
+
+ public enum AllowUnknownProfile {
+ NONE, // exception if there's any unknown profiles (the default)
+ NON_EXTNEIONS, // don't raise an exception except on Extension (because more is going on there
+ ALL_TYPES // allow any unknow profile
+ }
+
+ /**
+ * These extensions are stripped in inherited profiles (and may be replaced by
+ */
+
+ public static final List NON_INHERITED_ED_URLS = Arrays.asList(
+ "http://hl7.org/fhir/tools/StructureDefinition/binding-definition",
+ "http://hl7.org/fhir/tools/StructureDefinition/no-binding",
+ "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-category",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-implements",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version",
+ "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile",
+ "http://hl7.org/fhir/StructureDefinition/obligation-profile",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason",
+ ExtensionDefinitions.EXT_SUMMARY/*,
+ ExtensionDefinitions.EXT_OBLIGATION_CORE,
+ ExtensionDefinitions.EXT_OBLIGATION_TOOLS*/);
+
+ public static final List DEFAULT_INHERITED_ED_URLS = Arrays.asList(
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-optionRestriction",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceProfile",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
+
+ "http://hl7.org/fhir/StructureDefinition/mimeType");
+
+ /**
+ * These extensions are ignored when found in differentials
+ */
+ public static final List NON_OVERRIDING_ED_URLS = Arrays.asList(
+ "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable",
+ ExtensionDefinitions.EXT_JSON_NAME, ExtensionDefinitions.EXT_JSON_NAME_DEPRECATED,
+ "http://hl7.org/fhir/tools/StructureDefinition/implied-string-prefix",
+ "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior",
+ "http://hl7.org/fhir/tools/StructureDefinition/json-nullable",
+ "http://hl7.org/fhir/tools/StructureDefinition/json-primitive-choice",
+ "http://hl7.org/fhir/tools/StructureDefinition/json-property-key",
+ "http://hl7.org/fhir/tools/StructureDefinition/type-specifier",
+ "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group",
+ ExtensionDefinitions.EXT_XML_NAMESPACE, ExtensionDefinitions.EXT_XML_NAMESPACE_DEPRECATED,
+ ExtensionDefinitions.EXT_XML_NAME, ExtensionDefinitions.EXT_XML_NAME_DEPRECATED,
+ "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"
+ );
+
+ /**
+ * When these extensions are found, they override whatever is set on the ancestor element
+ */
+ public static final List OVERRIDING_ED_URLS = Arrays.asList(
+ "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format",
+ ExtensionDefinitions.EXT_DATE_RULES,
+ "http://hl7.org/fhir/StructureDefinition/designNote",
+ "http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits",
+ "http://hl7.org/fhir/StructureDefinition/elementdefinition-question",
+ "http://hl7.org/fhir/StructureDefinition/entryFormat",
+ "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces",
+ "http://hl7.org/fhir/StructureDefinition/maxSize",
+ "http://hl7.org/fhir/StructureDefinition/minLength",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-signatureRequired",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-supportLink",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-unit",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-unitValueSet",
+ "http://hl7.org/fhir/StructureDefinition/questionnaire-usageMode",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint",
+ "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"
+ );
+
+ public IWorkerContext getContext() {
+ return this.context;
+ }
+
+ public static class SourcedChildDefinitions {
+ private StructureDefinition source;
+ private List list;
+ private String path;
+ public SourcedChildDefinitions(StructureDefinition source, List list) {
+ super();
+ this.source = source;
+ this.list = list;
+ }
+ public SourcedChildDefinitions(StructureDefinition source, List list, String path) {
+ super();
+ this.source = source;
+ this.list = list;
+ this.path = path;
+ }
+ public StructureDefinition getSource() {
+ return source;
+ }
+ public List getList() {
+ return list;
+ }
+ public String getPath() {
+ return path;
+ }
+
+ }
+
+ public class ElementDefinitionResolution {
+
+ private StructureDefinition source;
+ private ElementDefinition element;
+
+ public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) {
+ this.source = source;
+ this.element = element;
+ }
+
+ public StructureDefinition getSource() {
+ return source;
+ }
+
+ public ElementDefinition getElement() {
+ return element;
+ }
+
+ }
+
+ public static class ElementChoiceGroup {
+ private Row row;
+ private String name;
+ private boolean mandatory;
+ private List elements = new ArrayList<>();
+
+ public ElementChoiceGroup(String name, boolean mandatory) {
+ super();
+ this.name = name;
+ this.mandatory = mandatory;
+ }
+ public Row getRow() {
+ return row;
+ }
+ public List getElements() {
+ return elements;
+ }
+ public void setRow(Row row) {
+ this.row = row;
+ }
+ public String getName() {
+ return name;
+ }
+ public boolean isMandatory() {
+ return mandatory;
+ }
+ public void setMandatory(boolean mandatory) {
+ this.mandatory = mandatory;
+ }
+
+ }
+
+ private static final int MAX_RECURSION_LIMIT = 10;
+
+ public static class ExtensionContext {
+
+ private ElementDefinition element;
+ private StructureDefinition defn;
+
+ public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
+ this.defn = ext;
+ this.element = ed;
+ }
+
+ public ElementDefinition getElement() {
+ return element;
+ }
+
+ public StructureDefinition getDefn() {
+ return defn;
+ }
+
+ public String getUrl() {
+ if (element == defn.getSnapshot().getElement().get(0))
+ return defn.getUrl();
+ else
+ return element.getSliceName();
+ }
+
+ public ElementDefinition getExtensionValueDefinition() {
+ int i = defn.getSnapshot().getElement().indexOf(element)+1;
+ while (i < defn.getSnapshot().getElement().size()) {
+ ElementDefinition ed = defn.getSnapshot().getElement().get(i);
+ if (ed.getPath().equals(element.getPath()))
+ return null;
+ if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing())
+ return ed;
+ i++;
+ }
+ return null;
+ }
+ }
+
+ private static final boolean COPY_BINDING_EXTENSIONS = false;
+ private static final boolean DONT_DO_THIS = false;
+
+ private boolean debug;
+ // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
+ private final IWorkerContext context;
+ private FHIRPathEngine fpe;
+ private List messages = new ArrayList();
+ private List snapshotStack = new ArrayList();
+ private ProfileKnowledgeProvider pkp;
+// private boolean igmode;
+ private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5);
+ private boolean newSlicingProcessing;
+ private String defWebRoot;
+ private boolean autoFixSliceNames;
+ private XVerExtensionManager xver;
+ private boolean wantFixDifferentialFirstElementType;
+ private Set masterSourceFileNames;
+ private Set localFileNames;
+ private Map childMapCache = new HashMap<>();
+ private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES;
+ private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND;
+ private boolean forPublication;
+ private List obligationProfiles = new ArrayList<>();
+ private boolean wantThrowExceptions;
+ private List suppressedMappings= new ArrayList<>();
+ @Getter @Setter private Parameters parameters;
+
+ public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) {
+ super();
+ this.context = context;
+ if (messages != null) {
+ this.messages = messages;
+ } else {
+ wantThrowExceptions = true;
+ }
+ this.pkp = pkp;
+
+ this.fpe = fpe;
+ if (context != null && this.fpe == null) {
+ this.fpe = new FHIRPathEngine(context, this);
+ }
+ if (context != null) {
+ parameters = context.getExpansionParameters();
+ }
+ }
+
+ public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp) {
+ super();
+ this.context = context;
+ if (messages != null) {
+ this.messages = messages;
+ } else {
+ wantThrowExceptions = true;
+ }
+ this.pkp = pkp;
+ if (context != null) {
+ this.fpe = new FHIRPathEngine(context, this);
+ }
+
+ if (context != null) {
+ parameters = context.getExpansionParameters();
+ }
+ }
+
+ public boolean isWantFixDifferentialFirstElementType() {
+ return wantFixDifferentialFirstElementType;
+ }
+
+ public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) {
+ this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType;
+ }
+
+ public boolean isAutoFixSliceNames() {
+ return autoFixSliceNames;
+ }
+
+ public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) {
+ this.autoFixSliceNames = autoFixSliceNames;
+ return this;
+ }
+
+ public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes) throws DefinitionException {
+ return getChildMap(profile, element, chaseTypes, null);
+ }
+ public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element, boolean chaseTypes, String type) throws DefinitionException {
+ String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath())+"."+chaseTypes;
+ if (childMapCache.containsKey(cacheKey)) {
+ return childMapCache.get(cacheKey);
+ }
+ StructureDefinition src = profile;
+ List res = new ArrayList();
+ List elements = profile.getSnapshot().getElement();
+ int iOffs = elements.indexOf(element) + 1;
+ boolean walksIntoElement = elements.size() > iOffs && elements.get(iOffs).getPath().startsWith(element.getPath());
+ if (element.getContentReference() != null && !walksIntoElement) {
+ List list = null;
+ String id = null;
+ if (element.getContentReference().startsWith("#")) {
+ // internal reference
+ id = element.getContentReference().substring(1);
+ list = profile.getSnapshot().getElement();
+ } else if (element.getContentReference().contains("#")) {
+ // external reference
+ String ref = element.getContentReference();
+ StructureDefinition sd = findProfile(ref.substring(0, ref.indexOf("#")), profile);
+ if (sd == null) {
+ throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
+ }
+ src = sd;
+ list = sd.getSnapshot().getElement();
+ id = ref.substring(ref.indexOf("#")+1);
+ } else {
+ throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
+ }
+
+ for (ElementDefinition e : list) {
+ if (id.equals(e.getId()))
+ return getChildMap(src, e, true);
+ }
+ throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath()));
+
+ } else {
+ String path = element.getPath();
+ for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
+ ElementDefinition e = elements.get(index);
+ if (e.getPath().startsWith(path + ".")) {
+ // We only want direct children, not all descendants
+ if (!e.getPath().substring(path.length()+1).contains("."))
+ res.add(e);
+ } else
+ break;
+ }
+ if (res.isEmpty() && chaseTypes) {
+ // we've got no in-line children. Some consumers of this routine will figure this out for themselves but most just want to walk into
+ // the type children.
+ src = null;
+ if (type != null) {
+ src = context.fetchTypeDefinition(type);
+ } else if (element.getType().isEmpty()) {
+ throw new DefinitionException("No defined children and no type information on element '"+element.getId()+"'");
+ } else if (element.getType().size() > 1) {
+ // this is a problem. There's two separate but related issues
+ // the first is what's going on here - the profile has walked into an element without fixing the type
+ // this might be ok - maybe it's just going to constrain extensions for all types, though this is generally a bad idea
+ // but if that's all it's doing, we'll just pretend we have an element. Only, it's not really an element so that might
+ // blow up on us later in mystifying ways. We'll have to wear it though, because there's profiles out there that do this
+ // the second problem is whether this should be some common descendent of Element - I'm not clear about that
+ // left as a problem for the future.
+ //
+ // this is what the code was prior to 2025-08-27:
+ // throw new DefinitionException("No defined children and multiple possible types '"+element.typeSummary()+"' on element '"+element.getId()+"'");
+ src = context.fetchTypeDefinition("Element");
+ } else if (element.getType().get(0).getProfile().size() > 1) {
+ throw new DefinitionException("No defined children and multiple possible type profiles '"+element.typeSummary()+"' on element '"+element.getId()+"'");
+ } else if (element.getType().get(0).hasProfile()) {
+ src = findProfile(element.getType().get(0).getProfile().get(0).getValue(), profile);
+ if (src == null) {
+ throw new DefinitionException("No defined children and unknown type profile '"+element.typeSummary()+"' on element '"+element.getId()+"'");
+ }
+ } else {
+ src = context.fetchTypeDefinition(element.getType().get(0).getWorkingCode());
+ if (src == null) {
+ throw new DefinitionException("No defined children and unknown type '"+element.typeSummary()+"' on element '"+element.getId()+"'");
+ }
+ }
+ SourcedChildDefinitions scd = getChildMap(src, src.getSnapshot().getElementFirstRep(), false);
+ res = scd.list;
+ }
+ SourcedChildDefinitions result = new SourcedChildDefinitions(src, res);
+ childMapCache.put(cacheKey, result);
+ return result;
+ }
+ }
+
+
+ public List getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
+ if (!element.hasSlicing())
+ throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING));
+
+ List res = new ArrayList();
+ List elements = profile.getSnapshot().getElement();
+ String path = element.getPath();
+ int start = findElementIndex(elements, element);
+ for (int index = start + 1; index < elements.size(); index++) {
+ ElementDefinition e = elements.get(index);
+ if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
+ // We want elements with the same path (until we hit an element that doesn't start with the same path)
+ if (e.getPath().equals(element.getPath()))
+ res.add(e);
+ } else
+ break;
+ }
+ return res;
+ }
+
+
+ private int findElementIndex(List elements, ElementDefinition element) {
+ int res = elements.indexOf(element);
+ if (res == -1) {
+ for (int i = 0; i < elements.size(); i++) {
+ Element t = elements.get(i);
+ if (t.getId().equals(element.getId())) {
+ res = i;
+ }
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Given a Structure, navigate to the element given by the path and return the direct children of that element
+ *
+ * @param profile The structure to navigate into
+ * @param path The path of the element within the structure to get the children for
+ * @return A List containing the element children (all of them are Elements)
+ */
+ public List getChildList(StructureDefinition profile, String path, String id) {
+ return getChildList(profile, path, id, false);
+ }
+
+ public List getChildList(StructureDefinition profile, String path, String id, boolean diff) {
+ return getChildList(profile, path, id, diff, false);
+ }
+
+ public List getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) {
+ List res = new ArrayList();
+
+ boolean capturing = id==null;
+ if (id==null && !path.contains("."))
+ capturing = true;
+
+ List list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
+ for (ElementDefinition e : list) {
+ if (e == null)
+ throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl()));
+// if (e.getId() == null) // this is sort of true, but in some corner cases it's not, and in those cases, we don't care
+// throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl()));
+
+ if (!capturing && id!=null && e.getId().equals(id)) {
+ capturing = true;
+ }
+
+ // If our element is a slice, stop capturing children as soon as we see the next slice
+ if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
+ break;
+
+ if (capturing) {
+ String p = e.getPath();
+
+ if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
+ if (path.length() > p.length()) {
+ return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
+ } else if (e.getContentReference().startsWith("#")) {
+ return getChildList(profile, e.getContentReference().substring(1), null, diff);
+ } else if (e.getContentReference().contains("#")) {
+ String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#"));
+ StructureDefinition sd = findProfile(url, profile);
+ if (sd == null) {
+ throw new DefinitionException("Unable to find Structure "+url);
+ }
+ return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff);
+ } else {
+ return getChildList(profile, e.getContentReference(), null, diff);
+ }
+
+ } else if (p.startsWith(path+".") && !p.equals(path)) {
+ String tail = p.substring(path.length()+1);
+ if (!tail.contains(".")) {
+ res.add(e);
+ }
+ }
+ }
+ }
+
+ return res;
+ }
+
+ public List getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) {
+ return getChildList(structure, element.getPath(), element.getId(), diff, refs);
+ }
+
+ public List getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
+ return getChildList(structure, element.getPath(), element.getId(), diff);
+ }
+
+ public List getChildList(StructureDefinition structure, ElementDefinition element) {
+ if (element.hasContentReference()) {
+ ElementDefinition target = element;
+ for (ElementDefinition t : structure.getSnapshot().getElement()) {
+ if (t.getId().equals(element.getContentReference().substring(1))) {
+ target = t;
+ }
+ }
+ return getChildList(structure, target.getPath(), target.getId(), false);
+ } else {
+ return getChildList(structure, element.getPath(), element.getId(), false);
+ }
+ }
+
+ /**
+ * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
+ *
+ * @param base - the base structure on which the differential will be applied
+ * @param derived - the differential to apply to the base
+ * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
+ * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
+ * @return
+ * @throws FHIRException
+ * @throws DefinitionException
+ * @throws Exception
+ */
+ public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
+ if (base == null) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
+ }
+ if (derived == null) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
+ }
+ checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl());
+ checkNotGenerating(derived, "Focus for generating a snapshot");
+
+ if (!base.hasType()) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
+ }
+ if (!derived.hasType()) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
+ }
+ if (!derived.hasDerivation()) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
+ }
+ if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
+ }
+ if (!base.hasSnapshot()) {
+ StructureDefinition sdb = findProfile(base.getBaseDefinition(), base);
+ if (sdb == null)
+ throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl()));
+ checkNotGenerating(sdb, "an extension base");
+ generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName());
+ }
+ fixTypeOfResourceId(base);
+ if (base.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) {
+ checkTypeParameters(base, derived);
+ }
+
+ if (snapshotStack.contains(derived.getUrl())) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
+ }
+ derived.setGeneratingSnapshot(true);
+ snapshotStack.add(derived.getUrl());
+ boolean oldCopyUserData = Base.isCopyUserData();
+ Base.setCopyUserData(true);
+ try {
+
+ if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
+ webUrl = webUrl + '/';
+
+ if (defWebRoot == null)
+ defWebRoot = webUrl;
+ derived.setSnapshot(new StructureDefinitionSnapshotComponent());
+
+ try {
+ checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl());
+ checkDifferentialBaseType(derived);
+
+ log.debug("Differential: ");
+ int debugPadding = 0;
+ for (ElementDefinition ed : derived.getDifferential().getElement()) {
+ log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
+ debugPadding++;
+ }
+ log.debug("Snapshot: ");
+ debugPadding = 0;
+ for (ElementDefinition ed : base.getSnapshot().getElement()) {
+ log.debug(" "+Utilities.padLeft(Integer.toString(debugPadding), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
+ debugPadding++;
+ }
+
+
+ copyInheritedExtensions(base, derived, webUrl);
+
+ findInheritedObligationProfiles(derived);
+ // so we have two lists - the base list, and the differential list
+ // the differential list is only allowed to include things that are in the base list, but
+ // is allowed to include them multiple times - thereby slicing them
+
+ // our approach is to walk through the base list, and see whether the differential
+ // says anything about them.
+ // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
+
+
+ for (ElementDefinition e : derived.getDifferential().getElement())
+ e.clearUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
+
+ // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
+ StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
+ new SnapshotGenerationPreProcessor(this).process(diff, derived);
+
+ StructureDefinitionSnapshotComponent baseSnapshot = base.getSnapshot();
+ if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
+ String derivedType = derived.getTypeName();
+
+ baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType);
+ }
+ // if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
+ // debug = true;
+ // }
+
+ MappingAssistant mappingDetails = new MappingAssistant(mappingMergeMode, base, derived, context.getVersion(), suppressedMappings);
+
+ ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot, mappingDetails);
+
+ checkGroupConstraints(derived);
+ if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
+ int i = 0;
+ for (ElementDefinition e : diff.getElement()) {
+ if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) {
+ ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement());
+ if (existing != null) {
+ updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails, false);
+ } else {
+ ElementDefinition outcome = updateURLs(url, webUrl, e.copy(), true);
+ e.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, outcome);
+ markExtensions(outcome, true, derived);
+ derived.getSnapshot().addElement(outcome);
+ if (walksInto(diff.getElement(), e)) {
+ if (e.getType().size() > 1) {
+ throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId());
+ } else {
+ addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl);
+ }
+ }
+ }
+ }
+ i++;
+ }
+ }
+
+ for (int i = 0; i < derived.getSnapshot().getElement().size(); i++) {
+ ElementDefinition ed = derived.getSnapshot().getElement().get(i);
+ if (ed.getType().size() > 1) {
+ List toRemove = new ArrayList();
+ for (TypeRefComponent tr : ed.getType()) {
+ ElementDefinition typeSlice = findTypeSlice(derived.getSnapshot().getElement(), i, ed.getPath(), tr.getWorkingCode());
+ if (typeSlice != null && typeSlice.prohibited()) {
+ toRemove.add(tr);
+ }
+ }
+ ed.getType().removeAll(toRemove);
+ }
+ }
+ if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
+ throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
+ mappingDetails.update();
+
+ setIds(derived, false);
+
+ log.debug("Differential: ");
+ int debugPad = 0;
+ for (ElementDefinition ed : derived.getDifferential().getElement()) {
+ log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed));
+ debugPad++;
+ }
+ log.debug("Diff (processed): ");
+ debugPad = 0;
+ for (ElementDefinition ed : diff.getElement()) {
+ log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+
+ " -> "+(destInfo(ed, derived.getSnapshot().getElement())));
+ debugPad++;
+ }
+ log.debug("Snapshot: ");
+ debugPad = 0;
+ for (ElementDefinition ed : derived.getSnapshot().getElement()) {
+ log.debug(" "+Utilities.padLeft(Integer.toString(debugPad), ' ', 3)+" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
+ debugPad++;
+ }
+
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ //Check that all differential elements have a corresponding snapshot element
+ int ce = 0;
+ int i = 0;
+ for (ElementDefinition e : diff.getElement()) {
+ if (!e.hasUserData(UserDataNames.SNAPSHOT_diff_source)) {
+ // was injected during preprocessing - this is ok
+ } else {
+ if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS))
+ ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS));
+ if (e.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER))
+ ((Base) e.getUserData(UserDataNames.SNAPSHOT_diff_source)).setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, e.getUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER));
+ }
+ if (!e.hasUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT)) {
+ b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath());
+ ce++;
+ if (e.hasId()) {
+ String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)";
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR));
+ }
+ } else {
+ ElementDefinition sed = (ElementDefinition) e.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
+ sed.setUserData(UserDataNames.SNAPSHOT_DERIVATION_DIFF, e); // note: this means diff/snapshot are cross-linked
+ }
+ i++;
+ }
+ if (!Utilities.noString(b.toString())) {
+ String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)";
+
+ log.debug("Error in snapshot generation: "+msg);
+
+ log.debug("Differential: ");
+ for (ElementDefinition ed : derived.getDifferential().getElement())
+ log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
+ log.debug("Snapshot: ");
+ for (ElementDefinition ed : derived.getSnapshot().getElement())
+ log.debug(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
+
+
+ handleError(url, msg);
+ }
+ // hack around a problem in R4 definitions (somewhere?)
+ for (ElementDefinition ed : derived.getSnapshot().getElement()) {
+ for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
+ if (mm.hasMap()) {
+ mm.setMap(mm.getMap().trim());
+ }
+ }
+ for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
+ if (s.hasSource()) {
+ String ref = s.getSource();
+ if (!Utilities.isAbsoluteUrl(ref)) {
+ if (ref.contains(".")) {
+ s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref);
+ } else {
+ s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref);
+ }
+ }
+ }
+ }
+ }
+ if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
+ for (ElementDefinition ed : derived.getSnapshot().getElement()) {
+ if (!ed.hasBase()) {
+ ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
+ }
+ }
+ }
+ // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to
+ String tn = derived.getType();
+ if (tn.contains("/")) {
+ tn = tn.substring(tn.lastIndexOf("/")+1);
+ }
+ Map slices = new HashMap<>();
+ i = 0;
+ for (ElementDefinition ed : derived.getSnapshot().getElement()) {
+ if (ed.hasSlicing()) {
+ slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i));
+ } else {
+ Set toRemove = new HashSet<>();
+ for (String s : slices.keySet()) {
+ if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) {
+ toRemove.add(s);
+ }
+ }
+ for (String s : toRemove) {
+ ElementDefinitionCounter slice = slices.get(s);
+ int count = slice.checkMin();
+ boolean repeats = !"1".equals(slice.getFocus().getBase().getMax()); // type slicing if repeats = 1
+ if (count > -1 && repeats) {
+ if (slice.getFocus().hasUserData(UserDataNames.SNAPSHOT_auto_added_slicing)) {
+ slice.getFocus().setMin(count);
+ } else {
+ String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count;
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true));
+ }
+ }
+ count = slice.checkMax();
+ if (count > -1 && repeats) {
+ String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended";
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION));
+ }
+ if (!slice.checkMinMax()) {
+ String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin();
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING));
+ }
+ slices.remove(s);
+ }
+ }
+ if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) {
+ throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+".");
+ }
+ if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) {
+ String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first";
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));
+ }
+ if (ed.hasSliceName() && slices.containsKey(ed.getPath())) {
+ if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) {
+ String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])";
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));
+ }
+ }
+ i++;
+ }
+
+ i = 0;
+ // last, check for wrong profiles or target profiles, or unlabeled extensions
+ for (ElementDefinition ed : derived.getSnapshot().getElement()) {
+ for (TypeRefComponent t : ed.getType()) {
+ for (UriType u : t.getProfile()) {
+ StructureDefinition sd = findProfile(u.getValue(), derived);
+ if (sd == null) {
+ if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
+ sd = xver.getDefinition(u.getValue());
+ }
+ }
+ if (sd == null) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE,
+ "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING));
+ } else {
+ String wt = t.getWorkingCode();
+ if (ed.getPath().equals("Bundle.entry.response.outcome")) {
+ wt = "OperationOutcome";
+ }
+ String tt = sd.getType();
+ boolean elementProfile = u.hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT);
+ if (elementProfile) {
+ ElementDefinition edt = sd.getSnapshot().getElementById(u.getExtensionString(ExtensionDefinitions.EXT_PROFILE_ELEMENT));
+ if (edt == null) {
+ handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
+ } else {
+ tt = edt.typeSummary();
+ }
+ }
+ if (!tt.equals(wt)) {
+ boolean ok = !elementProfile && isCompatibleType(wt, sd);
+ if (!ok) {
+ handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
+ }
+ }
+ }
+ }
+ }
+ i++;
+ }
+ } catch (Exception e) {
+ log.error("Exception generating snapshot for "+derived.getVersionedUrl()+": " +e.getMessage());
+ log.debug(e.getMessage(), e);
+ // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
+ derived.setSnapshot(null);
+ derived.setGeneratingSnapshot(false);
+ throw e;
+ }
+ } finally {
+ Base.setCopyUserData(oldCopyUserData);
+ derived.setGeneratingSnapshot(false);
+ snapshotStack.remove(derived.getUrl());
+ }
+ if (base.getVersion() != null) {
+ derived.getSnapshot().addExtension(ExtensionDefinitions.EXT_VERSION_BASE, new StringType(base.getVersion()));
+ }
+ derived.setGeneratedSnapshot(true);
+ //derived.setUserData(UserDataNames.SNAPSHOT_GENERATED, true); // used by the publisher
+ derived.setUserData(UserDataNames.SNAPSHOT_GENERATED_MESSAGES, messages); // used by the publisher
+ }
+
+
+ private String destInfo(ElementDefinition ed, List snapshot) {
+ ElementDefinition sed = (ElementDefinition) ed.getUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT);
+ if (sed == null) {
+ return "(null)";
+ } else {
+ int index = snapshot.indexOf(sed);
+ return ""+index+" "+sed.getId();
+ }
+ }
+
+ private ElementDefinition findTypeSlice(List list, int i, String path, String typeCode) {
+ for (int j = i+1; j < list.size(); j++) {
+ ElementDefinition ed = list.get(j);
+ if (pathMatches(path, ed) && typeMatches(ed, typeCode)) {
+ return ed;
+ }
+ }
+ return null;
+ }
+
+ private boolean pathMatches(String path, ElementDefinition ed) {
+ String p = ed.getPath();
+ if (path.equals(p)) {
+ return true;
+ }
+ if (path.endsWith("[x]")) { // it should
+ path = path.substring(0, path.length()-3);
+ if (p.startsWith(path) && p.length() > path.length() && !p.substring(path.length()).contains(".")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean typeMatches(ElementDefinition ed, String typeCode) {
+ return ed.getType().size() == 1 && typeCode.equals(ed.getTypeFirstRep().getWorkingCode());
+ }
+
+ private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) {
+ String bt = ExtensionUtilities.readStringSubExtension(base, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type");
+ if (!derived.hasExtension(ExtensionDefinitions.EXT_TYPE_PARAMETER)) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl()));
+ }
+ String dt = ExtensionUtilities.readStringSubExtension(derived, ExtensionDefinitions.EXT_TYPE_PARAMETER, "type");
+ StructureDefinition bsd = context.fetchTypeDefinition(bt);
+ StructureDefinition dsd = context.fetchTypeDefinition(dt);
+ if (bsd == null) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt));
+ }
+ if (dsd == null) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt));
+ }
+ StructureDefinition t = dsd;
+ while (t != bsd && t != null) {
+ t = findProfile(t.getBaseDefinition(), t);
+ }
+ if (t == null) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt));
+ }
+ }
+
+ private XVerExtensionManager makeXVer() {
+ if (xver == null) {
+ xver = XVerExtensionManagerFactory.createExtensionManager(context);
+ }
+ return xver;
+ }
+
+ private ElementDefinition getElementInCurrentContext(String path, List list) {
+ for (int i = list.size() -1; i >= 0; i--) {
+ ElementDefinition t = list.get(i);
+ if (t.getPath().equals(path)) {
+ return t;
+ } else if (!path.startsWith(head(t.getPath()))) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private String head(String path) {
+ return path.contains(".") ? path.substring(0, path.lastIndexOf(".")+1) : path;
+ }
+
+ private void findInheritedObligationProfiles(StructureDefinition derived) {
+ List list = derived.getExtensionsByUrl(ExtensionDefinitions.EXT_OBLIGATION_INHERITS_NEW, ExtensionDefinitions.EXT_OBLIGATION_INHERITS_OLD);
+ for (Extension ext : list) {
+ StructureDefinition op = findProfile(ext.getValueCanonicalType().primitiveValue(), derived);
+ if (op != null && ExtensionUtilities.readBoolExtension(op, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_NEW, ExtensionDefinitions.EXT_OBLIGATION_PROFILE_FLAG_OLD)) {
+ if (derived.getBaseDefinitionNoVersion().equals(op.getBaseDefinitionNoVersion())) {
+ obligationProfiles.add(op);
+ }
+ }
+ }
+ }
+
+ private void handleError(String url, String msg) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
+ }
+
+ private void addMessage(ValidationMessage msg) {
+ messages.add(msg);
+ if (msg.getLevel() == IssueSeverity.ERROR && wantThrowExceptions) {
+ throw new DefinitionException(msg.getMessage());
+ }
+ }
+
+ private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) {
+ for (Extension ext : base.getExtension()) {
+ if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS)) {
+ String action = getExtensionAction(ext.getUrl());
+ if (!"ignore".equals(action)) {
+ boolean exists = derived.hasExtension(ext.getUrl());
+ if ("add".equals(action) || !exists) {
+ Extension next = ext.copy();
+ if (next.hasValueMarkdownType()) {
+ MarkdownType md = next.getValueMarkdownType();
+ md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
+ }
+ derived.getExtension().add(next);
+ } else if ("overwrite".equals(action)) {
+ Extension oext = derived.getExtensionByUrl(ext.getUrl());
+ Extension next = ext.copy();
+ if (next.hasValueMarkdownType()) {
+ MarkdownType md = next.getValueMarkdownType();
+ md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
+ }
+ oext.setValue(next.getValue());
+ }
+ }
+ }
+ }
+ }
+
+ private String getExtensionAction(String url) {
+ StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url);
+ if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR)) {
+ return ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_SNAPSHOT_BEHAVIOR);
+ }
+ return "defer";
+ }
+
+ private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) {
+ StructureDefinition sd = context.fetchTypeDefinition(type);
+ if (sd != null) {
+ // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl);
+ for (ElementDefinition ed : sd.getSnapshot().getElement()) {
+ if (ed.getPath().contains(".")) {
+ ElementDefinition outcome = updateURLs(url, weburl, ed.copy(), true);
+ outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path));
+ markExtensions(outcome, false, sd);
+ snapshot.getElement().add(outcome);
+ } else {
+ focus.getConstraint().addAll(ed.getConstraint());
+ for (Extension ext : ed.getExtension()) {
+ if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) {
+ focus.getExtension().add(markExtensionSource(ext.copy(), false, sd));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean walksInto(List list, ElementDefinition ed) {
+ int i = list.indexOf(ed);
+ return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+".");
+ }
+
+ private void fixTypeOfResourceId(StructureDefinition base) {
+ if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) {
+ fixTypeOfResourceId(base.getSnapshot().getElement());
+ fixTypeOfResourceId(base.getDifferential().getElement());
+ }
+ }
+
+ private void fixTypeOfResourceId(List list) {
+ for (ElementDefinition ed : list) {
+ if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) {
+ for (TypeRefComponent tr : ed.getType()) {
+ tr.setCode("http://hl7.org/fhirpath/System.String");
+ tr.removeExtension(ExtensionDefinitions.EXT_FHIR_TYPE);
+ ExtensionUtilities.addUrlExtension(tr, ExtensionDefinitions.EXT_FHIR_TYPE, "id");
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if derived has the correct base type
+ *
+ * Clear first element of differential under certain conditions.
+ *
+ * @param derived
+ * @throws Error
+ */
+ private void checkDifferentialBaseType(StructureDefinition derived) throws Error {
+ if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) {
+ if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinitionNoVersion(), derived)) {
+ derived.getDifferential().getElementFirstRep().getType().clear();
+ } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) {
+ throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT));
+ }
+ }
+ }
+
+ private boolean typeMatchesAncestor(List type, String baseDefinition, StructureDefinition src) {
+ StructureDefinition sd = findProfile(baseDefinition, src);
+ return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode());
+ }
+
+
+ private void checkGroupConstraints(StructureDefinition derived) {
+ List toRemove = new ArrayList<>();
+// List processed = new ArrayList<>();
+ for (ElementDefinition element : derived.getSnapshot().getElement()) {
+ if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) {
+ checkForChildrenInGroup(derived, toRemove, element);
+ }
+ }
+ derived.getSnapshot().getElement().removeAll(toRemove);
+ }
+
+ private void checkForChildrenInGroup(StructureDefinition derived, List toRemove, ElementDefinition element) throws Error {
+ List children = getChildren(derived, element);
+ List groups = readChoices(element, children);
+ for (ElementChoiceGroup group : groups) {
+ String mandated = null;
+ Set names = new HashSet<>();
+ for (ElementDefinition ed : children) {
+ String name = tail(ed.getPath());
+ if (names.contains(name)) {
+ throw new Error("huh?");
+ } else {
+ names.add(name);
+ }
+ if (group.getElements().contains(name)) {
+ if (ed.getMin() == 1) {
+ if (mandated == null) {
+ mandated = name;
+ } else {
+ throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name);
+ }
+ }
+ }
+ }
+ if (mandated != null) {
+ for (ElementDefinition ed : children) {
+ String name = tail(ed.getPath());
+ if (group.getElements().contains(name) && !mandated.equals(name)) {
+ ed.setMax("0");
+ addAllChildren(derived, ed, toRemove);
+ }
+ }
+ }
+ }
+ }
+
+ private List getChildren(StructureDefinition derived, ElementDefinition element) {
+ List elements = derived.getSnapshot().getElement();
+ int index = elements.indexOf(element) + 1;
+ String path = element.getPath()+".";
+ List list = new ArrayList<>();
+ while (index < elements.size()) {
+ ElementDefinition e = elements.get(index);
+ String p = e.getPath();
+ if (p.startsWith(path) && !e.hasSliceName()) {
+ if (!p.substring(path.length()).contains(".")) {
+ list.add(e);
+ }
+ index++;
+ } else {
+ break;
+ }
+ }
+ return list;
+ }
+
+ private void addAllChildren(StructureDefinition derived, ElementDefinition element, List toRemove) {
+ List children = getChildList(derived, element);
+ for (ElementDefinition child : children) {
+ toRemove.add(child);
+ addAllChildren(derived, child, toRemove);
+ }
+ }
+
+ /**
+ * Check that a differential is valid.
+ * @param elements
+ * @param type
+ * @param url
+ */
+ private void checkDifferential(List elements, String type, String url) {
+ boolean first = true;
+ String t = urlTail(type);
+ for (ElementDefinition ed : elements) {
+ if (!ed.hasPath()) {
+ throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
+ }
+ String p = ed.getPath();
+ if (p == null) {
+ throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
+ }
+ if (!((first && t.equals(p)) || p.startsWith(t+"."))) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, t, (first ? " (or be '"+t+"')" : "")));
+ }
+ if (p.contains(".")) {
+ // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace)
+ // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{}
+ // Element names SHOULD not contain non-ASCII characters
+ // Element names SHALL NOT exceed 64 characters in length
+ String[] pl = p.split("\\.");
+ for (String pp : pl) {
+ if (pp.length() < 1) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url));
+ }
+ if (pp.length() > 64) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url));
+ }
+ for (char ch : pp.toCharArray()) {
+ if (Utilities.isWhitespace(ch)) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url));
+ }
+ if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
+ }
+ if (ch < ' ' || ch > 'z') {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
+ }
+ }
+ if (pp.contains("[") || pp.contains("]")) {
+ if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url));
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ private boolean isCompatibleType(String base, StructureDefinition sdt) {
+ StructureDefinition sdb = context.fetchTypeDefinition(base);
+ if (sdb.getType().equals(sdt.getType())) {
+ return true;
+ }
+ StructureDefinition sd = context.fetchTypeDefinition(sdt.getType());
+ while (sd != null) {
+ if (sd.getType().equals(sdb.getType())) {
+ return true;
+ }
+ if (sd.getUrl().equals(sdb.getUrl())) {
+ return true;
+ }
+ sd = findProfile(sd.getBaseDefinition(), sd);
+ }
+ return false;
+ }
+
+
+ private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
+ StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
+ for (ElementDefinition sed : source.getElement()) {
+ ElementDefinition ted = sed.copy();
+ diff.getElement().add(ted);
+ ted.setUserData(UserDataNames.SNAPSHOT_diff_source, sed);
+ }
+ return diff;
+ }
+
+ private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) {
+ StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent();
+ for (ElementDefinition sed : source.getElement()) {
+ ElementDefinition ted = sed.copy();
+ ted.setId(ted.getId().replaceFirst(baseType,derivedType));
+ ted.setPath(ted.getPath().replaceFirst(baseType,derivedType));
+ diff.getElement().add(ted);
+ }
+ return diff;
+ }
+
+ private String constraintSummary(ElementDefinition ed) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ if (ed.hasPattern())
+ b.append("pattern="+ed.getPattern().fhirType());
+ if (ed.hasFixed())
+ b.append("fixed="+ed.getFixed().fhirType());
+ if (ed.hasConstraint())
+ b.append("constraints="+ed.getConstraint().size());
+ return b.toString();
+ }
+
+
+ private String sliceSummary(ElementDefinition ed) {
+ if (!ed.hasSlicing() && !ed.hasSliceName())
+ return "";
+ if (ed.hasSliceName())
+ return " (slicename = "+ed.getSliceName()+")";
+
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
+ if (first)
+ first = false;
+ else
+ b.append("|");
+ b.append(d.getPath());
+ }
+ return " (slicing by "+b.toString()+")";
+ }
+
+
+// private String typeSummary(ElementDefinition ed) {
+// StringBuilder b = new StringBuilder();
+// boolean first = true;
+// for (TypeRefComponent tr : ed.getType()) {
+// if (first)
+// first = false;
+// else
+// b.append("|");
+// b.append(tr.getWorkingCode());
+// }
+// return b.toString();
+// }
+
+ private String typeSummaryWithProfile(ElementDefinition ed) {
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (TypeRefComponent tr : ed.getType()) {
+ if (first)
+ first = false;
+ else
+ b.append("|");
+ b.append(tr.getWorkingCode());
+ if (tr.hasProfile()) {
+ b.append("(");
+ b.append(tr.getProfile());
+ b.append(")");
+
+ }
+ }
+ return b.toString();
+ }
+
+
+// private boolean findMatchingElement(String id, List list) {
+// for (ElementDefinition ed : list) {
+// if (ed.getId().equals(id))
+// return true;
+// if (id.endsWith("[x]")) {
+// if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
+// return true;
+// }
+// }
+// return false;
+// }
+
+ protected ElementDefinition getById(List list, String baseId) {
+ for (ElementDefinition t : list) {
+ if (baseId.equals(t.getId())) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ protected void updateConstraintSources(ElementDefinition ed, String url) {
+ for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
+ if (!c.hasSource()) {
+ c.setSource(url);
+ }
+ }
+
+ }
+
+ protected Set getListOfTypes(ElementDefinition e) {
+ Set result = new HashSet<>();
+ for (TypeRefComponent t : e.getType()) {
+ result.add(t.getCode());
+ }
+ return result;
+ }
+
+ StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName,
+ List diffMatches, ElementDefinition outcome, String webUrl, StructureDefinition srcSD) {
+ if (outcome.getType().size() == 0) {
+ if (outcome.hasContentReference()) {
+ throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath()));
+ } else {
+ throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName));
+ }
+ }
+ if (outcome.getType().size() > 1) {
+ for (TypeRefComponent t : outcome.getType()) {
+ if (!t.getWorkingCode().equals("Reference"))
+ throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
+ }
+ }
+ StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD);
+ if (dt == null)
+ throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath()));
+ return dt;
+ }
+
+ protected String sliceNames(List diffMatches) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (ElementDefinition ed : diffMatches) {
+ if (ed.hasSliceName()) {
+ b.append(ed.getSliceName());
+ }
+ }
+ return b.toString();
+ }
+
+ protected boolean isMatchingType(StructureDefinition sd, List types, String inner) {
+ StructureDefinition tsd = sd;
+ while (tsd != null) {
+ for (TypeRefComponent tr : types) {
+ if (tsd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && tsd.getType().equals(tr.getCode())) {
+ return true;
+ }
+ if (inner == null && tsd.getUrl().equals(tr.getCode())) {
+ return true;
+ }
+ if (inner != null) {
+ ElementDefinition ed = null;
+ for (ElementDefinition t : tsd.getSnapshot().getElement()) {
+ if (inner.equals(t.getId())) {
+ ed = t;
+ }
+ }
+ if (ed != null) {
+ return isMatchingType(ed.getType(), types);
+ }
+ }
+ }
+ tsd = findProfile(tsd.getBaseDefinition(), tsd);
+ }
+ return false;
+ }
+
+ private boolean isMatchingType(List test, List desired) {
+ for (TypeRefComponent t : test) {
+ for (TypeRefComponent d : desired) {
+ if (t.getCode().equals(d.getCode())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean isValidType(TypeRefComponent t, ElementDefinition base) {
+ for (TypeRefComponent tr : base.getType()) {
+ if (tr.getCode().equals(t.getCode())) {
+ return true;
+ }
+ if (tr.getWorkingCode().equals(t.getCode())) {
+ log.error("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected void checkNotGenerating(StructureDefinition sd, String role) {
+ if (sd.isGeneratingSnapshot()) {
+ throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role));
+ }
+ }
+
+ protected boolean isBaseResource(List types) {
+ if (types.isEmpty())
+ return false;
+ for (TypeRefComponent type : types) {
+ String t = type.getWorkingCode();
+ if ("Resource".equals(t))
+ return false;
+ }
+ return true;
+
+ }
+
+ String determineFixedType(List diffMatches, String fixedType, int i) {
+ if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) {
+ String n = tail(diffMatches.get(i).getPath()).replace("[x]", "");
+ String t = diffMatches.get(i).getSliceName().substring(n.length());
+ if (isDataType(t)) {
+ fixedType = t;
+ } else if (isPrimitive(Utilities.uncapitalize(t))) {
+ fixedType = Utilities.uncapitalize(t);
+ } else {
+ throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__10_AND_IMPLICIT_SLICE_NAME_DOES_NOT_CONTAIN_A_VALID_TYPE__AT_, t, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
+ }
+ } else if (diffMatches.get(i).getType().size() == 1) {
+ fixedType = diffMatches.get(i).getType().get(0).getCode();
+ } else {
+ throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
+ }
+ return fixedType;
+ }
+
+
+ protected BaseTypeSlice chooseMatchingBaseSlice(List baseSlices, String type) {
+ for (BaseTypeSlice bs : baseSlices) {
+ if (bs.getType().equals(type)) {
+ return bs;
+ }
+ }
+ return null;
+ }
+
+
+ protected List findBaseSlices(StructureDefinitionSnapshotComponent list, int start) {
+ List res = new ArrayList<>();
+ ElementDefinition base = list.getElement().get(start);
+ int i = start + 1;
+ while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
+ i++;
+ };
+ while (i < list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) {
+ int s = i;
+ i++;
+ while (i < list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
+ i++;
+ };
+ res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1));
+ }
+ return res;
+ }
+
+
+ protected String getWebUrl(StructureDefinition dt, String webUrl) {
+ if (dt.hasWebPath()) {
+ // this is a hack, but it works for now, since we don't have deep folders
+ String url = dt.getWebPath();
+ int i = url.lastIndexOf("/");
+ if (i < 1) {
+ return defWebRoot;
+ } else {
+ return url.substring(0, i+1);
+ }
+ } else {
+ return webUrl;
+ }
+ }
+
+ protected String descED(List list, int index) {
+ return index >=0 && index < list.size() ? list.get(index).present() : "X";
+ }
+
+
+
+ protected String rootName(String cpath) {
+ String t = tail(cpath);
+ return t.replace("[x]", "");
+ }
+
+
+ protected String determineTypeSlicePath(String path, String cpath) {
+ String headP = path.substring(0, path.lastIndexOf("."));
+// String tailP = path.substring(path.lastIndexOf(".")+1);
+ String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
+ return headP+"."+tailC;
+ }
+
+
+ protected boolean isImplicitSlicing(ElementDefinition ed, String path) {
+ if (ed == null || ed.getPath() == null || path == null)
+ return false;
+ if (path.equals(ed.getPath()))
+ return false;
+ boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
+ return ok;
+ }
+
+
+ protected boolean diffsConstrainTypes(List diffMatches, String cPath, List typeList) {
+// if (diffMatches.size() < 2)
+ // return false;
+ String p = diffMatches.get(0).getPath();
+ if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
+ return false;
+ typeList.clear();
+ String rn = tail(cPath);
+ rn = rn.substring(0, rn.length()-3);
+ for (int i = 0; i < diffMatches.size(); i++) {
+ ElementDefinition ed = diffMatches.get(i);
+ String n = tail(ed.getPath());
+ if (!n.startsWith(rn))
+ return false;
+ String s = n.substring(rn.length());
+ if (!s.contains(".")) {
+ if (ed.hasSliceName() && ed.getType().size() == 1) {
+ typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
+ } else if (ed.hasSliceName() && ed.getType().size() == 0) {
+ if (isDataType(s)) {
+ typeList.add(new TypeSlice(ed, s));
+ } else if (isPrimitive(Utilities.uncapitalize(s))) {
+ typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
+ } else {
+ String tn = ed.getSliceName().substring(n.length());
+ if (isDataType(tn)) {
+ typeList.add(new TypeSlice(ed, tn));
+ } else if (isPrimitive(Utilities.uncapitalize(tn))) {
+ typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn)));
+ }
+ }
+ } else if (!ed.hasSliceName() && !s.equals("[x]")) {
+ if (isDataType(s))
+ typeList.add(new TypeSlice(ed, s));
+ else if (isConstrainedDataType(s))
+ typeList.add(new TypeSlice(ed, baseType(s)));
+ else if (isPrimitive(Utilities.uncapitalize(s)))
+ typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
+ } else if (!ed.hasSliceName() && s.equals("[x]"))
+ typeList.add(new TypeSlice(ed, null));
+ }
+ }
+ return true;
+ }
+
+
+ protected List redirectorStack(List redirector, ElementDefinition outcome, String path) {
+ List result = new ArrayList();
+ result.addAll(redirector);
+ result.add(new ElementRedirection(outcome, path));
+ return result;
+ }
+
+
+ protected List getByTypeName(List type, String t) {
+ List res = new ArrayList();
+ for (TypeRefComponent tr : type) {
+ if (t.equals(tr.getWorkingCode()))
+ res.add(tr);
+ }
+ return res;
+ }
+
+
+ protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
+ outcome.setContentReference(null);
+ outcome.getType().clear(); // though it should be clear anyway
+ outcome.getType().addAll(tgt.getType());
+ }
+
+
+ protected boolean baseWalksInto(List elements, int cursor) {
+ if (cursor >= elements.size())
+ return false;
+ String path = elements.get(cursor).getPath();
+ String prevPath = elements.get(cursor - 1).getPath();
+ return path.startsWith(prevPath + ".");
+ }
+
+
+ protected ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
+ ElementDefinition res = profile.copy();
+ if (!res.hasSliceName())
+ res.setSliceName(usage.getSliceName());
+ if (!res.hasLabel())
+ res.setLabel(usage.getLabel());
+ for (Coding c : usage.getCode())
+ if (!res.hasCode(c))
+ res.addCode(c);
+
+ if (!res.hasDefinition())
+ res.setDefinition(usage.getDefinition());
+ if (!res.hasShort() && usage.hasShort())
+ res.setShort(usage.getShort());
+ if (!res.hasComment() && usage.hasComment())
+ res.setComment(usage.getComment());
+ if (!res.hasRequirements() && usage.hasRequirements())
+ res.setRequirements(usage.getRequirements());
+ for (StringType c : usage.getAlias())
+ if (!res.hasAlias(c.getValue()))
+ res.addAlias(c.getValue());
+ if (!res.hasMin() && usage.hasMin())
+ res.setMin(usage.getMin());
+ if (!res.hasMax() && usage.hasMax())
+ res.setMax(usage.getMax());
+
+ if (!res.hasFixed() && usage.hasFixed())
+ res.setFixed(usage.getFixed());
+ if (!res.hasPattern() && usage.hasPattern())
+ res.setPattern(usage.getPattern());
+ if (!res.hasExample() && usage.hasExample())
+ res.setExample(usage.getExample());
+ if (!res.hasMinValue() && usage.hasMinValue())
+ res.setMinValue(usage.getMinValue());
+ if (!res.hasMaxValue() && usage.hasMaxValue())
+ res.setMaxValue(usage.getMaxValue());
+ if (!res.hasMaxLength() && usage.hasMaxLength())
+ res.setMaxLength(usage.getMaxLength());
+ if (!res.hasMustSupport() && usage.hasMustSupport())
+ res.setMustSupport(usage.getMustSupport());
+ if (!res.hasIsSummary() && usage.hasIsSummary())
+ res.setIsSummary(usage.getIsSummary());
+ if (!res.hasIsModifier() && usage.hasIsModifier())
+ res.setIsModifier(usage.getIsModifier());
+ if (!res.hasIsModifierReason() && usage.hasIsModifierReason())
+ res.setIsModifierReason(usage.getIsModifierReason());
+ if (!res.hasMustHaveValue() && usage.hasMustHaveValue())
+ res.setMustHaveValue(usage.getMustHaveValue());
+ if (!res.hasBinding() && usage.hasBinding())
+ res.setBinding(usage.getBinding().copy());
+ for (ElementDefinitionConstraintComponent c : usage.getConstraint())
+ if (!res.hasConstraint(c.getKey()))
+ res.addConstraint(c);
+ for (Extension e : usage.getExtension()) {
+ if (!res.hasExtension(e.getUrl()))
+ res.addExtension(e.copy());
+ }
+
+ return res;
+ }
+
+
+ protected boolean checkExtensionDoco(ElementDefinition base) {
+ // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
+ boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) &&
+ (!base.hasBase() || !"II.extension".equals(base.getBase().getPath()));
+ if (isExtension) {
+ base.setDefinition("An Extension");
+ base.setShort("Extension");
+ base.setCommentElement(null);
+ base.setRequirementsElement(null);
+ base.getAlias().clear();
+ base.getMapping().clear();
+ }
+ return isExtension;
+ }
+
+
+ protected String pathTail(List diffMatches, int i) {
+
+ ElementDefinition d = diffMatches.get(i);
+ String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
+ return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
+ }
+
+
+ protected void markDerived(ElementDefinition outcome) {
+ for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
+ inv.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true);
+ }
+
+
+ static String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ b.append(d.getType().toCode()+":"+d.getPath());
+ }
+ b.append(" (");
+ if (slice.hasOrdered())
+ b.append(slice.getOrdered() ? "ordered" : "unordered");
+ b.append("/");
+ if (slice.hasRules())
+ b.append(slice.getRules().toCode());
+ b.append(")");
+ if (slice.hasDescription()) {
+ b.append(" \"");
+ b.append(slice.getDescription());
+ b.append("\"");
+ }
+ return b.toString();
+ }
+
+
+ protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) {
+ derived.setUserData(UserDataNames.SNAPSHOT_BASE_MODEL, baseProfileUrl);
+ derived.setUserData(UserDataNames.SNAPSHOT_BASE_PATH, base.getPath());
+ if (base.hasBase()) {
+ if (!derived.hasBase())
+ derived.setBase(new ElementDefinitionBaseComponent());
+ derived.getBase().setPath(base.getBase().getPath());
+ derived.getBase().setMin(base.getBase().getMin());
+ derived.getBase().setMax(base.getBase().getMax());
+ } else {
+ if (!derived.hasBase())
+ derived.setBase(new ElementDefinitionBaseComponent());
+ derived.getBase().setPath(base.getPath());
+ derived.getBase().setMin(base.getMin());
+ derived.getBase().setMax(base.getMax());
+ }
+ }
+
+
+ protected boolean pathStartsWith(String p1, String p2) {
+ return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4)));
+ }
+
+ private boolean pathMatches(String p1, String p2) {
+ return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
+ }
+
+
+ protected String fixedPathSource(String contextPath, String pathSimple, List redirector) {
+ if (contextPath == null)
+ return pathSimple;
+// String ptail = pathSimple.substring(contextPath.length() + 1);
+ if (redirector != null && redirector.size() > 0) {
+ String ptail = null;
+ if (contextPath.length() >= pathSimple.length()) {
+ ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
+ } else {
+ ptail = pathSimple.substring(contextPath.length()+1);
+ }
+ return redirector.get(redirector.size()-1).getPath()+"."+ptail;
+// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
+ } else {
+ String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
+ return contextPath+"."+ptail;
+ }
+ }
+
+ protected String fixedPathDest(String contextPath, String pathSimple, List redirector, String redirectSource) {
+ String s;
+ if (contextPath == null)
+ s = pathSimple;
+ else {
+ if (redirector != null && redirector.size() > 0) {
+ String ptail = null;
+ if (redirectSource.length() >= pathSimple.length()) {
+ ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
+ } else {
+ ptail = pathSimple.substring(redirectSource.length()+1);
+ }
+ // ptail = ptail.substring(ptail.indexOf(".")+1);
+ s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
+ } else {
+ String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
+ s = contextPath+"."+ptail;
+ }
+ }
+ return s;
+ }
+
+ protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, StructureDefinition src) {
+ StructureDefinition sd = null;
+ if (type.hasProfile()) {
+ sd = findProfile(type.getProfile().get(0).getValue(), src);
+ if (sd == null) {
+ if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) {
+ sd = xver.getDefinition(type.getProfile().get(0).getValue());
+ generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName());
+ }
+ }
+ if (sd == null) {
+ log.debug("Failed to find referenced profile: " + type.getProfile());
+ }
+
+ }
+ if (sd == null)
+ sd = context.fetchTypeDefinition(type.getWorkingCode());
+ if (sd == null)
+ log.warn("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
+ return sd;
+ }
+
+ protected StructureDefinition getProfileForDataType(String type) {
+ StructureDefinition sd = context.fetchTypeDefinition(type);
+ if (sd == null)
+ log.warn("XX: failed to find profle for type: " + type); // debug GJM
+ return sd;
+ }
+
+ static String typeCode(List types) {
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (TypeRefComponent type : types) {
+ if (first) first = false; else b.append(", ");
+ b.append(type.getWorkingCode());
+ if (type.hasTargetProfile())
+ b.append("{"+type.getTargetProfile()+"}");
+ else if (type.hasProfile())
+ b.append("{"+type.getProfile()+"}");
+ }
+ return b.toString();
+ }
+
+
+ protected boolean isDataType(List types) {
+ if (types.isEmpty())
+ return false;
+ for (TypeRefComponent type : types) {
+ String t = type.getWorkingCode();
+ if (!isDataType(t) && !isPrimitive(t))
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
+ * @param url - the base url to use to turn internal references into absolute references
+ * @param element - the Element to update
+ * @return - the updated Element
+ */
+ public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element, boolean processRelatives) {
+ if (element != null) {
+ ElementDefinition defn = element;
+ if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
+ defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
+ for (TypeRefComponent t : defn.getType()) {
+ for (UriType u : t.getProfile()) {
+ if (u.getValue().startsWith("#"))
+ u.setValue(url+t.getProfile());
+ }
+ for (UriType u : t.getTargetProfile()) {
+ if (u.getValue().startsWith("#"))
+ u.setValue(url+t.getTargetProfile());
+ }
+ }
+ if (webUrl != null) {
+ // also, must touch up the markdown
+ if (element.hasDefinition()) {
+ element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ if (element.hasComment()) {
+ element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ if (element.hasRequirements()) {
+ element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ if (element.hasMeaningWhenMissing()) {
+ element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ if (element.hasBinding() && element.getBinding().hasDescription()) {
+ element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ for (Extension ext : element.getExtension()) {
+ if (ext.hasValueMarkdownType()) {
+ MarkdownType md = ext.getValueMarkdownType();
+ md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, processRelatives));
+ }
+ }
+ }
+ }
+ return element;
+ }
+
+
+ public static String processRelativeUrls(String markdown, String webUrl, String basePath, List resourceNames, Set baseFilenames, Set localFilenames, boolean processRelatives) {
+ if (markdown == null) {
+ return "";
+ }
+ Set anchorRefs = new HashSet<>();
+ markdown = markdown+" ";
+
+ StringBuilder b = new StringBuilder();
+ int i = 0;
+ int left = -1;
+ boolean processingLink = false;
+ int linkLeft = -1;
+ while (i < markdown.length()) {
+ if (markdown.charAt(i) == '[') {
+ if (left == -1) {
+ left = i;
+ } else {
+ left = Integer.MAX_VALUE;
+ }
+ }
+ if (markdown.charAt(i) == ']') {
+ if (left != -1 && left != Integer.MAX_VALUE && markdown.length() > i && markdown.charAt(i+1) != '(') {
+ String n = markdown.substring(left+1, i);
+ if (anchorRefs.contains(n) && markdown.length() > i && markdown.charAt(i+1) == ':') {
+ processingLink = true;
+ } else {
+ anchorRefs.add(n);
+ }
+ }
+ left = -1;
+ }
+ if (processingLink) {
+ char ch = markdown.charAt(i);
+ if (linkLeft == -1) {
+ if (ch != ']' && ch != ':' && !Character.isWhitespace(ch)) {
+ linkLeft = i;
+ } else {
+ b.append(ch);
+ }
+ } else {
+ if (Character.isWhitespace(ch)) {
+ // found the end of the processible link:
+ String url = markdown.substring(linkLeft, i);
+ if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
+ b.append(basePath);
+ if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
+ b.append("/");
+ }
+ }
+ b.append(url);
+ b.append(ch);
+ linkLeft = -1;
+ }
+ }
+ } else {
+ if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
+
+ int j = i + 2;
+ while (j < markdown.length() && markdown.charAt(j) != ')')
+ j++;
+ if (j < markdown.length()) {
+ String url = markdown.substring(i+2, j);
+ if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) {
+ //
+ // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots.
+ // that's what this code is doing.
+ //
+ // But that hasn't always happened and there's packages out there where the snapshots
+ // contain relative references that actually are references to the main specification
+ //
+ // This code is trying to guess which relative references are actually to the
+ // base specification.
+ //
+ if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
+ b.append("](");
+ b.append(basePath);
+ if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
+ b.append("/");
+ }
+ i = i + 1;
+ } else {
+ b.append("](");
+ // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all?
+ // re-enabled 11-Feb 2022 GDG - we do want to do this. At least, $assemble in davinci-dtr, where the markdown comes from the SDC IG, and an SDC local reference must be changed to point to SDC. in this case, it's called when generating snapshots
+ // added processRelatives parameter to deal with this (well, to try)
+ if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) {
+
+ b.append(webUrl);
+ if (!Utilities.noString(webUrl) && !webUrl.endsWith("/")) {
+ b.append("/");
+ }
+ } else {
+ //DO NOTHING
+ }
+ i = i + 1;
+ }
+ } else
+ b.append(markdown.charAt(i));
+ } else
+ b.append(markdown.charAt(i));
+ } else {
+ b.append(markdown.charAt(i));
+ }
+ }
+ i++;
+ }
+ String s = b.toString();
+ return Utilities.rightTrim(s);
+ }
+
+ private static boolean issLocalFileName(String url, Set localFilenames) {
+ if (localFilenames != null) {
+ for (String n : localFilenames) {
+ if (url.startsWith(n.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ private static boolean isLikelySourceURLReference(String url, List resourceNames, Set baseFilenames, Set localFilenames, String baseUrl) {
+ if (url == null) {
+ return false;
+ }
+ if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) {
+ if (resourceNames != null) {
+ for (String n : resourceNames) {
+ if (n != null && url.startsWith(n.toLowerCase()+".html")) {
+ return true;
+ }
+ if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) {
+ return true;
+ }
+ }
+ }
+ if (localFilenames != null) {
+ for (String n : localFilenames) {
+ if (n != null && url.startsWith(n.toLowerCase())) {
+ return false;
+ }
+ }
+ }
+ if (baseFilenames != null) {
+ for (String n : baseFilenames) {
+ if (n != null && url.startsWith(n.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ }
+ return
+ url.startsWith("extensibility.html") ||
+ url.startsWith("terminologies.html") ||
+ url.startsWith("observation.html") ||
+ url.startsWith("codesystem.html") ||
+ url.startsWith("fhirpath.html") ||
+ url.startsWith("datatypes.html") ||
+ url.startsWith("operations.html") ||
+ url.startsWith("resource.html") ||
+ url.startsWith("elementdefinition.html") ||
+ url.startsWith("element-definitions.html") ||
+ url.startsWith("snomedct.html") ||
+ url.startsWith("loinc.html") ||
+ url.startsWith("http.html") ||
+ url.startsWith("references") ||
+ url.startsWith("license.html") ||
+ url.startsWith("narrative.html") ||
+ url.startsWith("search.html") ||
+ url.startsWith("security.html") ||
+ url.startsWith("versions.html") ||
+ url.startsWith("patient-operation-match.html") ||
+ (url.startsWith("extension-") && url.contains(".html")) ||
+ url.startsWith("resource-definitions.html");
+ }
+
+ protected List getSiblings(List list, ElementDefinition current) {
+ List result = new ArrayList();
+ String path = current.getPath();
+ int cursor = list.indexOf(current)+1;
+ while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
+ if (pathMatches(list.get(cursor).getPath(), path))
+ result.add(list.get(cursor));
+ cursor++;
+ }
+ return result;
+ }
+
+ protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
+ if (src.hasOrderedElement())
+ dst.setOrderedElement(src.getOrderedElement().copy());
+ if (src.hasDiscriminator()) {
+ // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll because it uses object equality, not string equality
+ for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
+ boolean found = false;
+ for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
+ if (matches(d, s)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ dst.getDiscriminator().add(s);
+ }
+ }
+ if (src.hasRulesElement())
+ dst.setRulesElement(src.getRulesElement().copy());
+ }
+
+ protected boolean orderMatches(BooleanType diff, BooleanType base) {
+ return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
+ }
+
+ protected boolean discriminatorMatches(List diff, List base) {
+ if (diff.isEmpty() || base.isEmpty())
+ return true;
+ if (diff.size() < base.size())
+ return false;
+ for (int i = 0; i < base.size(); i++)
+ if (!matches(diff.get(i), base.get(i)))
+ return false;
+ return true;
+ }
+
+ private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
+ return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
+ }
+
+
+ protected boolean ruleMatches(SlicingRules diff, SlicingRules base) {
+ return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
+ ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
+ }
+
+ protected boolean isSlicedToOneOnly(ElementDefinition e) {
+ return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
+ }
+
+ protected boolean isTypeSlicing(ElementDefinition e) {
+ return (e.hasSlicing() && e.getSlicing().getDiscriminator().size() == 1 &&
+ e.getSlicing().getDiscriminatorFirstRep().getType() == DiscriminatorType.TYPE &&
+ "$this".equals(e.getSlicing().getDiscriminatorFirstRep().getPath()));
+ }
+
+
+ protected ElementDefinitionSlicingComponent makeExtensionSlicing() {
+ ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
+ slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
+ slice.setOrdered(false);
+ slice.setRules(SlicingRules.OPEN);
+ return slice;
+ }
+
+ protected boolean isExtension(ElementDefinition currentBase) {
+ return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
+ }
+
+ boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List base, boolean allowSlices) throws DefinitionException {
+ end = Math.min(context.getElement().size(), end);
+ start = Math.max(0, start);
+
+ for (int i = start; i <= end; i++) {
+ ElementDefinition ed = context.getElement().get(i);
+ String statedPath = ed.getPath();
+ if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) {
+ return false;
+ } else if (statedPath.startsWith(path+".")) {
+ return true;
+ } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) {
+ return true;
+ } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) {
+ return false;
+ } else if (i != start && allowSlices && !statedPath.startsWith(path)) {
+ return false;
+ } else {
+ // not sure why we get here, but returning false at this point makes a bunch of tests fail
+ }
+ }
+ return false;
+ }
+
+ protected List getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
+ List result = new ArrayList();
+ String[] p = path.split("\\.");
+ for (int i = start; i <= end; i++) {
+ String statedPath = context.getElement().get(i).getPath();
+ String[] sp = statedPath.split("\\.");
+ boolean ok = sp.length == p.length;
+ for (int j = 0; j < p.length; j++) {
+ ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j]));
+ }
+// don't need this debug check - everything is ok
+// if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 &&
+// statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) &&
+// (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) {
+//
+// }
+ if (ok) {
+ /*
+ * Commenting this out because it raises warnings when profiling inherited elements. For example,
+ * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
+ * Not sure we have enough information here to do the check properly. Might be better done when we're sorting the profile?
+
+ if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
+ addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING));
+
+ */
+ result.add(context.getElement().get(i));
+ }
+ }
+ if (debug) {
+ Set ids = new HashSet<>();
+ for (ElementDefinition ed : result) {
+ ids.add(ed.getIdOrPath());
+ }
+ }
+ return result;
+ }
+
+
+ private boolean isSameBase(String p, String sp) {
+ return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ;
+ }
+
+ protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
+ int result = cursor;
+ if (cursor >= context.getElement().size())
+ return result;
+ String path = context.getElement().get(cursor).getPath()+".";
+ while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
+ result++;
+ return result;
+ }
+
+ protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
+ int result = cursor;
+ String path = context.getElement().get(cursor).getPath()+".";
+ while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
+ result++;
+ return result;
+ }
+
+ protected int findEndOfElementNoSlices(StructureDefinitionSnapshotComponent context, int cursor) {
+ int result = cursor;
+ String path = context.getElement().get(cursor).getPath()+".";
+ while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path) && !context.getElement().get(result+1).hasSliceName())
+ result++;
+ return result;
+ }
+
+ protected boolean unbounded(ElementDefinition definition) {
+ StringType max = definition.getMaxElement();
+ if (max == null)
+ return false; // this is not valid
+ if (max.getValue().equals("1"))
+ return false;
+ if (max.getValue().equals("0"))
+ return false;
+ return true;
+ }
+
+
+ public void updateFromObligationProfiles(ElementDefinition base) {
+ List obligationProfileElements = new ArrayList<>();
+ for (StructureDefinition sd : obligationProfiles) {
+ ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
+ if (ed != null) {
+ obligationProfileElements.add(ed);
+ }
+ }
+ for (ElementDefinition ed : obligationProfileElements) {
+ for (Extension ext : ed.getExtension()) {
+ if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) {
+ base.getExtension().add(ext.copy());
+ }
+ }
+ }
+ boolean hasMustSupport = false;
+ for (ElementDefinition ed : obligationProfileElements) {
+ hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
+ }
+ if (hasMustSupport) {
+ for (ElementDefinition ed : obligationProfileElements) {
+ mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement());
+ if (ed.getMustSupport()) {
+ base.setMustSupport(true);
+ }
+ }
+ }
+ boolean hasBinding = false;
+ for (ElementDefinition ed : obligationProfileElements) {
+ hasBinding = hasBinding || ed.hasBinding();
+ }
+ if (hasBinding) {
+ ElementDefinitionBindingComponent binding = base.getBinding();
+ for (ElementDefinition ed : obligationProfileElements) {
+ for (Extension ext : ed.getBinding().getExtension()) {
+ if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
+ String p = ext.getExtensionString("purpose");
+ if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
+ if (!binding.hasExtension(ext)) {
+ binding.getExtension().add(ext.copy());
+ }
+ }
+ }
+ }
+ for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
+ if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
+ if (!binding.hasAdditional(ab)) {
+ binding.getAdditional().add(ab.copy());
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path, MappingAssistant mappings, boolean fromSlicer) throws DefinitionException, FHIRException {
+ source.setUserData(UserDataNames.SNAPSHOT_GENERATED_IN_SNAPSHOT, dest);
+ // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
+ // over the top for anything the source has
+ ElementDefinition base = dest;
+ ElementDefinition derived = source;
+ derived.setUserData(UserDataNames.SNAPSHOT_DERIVATION_POINTER, base);
+ boolean isExtension = checkExtensionDoco(base);
+ List obligationProfileElements = new ArrayList<>();
+ for (StructureDefinition sd : obligationProfiles) {
+ ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
+ if (ed != null) {
+ obligationProfileElements.add(ed);
+ }
+ }
+
+ // hack workaround for problem in R5 snapshots
+ List elist = dest.getExtensionsByUrl(ExtensionDefinitions.EXT_TRANSLATABLE);
+ if (elist.size() == 2) {
+ dest.getExtension().remove(elist.get(1));
+ }
+ updateExtensionsFromDefinition(dest, source, derivedSrc, srcSD);
+
+ for (ElementDefinition ed : obligationProfileElements) {
+ for (Extension ext : ed.getExtension()) {
+ if (Utilities.existsInList(ext.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) {
+ dest.getExtension().add(new Extension(ExtensionDefinitions.EXT_OBLIGATION_CORE, ext.getValue().copy()));
+ }
+ }
+ }
+
+ // Before applying changes, apply them to what's in the profile
+ // but only if it's an extension or a resource
+
+ StructureDefinition profile = null;
+ boolean msg = true;
+ if (base.hasSliceName()) {
+ profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? findProfile(base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null;
+ }
+ if (profile == null && source.getTypeFirstRep().hasProfile()) {
+ String pu = source.getTypeFirstRep().getProfile().get(0).getValue();
+ profile = findProfile(pu, derivedSrc);
+ if (profile == null) {
+ if (makeXVer().matchingUrl(pu)) {
+ switch (xver.status(pu)) {
+ case BadVersion:
+ throw new FHIRException("Reference to invalid version in extension url " + pu);
+ case Invalid:
+ throw new FHIRException("Reference to invalid extension " + pu);
+ case Unknown:
+ throw new FHIRException("Reference to unknown extension " + pu);
+ case Valid:
+ profile = xver.getDefinition(pu);
+ generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName());
+ }
+ }
+
+ }
+ if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) {
+ // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the
+ // inherited attributes, and sometimes not
+ profile = null;
+ msg = false;
+ }
+ }
+ if (profile != null && (profile.getKind() == StructureDefinitionKind.RESOURCE || "Extension".equals(profile.getType()))) {
+ if (profile.getSnapshot().getElement().isEmpty()) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl()));
+ }
+ ElementDefinition e = profile.getSnapshot().getElement().get(0);
+ String webroot = profile.getUserString(UserDataNames.render_webroot);
+
+ if (e.hasDefinition()) {
+ base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
+ }
+ if (e.getBinding().hasDescription()) {
+ base.getBinding().setDescription(processRelativeUrls(e.getBinding().getDescription(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
+ }
+ base.setShort(e.getShort());
+ if (e.hasCommentElement())
+ base.setCommentElement(e.getCommentElement());
+ if (e.hasRequirementsElement())
+ base.setRequirementsElement(e.getRequirementsElement());
+ base.getAlias().clear();
+ base.getAlias().addAll(e.getAlias());
+ base.getMapping().clear();
+ base.getMapping().addAll(e.getMapping());
+ } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ExtensionDefinitions.EXT_PROFILE_ELEMENT)) {
+ // todo: should we change down the profile_element if there's one?
+ String type = source.getTypeFirstRep().getWorkingCode();
+ if (msg) {
+ if ("Extension".equals(type)) {
+ log.warn("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");
+ if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) {
+ throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue());
+ }
+ } else {
+ log.warn("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");
+ if (allowUnknownProfile == AllowUnknownProfile.NONE) {
+ throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue());
+ }
+ }
+ }
+ }
+ if (derived != null) {
+ if (derived.hasSliceName()) {
+ base.setSliceName(derived.getSliceName());
+ }
+
+ if (derived.hasShortElement()) {
+ if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
+ base.setShortElement(derived.getShortElement().copy());
+ else if (trimDifferential)
+ derived.setShortElement(null);
+ else if (derived.hasShortElement())
+ derived.getShortElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasDefinitionElement()) {
+ if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false)) {
+ base.setDefinitionElement(mergeMarkdown(derived.getDefinitionElement(), base.getDefinitionElement()));
+ } else if (trimDifferential)
+ derived.setDefinitionElement(null);
+ else if (derived.hasDefinitionElement())
+ derived.getDefinitionElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasCommentElement()) {
+ if (!Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
+ base.setCommentElement(mergeMarkdown(derived.getCommentElement(), base.getCommentElement()));
+ else if (trimDifferential)
+ base.setCommentElement(derived.getCommentElement().copy());
+ else if (derived.hasCommentElement())
+ derived.getCommentElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasLabelElement()) {
+ if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
+ base.setLabelElement(mergeStrings(derived.getLabelElement(), base.getLabelElement()));
+ else if (trimDifferential)
+ base.setLabelElement(derived.getLabelElement().copy());
+ else if (derived.hasLabelElement())
+ derived.getLabelElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasRequirementsElement()) {
+ if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
+ base.setRequirementsElement(mergeMarkdown(derived.getRequirementsElement(), base.getRequirementsElement()));
+ else if (trimDifferential)
+ base.setRequirementsElement(derived.getRequirementsElement().copy());
+ else if (derived.hasRequirementsElement())
+ derived.getRequirementsElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+ // sdf-9
+ if (derived.hasRequirements() && !base.getPath().contains("."))
+ derived.setRequirements(null);
+ if (base.hasRequirements() && !base.getPath().contains("."))
+ base.setRequirements(null);
+
+ if (derived.hasAlias()) {
+ if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
+ for (StringType s : derived.getAlias()) {
+ if (!base.hasAlias(s.getValue()))
+ base.getAlias().add(s.copy());
+ }
+ else if (trimDifferential)
+ derived.getAlias().clear();
+ else
+ for (StringType t : derived.getAlias())
+ t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasMinElement()) {
+ if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
+ if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR));
+ base.setMinElement(derived.getMinElement().copy());
+ } else if (trimDifferential)
+ derived.setMinElement(null);
+ else
+ derived.getMinElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasMaxElement()) {
+ if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
+ if (isLargerMax(derived.getMax(), base.getMax()))
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
+ base.setMaxElement(derived.getMaxElement().copy());
+ } else if (trimDifferential)
+ derived.setMaxElement(null);
+ else
+ derived.getMaxElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasFixed()) {
+ if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
+ base.setFixed(derived.getFixed().copy());
+ } else if (trimDifferential)
+ derived.setFixed(null);
+ else
+ derived.getFixed().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasPattern()) {
+ if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
+ base.setPattern(derived.getPattern().copy());
+ } else
+ if (trimDifferential)
+ derived.setPattern(null);
+ else
+ derived.getPattern().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ List toDelB = new ArrayList<>();
+ List toDelD = new ArrayList<>();
+ for (ElementDefinitionExampleComponent ex : derived.getExample()) {
+ boolean delete = ex.hasExtension(ExtensionDefinitions.EXT_ED_SUPPRESS);
+ if (delete && "$all".equals(ex.getLabel())) {
+ toDelB.addAll(base.getExample());
+ } else {
+ boolean found = false;
+ for (ElementDefinitionExampleComponent exS : base.getExample()) {
+ if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) {
+ if (delete) {
+ toDelB.add(exS);
+ } else {
+ found = true;
+ }
+ }
+ }
+ if (delete) {
+ toDelD.add(ex);
+ } else if (!found) {
+ base.addExample(ex.copy());
+ } else if (trimDifferential) {
+ derived.getExample().remove(ex);
+ } else {
+ ex.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+ }
+ }
+ base.getExample().removeAll(toDelB);
+ derived.getExample().removeAll(toDelD);
+
+ if (derived.hasMaxLengthElement()) {
+ if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
+ base.setMaxLengthElement(derived.getMaxLengthElement().copy());
+ else if (trimDifferential)
+ derived.setMaxLengthElement(null);
+ else
+ derived.getMaxLengthElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasMaxValue()) {
+ if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
+ base.setMaxValue(derived.getMaxValue().copy());
+ else if (trimDifferential)
+ derived.setMaxValue(null);
+ else
+ derived.getMaxValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasMinValue()) {
+ if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
+ base.setMinValue(derived.getMinValue().copy());
+ else if (trimDifferential)
+ derived.setMinValue(null);
+ else
+ derived.getMinValue().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ // todo: what to do about conditions?
+ // condition : id 0..*
+
+ boolean hasMustSupport = derived.hasMustSupportElement();
+ for (ElementDefinition ed : obligationProfileElements) {
+ hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
+ }
+ if (hasMustSupport) {
+ BooleanType mse = derived.getMustSupportElement().copy();
+ for (ElementDefinition ed : obligationProfileElements) {
+ mergeExtensions(mse, ed.getMustSupportElement());
+ if (ed.getMustSupport()) {
+ mse.setValue(true);
+ }
+ }
+ if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) {
+ if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport() && !fromSlicer) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
+ }
+ base.setMustSupportElement(mse);
+ } else if (trimDifferential)
+ derived.setMustSupportElement(null);
+ else
+ derived.getMustSupportElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ if (derived.hasMustHaveValueElement()) {
+ if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) {
+ if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue() && !fromSlicer) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-have-value = false] when [must-have-value = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
+ }
+ base.setMustHaveValueElement(derived.getMustHaveValueElement().copy());
+ } else if (trimDifferential)
+ derived.setMustHaveValueElement(null);
+ else
+ derived.getMustHaveValueElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+ if (derived.hasValueAlternatives()) {
+ if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false))
+ for (CanonicalType s : derived.getValueAlternatives()) {
+ if (!base.hasValueAlternatives(s.getValue()))
+ base.getValueAlternatives().add(s.copy());
+ }
+ else if (trimDifferential)
+ derived.getValueAlternatives().clear();
+ else
+ for (CanonicalType t : derived.getValueAlternatives())
+ t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
+ // but extensions can change isModifier
+ if (isExtension) {
+ if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))) {
+ base.setIsModifierElement(derived.getIsModifierElement().copy());
+ } else if (trimDifferential) {
+ derived.setIsModifierElement(null);
+ } else if (derived.hasIsModifierElement()) {
+ derived.getIsModifierElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+ if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false))) {
+ base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
+ } else if (trimDifferential) {
+ derived.setIsModifierReasonElement(null);
+ } else if (derived.hasIsModifierReasonElement()) {
+ derived.getIsModifierReasonElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+ if (base.getIsModifier() && !base.hasIsModifierReason()) {
+ // we get here because modifier extensions don't get a modifier reason from the type
+ base.setIsModifierReason("Modifier extensions are labelled as such because they modify the meaning or interpretation of the resource or element that contains them");
+ }
+ }
+
+ boolean hasBinding = derived.hasBinding();
+ for (ElementDefinition ed : obligationProfileElements) {
+ hasBinding = hasBinding || ed.hasBinding();
+ }
+ if (hasBinding) {
+ updateExtensionsFromDefinition(dest.getBinding(), source.getBinding(), derivedSrc, srcSD);
+ ElementDefinitionBindingComponent binding = derived.getBinding();
+ for (ElementDefinition ed : obligationProfileElements) {
+ for (Extension ext : ed.getBinding().getExtension()) {
+ if (ExtensionDefinitions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
+ String p = ext.getExtensionString("purpose");
+ if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
+ if (!binding.hasExtension(ext)) {
+ binding.getExtension().add(ext.copy());
+ }
+ }
+ }
+ }
+ for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
+ if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
+ if (binding.hasAdditional(ab)) {
+ binding.getAdditional().add(ab.copy());
+ }
+ }
+ }
+ }
+
+ if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
+ if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
+// throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
+ else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
+ ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), null, srcSD);
+ ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), null, derivedSrc);
+ if (baseVs == null) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
+ } else if (contextVs == null) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
+ } else {
+ ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
+ ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
+ if (expBase.getValueset() == null)
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
+ else if (expDerived.getValueset() == null)
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
+ else if (ExtensionUtilities.hasExtension(expBase.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY)) {
+ if (ExtensionUtilities.hasExtension(expDerived.getValueset().getExpansion(), ExtensionDefinitions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
+ } else {
+ boolean ok = true;
+ for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) {
+ ValidationResult vr = context.validateCode(new ValidationOptions(), cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs);
+ if (!vr.isOk()) {
+ ok = false;
+ break;
+ }
+ }
+ if (!ok) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
+ }
+ }
+ } else if (expBase.getValueset().getExpansion().getContains().size() == 1000 ||
+ expDerived.getValueset().getExpansion().getContains().size() == 1000) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
+ } else {
+ String msgs = checkSubset(expBase.getValueset(), expDerived.getValueset());
+ if (msgs != null) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet()+" because "+msgs, ValidationMessage.IssueSeverity.ERROR));
+ }
+ }
+ }
+ }
+ ElementDefinitionBindingComponent d = derived.getBinding();
+ ElementDefinitionBindingComponent nb = base.getBinding().copy();
+ if (!COPY_BINDING_EXTENSIONS) {
+ nb.getExtension().clear();
+ }
+ nb.setDescription(null);
+ for (Extension dex : d.getExtension()) {
+ nb.getExtension().add(markExtensionSource(dex.copy(), false, srcSD));
+ }
+ if (d.hasStrength()) {
+ nb.setStrength(d.getStrength());
+ }
+ if (d.hasDescription()) {
+ nb.setDescription(d.getDescription());
+ }
+ if (d.hasValueSet()) {
+ nb.setValueSet(d.getValueSet());
+ }
+ for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) {
+ ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab);
+ if (eab != null) {
+ mergeAdditionalBinding(eab, ab);
+ } else {
+ nb.getAdditional().add(ab);
+ }
+ }
+ base.setBinding(nb);
+ } else if (trimDifferential)
+ derived.setBinding(null);
+ else
+ derived.getBinding().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ } else if (base.hasBinding()) {
+ base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
+ for (Extension ex : base.getBinding().getExtension()) {
+ markExtensionSource(ex, false, srcSD);
+ }
+ }
+
+ if (derived.hasIsSummaryElement()) {
+ if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
+ if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints
+ throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue()));
+ base.setIsSummaryElement(derived.getIsSummaryElement().copy());
+ } else if (trimDifferential)
+ derived.setIsSummaryElement(null);
+ else
+ derived.getIsSummaryElement().setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ }
+
+ // this would make sense but blows up the process later, so we let it happen anyway, and sort out the business rule elsewhere
+ //if (!derived.hasContentReference() && !base.hasContentReference()) {
+
+ if (derived.hasType()) {
+ if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
+ if (base.hasType()) {
+ for (TypeRefComponent ts : derived.getType()) {
+ checkTypeDerivation(purl, srcSD, base, derived, ts, path, derivedSrc.getDerivation() == TypeDerivationRule.SPECIALIZATION);
+ }
+ }
+ base.getType().clear();
+ for (TypeRefComponent t : derived.getType()) {
+ TypeRefComponent tt = t.copy();
+ // tt.setUserData(DERIVATION_EQUALS, true);
+ base.getType().add(tt);
+ for (Extension ex : tt.getExtension()) {
+ markExtensionSource(ex, false, srcSD);
+ }
+ }
+ }
+ else if (trimDifferential)
+ derived.getType().clear();
+ else {
+ for (TypeRefComponent t : derived.getType()) {
+ t.setUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS, true);
+ for (Extension ex : t.getExtension()) {
+ markExtensionSource(ex, true, derivedSrc);
+ }
+ }
+ }
+ }
+
+ mappings.merge(derived, base); // note reversal of names to be correct in .merge()
+
+ // todo: constraints are cumulative. there is no replacing
+ for (ElementDefinitionConstraintComponent s : base.getConstraint()) {
+ s.setUserData(UserDataNames.SNAPSHOT_IS_DERIVED, true);
+ if (!s.hasSource()) {
+ s.setSource(srcSD.getUrl());
+ }
+ }
+ if (derived.hasConstraint()) {
+ for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
+ if (!base.hasConstraint(s.getKey())) {
+ ElementDefinitionConstraintComponent inv = s.copy();
+ base.getConstraint().add(inv);
+ }
+ }
+ }
+ for (IdType id : derived.getCondition()) {
+ if (!base.hasCondition(id)) {
+ base.getCondition().add(id);
+ }
+ }
+
+ // now, check that we still have a bindable type; if not, delete the binding - see task 8477
+ if (dest.hasBinding() && !hasBindableType(dest)) {
+ dest.setBinding(null);
+ }
+
+// // finally, we copy any extensions from source to dest
+ //no, we already did.
+// for (Extension ex : derived.getExtension()) {
+// !
+// StructureDefinition sd = findProfile(ex.getUrl(), derivedSrc);
+// if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) {
+// ToolingExtensions.removeExtension(dest, ex.getUrl());
+// }
+// dest.addExtension(ex.copy());
+// }
+ }
+ if (dest.hasFixed()) {
+ checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed");
+ }
+ if (dest.hasPattern()) {
+ checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern");
+ }
+ //updateURLs(url, webUrl, dest);
+ }
+
+ private MarkdownType mergeMarkdown(MarkdownType dest, MarkdownType source) {
+ MarkdownType mergedMarkdown = dest.copy();
+ if (!mergedMarkdown.hasValue() && source.hasValue()) {
+ mergedMarkdown.setValue(source.getValue());
+ } else if (mergedMarkdown.hasValue() && source.hasValue() && mergedMarkdown.getValue().startsWith("...")) {
+ mergedMarkdown.setValue(Utilities.appendDerivedTextToBase(source.getValue(), mergedMarkdown.getValue()));
+ }
+ for (Extension sourceExtension : source.getExtension()) {
+ Extension matchingExtension = findMatchingExtension(mergedMarkdown, sourceExtension);
+ if (matchingExtension == null) {
+ mergedMarkdown.addExtension(sourceExtension.copy());
+ } else {
+ matchingExtension.setValue(sourceExtension.getValue());
+ }
+ }
+ return mergedMarkdown;
+ }
+
+ private StringType mergeStrings(StringType dest, StringType source) {
+ StringType res = dest.copy();
+ if (!res.hasValue() && source.hasValue()) {
+ res.setValue(source.getValue());
+ } else if (res.hasValue() && source.hasValue() && res.getValue().startsWith("...")) {
+ res.setValue(Utilities.appendDerivedTextToBase(res.getValue(), source.getValue()));
+ }
+ for (Extension sourceExtension : source.getExtension()) {
+ Extension matchingExtension = findMatchingExtension(res, sourceExtension);
+ if (matchingExtension == null) {
+ res.addExtension(sourceExtension.copy());
+ } else {
+ matchingExtension.setValue(sourceExtension.getValue());
+ }
+ }
+ return res;
+ }
+
+ private Extension findMatchingExtension(Element res, Extension extensionToMatch) {
+ for (Extension elementExtension : res.getExtensionsByUrl(extensionToMatch.getUrl())) {
+ if (ExtensionDefinitions.EXT_TRANSLATION.equals(elementExtension.getUrl())) {
+ String slang = extensionToMatch.getExtensionString("lang");
+ String dlang = elementExtension.getExtensionString("lang");
+ if (Utilities.stringsEqual(slang, dlang)) {
+ return elementExtension;
+ }
+ } else {
+ return elementExtension;
+ }
+
+ }
+ return null;
+ }
+
+ private static Extension markExtensionSource(Extension extension, boolean overrideSource, StructureDefinition srcSD) {
+ if (overrideSource || !extension.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) {
+ extension.setUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE, srcSD);
+ }
+ if (Utilities.existsInList(extension.getUrl(), ExtensionDefinitions.EXT_OBLIGATION_CORE, ExtensionDefinitions.EXT_OBLIGATION_TOOLS)) {
+ Extension sub = extension.getExtensionByUrl(ExtensionDefinitions.EXT_OBLIGATION_SOURCE, ExtensionDefinitions.EXT_OBLIGATION_SOURCE_SHORT);
+ if (sub == null || overrideSource) {
+ ExtensionUtilities.setCanonicalExtension(extension, ExtensionDefinitions.EXT_OBLIGATION_SOURCE, srcSD.getVersionedUrl());
+ }
+ }
+ return extension;
+ }
+
+ private void updateExtensionsFromDefinition(Element dest, Element source, StructureDefinition destSD, StructureDefinition srcSD) {
+ dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl())));
+
+ for (Extension ext : source.getExtension()) {
+ if (!dest.hasExtension(ext.getUrl())) {
+ dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD));
+ } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) {
+ // do nothing
+ for (Extension ex2 : dest.getExtensionsByUrl(ext.getUrl())) {
+ markExtensionSource(ex2, true, destSD);
+ }
+ } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) {
+ dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue());
+ markExtensionSource(dest.getExtensionByUrl(ext.getUrl()), false, srcSD);
+ } else {
+ dest.getExtension().add(markExtensionSource(ext.copy(), false, srcSD));
+ }
+ }
+ }
+
+ private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) {
+ for (UsageContext t : source.getUsage()) {
+ if (!hasUsage(dest, t)) {
+ dest.addUsage(t);
+ }
+ }
+ if (source.getAny()) {
+ source.setAny(true);
+ }
+ if (source.hasShortDoco()) {
+ dest.setShortDoco(source.getShortDoco());
+ }
+ if (source.hasDocumentation()) {
+ dest.setDocumentation(source.getDocumentation());
+ }
+
+ }
+
+ private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) {
+ for (UsageContext t : dest.getUsage()) {
+ if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) {
+ for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) {
+ if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ private void mergeExtensions(Element tgt, Element src) {
+ tgt.getExtension().addAll(src.getExtension());
+ }
+
+ private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path, boolean specialising) {
+ boolean ok = false;
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ String t = ts.getWorkingCode();
+ String tDesc = ts.toString();
+ for (TypeRefComponent td : base.getType()) {;
+ boolean matchType = false;
+ String tt = td.getWorkingCode();
+ b.append(td.toString());
+ if (td.hasCode() && (tt.equals(t))) {
+ matchType = true;
+ }
+ if (!matchType) {
+ StructureDefinition sdt = context.fetchTypeDefinition(tt);
+ if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) {
+ StructureDefinition sdb = context.fetchTypeDefinition(t);
+ while (sdb != null && !matchType) {
+ matchType = sdb.getType().equals(sdt.getType());
+ sdb = findProfile(sdb.getBaseDefinition(), sdb);
+ }
+ }
+ }
+ // work around for old badly generated SDs
+// if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
+// matchType = true;
+// }
+// if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
+// matchType = true;
+// }
+ if (matchType) {
+ ts.copyNewExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support");
+ ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation", "http://hl7.org/fhir/tools/StructureDefinition/obligation");
+ if (ts.hasTargetProfile()) {
+ // check that any derived target has a reference chain back to one of the base target profiles
+ for (UriType u : ts.getTargetProfile()) {
+ String url = u.getValue();
+ boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td);
+ if (tgtOk) {
+ ok = true;
+ } else if (specialising) {
+ ok = true;
+ } else {
+ addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()), IssueSeverity.ERROR));
+ }
+ }
+ } else {
+ ok = true;
+ }
+ }
+ }
+ if (!ok && !isSuppressIgnorableExceptions()) {
+ throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl()));
+ }
+ }
+
+
+ private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) {
+ if (td.hasTargetProfile(url)) {
+ return true;
+ }
+ if (url != null && url.contains("|") && td.hasTargetProfile(url.substring(0, url.indexOf("|")))) {
+ return true;
+ }
+ StructureDefinition sd = context.fetchResourceRaw(StructureDefinition.class, url);
+ if (sd == null) {
+ addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+dPath+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
+ return true;
+ } else {
+ if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) {
+ return true;
+ }
+ for (Extension ext : sd.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_IMPOSE_PROFILE)) {
+ if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) {
+ boolean ok = false;
+ Set types = new HashSet<>();
+ if (dest.getPath().contains(".")) {
+ for (TypeRefComponent t : dest.getType()) {
+ if (t.hasCode()) {
+ types.add(t.getWorkingCode());
+ }
+ ok = ok || ft.equals(t.getWorkingCode());
+ }
+ } else {
+ types.add(sd.getType());
+ ok = ok || ft.equals(sd.getType());
+
+ }
+ if (!ok) {
+ addMessage(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The "+fieldName+" value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR));
+ }
+ }
+
+ private boolean hasBindableType(ElementDefinition ed) {
+ for (TypeRefComponent tr : ed.getType()) {
+ if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) {
+ return true;
+ }
+ StructureDefinition sd = context.fetchTypeDefinition(tr.getCode());
+ if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_BINDING_STYLE)) {
+ return true;
+ }
+ if (sd != null && sd.hasExtension(ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS) &&
+ "can-bind".equals(ExtensionUtilities.readStringExtension(sd, ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private boolean isLargerMax(String derived, String base) {
+ if ("*".equals(base)) {
+ return false;
+ }
+ if ("*".equals(derived)) {
+ return true;
+ }
+ return Integer.parseInt(derived) > Integer.parseInt(base);
+ }
+
+
+ private String checkSubset(ValueSet expBase, ValueSet expDerived) {
+ Set codes = new HashSet<>();
+ checkCodesInExpansion(codes, expDerived.getExpansion().getContains(), expBase.getExpansion());
+ if (codes.isEmpty()) {
+ return null;
+ } else {
+ return "The codes '"+CommaSeparatedStringBuilder.join(",", codes)+"' are not in the base valueset";
+ }
+ }
+
+
+ private void checkCodesInExpansion(Set codes, List contains, ValueSetExpansionComponent expansion) {
+ for (ValueSetExpansionContainsComponent cc : contains) {
+ if (!inExpansion(cc, expansion.getContains())) {
+ codes.add(cc.getCode());
+ }
+ checkCodesInExpansion(codes, cc.getContains(), expansion);
+ }
+ }
+
+
+ private boolean inExpansion(ValueSetExpansionContainsComponent cc, List contains) {
+ for (ValueSetExpansionContainsComponent cc1 : contains) {
+ if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) {
+ return true;
+ }
+ if (inExpansion(cc, cc1.getContains())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
+ for (ElementDefinition edb : base.getSnapshot().getElement()) {
+ if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
+ ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
+ if (edm == null) {
+ ElementDefinition edd = derived.getDifferential().addElement();
+ edd.setPath(edb.getPath());
+ edd.setMax("0");
+ } else if (edb.hasSlicing()) {
+ closeChildren(base, edb, derived, edm);
+ }
+ }
+ }
+ sortDifferential(base, derived, derived.getName(), new ArrayList(), false);
+ }
+
+ private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
+// String path = edb.getPath()+".";
+ int baseStart = base.getSnapshot().getElement().indexOf(edb);
+ int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
+ int diffStart = derived.getDifferential().getElement().indexOf(edm);
+ int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
+
+ for (int cBase = baseStart; cBase < baseEnd; cBase++) {
+ ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
+ if (isImmediateChild(edBase, edb)) {
+ ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
+ if (edMatch == null) {
+ ElementDefinition edd = derived.getDifferential().addElement();
+ edd.setPath(edBase.getPath());
+ edd.setMax("0");
+ } else {
+ closeChildren(base, edBase, derived, edMatch);
+ }
+ }
+ }
+ }
+
+ private int findEnd(List list, ElementDefinition ed, int cursor) {
+ String path = ed.getPath()+".";
+ while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) {
+ cursor++;
+ }
+ return cursor;
+ }
+
+
+ private ElementDefinition getMatchInDerived(ElementDefinition ed, List list) {
+ for (ElementDefinition t : list) {
+ if (t.getPath().equals(ed.getPath())) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ private ElementDefinition getMatchInDerived(ElementDefinition ed, List list, int start, int end) {
+ for (int i = start; i < end; i++) {
+ ElementDefinition t = list.get(i);
+ if (t.getPath().equals(ed.getPath())) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+
+ private boolean isImmediateChild(ElementDefinition ed) {
+ String p = ed.getPath();
+ if (!p.contains(".")) {
+ return false;
+ }
+ p = p.substring(p.indexOf(".")+1);
+ return !p.contains(".");
+ }
+
+ private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
+ String p = candidate.getPath();
+ if (!p.contains("."))
+ return false;
+ if (!p.startsWith(base.getPath()+"."))
+ return false;
+ p = p.substring(base.getPath().length()+1);
+ return !p.contains(".");
+ }
+
+
+
+ private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
+ int i = ed.getSnapshot().getElement().indexOf(c) + 1;
+ while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
+ if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
+ return ed.getSnapshot().getElement().get(i);
+ i++;
+ }
+ return null;
+ }
+
+
+
+ protected ElementDefinitionResolution getElementById(StructureDefinition source, List elements, String contentReference) {
+ if (!contentReference.startsWith("#") && contentReference.contains("#")) {
+ String url = contentReference.substring(0, contentReference.indexOf("#"));
+ contentReference = contentReference.substring(contentReference.indexOf("#"));
+ if (!url.equals(source.getUrl())){
+ source = findProfile(url, source);
+ if (source == null) {
+ return null;
+ }
+ elements = source.getSnapshot().getElement();
+ }
+ }
+ for (ElementDefinition ed : elements)
+ if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
+ return new ElementDefinitionResolution(source, ed);
+ return null;
+ }
+
+
+ public static String describeExtensionContext(StructureDefinition ext) {
+ StringBuilder b = new StringBuilder();
+ b.append("Use on ");
+ for (int i = 0; i < ext.getContext().size(); i++) {
+ StructureDefinitionContextComponent ec = ext.getContext().get(i);
+ if (i > 0)
+ b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
+ b.append(ec.getType().getDisplay());
+ b.append(" ");
+ b.append(ec.getExpression());
+ }
+ if (ext.hasContextInvariant()) {
+ b.append(", with Context Invariant = ");
+ boolean first = true;
+ for (StringType s : ext.getContextInvariant()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ b.append(""+s.getValue()+"");
+ }
+ }
+ return b.toString();
+ }
+
+
+
+
+// public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
+// boolean logicalModel, boolean allInvariants, Set outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException {
+// return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, "");
+// }
+
+
+
+
+
+ protected String tail(String path) {
+ if (path == null) {
+ return "";
+ } else if (path.contains("."))
+ return path.substring(path.lastIndexOf('.')+1);
+ else
+ return path;
+ }
+
+ private boolean isDataType(String value) {
+ StructureDefinition sd = context.fetchTypeDefinition(value);
+ if (sd == null) // might be running before all SDs are available
+ return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing",
+ "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
+ else
+ return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
+ }
+
+ private boolean isConstrainedDataType(String value) {
+ StructureDefinition sd = context.fetchTypeDefinition(value);
+ if (sd == null) // might be running before all SDs are available
+ return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
+ else
+ return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
+ }
+
+ private String baseType(String value) {
+ StructureDefinition sd = context.fetchTypeDefinition(value);
+ if (sd != null) // might be running before all SDs are available
+ return sd.getTypeName();
+ if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
+ return "Quantity";
+ throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value));
+ }
+
+
+ protected boolean isPrimitive(String value) {
+ StructureDefinition sd = context.fetchTypeDefinition(value);
+ if (sd == null) // might be running before all SDs are available
+ return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
+ else
+ return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
+ }
+
+// private static String listStructures(StructureDefinition p) {
+// StringBuilder b = new StringBuilder();
+// boolean first = true;
+// for (ProfileStructureComponent s : p.getStructure()) {
+// if (first)
+// first = false;
+// else
+// b.append(", ");
+// if (pkp != null && pkp.hasLinkFor(s.getType()))
+// b.append(""+s.getType()+"");
+// else
+// b.append(s.getType());
+// }
+// return b.toString();
+// }
+
+
+ public StructureDefinition getProfile(StructureDefinition source, String url) {
+ StructureDefinition profile = null;
+ String code = null;
+ if (url.startsWith("#")) {
+ profile = source;
+ code = url.substring(1);
+ } else if (context != null) {
+ String[] parts = url.split("\\#");
+ profile = findProfile(parts[0], source);
+ code = parts.length == 1 ? null : parts[1];
+ }
+ if (profile == null)
+ return null;
+ if (code == null)
+ return profile;
+ for (Resource r : profile.getContained()) {
+ if (r instanceof StructureDefinition && r.getId().equals(code))
+ return (StructureDefinition) r;
+ }
+ return null;
+ }
+
+
+
+ private static class ElementDefinitionHolder {
+ private String name;
+ private ElementDefinition self;
+ private int baseIndex = 0;
+ private List children;
+ private boolean placeHolder = false;
+
+ public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
+ super();
+ this.self = self;
+ this.name = self.getPath();
+ this.placeHolder = isPlaceholder;
+ children = new ArrayList();
+ }
+
+ public ElementDefinitionHolder(ElementDefinition self) {
+ this(self, false);
+ }
+
+ public ElementDefinition getSelf() {
+ return self;
+ }
+
+ public List getChildren() {
+ return children;
+ }
+
+ public int getBaseIndex() {
+ return baseIndex;
+ }
+
+ public void setBaseIndex(int baseIndex) {
+ this.baseIndex = baseIndex;
+ }
+
+ public boolean isPlaceHolder() {
+ return this.placeHolder;
+ }
+
+ @Override
+ public String toString() {
+ if (self.hasSliceName())
+ return self.getPath()+"("+self.getSliceName()+")";
+ else
+ return self.getPath();
+ }
+ }
+
+ private static class ElementDefinitionComparer implements Comparator {
+
+ private boolean inExtension;
+ private StructureDefinition src;
+ private List snapshot;
+ private int prefixLength;
+ private String base;
+ private String name;
+ private String baseName;
+ private Set errors = new HashSet();
+
+ public ElementDefinitionComparer(boolean inExtension, StructureDefinition src, List snapshot, String base, int prefixLength, String name, String baseName) {
+ this.inExtension = inExtension;
+ this.src = src;
+ this.snapshot = snapshot;
+ this.prefixLength = prefixLength;
+ this.base = base;
+ if (Utilities.isAbsoluteUrl(base)) {
+ this.base = urlTail(base);
+ }
+ this.name = name;
+ this.baseName = baseName;
+ }
+
+ @Override
+ public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
+ if (o1.getBaseIndex() == 0) {
+ o1.setBaseIndex(find(o1.getSelf().getPath(), true));
+ }
+ if (o2.getBaseIndex() == 0) {
+ o2.setBaseIndex(find(o2.getSelf().getPath(), true));
+ }
+ return o1.getBaseIndex() - o2.getBaseIndex();
+ }
+
+ private int find(String path, boolean mandatory) {
+ String op = path;
+ int lc = 0;
+ String actual = base+path.substring(prefixLength);
+ for (int i = 0; i < snapshot.size(); i++) {
+ String p = snapshot.get(i).getPath();
+ if (p.equals(actual)) {
+ return i;
+ }
+ if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
+ return i;
+ }
+ if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) {
+ return i;
+ }
+ if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
+ String ref = snapshot.get(i).getContentReference();
+ if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
+ actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
+ path = actual;
+ } else if (ref.startsWith("http:")) {
+ actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength);
+ path = actual;
+ } else {
+ // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
+ actual = base+(path.substring(0, path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
+ path = actual;
+ }
+
+ i = 0;
+ lc++;
+ if (lc > MAX_RECURSION_LIMIT)
+ throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
+ }
+ }
+ if (mandatory) {
+ if (prefixLength == 0)
+ errors.add("Differential contains path "+path+" which is not found in the base "+baseName);
+ else
+ errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName);
+ }
+ return 0;
+ }
+
+ public void checkForErrors(List errorList) {
+ if (errors.size() > 0) {
+// CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+// for (String s : errors)
+// b.append("StructureDefinition "+name+": "+s);
+// throw new DefinitionException(b.toString());
+ for (String s : errors)
+ if (s.startsWith("!"))
+ errorList.add("!StructureDefinition "+name+": "+s.substring(1));
+ else
+ errorList.add("StructureDefinition "+name+": "+s);
+ }
+ }
+ }
+
+
+ public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List errors, boolean errorIfChanges) throws FHIRException {
+ int index = 0;
+ for (ElementDefinition ed : diff.getDifferential().getElement()) {
+ ed.setUserData(UserDataNames.SNAPSHOT_SORT_ed_index, Integer.toString(index));
+ index++;
+ }
+ List original = new ArrayList<>();
+ original.addAll(diff.getDifferential().getElement());
+ final List diffList = diff.getDifferential().getElement();
+ int lastCount = diffList.size();
+ // first, we move the differential elements into a tree
+ if (diffList.isEmpty())
+ return;
+
+ ElementDefinitionHolder edh = null;
+ int i = 0;
+ if (diffList.get(0).getPath().contains(".")) {
+ String newPath = diffList.get(0).getPath().split("\\.")[0];
+ ElementDefinition e = new ElementDefinition(newPath);
+ edh = new ElementDefinitionHolder(e, true);
+ } else {
+ edh = new ElementDefinitionHolder(diffList.get(0));
+ i = 1;
+ }
+
+ boolean hasSlicing = false;
+ List paths = new ArrayList(); // in a differential, slicing may not be stated explicitly
+ for(ElementDefinition elt : diffList) {
+ if (elt.hasSlicing() || paths.contains(elt.getPath())) {
+ hasSlicing = true;
+ break;
+ }
+ paths.add(elt.getPath());
+ }
+
+ processElementsIntoTree(edh, i, diff.getDifferential().getElement());
+
+ // now, we sort the siblings throughout the tree
+ ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base, base.getSnapshot().getElement(), "", 0, name, base.getType());
+ sortElements(edh, cmp, errors);
+
+ // now, we serialise them back to a list
+ List newDiff = new ArrayList<>();
+ writeElements(edh, newDiff);
+ if (errorIfChanges) {
+ compareDiffs(original, newDiff, errors);
+ }
+ diffList.clear();
+ diffList.addAll(newDiff);
+
+ if (lastCount != diffList.size())
+ errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
+ }
+
+ private void compareDiffs(List diffList, List newDiff, List errors) {
+ if (diffList.size() != newDiff.size()) {
+ errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+
+ " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]");
+ } else {
+ for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) {
+ ElementDefinition e = diffList.get(i);
+ ElementDefinition n = newDiff.get(i);
+ if (!n.getPath().equals(e.getPath())) {
+ errors.add("The element "+(e.hasId() ? e.getId() : e.getPath())+" @diff["+e.getUserString(UserDataNames.SNAPSHOT_SORT_ed_index)+"] is out of order (and maybe others after it)");
+ return;
+ }
+ }
+ }
+ }
+
+
+ private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List list) {
+ String path = edh.getSelf().getPath();
+ final String prefix = path + ".";
+ while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
+ if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
+ String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
+ ElementDefinition e = new ElementDefinition(newPath);
+ ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
+ edh.getChildren().add(child);
+ i = processElementsIntoTree(child, i, list);
+
+ } else {
+ ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
+ edh.getChildren().add(child);
+ i = processElementsIntoTree(child, i+1, list);
+ }
+ }
+ return i;
+ }
+
+ private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List errors) throws FHIRException {
+ if (edh.getChildren().size() == 1)
+ // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
+ edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
+ else
+ Collections.sort(edh.getChildren(), cmp);
+ if (debug) {
+ cmp.checkForErrors(errors);
+ }
+
+ for (ElementDefinitionHolder child : edh.getChildren()) {
+ if (child.getChildren().size() > 0) {
+ ElementDefinitionComparer ccmp = getComparer(cmp, child);
+ if (ccmp != null) {
+ sortElements(child, ccmp, errors);
+ }
+ }
+ }
+ }
+
+ public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
+ // what we have to check for here is running off the base profile into a data type profile
+ ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
+ ElementDefinitionComparer ccmp;
+ if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
+ if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) {
+ if (child.getSelf().getType().get(0).getProfile().size() > 1) {
+ throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE));
+ }
+ StructureDefinition profile = findProfile(child.getSelf().getType().get(0).getProfile().get(0).getValue(), cmp.src);
+ while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
+ profile = findProfile(profile.getBaseDefinition(), profile);
+ }
+ if (profile==null) {
+ ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
+ } else {
+ ccmp = new ElementDefinitionComparer(true, profile, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present());
+ }
+ } else {
+ ccmp = new ElementDefinitionComparer(true, cmp.src, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name);
+ }
+ } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
+ StructureDefinition profile = findProfile(child.getSelf().getType().get(0).getProfile().get(0).getValue(), cmp.src);
+ if (profile==null)
+ ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
+ else
+ ccmp = new ElementDefinitionComparer(true, profile, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode(), cmp.src), child.getSelf().getPath().length(), cmp.name, profile.present());
+ } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
+ StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src);
+ if (profile==null)
+ throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
+ ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode(), cmp.src), child.getSelf().getPath().length(), cmp.name, profile.present());
+ } else if (child.getSelf().getType().size() == 1) {
+ StructureDefinition profile = findProfile(sdNs(child.getSelf().getType().get(0).getWorkingCode()), cmp.src);
+ if (profile==null)
+ throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
+ ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
+ } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
+ String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
+ String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
+ String p = childLastNode.substring(edLastNode.length()-3);
+ if (isPrimitive(Utilities.uncapitalize(p)))
+ p = Utilities.uncapitalize(p);
+ StructureDefinition sd = findProfile(sdNs(p), cmp.src);
+ if (sd == null)
+ throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
+ ccmp = new ElementDefinitionComparer(false, sd, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present());
+ } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
+ for (TypeRefComponent t: child.getSelf().getType()) {
+ if (!t.getWorkingCode().equals("Reference")) {
+ throw new Error(context.formatMessage(I18nConstants.CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
+ }
+ }
+ StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src);
+ ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
+ } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
+ for (TypeRefComponent t: ed.getType()) {
+ if (!t.getWorkingCode().equals("Reference")) {
+ throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
+ }
+ }
+ StructureDefinition profile = findProfile(sdNs(ed.getType().get(0).getWorkingCode()), cmp.src);
+ ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
+ } else {
+ // this is allowed if we only profile the extensions
+ StructureDefinition profile = findProfile(sdNs("Element"), cmp.src);
+ if (profile==null)
+ throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
+ ccmp = new ElementDefinitionComparer(false, profile, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present());
+// throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
+ }
+ return ccmp;
+ }
+
+ private String resolveType(String code, StructureDefinition src) {
+ if (Utilities.isAbsoluteUrl(code)) {
+ StructureDefinition sd = findProfile(code, src);
+ if (sd != null) {
+ return sd.getType();
+ }
+ }
+ return code;
+ }
+
+ private static String sdNs(String type) {
+ return sdNs(type, null);
+ }
+
+ public static String sdNs(String type, String overrideVersionNs) {
+ if (Utilities.isAbsoluteUrl(type))
+ return type;
+ else if (overrideVersionNs != null)
+ return Utilities.pathURL(overrideVersionNs, type);
+ else
+ return "http://hl7.org/fhir/StructureDefinition/"+type;
+ }
+
+
+ private boolean isAbstract(String code) {
+ return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
+ }
+
+
+ private void writeElements(ElementDefinitionHolder edh, List list) {
+ if (!edh.isPlaceHolder())
+ list.add(edh.getSelf());
+ for (ElementDefinitionHolder child : edh.getChildren()) {
+ writeElements(child, list);
+ }
+ }
+
+ /**
+ * First compare element by path then by name if same
+ */
+ private static class ElementNameCompare implements Comparator {
+
+ @Override
+ public int compare(ElementDefinition o1, ElementDefinition o2) {
+ String path1 = normalizePath(o1);
+ String path2 = normalizePath(o2);
+ int cmp = path1.compareTo(path2);
+ if (cmp == 0) {
+ String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
+ String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
+ cmp = name1.compareTo(name2);
+ }
+ return cmp;
+ }
+
+ private static String normalizePath(ElementDefinition e) {
+ if (!e.hasPath()) return "";
+ String path = e.getPath();
+ // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
+ // so strip off the [x] suffix when comparing the path names.
+ if (path.endsWith("[x]")) {
+ path = path.substring(0, path.length()-3);
+ }
+ return path;
+ }
+
+ }
+
+
+ // generate schematrons for the rules in a structure definition
+ public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
+ if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
+ throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR));
+ if (!structure.hasSnapshot())
+ throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
+
+ StructureDefinition base = findProfile(structure.getBaseDefinition(), structure);
+
+ if (base != null) {
+ SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
+
+ ElementDefinition ed = structure.getSnapshot().getElement().get(0);
+ generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
+ sch.dump();
+ }
+ }
+
+ public StructureDefinition findProfile(String url, Resource source) {
+ if (url == null) {
+ return null;
+ }
+ String u = url;
+ String v = null;
+ if (url.contains("|")) {
+ v = url.substring(u.indexOf("|")+1);
+ u = u.substring(0, u.indexOf("|"));
+ }
+ if (parameters != null) {
+ if (v == null) {
+ for (Parameters.ParametersParameterComponent p : parameters.getParameter()) {
+ if ("default-profile-version".equals(p.getName())) {
+ String s = p.getValue().primitiveValue();
+ if (s.startsWith(u + "|")) {
+ v = s.substring(s.indexOf("|") + 1);
+ }
+ }
+ }
+ }
+ for (Parameters.ParametersParameterComponent p : parameters.getParameter()) {
+ if ("force-profile-version".equals(p.getName())) {
+ String s = p.getValue().primitiveValue();
+ if (s.startsWith(u + "|")) {
+ v = s.substring(s.indexOf("|") + 1);
+ }
+ }
+ }
+ for (Parameters.ParametersParameterComponent p : parameters.getParameter()) {
+ if ("check-profile-version".equals(p.getName())) {
+ String s = p.getValue().primitiveValue();
+ if (s.startsWith(u + "|")) {
+ String vc = s.substring(s.indexOf("|") + 1);
+ if (!vc.equals(v)) {
+ throw new FHIRException("Profile resolves to " + v + " which does not match required profile version v" + vc);
+ }
+ }
+ }
+ }
+ }
+ // switch the extension pack in
+ if (source != null && source.getSourcePackage() != null && source.getSourcePackage().isCore()) {
+ source = null;
+ }
+ // matchbox patch #424 findProfile gets called by FHIRPathEngine
+ StructureDefinition sd = context.fetchResource(StructureDefinition.class, u, v, source);
+ if (sd == null) {
+ if (makeXVer().matchingUrl(u) && xver.status(u) == XVerExtensionStatus.Valid) {
+ sd = xver.getDefinition(u);
+ if (sd!=null) {
+ generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), context.getSpecUrl(), sd.getName());
+ }
+ }
+ }
+ return sd;
+ }
+
+ // generate a CSV representation of the structure definition
+ public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
+ if (!structure.hasSnapshot())
+ throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
+
+ CSVWriter csv = new CSVWriter(dest, structure, asXml);
+
+ for (ElementDefinition child : structure.getSnapshot().getElement()) {
+ csv.processElement(null, child);
+ }
+ csv.dump();
+ }
+
+ // generate a CSV representation of the structure definition
+ public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception {
+ if (!structure.hasSnapshot())
+ throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
+
+ for (ElementDefinition child : structure.getSnapshot().getElement()) {
+ csv.processElement(structure, child);
+ }
+ }
+
+
+ private class Slicer extends ElementDefinitionSlicingComponent {
+ String criteria = "";
+ String name = "";
+ boolean check;
+ public Slicer(boolean cantCheck) {
+ super();
+ this.check = cantCheck;
+ }
+ }
+
+ private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
+ // given a child in a structure, it's sliced. figure out the slicing xpath
+ if (child.getPath().endsWith(".extension")) {
+ ElementDefinition ued = getUrlFor(structure, child);
+ if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
+ return new Slicer(false);
+ else {
+ Slicer s = new Slicer(true);
+ String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
+ s.name = " with URL = '"+url+"'";
+ s.criteria = "[@url = '"+url+"']";
+ return s;
+ }
+ } else
+ return new Slicer(false);
+ }
+
+ private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
+ // generateForChild(txt, structure, child);
+ List children = getChildList(structure, ed);
+ String sliceName = null;
+ ElementDefinitionSlicingComponent slicing = null;
+ for (ElementDefinition child : children) {
+ String name = tail(child.getPath());
+ if (child.hasSlicing()) {
+ sliceName = name;
+ slicing = child.getSlicing();
+ } else if (!name.equals(sliceName))
+ slicing = null;
+
+ ElementDefinition based = getByPath(base, child.getPath());
+ boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
+ boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
+ Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
+ if (slicer.check) {
+ if (doMin || doMax) {
+ Section s = sch.section(xpath);
+ Rule r = s.rule(xpath);
+ if (doMin)
+ r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
+ if (doMax)
+ r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
+ }
+ }
+ }
+/// xpath has been removed
+// for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
+// if (inv.hasXpath()) {
+// Section s = sch.section(ed.getPath());
+// Rule r = s.rule(xpath);
+// r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
+// }
+// }
+ if (!ed.hasContentReference()) {
+ for (ElementDefinition child : children) {
+ String name = tail(child.getPath());
+ generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
+ }
+ }
+ }
+
+
+
+
+ private ElementDefinition getByPath(StructureDefinition base, String path) {
+ for (ElementDefinition ed : base.getSnapshot().getElement()) {
+ if (ed.getPath().equals(path))
+ return ed;
+ if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 && ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3)))
+ return ed;
+ }
+ return null;
+ }
+
+
+ public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException {
+ if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
+ if (!sd.hasDifferential())
+ sd.setDifferential(new StructureDefinitionDifferentialComponent());
+ generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd);
+ }
+ if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
+ if (!sd.hasSnapshot())
+ sd.setSnapshot(new StructureDefinitionSnapshotComponent());
+ generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd);
+ }
+ }
+
+
+ private boolean hasMissingIds(List list) {
+ for (ElementDefinition ed : list) {
+ if (!ed.hasId())
+ return true;
+ }
+ return false;
+ }
+
+ private class SliceList {
+
+ private Map slices = new HashMap<>();
+
+ public void seeElement(ElementDefinition ed) {
+ Iterator> iter = slices.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry entry = iter.next();
+ if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
+ iter.remove();
+ }
+
+ if (ed.hasSliceName())
+ slices.put(ed.getPath(), ed.getSliceName());
+ }
+
+ public String[] analyse(List paths) {
+ String s = paths.get(0);
+ String[] res = new String[paths.size()];
+ res[0] = null;
+ for (int i = 1; i < paths.size(); i++) {
+ s = s + "."+paths.get(i);
+ if (slices.containsKey(s))
+ res[i] = slices.get(s);
+ else
+ res[i] = null;
+ }
+ return res;
+ }
+
+ }
+
+ protected void generateIds(List list, String name, String type, StructureDefinition srcSD) throws DefinitionException {
+ if (list.isEmpty())
+ return;
+
+ Map idList = new HashMap();
+ Map replacedIds = new HashMap();
+
+ SliceList sliceInfo = new SliceList();
+ // first pass, update the element ids
+ for (ElementDefinition ed : list) {
+ generateIdForElement(list, name, type, srcSD, ed, sliceInfo, replacedIds, idList);
+ }
+ // second path - fix up any broken path based id references
+
+ }
+
+ private void generateIdForElement(List list, String name, String type, StructureDefinition srcSD, ElementDefinition ed, SliceList sliceInfo, Map replacedIds, Map idList) {
+ List paths = new ArrayList();
+ if (!ed.hasPath())
+ throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name));
+ sliceInfo.seeElement(ed);
+ String[] pl = ed.getPath().split("\\.");
+ for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
+ paths.add(pl[i]);
+ String slices[] = sliceInfo.analyse(paths);
+
+ StringBuilder b = new StringBuilder();
+ b.append(paths.get(0));
+ for (int i = 1; i < paths.size(); i++) {
+ b.append(".");
+ String s = paths.get(i);
+ String p = slices[i];
+ b.append(fixChars(s));
+ if (p != null) {
+ b.append(":");
+ b.append(p);
+ }
+ }
+ String bs = b.toString();
+ if (ed.hasId()) {
+ replacedIds.put(ed.getId(), ed.getPath());
+ }
+ ed.setId(bs);
+ if (idList.containsKey(bs)) {
+ addMessage(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, name +"."+bs, context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name), IssueSeverity.ERROR));
+ }
+ idList.put(bs, ed.getPath());
+ if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
+ String s = ed.getContentReference();
+ String typeURL = getUrlForSource(type, srcSD);
+ if (replacedIds.containsKey(s.substring(1))) {
+ ed.setContentReference(typeURL+"#"+ replacedIds.get(s.substring(1)));
+ } else {
+ ed.setContentReference(typeURL+s);
+ }
+ }
+ }
+
+
+ private String getUrlForSource(String type, StructureDefinition srcSD) {
+ if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) {
+ return srcSD.getUrl();
+ } else {
+ return "http://hl7.org/fhir/StructureDefinition/"+type;
+ }
+ }
+
+ private Object fixChars(String s) {
+ return s.replace("_", "-");
+ }
+
+
+// private String describeExtension(ElementDefinition ed) {
+// if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
+// return "";
+// return "$"+urlTail(ed.getTypeFirstRep().getProfile());
+// }
+//
+
+ private static String urlTail(String profile) {
+ return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
+ }
+//
+//
+// private String checkName(String name) {
+//// if (name.contains("."))
+////// throw new Exception("Illegal name "+name+": no '.'");
+//// if (name.contains(" "))
+//// throw new Exception("Illegal name "+name+": no spaces");
+// StringBuilder b = new StringBuilder();
+// for (char c : name.toCharArray()) {
+// if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
+// b.append(c);
+// }
+// return b.toString().toLowerCase();
+// }
+//
+//
+// private int charCount(String path, char t) {
+// int res = 0;
+// for (char ch : path.toCharArray()) {
+// if (ch == t)
+// res++;
+// }
+// return res;
+// }
+
+//
+//private void generateForChild(TextStreamWriter txt,
+// StructureDefinition structure, ElementDefinition child) {
+// // TODO Auto-generated method stub
+//
+//}
+
+ private interface ExampleValueAccessor {
+ DataType getExampleValue(ElementDefinition ed);
+ String getId();
+ }
+
+ private class BaseExampleValueAccessor implements ExampleValueAccessor {
+ @Override
+ public DataType getExampleValue(ElementDefinition ed) {
+ if (ed.hasFixed())
+ return ed.getFixed();
+ if (ed.hasExample())
+ return ed.getExample().get(0).getValue();
+ else
+ return null;
+ }
+
+ @Override
+ public String getId() {
+ return "-genexample";
+ }
+ }
+
+ private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
+ private String index;
+
+ public ExtendedExampleValueAccessor(String index) {
+ this.index = index;
+ }
+ @Override
+ public DataType getExampleValue(ElementDefinition ed) {
+ if (ed.hasFixed())
+ return ed.getFixed();
+ for (Extension ex : ed.getExtension()) {
+ String ndx = ExtensionUtilities.readStringExtension(ex, "index");
+ DataType value = ExtensionUtilities.getExtension(ex, "exValue").getValue();
+ if (index.equals(ndx) && value != null)
+ return value;
+ }
+ return null;
+ }
+ @Override
+ public String getId() {
+ return "-genexample-"+index;
+ }
+ }
+
+ public List generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
+ List examples = new ArrayList();
+ if (sd.hasSnapshot()) {
+ if (evenWhenNoExamples || hasAnyExampleValues(sd))
+ examples.add(generateExample(sd, new BaseExampleValueAccessor()));
+ for (int i = 1; i <= 50; i++) {
+ if (hasAnyExampleValues(sd, Integer.toString(i)))
+ examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
+ }
+ }
+ return examples;
+ }
+
+ private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
+ ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
+ org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
+ SourcedChildDefinitions children = getChildMap(profile, ed, true);
+ for (ElementDefinition child : children.getList()) {
+ if (child.getPath().endsWith(".id")) {
+ org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile));
+ id.setValue(profile.getId()+accessor.getId());
+ r.getChildren().add(id);
+ } else {
+ org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
+ if (e != null)
+ r.getChildren().add(e);
+ }
+ }
+ return r;
+ }
+
+ private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
+ DataType v = accessor.getExampleValue(ed);
+ if (v != null) {
+ return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
+ } else {
+ org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
+ boolean hasValue = false;
+ SourcedChildDefinitions children = getChildMap(profile, ed, true);
+ for (ElementDefinition child : children.getList()) {
+ if (!child.hasContentReference()) {
+ org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
+ if (e != null) {
+ hasValue = true;
+ res.getChildren().add(e);
+ }
+ }
+ }
+ if (hasValue)
+ return res;
+ else
+ return null;
+ }
+ }
+
+ private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
+ for (ElementDefinition ed : sd.getSnapshot().getElement())
+ for (Extension ex : ed.getExtension()) {
+ String ndx = ExtensionUtilities.readStringExtension(ex, "index");
+ Extension exv = ExtensionUtilities.getExtension(ex, "exValue");
+ if (exv != null) {
+ DataType value = exv.getValue();
+ if (index.equals(ndx) && value != null)
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private boolean hasAnyExampleValues(StructureDefinition sd) {
+ for (ElementDefinition ed : sd.getSnapshot().getElement())
+ if (ed.hasExample())
+ return true;
+ return false;
+ }
+
+
+ public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
+ sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
+
+ if (sd.hasBaseDefinition()) {
+ StructureDefinition base = findProfile(sd.getBaseDefinition(), sd);
+ if (base == null)
+ throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl()));
+ copyElements(sd, base.getSnapshot().getElement());
+ }
+ copyElements(sd, sd.getDifferential().getElement());
+ }
+
+
+ private void copyElements(StructureDefinition sd, List list) {
+ for (ElementDefinition ed : list) {
+ if (ed.getPath().contains(".")) {
+ ElementDefinition n = ed.copy();
+ n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
+ sd.getSnapshot().addElement(n);
+ }
+ }
+ }
+
+
+ public void cleanUpDifferential(StructureDefinition sd) {
+ if (sd.getDifferential().getElement().size() > 1)
+ cleanUpDifferential(sd, 1);
+ }
+
+ private void cleanUpDifferential(StructureDefinition sd, int start) {
+ int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
+ int c = start;
+ int len = sd.getDifferential().getElement().size();
+ HashSet paths = new HashSet();
+ while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
+ ElementDefinition ed = sd.getDifferential().getElement().get(c);
+ if (!paths.contains(ed.getPath())) {
+ paths.add(ed.getPath());
+ int ic = c+1;
+ while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level)
+ ic++;
+ ElementDefinition slicer = null;
+ List slices = new ArrayList();
+ slices.add(ed);
+ while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
+ ElementDefinition edi = sd.getDifferential().getElement().get(ic);
+ if (ed.getPath().equals(edi.getPath())) {
+ if (slicer == null) {
+ slicer = new ElementDefinition();
+ slicer.setPath(edi.getPath());
+ slicer.getSlicing().setRules(SlicingRules.OPEN);
+ sd.getDifferential().getElement().add(c, slicer);
+ c++;
+ ic++;
+ }
+ slices.add(edi);
+ }
+ ic++;
+ while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level)
+ ic++;
+ }
+ // now we're at the end, we're going to figure out the slicing discriminator
+ if (slicer != null)
+ determineSlicing(slicer, slices);
+ }
+ c++;
+ if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
+ cleanUpDifferential(sd, c);
+ c++;
+ while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level)
+ c++;
+ }
+ }
+ }
+
+
+ private void determineSlicing(ElementDefinition slicer, List slices) {
+ // first, name them
+ int i = 0;
+ for (ElementDefinition ed : slices) {
+ if (ed.hasUserData(UserDataNames.SNAPSHOT_slice_name)) {
+ ed.setSliceName(ed.getUserString(UserDataNames.SNAPSHOT_slice_name));
+ } else {
+ i++;
+ ed.setSliceName("slice-"+Integer.toString(i));
+ }
+ }
+ // now, the hard bit, how are they differentiated?
+ // right now, we hard code this...
+ if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
+ slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
+ else if (slicer.getPath().equals("DiagnosticReport.result"))
+ slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
+ else if (slicer.getPath().equals("Observation.related"))
+ slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
+ else if (slicer.getPath().equals("Bundle.entry"))
+ slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
+ else
+ throw new Error("No slicing for "+slicer.getPath());
+ }
+
+
+ public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
+ if (discriminator.endsWith("@pattern"))
+ return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9));
+ if (discriminator.endsWith("@profile"))
+ return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9));
+ if (discriminator.endsWith("@type"))
+ return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
+ if (discriminator.endsWith("@exists"))
+ return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8));
+ if (isExists)
+ return makeDiscriminator(DiscriminatorType.EXISTS, discriminator);
+ return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
+ }
+
+
+ private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
+ return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
+ }
+
+
+ public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
+ switch (t.getType()) {
+ case PROFILE: return t.getPath()+"/@profile";
+ case PATTERN: return t.getPath()+"/@pattern";
+ case TYPE: return t.getPath()+"/@type";
+ case VALUE: return t.getPath();
+ case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
+ default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");
+ }
+ }
+
+
+ public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
+ String epath = url.substring(54);
+ if (!epath.contains("."))
+ return null;
+ String type = epath.substring(0, epath.indexOf("."));
+ StructureDefinition sd = context.fetchTypeDefinition(type);
+ if (sd == null)
+ return null;
+ ElementDefinition ed = null;
+ for (ElementDefinition t : sd.getSnapshot().getElement()) {
+ if (t.getPath().equals(epath)) {
+ ed = t;
+ break;
+ }
+ }
+ if (ed == null)
+ return null;
+ if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
+ return null;
+ } else {
+ StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
+ StructureDefinition ext = template.copy();
+ ext.setUrl(url);
+ ext.setId("extension-"+epath);
+ ext.setName("Extension-"+epath);
+ ext.setTitle("Extension for r4 "+epath);
+ ext.setStatus(sd.getStatus());
+ ext.setDate(sd.getDate());
+ ext.getContact().clear();
+ ext.getContact().addAll(sd.getContact());
+ ext.setFhirVersion(sd.getFhirVersion());
+ ext.setDescription(ed.getDefinition());
+ ext.getContext().clear();
+ ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
+ ext.getDifferential().getElement().clear();
+ ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
+ ext.getSnapshot().getElement().set(4, ed.copy());
+ ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
+ return ext;
+ }
+
+ }
+
+
+ public boolean isThrowException() {
+ return wantThrowExceptions;
+ }
+
+
+ public void setThrowException(boolean exception) {
+ this.wantThrowExceptions = exception;
+ }
+
+
+ public ValidationOptions getTerminologyServiceOptions() {
+ return terminologyServiceOptions;
+ }
+
+
+ public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
+ this.terminologyServiceOptions = terminologyServiceOptions;
+ }
+
+
+ public boolean isNewSlicingProcessing() {
+ return newSlicingProcessing;
+ }
+
+
+ public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) {
+ this.newSlicingProcessing = newSlicingProcessing;
+ return this;
+ }
+
+
+ public boolean isDebug() {
+ return debug;
+ }
+
+
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+
+ public String getDefWebRoot() {
+ return defWebRoot;
+ }
+
+
+ public void setDefWebRoot(String defWebRoot) {
+ this.defWebRoot = defWebRoot;
+ if (!this.defWebRoot.endsWith("/"))
+ this.defWebRoot = this.defWebRoot + '/';
+ }
+
+
+ public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) {
+ return makeBaseDefinition(fhirVersion.toCode());
+ }
+ public static StructureDefinition makeBaseDefinition(String fhirVersion) {
+ StructureDefinition base = new StructureDefinition();
+ base.setId("Base");
+ base.setUrl("http://hl7.org/fhir/StructureDefinition/Base");
+ base.setVersion(fhirVersion);
+ base.setName("Base");
+ base.setStatus(PublicationStatus.ACTIVE);
+ base.setDate(new Date());
+ base.setFhirVersion(FHIRVersion.fromCode(fhirVersion));
+ base.setKind(StructureDefinitionKind.COMPLEXTYPE);
+ base.setAbstract(true);
+ base.setType("Base");
+ base.setWebPath("http://build.fhir.org/types.html#Base");
+ ElementDefinition e = base.getSnapshot().getElementFirstRep();
+ e.setId("Base");
+ e.setPath("Base");
+ e.setMin(0);
+ e.setMax("*");
+ e.getBase().setPath("Base");
+ e.getBase().setMin(0);
+ e.getBase().setMax("*");
+ e.setIsModifier(false);
+ e = base.getDifferential().getElementFirstRep();
+ e.setId("Base");
+ e.setPath("Base");
+ e.setMin(0);
+ e.setMax("*");
+ return base;
+ }
+
+ public XVerExtensionManager getXver() {
+ return xver;
+ }
+
+ public ProfileUtilities setXver(XVerExtensionManager xver) {
+ this.xver = xver;
+ return this;
+ }
+
+
+ private List readChoices(ElementDefinition ed, List children) {
+ List result = new ArrayList<>();
+ for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
+ ElementChoiceGroup grp = processConstraint(children, c);
+ if (grp != null) {
+ result.add(grp);
+ }
+ }
+ return result;
+ }
+
+ public ElementChoiceGroup processConstraint(List children, ElementDefinitionConstraintComponent c) {
+ if (!c.hasExpression()) {
+ return null;
+ }
+ ExpressionNode expr = null;
+ try {
+ expr = fpe.parse(c.getExpression());
+ } catch (Exception e) {
+ return null;
+ }
+ if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) {
+ return null;
+ }
+ ExpressionNode n1 = expr.getGroup();
+ ExpressionNode n2 = expr.getOpNext();
+ if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) {
+ return null;
+ }
+ ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals);
+ while (n1 != null) {
+ if (n1.getKind() != Kind.Name || n1.getInner() != null) {
+ return null;
+ }
+ grp.elements.add(n1.getName());
+ if (n1.getOperation() == null || n1.getOperation() == Operation.Union) {
+ n1 = n1.getOpNext();
+ } else {
+ return null;
+ }
+ }
+ int total = 0;
+ for (String n : grp.elements) {
+ boolean found = false;
+ for (ElementDefinition child : children) {
+ String name = tail(child.getPath());
+ if (n.equals(name)) {
+ found = true;
+ if (!"0".equals(child.getMax())) {
+ total++;
+ }
+ }
+ }
+ if (!found) {
+ return null;
+ }
+ }
+ if (total <= 1) {
+ return null;
+ }
+ return grp;
+ }
+
+ public Set getMasterSourceFileNames() {
+ return masterSourceFileNames;
+ }
+
+ public void setMasterSourceFileNames(Set masterSourceFileNames) {
+ this.masterSourceFileNames = masterSourceFileNames;
+ }
+
+
+ public Set getLocalFileNames() {
+ return localFileNames;
+ }
+
+ public void setLocalFileNames(Set localFileNames) {
+ this.localFileNames = localFileNames;
+ }
+
+ public ProfileKnowledgeProvider getPkp() {
+ return pkp;
+ }
+
+
+ public static final String UD_ERROR_STATUS = "error-status";
+ public static final int STATUS_OK = 0;
+ public static final int STATUS_HINT = 1;
+ public static final int STATUS_WARNING = 2;
+ public static final int STATUS_ERROR = 3;
+ public static final int STATUS_FATAL = 4;
+ private static final String ROW_COLOR_ERROR = "#ffcccc";
+ private static final String ROW_COLOR_FATAL = "#ff9999";
+ private static final String ROW_COLOR_WARNING = "#ffebcc";
+ private static final String ROW_COLOR_HINT = "#ebf5ff";
+ private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
+
+ public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
+ switch (element.getUserInt(UD_ERROR_STATUS)) {
+ case STATUS_HINT: return ROW_COLOR_HINT;
+ case STATUS_WARNING: return ROW_COLOR_WARNING;
+ case STATUS_ERROR: return ROW_COLOR_ERROR;
+ case STATUS_FATAL: return ROW_COLOR_FATAL;
+ }
+ if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
+ return null; // ROW_COLOR_NOT_MUST_SUPPORT;
+ else
+ return null;
+ }
+
+ public static boolean isExtensionDefinition(StructureDefinition sd) {
+ return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension");
+ }
+
+ public AllowUnknownProfile getAllowUnknownProfile() {
+ return allowUnknownProfile;
+ }
+
+ public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) {
+ this.allowUnknownProfile = allowUnknownProfile;
+ }
+
+ public static boolean isSimpleExtension(StructureDefinition sd) {
+ if (!isExtensionDefinition(sd)) {
+ return false;
+ }
+ ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
+ return value != null && !value.isProhibited();
+ }
+
+ public static boolean isComplexExtension(StructureDefinition sd) {
+ if (!isExtensionDefinition(sd)) {
+ return false;
+ }
+ ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
+ return value == null || value.isProhibited();
+ }
+
+ public static boolean isModifierExtension(StructureDefinition sd) {
+ ElementDefinition defn = sd.getSnapshot().hasElement() ? sd.getSnapshot().getElementByPath("Extension") : sd.getDifferential().getElementByPath("Extension");
+ return defn != null && defn.getIsModifier();
+ }
+
+ public boolean isForPublication() {
+ return forPublication;
+ }
+
+ public void setForPublication(boolean forPublication) {
+ this.forPublication = forPublication;
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ public static boolean isResourceBoundary(ElementDefinition ed) {
+ return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode());
+ }
+
+ public static boolean isSuppressIgnorableExceptions() {
+ return suppressIgnorableExceptions;
+ }
+
+ public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) {
+ ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions;
+ }
+
+ public void setMessages(List messages) {
+ if (messages != null) {
+ this.messages = messages;
+ wantThrowExceptions = false;
+ }
+ }
+
+ private Map> propertyCache = new HashMap<>();
+
+ public Map> getCachedPropertyList() {
+ return propertyCache;
+ }
+
+ public void checkExtensions(ElementDefinition outcome) {
+ outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
+ if (outcome.hasBinding()) {
+ outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
+ }
+
+ }
+
+ public static void markExtensions(ElementDefinition ed, boolean overrideSource, StructureDefinition src) {
+ for (Extension ex : ed.getExtension()) {
+ markExtensionSource(ex, overrideSource, src);
+ }
+ for (Extension ex : ed.getBinding().getExtension()) {
+ markExtensionSource(ex, overrideSource, src);
+ }
+ for (TypeRefComponent t : ed.getType()) {
+ for (Extension ex : t.getExtension()) {
+ markExtensionSource(ex, overrideSource, src);
+ }
+ }
+ }
+
+ public static boolean hasObligations(StructureDefinition sd) {
+ if (sd.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) {
+ return true;
+ }
+ for (ElementDefinition ed : sd.getSnapshot().getElement()) {
+ if (ed.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) {
+ return true;
+ }
+ for (TypeRefComponent tr : ed.getType()) {
+ if (tr.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_CORE)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public List getSuppressedMappings() {
+ return suppressedMappings;
+ }
+
+ public void setSuppressedMappings(List suppressedMappings) {
+ this.suppressedMappings = suppressedMappings;
+ }
+
+ public static String getCSUrl(StructureDefinition profile) {
+ if (profile.hasExtension(ExtensionDefinitions.EXT_SD_CS_URL)) {
+ return ExtensionUtilities.readStringExtension(profile, ExtensionDefinitions.EXT_SD_CS_URL);
+ } else {
+ return profile.getUrl()+"?codesystem";
+ }
+ }
+
+ public static String getUrlFromCSUrl(String url) {
+ if (url == null) {
+ return null;
+ }
+ if (url.endsWith("?codesystem")) {
+ return url.replace("?codesystem", "");
+ } else {
+ return null;
+ }
+ }
+
+ public FHIRPathEngine getFpe() {
+ return fpe;
+ }
+
+}
diff --git a/matchbox-engine/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/matchbox-engine/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java
index 7cab0666ab8..02e36baf52b 100644
--- a/matchbox-engine/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java
+++ b/matchbox-engine/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java
@@ -48,8 +48,10 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
import lombok.Getter;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nonnull;
@@ -124,15 +126,11 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import org.hl7.fhir.r5.terminologies.client.TerminologyClientR5;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
-import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
-import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache;
+import org.hl7.fhir.r5.terminologies.utilities.*;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.CacheToken;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedCodeSystem;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
-import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext.TerminologyServiceProtectionException;
-import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
-import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.terminologies.validation.VSCheckerException;
import org.hl7.fhir.r5.terminologies.validation.ValueSetValidator;
import org.hl7.fhir.r5.utils.PackageHackerR5;
@@ -156,9 +154,10 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
@Slf4j
@MarkedToMoveToAdjunctPackage
-public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext {
+public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext, IWorkerContextManager, IOIDServices {
private static boolean allowedToIterateTerminologyResources;
+
public interface IByteProvider {
byte[] bytes() throws IOException;
}
@@ -288,7 +287,7 @@ public int compare(T arg1, T arg2) {
}
}
- private Object lock = new Object(); // used as a lock for the data that follows
+ private final Object lock = new Object(); // used as a lock for the data that follows
protected String version; // although the internal resources are all R5, the version of FHIR they describe may not be
private boolean minimalMemory = false;
@@ -322,11 +321,13 @@ public int compare(T arg1, T arg2) {
private UcumService ucumService;
protected Map binaries = new HashMap();
- protected Map> oidCacheManual = new HashMap<>();
+ protected Map> oidCacheManual = new HashMap<>();
protected List oidSources = new ArrayList<>();
protected Map> validationCache = new HashMap>();
protected String name;
+ @Setter
+ @Getter
private boolean allowLoadingDuplicates;
private final Set codeSystemsUsed = new HashSet<>();
@@ -336,7 +337,7 @@ public int compare(T arg1, T arg2) {
private int expandCodesLimit = 1000;
protected org.hl7.fhir.r5.context.ILoggingService logger = new Slf4JLoggingService(log);
protected final TerminologyClientManager terminologyClientManager = new TerminologyClientManager(new TerminologyClientR5.TerminologyClientR5Factory(), UUID.randomUUID().toString(), logger);
- protected Parameters expParameters;
+ protected AtomicReference expansionParameters = new AtomicReference<>(null);
private Map packages = new HashMap<>();
@Getter
@@ -347,9 +348,6 @@ public int compare(T arg1, T arg2) {
protected String userAgent;
protected ContextUtilities cutils;
private List suppressedMappings;
-
- // matchbox patch https://github.com/ahdis/matchbox/issues/425
- private Locale locale;
protected BaseWorkerContext() throws FileNotFoundException, IOException, FHIRException {
setValidationMessageLanguage(getLocale());
@@ -422,7 +420,7 @@ protected void copy(BaseWorkerContext other) {
txCache = other.txCache; // no copy. for now?
expandCodesLimit = other.expandCodesLimit;
logger = other.logger;
- expParameters = other.expParameters != null ? other.expParameters.copy() : null;
+ expansionParameters = other.expansionParameters != null ? new AtomicReference<>(other.copyExpansionParametersWithUserData()) : null;
version = other.version;
supportedCodeSystems.putAll(other.supportedCodeSystems);
unsupportedCodeSystems.addAll(other.unsupportedCodeSystems);
@@ -439,8 +437,7 @@ protected void copy(BaseWorkerContext other) {
cachingAllowed = other.cachingAllowed;
suppressedMappings = other.suppressedMappings;
cutils.setSuppressedMappings(other.suppressedMappings);
- locale = other.locale;
- }
+ }
}
@@ -543,7 +540,7 @@ public void registerResourceFromPackage(CanonicalResourceProxy r, PackageInforma
}
public void cacheResourceFromPackage(Resource r, PackageInformation packageInfo) throws FHIRException {
-
+
synchronized (lock) {
if (packageInfo != null) {
packages.put(packageInfo.getVID(), packageInfo);
@@ -603,7 +600,7 @@ public void cacheResourceFromPackage(Resource r, PackageInformation packageInfo)
if (!oidCacheManual.containsKey(s)) {
oidCacheManual.put(s, new HashSet<>());
}
- oidCacheManual.get(s).add(new OIDDefinition(r.fhirType(), s, url, ((CanonicalResource) r).getVersion(), null, null));
+ oidCacheManual.get(s).add(new IOIDServices.OIDDefinition(r.fhirType(), s, url, ((CanonicalResource) r).getVersion(), null, null));
}
}
}
@@ -802,11 +799,6 @@ protected void seeMetadataResource(T r, Map supplements = codeSystems.getSupplements(cs);
if (supplements.size() > 0) {
@@ -886,16 +869,12 @@ public CodeSystem fetchSupplementedCodeSystem(String system, String version) {
return cs;
}
- @Override
- public SystemSupportInformation getTxSupportInfo(String system) throws TerminologyServiceException {
- return getTxSupportInfo(system, null);
- }
@Override
public SystemSupportInformation getTxSupportInfo(String system, String version) throws TerminologyServiceException {
synchronized (lock) {
String vurl = CanonicalType.urlWithVersion(system, version);
if (codeSystems.has(vurl) && codeSystems.get(vurl).getContent() != CodeSystemContentMode.NOTPRESENT) {
- return new SystemSupportInformation(true, "internal", TerminologyClientContext.LATEST_VERSION);
+ return new SystemSupportInformation(true, "internal", TerminologyClientContext.LATEST_VERSION, null);
} else if (supportedCodeSystems.containsKey(vurl)) {
return supportedCodeSystems.get(vurl);
} else if (system.startsWith("http://example.org") || system.startsWith("http://acme.com") || system.startsWith("http://hl7.org/fhir/valueset-") || system.startsWith("urn:oid:")) {
@@ -907,7 +886,7 @@ public SystemSupportInformation getTxSupportInfo(String system, String version)
if (terminologyClientManager != null) {
try {
TerminologyClientContext client = terminologyClientManager.chooseServer(null, Set.of(vurl), false);
- supportedCodeSystems.put(vurl, new SystemSupportInformation(client.supportsSystem(vurl), client.getAddress(), client.getTxTestVersion()));
+ supportedCodeSystems.put(vurl, new SystemSupportInformation(client.supportsSystem(vurl), client.getAddress(), client.getTxTestVersion(), client.supportsSystem(vurl) ? null : "The server does not support this code system"));
} catch (Exception e) {
if (canRunWithoutTerminology) {
noTerminologyServer = true;
@@ -932,24 +911,6 @@ public SystemSupportInformation getTxSupportInfo(String system, String version)
}
}
- @Override
- public boolean supportsSystem(String system) throws TerminologyServiceException {
- SystemSupportInformation si = getTxSupportInfo(system);
- return si.isSupported();
- }
-
- @Override
- public boolean supportsSystem(String system, FhirPublication fhirVersion) throws TerminologyServiceException {
- SystemSupportInformation si = getTxSupportInfo(system);
- return si.isSupported();
- }
-
- public boolean isServerSideSystem(String url) {
- boolean check = supportsSystem(url);
- return check && supportedCodeSystems.containsKey(url);
- }
-
-
protected void txLog(String msg) {
if (tlogging ) {
logger.logDebugMessage(LogCategory.TX, msg);
@@ -969,22 +930,20 @@ public void setExpandCodesLimit(int expandCodesLimit) {
@Override
public ValueSetExpansionOutcome expandVS(Resource src, ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heirarchical) throws FHIRException {
ValueSet vs = null;
- vs = fetchResource(ValueSet.class, binding.getValueSet(), src);
+ vs = fetchResource(ValueSet.class, binding.getValueSet(), null, src);
if (vs == null) {
throw new FHIRException(formatMessage(I18nConstants.UNABLE_TO_RESOLVE_VALUE_SET_, binding.getValueSet()));
}
return expandVS(vs, cacheOk, heirarchical);
}
-
- @Override
- public ValueSetExpansionOutcome expandVS(ITerminologyOperationDetails opCtxt, ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException {
+ public ValueSetExpansionOutcome expandVS(ValueSetProcessBase.TerminologyOperationDetails opCtxt, ConceptSetComponent inc, boolean hierarchical, boolean noInactive) throws TerminologyServiceException {
ValueSet vs = new ValueSet();
vs.setStatus(PublicationStatus.ACTIVE);
vs.setCompose(new ValueSetComposeComponent());
vs.getCompose().setInactive(!noInactive);
vs.getCompose().getInclude().add(inc);
- CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
+ CacheToken cacheToken = txCache.generateExpandToken(vs, new ExpansionOptions().withHierarchical(hierarchical));
ValueSetExpansionOutcome res;
res = txCache.getExpansion(cacheToken);
if (res != null) {
@@ -1031,44 +990,43 @@ public ValueSetExpansionOutcome expandVS(ITerminologyOperationDetails opCtxt, Co
@Override
public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical) {
- if (expParameters == null)
+ if (expansionParameters.get() == null)
throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
- Parameters p = expParameters.copy();
- return expandVS(vs, cacheOk, heirarchical, false, p);
+ return expandVS(vs, cacheOk, heirarchical, false, getExpansionParameters());
}
@Override
public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, int count) {
- if (expParameters == null)
+ if (expansionParameters.get() == null)
throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
- Parameters p = expParameters.copy();
+ Parameters p = getExpansionParameters();
p.addParameter("count", count);
return expandVS(vs, cacheOk, heirarchical, false, p);
}
@Override
- public ValueSetExpansionOutcome expandVS(String url, boolean cacheOk, boolean hierarchical, int count) {
- if (expParameters == null)
+ public ValueSetExpansionOutcome expandVS(ExpansionOptions options, String url) {
+ if (expansionParameters.get() == null)
throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
if (noTerminologyServer) {
return new ValueSetExpansionOutcome(formatMessage(I18nConstants.ERROR_EXPANDING_VALUESET_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null, false);
}
- Parameters p = expParameters.copy();
- p.addParameter("count", count);
+ Parameters p = getExpansionParameters();
+ p.addParameter("count", options.getMaxCount());
p.addParameter("url", new UriType(url));
p.setParameter("_limit",new IntegerType("10000"));
p.setParameter("_incomplete", new BooleanType("true"));
- CacheToken cacheToken = txCache.generateExpandToken(url, hierarchical);
+ CacheToken cacheToken = txCache.generateExpandToken(url, options);
ValueSetExpansionOutcome res;
- if (cacheOk) {
+ if (options.isCacheOk()) {
res = txCache.getExpansion(cacheToken);
if (res != null) {
return res;
}
}
- p.setParameter("excludeNested", !hierarchical);
+ p.setParameter("excludeNested", !options.isHierarchical());
List allErrors = new ArrayList<>();
p.addParameter().setName("cache-id").setValue(new IdType(terminologyClientManager.getCacheId()));
@@ -1099,19 +1057,15 @@ public ValueSetExpansionOutcome expandVS(String url, boolean cacheOk, boolean hi
return res;
}
- @Override
- public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean heirarchical, boolean incompleteOk) {
- if (expParameters == null)
- throw new Error(formatMessage(I18nConstants.NO_EXPANSION_PARAMETERS_PROVIDED));
- Parameters p = expParameters.copy();
- return expandVS(vs, cacheOk, heirarchical, incompleteOk, p);
+ public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn) {
+ return expandVS(new ExpansionOptions(cacheOk, hierarchical, 0, incompleteOk, null), vs, pIn, false);
}
- public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn) {
- return expandVS(vs, cacheOk, hierarchical, incompleteOk, pIn, false);
+ public ValueSetExpansionOutcome expandVS(ExpansionOptions options, ValueSet vs) {
+ return expandVS(options, vs, getExpansionParameters(), false);
}
-
- public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean hierarchical, boolean incompleteOk, Parameters pIn, boolean noLimits) {
+
+ public ValueSetExpansionOutcome expandVS(ExpansionOptions options, ValueSet vs, Parameters pIn, boolean noLimits) {
if (pIn == null) {
throw new Error(formatMessage(I18nConstants.NO_PARAMETERS_PROVIDED_TO_EXPANDVS));
}
@@ -1119,9 +1073,43 @@ public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean h
return new ValueSetExpansionOutcome("This value set is not expanded correctly at this time (will be fixed in a future version)", TerminologyServiceErrorClass.VALUESET_UNSUPPORTED, false);
}
- Parameters p = pIn.copy();
+ Parameters p = getExpansionParameters(); // it's already a copy
+ if (p == null) {
+ p = new Parameters();
+ }
+ for (ParametersParameterComponent pp : pIn.getParameter()) {
+ if (Utilities.existsInList(pp.getName(), "designation", "filterProperty", "useSupplement", "property", "tx-resource")) {
+ // these parameters are additive
+ p.getParameter().add(pp.copy());
+ } else {
+ ParametersParameterComponent existing = null;
+ if (Utilities.existsInList(pp.getName(), "system-version", "check-system-version", "force-system-version",
+ "default-valueset-version", "check-valueset-version", "force-valueset-version") && pp.hasValue() && pp.getValue().isPrimitive()) {
+ String url = pp.getValue().primitiveValue();
+ if (url.contains("|")) {
+ url = url.substring(0, url.indexOf("|") + 1);
+ }
+ for (ParametersParameterComponent t : p.getParameter()) {
+ if (pp.getName().equals(t.getName()) && t.hasValue() && t.getValue().isPrimitive() && t.getValue().primitiveValue().startsWith(url)) {
+ existing = t;
+ break;
+ }
+ }
+ } else {
+ existing = p.getParameter(pp.getName());
+ }
+ if (existing != null) {
+ existing.setValue(pp.getValue());
+ } else {
+ p.getParameter().add(pp.copy());
+ }
+ }
+ }
p.setParameter("_limit",new IntegerType("10000"));
p.setParameter("_incomplete", new BooleanType("true"));
+ if (options.hasLanguage()) {
+ p.setParameter("displayLanguage", new CodeType(options.getLanguage()));
+ }
if (vs.hasExpansion()) {
return new ValueSetExpansionOutcome(vs.copy());
}
@@ -1139,28 +1127,29 @@ public ValueSetExpansionOutcome expandVS(ValueSet vs, boolean cacheOk, boolean h
}
}
- CacheToken cacheToken = txCache.generateExpandToken(vs, hierarchical);
+ if (!noLimits && !p.hasParameter("count")) {
+ p.addParameter("count", expandCodesLimit);
+ p.addParameter("offset", 0);
+ }
+ p.setParameter("excludeNested", !options.isHierarchical());
+ if (options.isIncompleteOk()) {
+ p.setParameter("incomplete-ok", true);
+ }
+
+ CacheToken cacheToken = txCache.generateExpandToken(vs, options);
ValueSetExpansionOutcome res;
- if (cacheOk) {
+ if (options.isCacheOk()) {
res = txCache.getExpansion(cacheToken);
if (res != null) {
return res;
}
}
- if (!noLimits && !p.hasParameter("count")) {
- p.addParameter("count", expandCodesLimit);
- p.addParameter("offset", 0);
- }
- p.setParameter("excludeNested", !hierarchical);
- if (incompleteOk) {
- p.setParameter("incomplete-ok", true);
- }
-
List allErrors = new ArrayList<>();
// ok, first we try to expand locally
ValueSetExpander vse = constructValueSetExpanderSimple(new ValidationOptions(vs.getFHIRPublicationVersion()));
+ vse.setNoTerminologyServer(noTerminologyServer);
res = null;
try {
res = vse.expand(vs, p);
@@ -1261,7 +1250,7 @@ public void validateCodeBatch(ValidationOptions options, List extends CodingVa
// 2nd pass: What can we do internally
// 3rd pass: hit the server
for (CodingValidationRequest t : codes) {
- t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs, expParameters) : null);
+ t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vs, getExpansionParameters()) : null);
if (t.getCoding().hasSystem()) {
codeSystemsUsed.add(t.getCoding().getSystem());
}
@@ -1299,7 +1288,7 @@ public void validateCodeBatch(ValidationOptions options, List extends CodingVa
}
}
- if (expParameters == null)
+ if (expansionParameters.get() == null)
throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
// for those that that failed, we try to validate on the server
Parameters batch = new Parameters();
@@ -1485,7 +1474,7 @@ public ValidationResult validateCode(final ValidationOptions optionsArg, String
codeSystemsUsed.add(code.getSystem());
}
- final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateValidationToken(options, code, vs, expParameters) : null;
+ final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateValidationToken(options, code, vs, getExpansionParameters()) : null;
ValidationResult res = null;
if (cachingAllowed && txCache != null) {
res = txCache.getValidation(cacheToken);
@@ -1621,7 +1610,7 @@ public Boolean subsumes(ValidationOptions optionsArg, Coding parent, Coding chil
return null;
}
- final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateSubsumesToken(options, parent, child, expParameters) : null;
+ final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateSubsumesToken(options, parent, child, getExpansionParameters()) : null;
if (cachingAllowed && txCache != null) {
Boolean res = txCache.getSubsumes(cacheToken);
if (res != null) {
@@ -1683,15 +1672,15 @@ protected ValueSetExpander constructValueSetExpanderSimple(ValidationOptions opt
}
protected ValueSetValidator constructValueSetCheckerSimple(ValidationOptions options, ValueSet vs, ValidationContextCarrier ctxt) {
- return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, ctxt, expParameters, terminologyClientManager, registry);
+ return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, ctxt, getExpansionParameters(), terminologyClientManager, registry);
}
protected ValueSetValidator constructValueSetCheckerSimple( ValidationOptions options, ValueSet vs) {
- return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, expParameters, terminologyClientManager, registry);
+ return new ValueSetValidator(this, new TerminologyOperationContext(this, options, "validation"), options, vs, getExpansionParameters(), terminologyClientManager, registry);
}
- protected Parameters constructParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext tcd, ValueSet vs, boolean hierarchical) {
- Parameters p = expParameters.copy();
+ protected Parameters constructParameters(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tcd, ValueSet vs, boolean hierarchical) {
+ Parameters p = getExpansionParameters();
p.setParameter("includeDefinition", false);
p.setParameter("excludeNested", !hierarchical);
@@ -1727,7 +1716,7 @@ protected Parameters constructParameters(ValidationOptions options, CodingValida
} else {
pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
}
- pIn.addParameters(expParameters);
+ pIn.addParameters(getExpansionParameters());
return pIn;
}
@@ -1754,7 +1743,7 @@ private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
@Override
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
- CacheToken cacheToken = txCache.generateValidationToken(options, code, vs, expParameters);
+ CacheToken cacheToken = txCache.generateValidationToken(options, code, vs, getExpansionParameters());
ValidationResult res = null;
if (cachingAllowed) {
res = txCache.getValidation(cacheToken);
@@ -1896,6 +1885,10 @@ private void findRelevantSystems(Set set, ConceptSetComponent inc) {
ValueSet vs = fetchResource(ValueSet.class, u.getValue());
if (vs != null) {
findRelevantSystems(set, vs);
+ } else if (u.getValue() != null && u.getValue().startsWith("http://snomed.info/sct")) {
+ set.add("http://snomed.info/sct");
+ } else if (u.getValue() != null && u.getValue().startsWith("http://loinc.org")) {
+ set.add("http://loinc.org");
} else {
set.add(TerminologyClientManager.UNRESOLVED_VALUESET);
}
@@ -1944,11 +1937,11 @@ protected ValidationResult validateOnServer2(TerminologyClientContext tc, ValueS
return processValidationResult(pOut, vs == null ? null : vs.getUrl(), tc.getClient().getAddress());
}
- protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options) {
+ protected void addServerValidationParameters(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options) {
addServerValidationParameters(opCtxt, terminologyClientContext, vs, pin, options, null);
}
- protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options, Set systems) {
+ protected void addServerValidationParameters(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext terminologyClientContext, ValueSet vs, Parameters pin, ValidationOptions options, Set systems) {
boolean cache = false;
if (vs != null) {
if (terminologyClientContext != null && terminologyClientContext.isTxCaching() && terminologyClientContext.getCacheId() != null && vs.getUrl() != null && terminologyClientContext.getCached().contains(vs.getUrl()+"|"+ vs.getVersion())) {
@@ -1982,11 +1975,11 @@ protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt
throw new Error(formatMessage(I18nConstants.CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT));
}
}
- if (expParameters == null) {
+ if (expansionParameters.get() == null) {
throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
}
String defLang = null;
- for (ParametersParameterComponent pp : expParameters.getParameter()) {
+ for (ParametersParameterComponent pp : expansionParameters.get().getParameter()) {
if ("defaultDisplayLanguage".equals(pp.getName())) {
defLang = pp.getValue().primitiveValue();
} else if (!pin.hasParameter(pp.getName())) {
@@ -2005,7 +1998,7 @@ protected void addServerValidationParameters(ITerminologyOperationDetails opCtxt
pin.addParameter("diagnostics", true);
}
- private boolean addDependentResources(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ValueSet vs) {
+ private boolean addDependentResources(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ValueSet vs) {
boolean cache = false;
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
cache = addDependentResources(opCtxt, tc, pin, inc, vs) || cache;
@@ -2016,10 +2009,10 @@ private boolean addDependentResources(ITerminologyOperationDetails opCtxt, Termi
return cache;
}
- private boolean addDependentResources(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ConceptSetComponent inc, Resource src) {
+ private boolean addDependentResources(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, ConceptSetComponent inc, Resource src) {
boolean cache = false;
for (CanonicalType c : inc.getValueSet()) {
- ValueSet vs = fetchResource(ValueSet.class, c.getValue(), src);
+ ValueSet vs = fetchResource(ValueSet.class, c.getValue(), null, src);
if (vs != null && !hasCanonicalResource(pin, "tx-resource", vs.getVUrl())) {
cache = checkAddToParams(tc, pin, vs) || cache;
addDependentResources(opCtxt, tc, pin, vs);
@@ -2042,9 +2035,9 @@ private boolean addDependentResources(ITerminologyOperationDetails opCtxt, Termi
return cache;
}
- public boolean addDependentCodeSystem(ITerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, String sys, Resource src) {
+ public boolean addDependentCodeSystem(ValueSetProcessBase.TerminologyOperationDetails opCtxt, TerminologyClientContext tc, Parameters pin, String sys, Resource src) {
boolean cache = false;
- CodeSystem cs = fetchResource(CodeSystem.class, sys, src);
+ CodeSystem cs = fetchResource(CodeSystem.class, sys, null, src);
if (cs != null && !hasCanonicalResource(pin, "tx-resource", cs.getVUrl()) && (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
cache = checkAddToParams(tc, pin, cs) || cache;
}
@@ -2137,6 +2130,7 @@ public ValidationResult processValidationResult(Parameters pOut, String vs, Stri
String version = null;
boolean inactive = false;
String status = null;
+ String diagnostics = null;
List issues = new ArrayList<>();
Set unknownSystems = new HashSet<>();
@@ -2155,6 +2149,8 @@ public ValidationResult processValidationResult(Parameters pOut, String vs, Stri
version = ((PrimitiveType>) p.getValue()).asStringValue();
} else if (p.getName().equals("code")) {
code = ((PrimitiveType>) p.getValue()).asStringValue();
+ } else if (p.getName().equals("diagnostics")) {
+ diagnostics = ((PrimitiveType>) p.getValue()).asStringValue();
} else if (p.getName().equals("inactive")) {
inactive = "true".equals(((PrimitiveType>) p.getValue()).asStringValue());
} else if (p.getName().equals("status")) {
@@ -2248,6 +2244,7 @@ public ValidationResult processValidationResult(Parameters pOut, String vs, Stri
res.setUnknownSystems(unknownSystems);
res.setServer(server);
res.setParameters(pOut);
+ res.setDiagnostics(diagnostics);
return res;
}
@@ -2292,13 +2289,22 @@ public void setLogger(@Nonnull org.hl7.fhir.r5.context.ILoggingService logger) {
getTxClientManager().setLogger(logger);
}
+ /**
+ * Returns a copy of the expansion parameters used by this context. Note that because the return value is a copy, any
+ * changes done to it will not be reflected in the context and any changes to the context will likewise not be
+ * reflected in the return value after it is returned. If you need to change the expansion parameters, use
+ * {@link #setExpansionParameters(Parameters)}.
+ *
+ * @return a copy of the expansion parameters
+ */
public Parameters getExpansionParameters() {
- return expParameters;
+ final Parameters parameters = expansionParameters.get();
+ return parameters == null ? null : parameters.copy();
}
- public void setExpansionParameters(Parameters expParameters) {
- this.expParameters = expParameters;
- this.terminologyClientManager.setExpansionParameters(expParameters);
+ public void setExpansionParameters(Parameters expansionParameters) {
+ this.expansionParameters.set(expansionParameters);
+ this.terminologyClientManager.setExpansionParameters(expansionParameters);
}
@Override
@@ -2334,17 +2340,9 @@ public Set getResourceNamesAsSet() {
return res;
}
- public boolean isAllowLoadingDuplicates() {
- return allowLoadingDuplicates;
- }
-
- public void setAllowLoadingDuplicates(boolean allowLoadingDuplicates) {
- this.allowLoadingDuplicates = allowLoadingDuplicates;
- }
-
@Override
public T fetchResourceWithException(Class class_, String uri) throws FHIRException {
- return fetchResourceWithException(class_, uri, null);
+ return fetchResourceWithException(class_, uri, null, null);
}
public T fetchResourceWithException(String cls, String uri) throws FHIRException {
@@ -2355,8 +2353,8 @@ public T fetchResourceByVersionWithException(Class class
return fetchResourceWithExceptionByVersion(class_, uri, version, null);
}
- public T fetchResourceWithException(Class class_, String uri, Resource sourceForReference) throws FHIRException {
- return fetchResourceWithExceptionByVersion(class_, uri, null, sourceForReference);
+ public T fetchResourceWithException(Class class_, String uri, String version, Resource sourceForReference) throws FHIRException {
+ return fetchResourceWithExceptionByVersion(class_, uri, version, sourceForReference);
}
@SuppressWarnings("unchecked")
@@ -2943,29 +2941,23 @@ public Resource fetchResourceById(String type, String uri) {
}
}
- public T fetchResource(Class class_, String uri, Resource sourceForReference) {
+ public T fetchResource(Class class_, String uri, String version, Resource sourceForReference) {
try {
- return fetchResourceWithException(class_, uri, sourceForReference);
+ return fetchResourceWithException(class_, uri, version, sourceForReference);
} catch (FHIRException e) {
throw new Error(e);
}
}
- public T fetchResource(Class class_, String uri, FhirPublication fhirVersion) {
- return fetchResource(class_, uri);
- }
-
+
public T fetchResource(Class class_, String uri) {
try {
- return fetchResourceWithException(class_, uri, null);
+ return fetchResourceWithException(class_, uri, null, null);
} catch (FHIRException e) {
throw new Error(e);
}
}
- public T fetchResource(Class class_, String uri, String version, FhirPublication fhirVersion) {
- return fetchResource(class_, uri, version);
- }
public T fetchResource(Class class_, String uri, String version) {
try {
return fetchResourceWithExceptionByVersion(class_, uri, version, null);
@@ -3007,26 +2999,9 @@ public boolean hasResourceVersion(String cls, String uri, S
}
}
- @Override
- public boolean hasResource(Class class_, String uri, String fhirVersion) {
- try {
- return fetchResourceByVersionWithException(class_, uri, fhirVersion) != null;
- } catch (Exception e) {
- return false;
- }
- }
-
- public boolean hasResource(String cls, String uri, FhirPublication fhirVersion) {
- try {
- return fetchResourceWithException(cls, uri) != null;
- } catch (Exception e) {
- return false;
- }
- }
-
- public boolean hasResourceVersion(Class class_, String uri, String version, FhirPublication fhirVersion) {
+ public boolean hasResource(Class class_, String uri, String version, Resource sourceForReference) {
try {
- return fetchResourceWithExceptionByVersion(class_, uri, version, null) != null;
+ return fetchResourceWithExceptionByVersion(class_, uri, version, sourceForReference) != null;
} catch (Exception e) {
return false;
}
@@ -3156,11 +3131,27 @@ public List listMaps() {
}
return m;
}
-
+
public List listStructures() {
List m = new ArrayList();
synchronized (lock) {
- structures.listAll(m);
+ structures.listAll(m);
+ }
+ return m;
+ }
+
+ public List listValueSets() {
+ List m = new ArrayList();
+ synchronized (lock) {
+ valueSets.listAll(m);
+ }
+ return m;
+ }
+
+ public List listCodeSystems() {
+ List m = new ArrayList();
+ synchronized (lock) {
+ codeSystems.listAll(m);
}
return m;
}
@@ -3252,12 +3243,6 @@ public List fetchTypeDefinitions(String typeName) {
return typeManager.getDefinitions(typeName);
}
- @Override
- public List fetchTypeDefinitions(String typeName, FhirPublication fhirVersion) {
- return typeManager.getDefinitions(typeName);
- }
-
-
public boolean isPrimitiveType(String type) {
return typeManager.isPrimitive(type);
}
@@ -3387,6 +3372,7 @@ public void finishLoading(boolean genSnapshots) {
if (!hasResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Base")) {
cacheResource(ProfileUtilities.makeBaseDefinition(version));
}
+ new CoreVersionPinner(this).pinCoreVersions(listCodeSystems(), listValueSets(), listStructures());
if(genSnapshots) {
for (StructureDefinition sd : listStructures()) {
try {
@@ -3512,16 +3498,16 @@ public void setCachingAllowed(boolean cachingAllowed) {
}
@Override
- public OIDSummary urlsForOid(String oid, String resourceType) {
- OIDSummary set = urlsForOid(oid, resourceType, true);
+ public IOIDServices.OIDSummary urlsForOid(String oid, String resourceType) {
+ IOIDServices.OIDSummary set = urlsForOid(oid, resourceType, true);
if (set.getDefinitions().size() > 1) {
set = urlsForOid(oid, resourceType, false);
}
return set;
}
- public OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
- OIDSummary summary = new OIDSummary();
+ public IOIDServices.OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
+ IOIDServices.OIDSummary summary = new IOIDServices.OIDSummary();
if (oid != null) {
if (oidCacheManual.containsKey(oid)) {
summary.addOIDs(oidCacheManual.get(oid));
@@ -3543,7 +3529,7 @@ public OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
String url = rs.getString(2);
String version = rs.getString(3);
String status = rs.getString(4);
- summary.addOID(new OIDDefinition(rt, oid, url, version, os.pid, status));
+ summary.addOID(new IOIDServices.OIDDefinition(rt, oid, url, version, os.pid, status));
}
}
} catch (Exception e) {
@@ -3555,13 +3541,13 @@ public OIDSummary urlsForOid(String oid, String resourceType, boolean retired) {
switch (oid) {
case "2.16.840.1.113883.6.1" :
- summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null, null));
+ summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null, null));
break;
case "2.16.840.1.113883.6.8" :
- summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null, null));
+ summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null, null));
break;
case "2.16.840.1.113883.6.96" :
- summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null, null));
+ summary.addOID(new IOIDServices.OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null, null));
break;
default:
}
@@ -3665,11 +3651,11 @@ private