All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fabric3.introspection.xml.composite.ComponentLoader Maven / Gradle / Ivy

/*
 * Fabric3
 * Copyright (c) 2009-2013 Metaform Systems
 *
 * Fabric3 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version, with the
 * following exception:
 *
 * Linking this software statically or dynamically with other
 * modules is making a combined work based on this software.
 * Thus, the terms and conditions of the GNU General Public
 * License cover the whole combination.
 *
 * As a special exception, the copyright holders of this software
 * give you permission to link this software with independent
 * modules to produce an executable, regardless of the license
 * terms of these independent modules, and to copy and distribute
 * the resulting executable under terms of your choice, provided
 * that you also meet, for each linked independent module, the
 * terms and conditions of the license of that module. An
 * independent module is a module which is not derived from or
 * based on this software. If you modify this software, you may
 * extend this exception to your version of the software, but
 * you are not obligated to do so. If you do not wish to do so,
 * delete this exception statement from your version.
 *
 * Fabric3 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the
 * GNU General Public License along with Fabric3.
 * If not, see .
 *
 * ----------------------------------------------------
 *
 * Portions originally based on Apache Tuscany 2007
 * licensed under the Apache 2.0 license.
 *
 */
package org.fabric3.introspection.xml.composite;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.oasisopen.sca.annotation.Constructor;
import org.oasisopen.sca.annotation.EagerInit;
import org.oasisopen.sca.annotation.Reference;
import org.w3c.dom.Document;

import org.fabric3.introspection.xml.common.AbstractExtensibleTypeLoader;
import org.fabric3.introspection.xml.common.InvalidAttributes;
import org.fabric3.introspection.xml.common.InvalidPropertyValue;
import org.fabric3.model.type.component.AbstractReference;
import org.fabric3.model.type.component.AbstractService;
import org.fabric3.model.type.component.Autowire;
import org.fabric3.model.type.component.ComponentConsumer;
import org.fabric3.model.type.component.ComponentDefinition;
import org.fabric3.model.type.component.ComponentProducer;
import org.fabric3.model.type.component.ComponentReference;
import org.fabric3.model.type.component.ComponentService;
import org.fabric3.model.type.component.ComponentType;
import org.fabric3.model.type.component.ConsumerDefinition;
import org.fabric3.model.type.component.Implementation;
import org.fabric3.model.type.component.Multiplicity;
import org.fabric3.model.type.component.ProducerDefinition;
import org.fabric3.model.type.component.Property;
import org.fabric3.model.type.component.PropertyMany;
import org.fabric3.model.type.component.PropertyValue;
import org.fabric3.model.type.component.Target;
import org.fabric3.model.type.contract.ServiceContract;
import org.fabric3.spi.contract.ContractMatcher;
import org.fabric3.spi.contract.MatchResult;
import org.fabric3.spi.introspection.IntrospectionContext;
import org.fabric3.spi.introspection.xml.IncompatibleContracts;
import org.fabric3.spi.introspection.xml.InvalidValue;
import org.fabric3.spi.introspection.xml.LoaderHelper;
import org.fabric3.spi.introspection.xml.LoaderRegistry;
import org.fabric3.spi.introspection.xml.LoaderUtil;
import org.fabric3.spi.introspection.xml.MissingAttribute;
import org.fabric3.spi.introspection.xml.UnrecognizedElement;

import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
import static org.oasisopen.sca.Constants.SCA_NS;

/**
 * Loads a component definition from an XML-based assembly file
 */
@EagerInit
public class ComponentLoader extends AbstractExtensibleTypeLoader> {

    private static final QName COMPONENT = new QName(SCA_NS, "component");
    private static final QName PROPERTY = new QName(SCA_NS, "property");
    private static final QName SERVICE = new QName(SCA_NS, "service");
    private static final QName REFERENCE = new QName(SCA_NS, "reference");
    private static final QName PRODUCER = new QName(SCA_NS, "producer");
    private static final QName CONSUMER = new QName(SCA_NS, "consumer");

    private LoaderHelper loaderHelper;
    private ContractMatcher contractMatcher;
    private boolean roundTrip;

