12.5.1FHIR Storage Examples

 

This page contains example interceptors that can be registered with the FHIR Storage module.

12.5.2Example: Response Attribute Enhancement

 

The following example shows an interceptor that can be used to enhance resources that are submitted for update by a client. In this example, for every time that a Patient resource is created or updated, we are calling a REST MDM service to fetch an EUID (an enterprise identifier for a Patient). We are then adding this EUID as an additional identifier on the list of patient identifiers.

/*-
 * #%L
 * Smile CDR - CDR
 * %%
 * Copyright (C) 2016 - 2024 Smile CDR, Inc.
 * %%
 * All rights reserved.
 * #L%
 */
package com.smilecdr.demo.fhirstorage;

import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Patient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.HashMap;
import java.util.Objects;

/**
 * This example interceptor fetches a set of attributes from a third-party
 * REST service and uses them to enhance the resource being submitted.
 */
@SuppressWarnings("unchecked")
public class ExampleAttributeEnhancingInterceptor {

	private static final Logger ourLog = LoggerFactory.getLogger(ExampleAttributeEnhancingInterceptor.class);

	/**
	 * We are going to treat creates and updates the same way in this example, so the
	 * create method redirects to the update method.
	 */
	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
	public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
		handleResource(theResource);
	}

	/**
	 * For this example we are overriding the <code>resourcePreUpdate</code>
	 * method, which will be called when a resource is being updated. There
	 * are many other methods that can be overridden, but this one is
	 * appropriate for the desired functionality.
	 *
	 * @param theRequest     Contains details about the incoming request. You may wish to cast this
	 *                       object to a {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails},
	 *                       which is always possible when running interceptors inside Smile CDR.
	 * @param theOldResource The previous version of the resource, before this update was
	 *                       requested by the client.
	 * @param theNewResource The new version of the resource as submitted by the client. This
	 *                       object can be modified.
	 */
	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
	public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
		handleResource(theNewResource);
	}

	private void handleResource(IBaseResource theResource) {
		// This interceptor only cares about Patient resources
		if (!(theResource instanceof Patient)) {
			return;
		}

		Patient patient = (Patient) theResource;

		/*
		 * We want to know the primary identifier, which is the first instance of
		 * Patient.identifier where identifier.system = "http://example.com/mrn". If the
		 * client has submitted a resource that does not have such an identifier, this
		 * is an error, and we will throw a PreconditionFailedException (HTTP 412)
		 */
		String primaryIdentifier = patient
			.getIdentifier()
			.stream()
			.filter(t -> t.getSystem().equals("http://example.com/mrn"))
			.map(Identifier::getValue)
			.findFirst()
			.orElseThrow(() -> new PreconditionFailedException("No MRN supplied with request"));

		// We'll use a system property to pull the port number for this example. If the port
		// number won't change across deployments you don't need to do this.
		int port = Integer.parseInt(System.getProperty("mqm_query_port"));

		// We're going to make a web service call to a simple web service that
		// returns a JSON response. There are lots of ways of doing this, but
		// in our example we'll make a call using the Spring RestTemplate
		RestTemplate restTempate = new RestTemplate();
		restTempate.setMessageConverters(Collections.singletonList(new GsonHttpMessageConverter()));
		HashMap<String, Object> response = restTempate.getForObject("http://localhost:" + port + "/mdmquery?mrn=" + primaryIdentifier, HashMap.class);
		Objects.requireNonNull(response);

		// We'll log the response. Generally this isn't a good idea in a real
		// production system, but it's handy for debugging at first.
		ourLog.info("Response: {}", response);

		// Grab the existing EUID (identifier with a system of "http://example.com/euid")
		// and create one if it doesn't exist
		Identifier euidIdentifier = patient
			.getIdentifier()
			.stream()
			.filter(t -> t.getSystem().equals("http://example.com/euid"))
			.findFirst()
			.orElseGet(patient::addIdentifier);

		// Set the identifier value using the web service response
		euidIdentifier.setSystem("http://example.com/euid");
		euidIdentifier.setValue((String) response.get("euid"));
	}
}

