Wiki

Clone wiki

MoleculeDatabaseFramework / Overview of Framework Design

Entities

The Framework offers below outlined entity classes. ChemicalStructure and ChemicalCompoundComposition are concrete classes and not meant to be extended. The other classes are abstract and you need to create an implementation suiting your needs.

Simple Class Diagram

ChemicalCompounds consist of one or more ChemicalStructures. The relative amount of a ChemicalStructure within a ChemicalCompound is given by its ChemicalCompoundComposition.

The Framework manages the ChemicalStructures. Each ChemicalStructures is unique within the database. Also a ChemicalStructures is immutable. If you edit the ChemicalStructure of a ChemicalCompoundComposition the framework will check if the new ChemicalStructure already exists and use that one or else create a new ChemicalStructure. If you actually want to update an existing ChemicalStructure and the change should affect all ChemicalCompoundCompositions it appears in, then the special service method updateExistingStructure(entity) must be used instead of save(entity).

A ChemicalCompound is considered a virtual entity. It does not really exist in the real world. It's more like a description or model but not a physical available entity. For physical entities containing a ChemicalCompound you can use ChemicalCompoundContainers. However ChemicalCompoundContainers do not directly contain a ChemicalCompound, they contain a Containable. The reasoning behind this can be explained best with an example. Consider you have a factory and produce compound A. Now each lot of compound A might have slightly different properties and you must be able to track it. What you sell to the customer is a container containing a specific lot of compound A. Hence a lot is a Containable.

Containable and ChemicalCompound is a "strict association", meaning a ChemicalCompounds can only occur in 1 specific Type of Containable and a Containable may only contain 1 specific implementation of ChemicalCompound. A ChemicalCompoundContainer can hold any type of Containable and there must be only 1 implementation of ChemicalCompoundContainer in your application.

The important part is that the framework requires that the path from the entity you search by chemical structure to the actual ChemicalStructure entites must be predefined. Hence above design.

The framework hence features chemical structure search of ChemicalCompoundContainers. Available structure search includes full-structure, sub-structure and SMARTS. Note that if you enable Spring Security, a Container search will only return containers that contain a ChemicalCompound implementation the current user is allowed to read!

Also since you can create different implementations of ChemicalCompound your application can have multiple separate "pools" you can search individually.

Cascading of Persist

ChemicalCompound, Containable and ChemicalCompoundContainer in their JPA relationships between each other all use CascadeType.PERSIST and CascadeType.REFRESH. This means changes (updates) to existing entities must always be done using that entities service.save(entity) method because CascadeType.MERGE (update) is not set, updates are not cascaded.

Below example will create a new RegistrationCompound, a new Batch and a new CompoundContainer.

RegistrationCompound regCompound = new RegistrationCompound();
regCompound.setCompoundName("Registration Compound");
regCompound.setCas(cas);
regCompound.setRegNumber(regNumber);

ChemicalStructure structure =
        chemicalStructureFactory.createChemicalStructure(structureData);

ChemicalCompoundComposition composition = new ChemicalCompoundComposition();
composition.setCompound(regCompound);
composition.setChemicalStructure(structure);
composition.setPercentage(100.0);

regCompound.getCompositions().add(composition);

Batch batch = new Batch(regCompound, batchNumber);

regCompound.getBatches().add(batch);

CompoundContainer container = new CompoundContainer("C00001", batch);
container = compoundContainerService.save(container);

Note: CascadeType.MERGE is not used due to the Spring Security Integration. There is no easy way to check if associated entities were changed and hence if the current user is allowed to do the update (merge).

Repositories and Services

For each implementation of the abstract classes you need to create a a repository and a Service extending the ones provided by the framework. The provided repository classes implement the chemical structure search. Add your custom search methods to your repositories and services (see spring-data reference for further information).

Repository Example

Below a basic example of extending a provided Repository and adding a custom search method to it:

@Repository
public interface RegistrationCompoundRepository extends ChemicalCompoundRepository<RegistrationCompound> {    

    List<RegistrationCompound> findByRegistrationNumberStartingWith(String registrationNumber);

}

This examples assumes that RegistrationCompound has a String property registrationNumber. Spring Data will automatically create the implementations for your search methods.

To enable chemical structure search you need to extend the provided repository implementation. Mind the naming conventions, eg. it is required to name it same as the repository interface + Impl:

public class RegistrationCompoundRepositoryImpl extends ChemicalCompoundRepositoryImpl<RegistrationCompound>{

    public RegistrationCompoundRepositoryImpl() {
        super();
    }

    public RegistrationCompoundRepositoryImpl(EntityManager entityManager,
            CacheManager cacheManager) {
        super(entityManager, cacheManager);
    }
}

You now have created a repository for your custom class which provides chemical structure search!

Service Example

The important part is to always create an interface + an implementation and only use the interface in code. This is required due to how Spring works. First create the interface for your service:

public interface RegistrationCompoundService extends ChemicalCompoundService<RegistrationCompound> {

    List<RegistrationCompound> findByRegistrationNumberStartingWith(String registrationNumber);
}

Then you need to create an implementation of your service following the convention as shown in below example:

@Service
@Transactional
public class RegistrationCompoundServiceImpl extends ChemicalCompoundServiceImpl<RegistrationCompound>
        implements RegistrationCompoundService {

    @Autowired
    private RegistrationCompoundRepository registrationCompoundRepository;

    public RegistrationCompoundServiceImpl() {
        super(RegistrationCompound.class);
    }

    @Override
    protected ChemicalCompoundRepository<RegistrationCompound> getRepository(){
        return this.registrationCompoundRepository;
    }

    public List<RegistrationCompound> findByRegNumberStartingWith(String regNumber){
        return this.registrationCompoundRepository.findByRegNumberStartingWith(regNumber);
    }

    @Override
    public UniquenesscheckResult checkUniqueness(RegistrationCompound compound) {
        QRegistrationCompound qCompound = QRegistrationCompound.registrationCompound;
        Predicate predicateReg = qCompound.regNumber.eq(compound.getRegNumber());
        RegistrationCompound existingCompound = getRepository().findOne(predicateReg);

        HashMap<String, Object> fields = new HashMap<>();
        boolean isUnique = true;

        if (existingCompound != null) {
            fields.put("regNumber", compound.getRegNumber());
            isUnique = false;
        }
        return new UniquenesscheckResult(isUnique, fields);
    }

    @Override
    public RegistrationCompound getExistingCompound(SdfRecord sdfRecord,
            Map<String, String> mappedCompoundProperties) {

        String regNumberField = "regNumber";

        if (mappedCompoundProperties == null
                || !mappedCompoundProperties.containsValue(regNumberField)) {
            return null;
        }

        String uniqueKeyRegNumber = null;
        for (String key : mappedCompoundProperties.keySet()) {
            if (mappedCompoundProperties.get(key).equals(regNumberField)) {
                uniqueKeyRegNumber = key;
                break;
            }
        }
        String regNumber = sdfRecord.getProperty(uniqueKeyRegNumber);
        Predicate predicate = QRegistrationCompound.registrationCompound.regNumber.eq(regNumber);
        return findOne(predicate);
    }    
}

You now have a service layer for the RegistrationCompound that offers chemical structure search and simple to use, type-safe, transactional search methods!

Updated