    /**
     * Constructor used during bootstrap
     *
     * @param registry     the loader registry
     * @param loaderHelper the helper
     */
    public ComponentLoader(LoaderRegistry registry, LoaderHelper loaderHelper) {
        this(registry, loaderHelper, null);
    }

    @Constructor
    public ComponentLoader(@Reference LoaderRegistry registry, @Reference LoaderHelper loaderHelper, @Reference ContractMatcher contractMatcher) {
        super(registry);
        addAttributes("name", "autowire", "requires", "policySets", "key", "order");
        this.loaderHelper = loaderHelper;
        this.contractMatcher = contractMatcher;
    }

    @org.oasisopen.sca.annotation.Property(required = false)
    public void setRoundTrip(boolean roundTrip) {
        this.roundTrip = roundTrip;
    }

    @SuppressWarnings({"VariableNotUsedInsideIf"})
    public ComponentDefinition load(XMLStreamReader reader, IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();
        String name = reader.getAttributeValue(null, "name");
        if (name == null) {
            MissingAttribute failure = new MissingAttribute("Component name not specified", startLocation);
            context.addError(failure);
            return null;
        }
        String autowireStr = reader.getAttributeValue(null, "autowire");
        Autowire autowire = Autowire.fromString(autowireStr);
        String key = loaderHelper.loadKey(reader);

        ComponentDefinition> definition = new ComponentDefinition>(name);

        int order = parserOrder(reader, definition, startLocation, context);

        if (roundTrip) {
            definition.enableRoundTrip();
            if (autowireStr != null) {
                definition.attributeSpecified("autowire");
            }
            if (key != null) {
                definition.attributeSpecified("key");
            }
            if (order != Integer.MIN_VALUE) {
                definition.attributeSpecified("order");
            }
        }
        definition.setContributionUri(context.getContributionUri());
        definition.setAutowire(autowire);
        definition.setKey(key);
        definition.setOrder(order);

        loaderHelper.loadPolicySetsAndIntents(definition, reader, context);

        validateAttributes(reader, context, definition);

        if (roundTrip) {
            LoaderUtil.nextTagRecord(definition, reader);
        } else {
            reader.nextTag();
        }
        QName elementName = reader.getName();

        if (COMPONENT.equals(elementName)) {
            // the reader has hit the end of the component definition without an implementation being specified
            MissingComponentImplementation error =
                    new MissingComponentImplementation("The component " + name + " must specify an implementation", startLocation, definition);
            context.addError(error);
            return definition;
        } else if (PROPERTY.equals(elementName) || REFERENCE.equals(elementName) || SERVICE.equals(elementName) || PRODUCER.equals(elementName)) {
            MissingComponentImplementation error = new MissingComponentImplementation("The component " + name
                                                                                              + " must specify an implementation as the first child element",
                                                                                      startLocation, definition);
            context.addError(error);
            return definition;
        }
        Implementation impl = registry.load(reader, Implementation.class, context);
        if (impl == null || impl.getComponentType() == null) {
            // error loading impl
            return definition;
        }

        if (!reader.getName().equals(elementName) || reader.getEventType() != END_ELEMENT) {
            // ensure that the implementation loader has positioned the cursor to the end element
            throw new AssertionError("Implementation loader must position the cursor to the end element");
        }
        definition.setImplementation(impl);
        return parseSubElements(definition, reader, context);
    }

    public QName getXMLType() {
        return COMPONENT;
    }

    private void parseService(ComponentDefinition definition,
                              ComponentType componentType,
                              XMLStreamReader reader,
                              IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();
        ComponentService service = registry.load(reader, ComponentService.class, context);
        if (service == null) {
            // there was an error with the service configuration, just skip it
            return;
        }
        String name = service.getName();
        AbstractService typeService = componentType.getServices().get(name);
        if (typeService == null) {
            // ensure the service exists
            ComponentServiceNotFound failure = new ComponentServiceNotFound(name, definition, startLocation);
            context.addError(failure);
            return;
        }

        processServiceContract(service, typeService, startLocation, context);

        if (definition.getServices().containsKey(name)) {
            DuplicateComponentService failure = new DuplicateComponentService(name, startLocation, definition);
            context.addError(failure);
        } else {
            definition.add(service);
        }
    }