12.5.3Example: Server-Reserved Tags

 

The following example shows an interceptor that implements reserved tags. For this example, a reserved tag is a tag that can only be set or removed by the server itself. These tags can not be added or removed by a client, and are hidden from the client as well.

/*-
 * #%L
 * Smile CDR - CDR
 * %%
 * Copyright (C) 2016 - 2024 Smile CDR, Inc.
 * %%
 * All rights reserved.
 * #L%
 */
package com.smilecdr.demo.fhirstorage;

import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.Iterator;
import java.util.Set;

/**
 * This interceptor implements the concept of "reserved tags", which are resource tags (tags in
 * <code>Resource.meta.tag</code>) that are reserved for internal system use. This means that:
 * <ul>
 *    <li>Clients can not add these tags manually</li>
 *    <li>Reserved tags are preserved between updates of resources that have them</li>
 *    <li>The reserved tags are not shown to clients and are filtered before serialization</li>
 * </ul>
 * <p>
 * The assumption with this interceptor is that there is a separate process that manages resource tags.
 * This could be a second interceptor, or some other non-client-facing process.
 * <p>
 * Note that each of the hook methods are annotated with {@literal @Hook(..., order=100)}. The assumption
 * here is that a separate interceptor is managing the reserved tags. That interceptor could use
 * {@literal @Hook(..., order=101)} to ensure that it comes afterward so that it can add or remove
 * the reserved tags as necessary.
 */
@SuppressWarnings("unchecked")
public class TagPreservingInterceptor {

	private static final Logger ourLog = LoggerFactory.getLogger(TagPreservingInterceptor.class);

	private Set<Pair> myTagsToPreserve;

	/**
	 * Constructor
	 */
	public TagPreservingInterceptor() {
		// This is the list of tags we want to preserve
		myTagsToPreserve = Collections.unmodifiableSet(Sets.newHashSet(
			Pair.of("http://my-tags", "tag1"),
			Pair.of("http://my-tags", "tag2")
		));
	}

	/**
	 * When a new resource is being created, don't allow the client to include any reserved tags
	 *
	 * @param theRequest  Contains details about the incoming request. You may wish to cast this
	 *                    object to a {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails},
	 *                    which is always possible when running interceptors inside Smile CDR.
	 * @param theResource The previous resource being created
	 */
	@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = 100)
	public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
		removeReservedTags(theResource);
	}

	/**
	 * For this example we are overriding the <code>resourcePreUpdate</code>
	 * method, which will be called when a resource is being updated. There
	 * are many other methods that can be overridden, but this one is
	 * appropriate for the desired functionality.
	 *
	 * @param theRequest     Contains details about the incoming request. You may wish to cast this
	 *                       object to a {@link ca.uhn.fhir.rest.server.servlet.ServletRequestDetails},
	 *                       which is always possible when running interceptors inside Smile CDR.
	 * @param theOldResource The previous version of the resource, before this update was
	 *                       requested by the client.
	 * @param theNewResource The new version of the resource as submitted by the client. This
	 *                       object can be modified.
	 */
	@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = 100)
	public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {

		// Filter out any reserved tags
		removeReservedTags(theNewResource);

		// Copy tags forward
		theOldResource
			.getMeta()
			.getTag()
			.stream()
			.filter(t -> myTagsToPreserve.contains(Pair.of(t.getSystem(), t.getCode())))
			.forEach(t -> theNewResource.getMeta().addTag()
				.setSystem(t.getSystem())
				.setCode(t.getCode())
				.setDisplay(t.getDisplay()));
	}

	/**
	 * Before returning any resource to the client, strip the reserved tags
	 */
	@Hook(value = Pointcut.STORAGE_PRESHOW_RESOURCES)
	public void preShow(IPreResourceShowDetails thePreShowDetails) {
		for (IBaseResource nextResource : thePreShowDetails) {
			removeReservedTags(nextResource);
		}
	}

	private void removeReservedTags(IBaseResource theResource) {
		for (Iterator<? extends IBaseCoding> iter = theResource.getMeta().getTag().iterator(); iter.hasNext(); ) {
			IBaseCoding nextTag = iter.next();
			String system = nextTag.getSystem();
			String code = nextTag.getCode();
			if (myTagsToPreserve.contains(Pair.of(system, code))) {
				iter.remove();
			}
		}
	}

}

