.claude/skills/hapi-fhir-server/SKILL.md
Expert guidance for implementing FHIR servers using HAPI FHIR Plain Server framework. Use this skill when creating RESTful FHIR server implementations, implementing resource providers, adding FHIR operations (read, create, update, delete, search, $operations), implementing server interceptors for logging, security, and validation, setting up authentication and authorisation, or configuring FHIR server behaviour. Trigger keywords include "HAPI", "FHIR server", "RestfulServer", "resource provider", "IResourceProvider", "FHIR interceptor", "AuthorizationInterceptor", "FhirContext", "FHIR validation", "FHIR search", "FHIR operation".
npx skillsauth add aehrc/pathling hapi-fhir-serverInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
public class MyFhirServer extends RestfulServer {
@Override
protected void initialize() throws ServletException {
setFhirContext(FhirContext.forR4());
setResourceProviders(List.of(
new PatientResourceProvider(),
new ObservationResourceProvider()
));
registerInterceptor(new ResponseHighlighterInterceptor());
}
}
public class PatientResourceProvider implements IResourceProvider {
@Override
public Class<Patient> getResourceType() { return Patient.class; }
@Read
public Patient read(@IdParam IdType id) {
return loadPatient(id.getIdPart());
}
@Search
public List<Patient> search(
@OptionalParam(name = Patient.SP_FAMILY) StringParam family) {
return searchPatients(family);
}
}
RestfulServer (Servlet)
├── FhirContext (version-specific, expensive to create - reuse)
├── Resource Providers (IResourceProvider per resource type)
├── Plain Providers (cross-resource operations)
├── Interceptors (request/response hooks)
└── Configuration (paging, address strategy, encoding)
| Operation | Annotation | Key Parameters |
| --------- | --------------------- | ---------------------------------------------------- |
| Read | @Read | @IdParam IdType |
| VRead | @Read(version=true) | @IdParam IdType (with version) |
| Create | @Create | @ResourceParam Patient |
| Update | @Update | @IdParam, @ResourceParam |
| Delete | @Delete | @IdParam IdType |
| Patch | @Patch | @IdParam, PatchTypeEnum, @ResourceParam String |
Add @ConditionalUrlParam String to support conditional create/update/delete:
@Update
public MethodOutcome update(
@ResourceParam Patient patient,
@IdParam IdType id,
@ConditionalUrlParam String conditional) {
if (conditional != null) {
// Find by search criteria in conditional URL
}
// Perform update
}
MethodOutcome outcome = new MethodOutcome();
outcome.setId(new IdType("Patient", "123", "1"));
outcome.setCreated(true); // For create operations
return outcome;
| Type | Class | Example URL |
| --------- | ---------------------------- | --------------------------- |
| String | StringParam | ?family=Smith |
| Token | TokenParam | ?identifier=mrn\|123 |
| Date | DateParam/DateRangeParam | ?date=ge2020-01-01 |
| Reference | ReferenceParam | ?subject=Patient/123 |
| Quantity | QuantityParam | ?value-quantity=gt5\|\|kg |
// OR logic (comma-separated): ?family=Smith,Jones
@OptionalParam(name = "family") StringOrListParam families
// AND logic (repeated param): ?family=Smith&family=Jones
@OptionalParam(name = "family") StringAndListParam families
For large results, return IBundleProvider instead of List<IBaseResource>:
@Search
public IBundleProvider search(...) {
List<String> ids = findMatchingIds();
return new SimpleBundleProvider(ids) {
@Override
public List<IBaseResource> getResources(int from, int to) {
return loadResources(ids.subList(from, Math.min(to, ids.size())));
}
};
}
Configure paging provider on server:
FifoMemoryPagingProvider paging = new FifoMemoryPagingProvider(10);
paging.setDefaultPageSize(20);
paging.setMaximumPageSize(100);
setPagingProvider(paging);
@Operation(name = "$everything", idempotent = true)
public Bundle everything(
@IdParam IdType patientId,
@OperationParam(name = "start") DateType start,
@OperationParam(name = "end") DateType end) {
// Return bundle with patient and related resources
}
@IdParam@IdParam, register on resource providerIResourceProvider)idempotent = true: Allows HTTP GET (only for primitive params)registerInterceptor(new LoggingInterceptor());
registerInterceptor(new ResponseHighlighterInterceptor());
@Interceptor
public class MyInterceptor {
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
public void preHandle(RequestDetails details, HttpServletRequest request) {
// Before request processing
}
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public void postHandle(RequestDetails details, IBaseResource resource) {
// After response generated
}
}
SERVER_INCOMING_REQUEST_PRE_PROCESSED: Earliest hookSERVER_INCOMING_REQUEST_PRE_HANDLED: After handler selectedSERVER_OUTGOING_RESPONSE: Before response sentSERVER_HANDLE_EXCEPTION: On any exception| Interceptor | Purpose |
| -------------------------------- | --------------------------- |
| LoggingInterceptor | Request/response logging |
| ResponseHighlighterInterceptor | HTML view for browsers |
| RequestValidatingInterceptor | Validate incoming resources |
| ResponseValidatingInterceptor | Validate outgoing resources |
| CorsInterceptor | CORS support |
| ExceptionHandlingInterceptor | Custom error responses |
@Interceptor
public class AuthInterceptor {
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
public void authenticate(RequestDetails details, HttpServletRequest request) {
String auth = request.getHeader("Authorization");
if (!validateToken(auth)) {
throw new AuthenticationException("Invalid credentials");
}
details.getUserData().put("user", extractUser(auth));
}
}
public class MyAuthInterceptor extends AuthorizationInterceptor {
@Override
public List<IAuthRule> buildRuleList(RequestDetails details) {
String userId = (String) details.getUserData().get("user");
return new RuleBuilder()
.allow().read().allResources()
.inCompartment("Patient", new IdType("Patient", userId))
.andThen()
.allow().write().allResources()
.inCompartment("Patient", new IdType("Patient", userId))
.andThen()
.denyAll()
.build();
}
}
ValidationSupportChain chain = new ValidationSupportChain(
new DefaultProfileValidationSupport(ctx),
new InMemoryTerminologyServerValidationSupport(ctx),
new CommonCodeSystemsTerminologyService(ctx)
);
FhirInstanceValidator validator = new FhirInstanceValidator(chain);
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.addValidatorModule(validator);
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
registerInterceptor(interceptor);
Add NpmPackageValidationSupport for implementation guide validation:
NpmPackageValidationSupport npm = new NpmPackageValidationSupport(ctx);
npm.loadPackageFromClasspath("classpath:package/us.core.tgz");
| Exception | HTTP Status | Use Case |
| ---------------------------------- | ----------- | ---------------------- |
| ResourceNotFoundException | 404 | Resource not found |
| InvalidRequestException | 400 | Bad request parameters |
| UnprocessableEntityException | 422 | Validation failure |
| AuthenticationException | 401 | Auth required |
| ForbiddenOperationException | 403 | Access denied |
| ResourceVersionConflictException | 409 | Version mismatch |
| InternalErrorException | 500 | Server error |
// For reverse proxy
setServerAddressStrategy(new HardcodedServerAddressStrategy("https://api.example.com/fhir"));
// For Apache mod_proxy
setServerAddressStrategy(new ApacheProxyAddressStrategy(true));
setDefaultResponseEncoding(EncodingEnum.JSON);
setDefaultPrettyPrint(true);
setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
// Access via: details.getTenantId()
tools
Expert guidance for using WireMock in Java applications for HTTP API mocking and testing. Use this skill when the user asks to mock HTTP APIs, create API stubs, test REST clients, simulate network faults, verify HTTP requests, or integrate WireMock with Spring Boot. Trigger keywords include "wiremock", "mock http", "stub api", "http mock", "api testing", "rest mock", "simulate fault", "verify request", "spring boot wiremock".
documentation
Expert guidance for implementing SQL on FHIR v2 ViewDefinitions and operations to create portable, tabular projections of FHIR data. Use this skill when the user asks to create ViewDefinitions, flatten FHIR resources into tables, write FHIRPath expressions for data extraction, implement forEach/forEachOrNull/repeat patterns for unnesting, create where clauses for filtering, use constants in view definitions, combine data with unionAll, execute ViewDefinitions with $run or $export operations, or implement SQL on FHIR server capabilities. Trigger keywords include "ViewDefinition", "SQL on FHIR", "flatten FHIR", "tabular FHIR", "FHIR to SQL", "FHIR analytics", "FHIRPath columns", "unnest FHIR", "$viewdefinition-run", "$export", "view runner", "repeat", "recursive", "QuestionnaireResponse".
development
Expert guidance for working with the Apache Spark Catalyst query optimisation framework. Use this skill when working with Spark SQL internals, creating custom expressions, implementing query optimisations, working with logical/physical plans, or extending Catalyst. Trigger keywords include "catalyst", "spark sql", "expression", "logical plan", "physical plan", "tree node", "query optimisation", "rule executor", "analyzer", "optimizer", "code generation".
development
Expert guidance for using the SonarCloud API to interact with code quality analysis, projects, issues, quality gates, and metrics. Use this skill when making API calls to SonarCloud, automating code quality workflows, retrieving analysis results, managing projects programmatically, or integrating SonarCloud with CI/CD pipelines. Trigger keywords include "SonarCloud", "SonarCloud API", "code quality API", "SonarQube Cloud", "quality gate", "code analysis API", "SonarCloud measures", "SonarCloud issues".