    private ComponentDefinition parseSubElements(ComponentDefinition> definition,
                                                    XMLStreamReader reader,
                                                    IntrospectionContext context) throws XMLStreamException {
        ComponentType componentType = definition.getImplementation().getComponentType();

        Map propertyLocations = new HashMap();
        while (true) {
            switch (reader.next()) {
            case START_ELEMENT:
                Location location = reader.getLocation();
                QName qname = reader.getName();
                if (PROPERTY.equals(qname)) {
                    parsePropertyValue(definition, componentType, reader, propertyLocations, context);
                } else if (REFERENCE.equals(qname)) {
                    parseReference(definition, componentType, reader, context);
                } else if (SERVICE.equals(qname)) {
                    parseService(definition, componentType, reader, context);
                } else if (PRODUCER.equals(qname)) {
                    parseProducer(definition, componentType, reader, context);
                } else if (CONSUMER.equals(qname)) {
                    parseConsumer(definition, componentType, reader, context);
                } else {
                    // Unknown extension element - issue an error and continue
                    UnrecognizedElement failure = new UnrecognizedElement(reader, location, definition);
                    context.addError(failure);
                    LoaderUtil.skipToEndElement(reader);
                }
                break;
            case END_ELEMENT:
                validateRequiredProperties(definition, propertyLocations, context);
                return definition;
            case XMLStreamReader.COMMENT:
                if (!roundTrip) {
                    continue;
                }
                String comment = reader.getText();
                definition.addComment(comment);
                continue;
            default:
                if (!roundTrip) {
                    continue;
                }
                comment = reader.getText();
                definition.addText(comment);
            }
        }
    }

    private void parseReference(ComponentDefinition definition,
                                ComponentType componentType,
                                XMLStreamReader reader,
                                IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();
        ComponentReference reference = registry.load(reader, ComponentReference.class, context);
        if (reference == null) {
            // there was an error with the reference configuration, just skip it
            return;
        }
        String name = reference.getName();
        AbstractReference typeReference = componentType.getReferences().get(name);
        if (typeReference == null) {
            // ensure the reference exists
            ComponentReferenceNotFound failure = new ComponentReferenceNotFound(name, definition, startLocation);
            context.addError(failure);
            return;
        }

        if (!reference.getCallbackBindings().isEmpty()) {
            if (typeReference.getServiceContract() != null && typeReference.getServiceContract().getCallbackContract() == null) {
                InvalidServiceContract failure = new InvalidServiceContract(
                        "Reference is configured with a callback binding but its service contract is not bidirectional: " + name,
                        startLocation, reference);
                context.addError(failure);
            }
        }
        processReferenceContract(reference, typeReference, startLocation, context);

        if (definition.getReferences().containsKey(name)) {
            DuplicateComponentReference failure = new DuplicateComponentReference(name, startLocation, definition);
            context.addError(failure);
            return;
        }

        processMultiplicity(reference, typeReference, startLocation, context);
        definition.add(reference);

    }

    private void parseProducer(ComponentDefinition> definition,
                               ComponentType componentType,
                               XMLStreamReader reader,
                               IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();
        ComponentProducer producer = registry.load(reader, ComponentProducer.class, context);
        if (producer == null) {
            // there was an error with the producer configuration, just skip it
            return;
        }
        String name = producer.getName();
        ProducerDefinition typeProducer = componentType.getProducers().get(name);
        if (typeProducer == null) {
            // ensure the producer exists
            ComponentProducerNotFound failure = new ComponentProducerNotFound(name, definition, startLocation);
            context.addError(failure);
            return;
        }
        definition.add(producer);
    }