12.5.4Example: JavaScript Storage Interceptor

 
function fhirResourcePreCreate(theRequestDetails, theResource) {
   if (theResource.name[0].family === "initial-family-name") {
      theResource.name[0].family = "after-pre-create";
   }
}

function fhirResourceCreated(theRequestDetails, theResource) {
   if (theResource.name[0].family === "after-pre-create") {
      throw "the resource was created";
   }
}

function fhirResourcePreUpdate(theRequestDetails, theOldResource, theNewResource) {
   if (theNewResource.resourceType === 'Patient') {
      if (theOldResource.name[0].family === 'old-name' && theNewResource.name[0].family === 'new-name') {
         theNewResource.name[0].given[0] = 'a-given-name';
      }
   } else {
      Log.info("Unknown resource type " + theNewResource.resourceType);
   }
}

function fhirResourceUpdated(theRequestDetails, theOldResource, theNewResource) {
   if (theOldResource.name[0].family === 'old-name' && theNewResource.name[0].given[0].toString() === "a-given-name") {
      throw "the resource was updated";
   }
}

function fhirResourcePreDelete(theRequestDetails, theResource) {
   if (theResource.name[0].family === 'removed-family-name') {
      theResource.name[0].given[0] = 'b-given-name';
   }
}


function fhirResourceDeleted(theRequestDetails, theResource) {
   if (theResource.resourceType === 'Patient') {
      throw "This patient was deleted";
   }
}

12.5.5Example: MegaScale Connection Provider

 

The following example shows an interceptor that is used to provide connection details for a FHIR Storage module configured in MegaScale mode.

/*-
 * #%L
 * Smile CDR - CDR
 * %%
 * Copyright (C) 2016 - 2024 Smile CDR, Inc.
 * %%
 * All rights reserved.
 * #L%
 */
package com.smilecdr.demo.fhirstorage;

import ca.cdr.api.fhir.interceptor.CdrHook;
import ca.cdr.api.fhir.interceptor.CdrPointcut;
import ca.cdr.api.persistence.megascale.MegaScaleCredentialRequestJson;
import ca.cdr.api.persistence.megascale.MegaScaleCredentialResponseJson;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.StringUtils;

/**
 * This interceptor is invoked when initiating a request for a MegaScale
 * connection for a specific partition. It returns the associated JDBC
 * URL, username and password to use for that partition.
 *
 * Responses are cached, so this pointcut will not be repeatedly invoked
 * every time a partition is accessed and this interceptor is therefore
 * not considered performance critical if latency is incurred.
 */
@Interceptor
public class MegaScaleConnectionProvidingInterceptor {

	@CdrHook(CdrPointcut.STORAGE_MEGASCALE_PROVIDE_DB_INFO)
	public MegaScaleCredentialResponseJson provideMegaScaleCredentials(MegaScaleCredentialRequestJson theRequest) {

		MegaScaleCredentialResponseJson response;
		if (theRequest.getPartitionId() != 0) {
			response = determinePartitionById(theRequest.getPartitionId());
		} else if (!StringUtils.isBlank(theRequest.getPartitionName())){
			response = determinePartitionByName(theRequest.getPartitionName());
		} else {
			throw new InvalidRequestException("No way to identify this partition!");
		}
		/*
		 * If any additional SQL should be executed in the database when it is
		 * first initialized, it may be added here. The example below shows a
		 * no-op query which does not do anything (and this should be removed
		 * in a real deployment), but you could replace this with a script that
		 * sets permissions or adds placeholders.
		 */
		response.addDatabaseInitializationStatement("SELECT 1");
		return response;
	}

