Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Sometimes a module might want to remove an object. Assuming that the object may or may not exist, this can easily be done from inside a bundle with a combination of possible(...) and uninstall(...), e.g.

...

Object installation ensures that individual metadata objects exist as expected in the database. Sometimes we are also concerned with the set of all objects of that type and this is where synchronization comes in:

Object synchronization guarantees that the set of all objects of a specific type in the database matches those in an object source

So synchronization differs from simply "installing all objects from a source" in this fundamental way: any existing object in the database that is not found in the source is uninstalled.

Defining the synchronization logic

The logic for a synchronization is provided via a custom implementation of ObjectSynchronization. For example if we have a list of locations in a CSV file (same format as previous example), we could define a simple synchronization operation as follows:

Code Block
languagejava
titleLocation synchronization example
@Component
public class LocationSynchronization implements ObjectSynchronization<Location> {
	@Autowired
	private LocationService locationService;

	@Override
	public List<Location> fetchAllExisting() {
		return locationService.getAllLocations(true);
	}

	@Override
	public Object getObjectSyncKey(Location obj) {
		return obj.getUuid();
	}

	@Override
	public boolean updateRequired(Location incoming, Location existing) {
		return true; // Always update the existing object (not very efficient)
	}
}

In this example we instruct metadata deploy to always update existing objects in the database. A more efficient approach here is to compare the two objects and only update if they are actually different.

This synchronization can then be performed during a bundle installation by passing it and a suitable object source to the sync(...) method, e.g.

Code Block
languagejava
titleSynchronization within a bundle
@Component
public class LocationsMetadata extends AbstractMetadataBundle {
	
	@Autowired
	private LocationSynchronization locationSync;

	@Override
	public void install() throws Exception {
		sync(new LocationCsvSource("locations.csv"), locationSync);
	}
}

To maximize performance, internally metadata deploy will maintain a cache of all existing objects.

Info

You can synchronize on something other than the main identifier of an object by returning something else from getObjectSyncKey(...). This is useful if you are synchronizing against a list of objects defined outside of OpenMRS which have some other unique identifier.

Extending

Supporting new types

The module currently has support for most metadata objects in OpenMRS core but may be missing some. If you find it is missing support for an class defined in OpenMRS core then submit a ticket. If you need it to support a class defined outside of core then you can provide support for that type yourself as described below.

Providing new constructors

The class CoreConstructors contains convenience constructors for many different classes. It is straightforward to define your own class of constructors which can be statically imported into a bundle class. You can use this to define constructors for new types, or additional constructors for core types, e.g.

Code Block
languagejava
titleCustom constructor class example
public class MyConstructors {
  public static MyMetadataType myMetadataType(String name, String description, String uuid) {
    MyMetadataType obj = new MyMetadataType();
    obj.setName(name);
    obj.setDescription(description);
    obj.setUuid(uuid);
    return obj;  
  } 
}

Which can then be used in a bundle like this:

Code Block
languagejava
titleCustom constructor use in a bundle example
import static com.example.MyConstructors.*;
 
public class MyMetadata extends AbstractMetadataBundle {
 
  public static final class _MyMetadataType {
    public static final String EXAMPLE = "88B7D480-5147-4B44-AC66-9F5A20822F7";  
  }
 
  public void install() {
    install(myMetadataType("Name", "Testing", _MyMetadataType.EXAMPLE));
  } 
}

Providing new handlers

If you want to include a new object class in a bundle then you will also have to tell the module how to handle that class. You can do this by providing a new "deploy handler" component. For example the deploy handler for locations looks like this:

Code Block
languagejava
titleThe location deploy handler
@Handler(supports = { Location.class })
public class LocationDeployHandler extends AbstractObjectDeployHandler<Location> {
	@Autowired
	@Qualifier("locationService")
	private LocationService locationService;

	@Override
	public Location fetch(String uuid) {
		return locationService.getLocationByUuid(uuid);
	}

	@Override
	public Location save(Location obj) {
		return locationService.saveLocation(obj);
	}

	@Override
	public void uninstall(Location obj, String reason) {
		locationService.retireLocation(obj, reason);
	}
}

 


...

1.0

Initial public release.