    private void parseConsumer(ComponentDefinition> definition,
                               ComponentType componentType,
                               XMLStreamReader reader,
                               IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();

        ComponentConsumer consumer = registry.load(reader, ComponentConsumer.class, context);
        if (consumer == null) {
            // there was an error with the consumer configuration, just skip it
            return;
        }
        String name = consumer.getName();
        ConsumerDefinition typeConsumer = componentType.getConsumers().get(name);
        if (typeConsumer == null) {
            // ensure the consumer exists
            ComponentConsumerNotFound failure = new ComponentConsumerNotFound(name, definition, startLocation);
            context.addError(failure);
            return;
        }
        consumer.setTypes(typeConsumer.getTypes());
        definition.add(consumer);
    }


    private void parsePropertyValue(ComponentDefinition definition,
                                    ComponentType componentType,
                                    XMLStreamReader reader,
                                    Map propertyLocations,
                                    IntrospectionContext context) throws XMLStreamException {
        Location startLocation = reader.getLocation();
        PropertyValue value = registry.load(reader, PropertyValue.class, context);
        if (value == null) {
            // there was an error with the property configuration, just skip it
            return;
        }
        String name = value.getName();
        Property property = componentType.getProperties().get(name);
        if (property == null) {
            // ensure the property exists
            ComponentPropertyNotFound failure = new ComponentPropertyNotFound(value.getName(), definition, startLocation);
            context.addError(failure);
            return;
        }
        validatePropertyType(value, property, startLocation, context);
        propertyLocations.put(property, startLocation);
        if (definition.getPropertyValues().containsKey(value.getName())) {
            String id = value.getName();
            DuplicateConfiguredProperty failure = new DuplicateConfiguredProperty(id, definition, startLocation);
            context.addError(failure);
        } else {
            definition.add(value);
        }
        if (value.getValue() != null && value.getValue().getDocumentElement().getChildNodes().getLength() == 0 && property.isRequired()) {
            // property value not specified
            PropertyValueNotSpecified failure = new PropertyValueNotSpecified(value.getName(), definition, startLocation);
            context.addError(failure);
        }
    }

    /**
     * Sets the composite service contract from the component type reference if not explicitly configured. If configured, validates the contract
     * matches the component type service contract.
     *
     * @param service     the service
     * @param typeService the component type service
     * @param location    the location in the composite where the contract is defined
     * @param context     the context
     */
    private void processServiceContract(ComponentService service, AbstractService typeService, Location location, IntrospectionContext context) {
        if (service.getServiceContract() == null) {
            // if the service contract is not set, inherit from the component type service
            service.setServiceContract(typeService.getServiceContract());
        } else if (contractMatcher != null) { // null check for contract matcher as it is not used during bootstrap
            // verify service contracts are compatible - the component service contract can be a subset of the component type service contract
            MatchResult result = contractMatcher.isAssignableFrom(service.getServiceContract(), typeService.getServiceContract(), true);
            if (!result.isAssignable()) {
                String name = service.getName();
                IncompatibleContracts error = new IncompatibleContracts("The component service interface " + name
                                                                                + " is not compatible with the promoted service "
                                                                                + typeService.getName() + ": " + result.getError(),
                                                                        location,
                                                                        service);
                context.addError(error);
            } else {
                matchServiceCallbackContracts(service, typeService, location, context);
            }
        }
    }

    /**
     * Sets the composite reference service contract from the component type reference if not explicitly configured. If configured, validates the
     * contract matches the promoted reference contract.
     *
     * @param reference     the reference
     * @param typeReference the component type reference
     * @param location      the location in the composite where the reference is defines
     * @param context       the context
     */
    private void processReferenceContract(ComponentReference reference,
                                          AbstractReference typeReference,
                                          Location location,
                                          IntrospectionContext context) {
        if (reference.getServiceContract() == null) {
            // if the reference contract is not set, inherit from the component type service
            reference.setServiceContract(typeReference.getServiceContract());
        } else if (contractMatcher != null) { // null check for contract matcher as it is not used during bootstrap
            // verify service contracts are compatible - the component type reference contract can be a subset of the component reference contract
            MatchResult result = contractMatcher.isAssignableFrom(typeReference.getServiceContract(), reference.getServiceContract(), true);
            if (!result.isAssignable()) {
                String name = reference.getName();
                IncompatibleContracts error = new IncompatibleContracts("The component reference contract " + name
                                                                                + " is not compatible with the promoted reference "
                                                                                + typeReference.getName() + ": " + result.getError(),
                                                                        location,
                                                                        reference);
                context.addError(error);
            } else {
                matchReferenceCallbackContracts(reference, typeReference, location, context);
            }
        }
    }