	/**
	 * You can elect to determine data source selection by tenant name, instead of by partition ID, if you so desire.
	 */
	private MegaScaleCredentialResponseJson determinePartitionByName(String partitionName) {
		/*
		 * In this example, we'll use one connection URL for tenants "TENANT-A" & "TENANT-C", and
		 * a different connection URL for partitions "TENANT-B" & "TENANT-D". You can group partitions
		 * any way you would like.
		 */
		MegaScaleCredentialResponseJson retVal = new MegaScaleCredentialResponseJson();
		switch (partitionName) {
			case "TENANT-A":
			case "TENANT-C":
				setConnectionToFirstDatabase(retVal);
				break;
			case "TENANT-B":
			case "TENANT-D":
				setConnectionToSecondDatabase(retVal);
				break;
			default:
				// Shouldn't happen, and will cause an error if it does
		}
		return retVal;

	}

	private void setConnectionToSecondDatabase(MegaScaleCredentialResponseJson retVal) {
		retVal.setDatabaseUrl("jdbc:h2:mem:ds2");
		retVal.setDatabaseUsername("SA");
		retVal.setDatabasePassword("SA");
	}

	private void setConnectionToFirstDatabase(MegaScaleCredentialResponseJson retVal) {
		retVal.setDatabaseUrl("jdbc:h2:mem:ds1");
		retVal.setDatabaseUsername("SA");
		retVal.setDatabasePassword("SA");
	}


	private MegaScaleCredentialResponseJson determinePartitionById(int partitionId) {
		/*
		 * In this example, we'll use one connection URL for partitions 1 & 3, and
		 * a different connection URL for partitions 2 & 4. You can group partitions
		 * any way you would like.
		 */
		MegaScaleCredentialResponseJson retVal = new MegaScaleCredentialResponseJson();
		switch (partitionId) {
			case 1:
			case 3:
				setConnectionToFirstDatabase(retVal);
				break;
			case 2:
			case 4:
				setConnectionToSecondDatabase(retVal);
				break;
			default:
				// Shouldn't happen, and will cause an error if it does
		}
		/*
		 * If any additional SQL should be executed in the database when it is
		 * first initialized, it may be added here. The example below shows a
		 * no-op query which does not do anything (and this should be removed
		 * in a real deployment), but you could replace this with a script that
		 * sets permissions or adds placeholders.
		 */
		retVal.addDatabaseInitializationStatement("SELECT 1");

		return retVal;
	}

}

12.5.6Example: Starter Storage interceptor for all STORAGE_xxx pointcuts

 

The following example shows an interceptor that can be used as a starter Storage interceptor, implementing a hook method for each available pointcut.

/*-
 * #%L
 * Smile CDR - CDR
 * %%
 * Copyright (C) 2016 - 2024 Smile CDR, Inc.
 * %%
 * All rights reserved.
 * #L%
 */
package com.smilecdr.demo.fhirstorage;

import ca.cdr.api.broker.IPublicBrokerSender;
import ca.cdr.api.i18n.ILocalizer;
import ca.cdr.api.transactionlog.ITransactionLogFetchingSvc;
import ca.cdr.api.transactionlog.ITransactionLogStoringSvc;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.interceptor.model.TransactionWriteOperationsDetails;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
import ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.ICachedSearchDetails;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Sample Storage Interceptor implementing all STORAGE_XXX pointcuts.
 * It is indented to be used in FHIR Storage Persistence module 'Interceptor Bean Types'.
 * Can be used as a starting point for your storage interceptor.
 */
@SuppressWarnings({"unused", "EmptyTryBlock"})
@Interceptor
public class StorageInterceptorTemplate {

	private static final Logger ourLog = LoggerFactory.getLogger(StorageInterceptorTemplate.class);

	@Autowired
	private IPublicBrokerSender myBrokerSender;

	@Autowired
	private ITransactionLogFetchingSvc myTransactionLogFetchingSvc;

	@Autowired
	private ITransactionLogStoringSvc myTransactionLogStoringSvc;

	@Autowired
	private ILocalizer myLocalizer;

	@Autowired
	private FhirContext myFhirContext;

	@Autowired
	private DaoRegistry myDaoRegistry;

	@Autowired
	private RepositoryValidatingRuleBuilder myRuleBuilder;

	@Autowired
	private JpaStorageSettings myStorageSettings;

	@Autowired
	private PlatformTransactionManager myTransactionManager;

	@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
	public void storagePreSearchRegistered(
		ICachedSearchDetails theCachedSearchDetails,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		SearchParameterMap theSearchParameterMap) {

		ourLog.info("Interceptor STORAGE_PRESEARCH_REGISTERED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESEARCH_REGISTERED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRECHECK_FOR_CACHED_SEARCH)
	public boolean storagePreCheckForCachedSearch(
		SearchParameterMap theSearchParameterMap,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRECHECK_FOR_CACHED_SEARCH - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRECHECK_FOR_CACHED_SEARCH - ended, execution took {}", stopWatch);
		}
		return true;
	}

	@Hook(Pointcut.STORAGE_PREACCESS_RESOURCES)
	public void storagePreAccessResources(
		IPreResourceAccessDetails thePreResourceAccessDetails,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PREACCESS_RESOURCES - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PREACCESS_RESOURCES - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESHOW_RESOURCES)
	public void storagePreShowResources(
		IPreResourceShowDetails thePreResourceShowDetails,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRESHOW_RESOURCES - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESHOW_RESOURCES - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
	public void storagePreStorageResourceCreated(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails,
		RequestPartitionId theRequestPartitionId) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_CREATED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_CREATED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
	public void storagePreStorageResourceUpdated(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_UPDATED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_UPDATED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED)
	public void storagePreStorageResourceDeleted(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_DELETED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_RESOURCE_DELETED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
	public void storagePreCommitResourceCreated(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails,
		InterceptorInvocationTimingEnum theInterceptorInvocationTimingEnum) {

		ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_CREATED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_CREATED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED)
	public void storagePreCommitResourceUpdated(
		IBaseResource thePreviousContentBaseResource,
		IBaseResource theProposedBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails,
		InterceptorInvocationTimingEnum theInterceptorInvocationTimingEnum) {

		ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_UPDATED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_UPDATED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED)
	public void storagePreCommitResourceDeleted(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails,
		InterceptorInvocationTimingEnum theInterceptorInvocationTimingEnum) {

		ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_DELETED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRECOMMIT_RESOURCE_DELETED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
	public void storagePreStorageExpungeResource(
		AtomicInteger theAtomicInteger,
		IIdType theIdType,
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_EXPUNGE_RESOURCE - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_EXPUNGE_RESOURCE - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING)
	public void storagePartitionSelected(
		AtomicInteger theAtomicInteger,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRE_DELETE_EXPUNGE_PID_LIST)
	public void storagePreDeleteExpungePidList(
		String theResourceType,
		List<Long> theResourcePidList,
		AtomicLong theAtomicLong,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRE_DELETE_EXPUNGE_PID_LIST - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRE_DELETE_EXPUNGE_PID_LIST - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRE_DELETE_EXPUNGE)
	public void storagePreDeleteExpunge(
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		String theUrl) {

		ourLog.info("Interceptor STORAGE_PRE_DELETE_EXPUNGE - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRE_DELETE_EXPUNGE - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_CASCADE_DELETE)
	public void storageCascadeDelete(
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		DeleteConflictList theDeleteConflictList,
		IBaseResource theBaseResource) {

		ourLog.info("Interceptor STORAGE_CASCADE_DELETE - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_CASCADE_DELETE - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
	public RequestPartitionId storagePartitionIdentifyCreate(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_PARTITION_IDENTIFY_CREATE - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PARTITION_IDENTIFY_CREATE - ended, execution took {}", stopWatch);
		}
		return null;
	}

	@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
	public RequestPartitionId storagePartitionIdentifyRead(
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		ReadPartitionIdRequestDetails theReadPartitionIdRequestDetails) {

		ourLog.info("Interceptor STORAGE_PARTITION_IDENTIFY_READ - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PARTITION_IDENTIFY_READ - ended, execution took {}", stopWatch);
		}
		return null;
	}

	@Hook(Pointcut.STORAGE_PARTITION_SELECTED)
	public void storagePartitionSelected(
		RequestPartitionId theRequestPartitionId,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		RuntimeResourceDefinition theRuntimeResourceDefinition) {

		ourLog.info("Interceptor STORAGE_PARTITION_SELECTED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PARTITION_SELECTED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_TRANSACTION_PROCESSED)
	public void storageTransactionProcessed(
		IBaseBundle theBaseBundle,
		DeferredInterceptorBroadcasts theDeferredInterceptorBroadcasts,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails,
		TransactionDetails theTransactionDetails) {

		ourLog.info("Interceptor STORAGE_TRANSACTION_PROCESSED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_TRANSACTION_PROCESSED - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_INITIATE_BULK_EXPORT)
	public void storageInitiateBulkExport(
		BulkExportJobParameters theBulkDataExportOptions,
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_INITIATE_BULK_EXPORT - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_INITIATE_BULK_EXPORT - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_PRESTORAGE_CLIENT_ASSIGNED_ID)
	public void storagePreStorageClientAssignedId(
		IBaseResource theBaseResource,
		RequestDetails theRequestDetails) {

		ourLog.info("Interceptor STORAGE_PRESTORAGE_CLIENT_ASSIGNED_ID - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_PRESTORAGE_CLIENT_ASSIGNED_ID - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE)
	public void storageTransactionWriteOperationsPre(
		TransactionWriteOperationsDetails theTransactionWriteOperationsDetails,
		TransactionDetails theTransactionDetails) {

		ourLog.info("Interceptor STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_TRANSACTION_WRITE_OPERATIONS_PRE - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_TRANSACTION_WRITE_OPERATIONS_POST)
	public void storageTransactionWriteOperationsPost(
		TransactionWriteOperationsDetails theTransactionWriteOperationsDetails,
		TransactionDetails theTransactionDetails) {

		ourLog.info("Interceptor STORAGE_TRANSACTION_WRITE_OPERATIONS_POST - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_TRANSACTION_WRITE_OPERATIONS_POST - ended, execution took {}", stopWatch);
		}
	}

	@Hook(Pointcut.STORAGE_VERSION_CONFLICT)
	public ResourceVersionConflictResolutionStrategy storageVersionConflict(
		RequestDetails theRequestDetails,
		ServletRequestDetails theServletRequestDetails) {

		ourLog.info("Interceptor STORAGE_VERSION_CONFLICT - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor STORAGE_VERSION_CONFLICT - ended, execution took {}", stopWatch);
		}
		return null;
	}

	// should be called when repository validation is enabled
	@Hook(Pointcut.VALIDATION_COMPLETED)
	public ValidationResult validationCompleted(
		IBaseResource theBaseResource,
		String theRawBaseResource,
		ValidationResult theValidationResult) {

		ourLog.info("Interceptor VALIDATION_COMPLETED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		}
		finally {
			ourLog.info("Interceptor VALIDATION_COMPLETED - ended, execution took {}", stopWatch);
		}
		return null;
	}

	@Hook(Pointcut.INTERCEPTOR_REGISTERED)
	public void interceptorRegistered() {

		ourLog.info("Interceptor INTERCEPTOR_REGISTERED - started");
		StopWatch stopWatch = new StopWatch();
		try {
			// your implementation goes here
		} finally {
			ourLog.info("Interceptor INTERCEPTOR_REGISTERED - ended, execution took {}", stopWatch);
		}
	}
}