    /**
     * Matches the service contract declared on the promoted service and component type service.
     *
     * @param service     the service
     * @param typeService the component type service
     * @param location    the location where the contract is defined in the composite
     * @param context     the context
     */
    private void matchServiceCallbackContracts(ComponentService service,
                                               AbstractService typeService,
                                               Location location,
                                               IntrospectionContext context) {
        ServiceContract callbackContract = service.getServiceContract().getCallbackContract();
        if (callbackContract == null) {
            return;
        }
        ServiceContract typeCallbackContract = typeService.getServiceContract().getCallbackContract();
        if (typeCallbackContract == null) {
            IncompatibleContracts error =
                    new IncompatibleContracts("Component type for service " + service.getName() + " does not have a callback contract",
                                              location,
                                              service);
            context.addError(error);
            return;
        }
        MatchResult result = contractMatcher.isAssignableFrom(typeCallbackContract, callbackContract, true);
        if (!result.isAssignable()) {
            String name = service.getName();
            IncompatibleContracts error = new IncompatibleContracts("The component service " + name + " callback contract is not compatible with " +
                                                                            "the promoted service " + typeService.getName()
                                                                            + " callback contract: " + result.getError(), location, service);
            context.addError(error);
        }
    }

    /**
     * Matches the service contract declared on the promoted reference and component type reference.
     *
     * @param reference     the reference
     * @param typeReference the component type reference
     * @param location      the location where the contract is defined in the composite
     * @param context       the context
     */
    private void matchReferenceCallbackContracts(ComponentReference reference,
                                                 AbstractReference typeReference,
                                                 Location location,
                                                 IntrospectionContext context) {
        ServiceContract callbackContract = reference.getServiceContract().getCallbackContract();
        if (callbackContract == null) {
            return;
        }
        ServiceContract typeCallbackContract = typeReference.getServiceContract().getCallbackContract();
        if (typeCallbackContract == null) {
            IncompatibleContracts error =
                    new IncompatibleContracts("Component type for reference " + reference.getName() + " does not have a callback contract",
                                              location,
                                              reference);
            context.addError(error);
            return;
        }
        MatchResult result = contractMatcher.isAssignableFrom(typeCallbackContract, callbackContract, true);
        if (!result.isAssignable()) {
            String name = reference.getName();
            IncompatibleContracts error = new IncompatibleContracts("The component reference " + name + " callback contract is not compatible with " +
                                                                            "the promoted reference " + typeReference.getName()
                                                                            + " callback contract: " + result.getError(), location, reference);
            context.addError(error);
        }
    }


    /**
     * Sets the composite multiplicity to inherit from the component type reference if not explicitly configured. If configured, validates the setting
     * against the component type setting.
     *
     * @param reference     the reference
     * @param typeReference the promoted reference
     * @param location      the current location
     * @param context       the context
     */
    private void processMultiplicity(ComponentReference reference,
                                     AbstractReference typeReference,
                                     Location location,
                                     IntrospectionContext context) {
        String name = reference.getName();
        if (reference.getMultiplicity() == null) {
            Multiplicity multiplicity = typeReference.getMultiplicity();
            reference.setMultiplicity(multiplicity);
        } else {
            if (!loaderHelper.canNarrow(reference.getMultiplicity(), typeReference.getMultiplicity())) {
                InvalidValue failure = new InvalidValue("The multiplicity setting for reference " + name + " widens the default setting", location);
                context.addError(failure);
            }
        }
        List targets = reference.getTargets();
        Multiplicity multiplicity = reference.getMultiplicity();
        if (targets.size() > 1 && (Multiplicity.ZERO_ONE == multiplicity || Multiplicity.ONE_ONE == multiplicity)) {
            InvalidValue failure = new InvalidValue("Multiple targets configured on reference " + name + ", which takes a single target", location);
            context.addError(failure);
        }
    }

    private int parserOrder(XMLStreamReader reader,
                             ComponentDefinition> definition,
                             Location startLocation,
                             IntrospectionContext context) {
        String orderStr = reader.getAttributeValue(null, "order");
        int order = Integer.MIN_VALUE;
        if (orderStr != null) {
            try {
            order = Integer.parseInt(orderStr);
            }catch (NumberFormatException e) {
                InvalidValue failure = new InvalidValue("Invalid order value", startLocation, definition);
                context.addError(failure);
            }
        }
        return order;
    }

    private void validateRequiredProperties(ComponentDefinition definition,
                                            Map propertyLocations,
                                            IntrospectionContext context) {
        ComponentType type = definition.getImplementation().getComponentType();
        Map properties = type.getProperties();
        Map values = definition.getPropertyValues();
        for (Property property : properties.values()) {
            PropertyValue value = values.get(property.getName());
            if (property.isRequired() && value == null) {
                Location location = propertyLocations.get(property);
                RequiredPropertyNotProvided failure = new RequiredPropertyNotProvided(property, definition, location);
                context.addError(failure);
                continue;
            }
            if (value != null) {
                Location location = propertyLocations.get(property);
                // null check since an optional property may not be configured on the component
                validateAndSetMany(value, property, location, context);
            }
        }
    }

    private void validateAndSetMany(PropertyValue propertyValue, Property property, Location location, IntrospectionContext context) {
        PropertyMany propertyMany = propertyValue.getMany();
        if (PropertyMany.NOT_SPECIFIED == propertyMany) {
            if (property.isMany()) {
                propertyValue.setMany(PropertyMany.MANY);
            } else {
                propertyValue.setMany(PropertyMany.SINGLE);
            }
        } else if (PropertyMany.MANY == propertyMany) {
            if (!property.isMany()) {
                InvalidPropertyConfiguration error = new InvalidPropertyConfiguration("Illegal attempt to make a property many-valued when its " +
                                                                                              "component type is single-valued", location, property);
                context.addError(error);
                return;

            }
            propertyValue.setMany(PropertyMany.MANY);
        } else {
            propertyValue.setMany(PropertyMany.SINGLE);
        }
        Document value = propertyValue.getValue();
        if (value != null && PropertyMany.MANY != propertyValue.getMany() && value.getDocumentElement().getChildNodes().getLength() > 1) {
            // null check since optional properties may have null values
            // validate the many
            String name = propertyValue.getName();
            InvalidPropertyValue error =
                    new InvalidPropertyValue("A single-valued property is configured with multiple values: " + name, location, property);
            context.addError(error);
        }
    }

    @SuppressWarnings({"VariableNotUsedInsideIf"})
    private void validatePropertyType(PropertyValue value, Property property, Location location, IntrospectionContext context) {
        QName propType = property.getType();
        QName propElement = property.getElement();
        QName valType = value.getType();
        QName valElement = value.getElement();
        if (propType != null) {
            if (valElement != null) {
                InvalidAttributes error = new InvalidAttributes("Cannot specify property schema type and element type on property configuration: "
                                                                        + value.getName(), location, property);
                context.addError(error);
            } else if (valType != null && !valType.equals(propType)) {
                InvalidAttributes error = new InvalidAttributes("Property type " + propType + " and property configuration type " + valType
                                                                        + " do not match: " + value.getName(), location, property);
                context.addError(error);
            }
        } else if (propElement != null) {
            if (valType != null) {
                InvalidAttributes error = new InvalidAttributes("Cannot specify property element type and property configuration schema type: "
                                                                        + value.getName(), location, property);
                context.addError(error);
            } else if (valElement != null && !valElement.equals(propElement)) {
                InvalidAttributes error = new InvalidAttributes("Property element type " + propElement + " and property configuration element type "
                                                                        + valElement + " do not match: " + value.getName(), location, property);
                context.addError(error);
            }
        }

    }


}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy