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

org.apache.camel.management.mbean.ManagedCamelContext Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.management.mbean;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.w3c.dom.Document;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ManagementStatisticsLevel;
import org.apache.camel.Producer;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.Route;
import org.apache.camel.TimerListener;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.api.management.mbean.ManagedCamelContextMBean;
import org.apache.camel.api.management.mbean.ManagedProcessorMBean;
import org.apache.camel.api.management.mbean.ManagedRouteMBean;
import org.apache.camel.api.management.mbean.ManagedStepMBean;
import org.apache.camel.model.Model;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RouteTemplateDefinition;
import org.apache.camel.model.RouteTemplatesDefinition;
import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestsDefinition;
import org.apache.camel.spi.ManagementStrategy;
import org.apache.camel.spi.UnitOfWork;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.PluginHelper;

@ManagedResource(description = "Managed CamelContext")
public class ManagedCamelContext extends ManagedPerformanceCounter implements TimerListener, ManagedCamelContextMBean {

    private final CamelContext context;
    private final LoadTriplet load = new LoadTriplet();
    private final LoadThroughput thp = new LoadThroughput();
    private final String jmxDomain;
    private final boolean includeRouteTemplates;
    private final boolean includeKamelets;
    private Statistic remoteExchangesTotal;
    private Statistic remoteExchangesCompleted;
    private Statistic remoteExchangesFailed;
    private Statistic remoteExchangesInflight;

    public ManagedCamelContext(CamelContext context) {
        this.context = context;
        this.jmxDomain = context.getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
        this.includeRouteTemplates = context.getManagementStrategy().getManagementAgent().getRegisterRoutesCreateByTemplate();
        this.includeKamelets = context.getManagementStrategy().getManagementAgent().getRegisterRoutesCreateByKamelet();
    }

    @Override
    public void init(ManagementStrategy strategy) {
        super.init(strategy);
        this.remoteExchangesTotal = new StatisticCounter();
        this.remoteExchangesCompleted = new StatisticCounter();
        this.remoteExchangesFailed = new StatisticCounter();
        this.remoteExchangesInflight = new StatisticCounter();
        boolean enabled = context.getManagementStrategy().getManagementAgent() != null
                && context.getManagementStrategy().getManagementAgent().getStatisticsLevel() != ManagementStatisticsLevel.Off;
        setStatisticsEnabled(enabled);
    }

    @Override
    public void reset() {
        super.reset();
        remoteExchangesTotal.reset();
        remoteExchangesCompleted.reset();
        remoteExchangesFailed.reset();
        remoteExchangesInflight.reset();
    }

    @Override
    public void completedExchange(Exchange exchange, long time) {
        // the camel-context mbean is triggered for every route mbean
        // so we must only trigger on the root level, otherwise the context mbean
        // total counter will be incorrect. For example if an exchange is routed via 3 routes
        // we should only count this as 1 instead of 3.
        UnitOfWork uow = exchange.getUnitOfWork();
        if (uow != null) {
            int level = uow.routeStackLevel(includeRouteTemplates, includeKamelets);
            if (level <= 1) {
                super.completedExchange(exchange, time);
                if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                    remoteExchangesTotal.increment();
                    remoteExchangesCompleted.increment();
                    remoteExchangesInflight.decrement();
                }
            }
        } else {
            super.completedExchange(exchange, time);
            if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                remoteExchangesTotal.increment();
                remoteExchangesCompleted.increment();
                remoteExchangesInflight.decrement();
            }
        }
    }

    @Override
    public void failedExchange(Exchange exchange) {
        // the camel-context mbean is triggered for every route mbean
        // so we must only trigger on the root level, otherwise the context mbean
        // total counter will be incorrect. For example if an exchange is routed via 3 routes
        // we should only count this as 1 instead of 3.
        UnitOfWork uow = exchange.getUnitOfWork();
        if (uow != null) {
            int level = uow.routeStackLevel(includeRouteTemplates, includeKamelets);
            if (level <= 1) {
                super.failedExchange(exchange);
                if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                    remoteExchangesTotal.increment();
                    remoteExchangesFailed.increment();
                    remoteExchangesInflight.decrement();
                }
            }
        } else {
            super.failedExchange(exchange);
            if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                remoteExchangesTotal.increment();
                remoteExchangesFailed.increment();
                remoteExchangesInflight.decrement();
            }
        }
    }

    @Override
    public void processExchange(Exchange exchange, String type) {
        // the camel-context mbean is triggered for every route mbean
        // so we must only trigger on the root level, otherwise the context mbean
        // total counter will be incorrect. For example if an exchange is routed via 3 routes
        // we should only count this as 1 instead of 3.
        UnitOfWork uow = exchange.getUnitOfWork();
        if (uow != null) {
            int level = uow.routeStackLevel(includeRouteTemplates, includeKamelets);
            if (level <= 1) {
                super.processExchange(exchange, type);
                if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                    remoteExchangesInflight.increment();
                }
            }
        } else {
            super.processExchange(exchange, type);
            if (exchange.getFromEndpoint() != null && exchange.getFromEndpoint().isRemote()) {
                remoteExchangesInflight.increment();
            }
        }
    }

    public CamelContext getContext() {
        return context;
    }

    @Override
    public String getCamelId() {
        return context.getName();
    }

    @Override
    public String getCamelDescription() {
        return context.getDescription();
    }

    @Override
    public String getManagementName() {
        return context.getManagementName();
    }

    @Override
    public String getCamelVersion() {
        return context.getVersion();
    }

    @Override
    public String getProfile() {
        return context.getCamelContextExtension().getProfile();
    }

    @Override
    public String getState() {
        return context.getStatus().name();
    }

    @Override
    public String getUptime() {
        return CamelContextHelper.getUptime(context);
    }

    @Override
    public long getUptimeMillis() {
        return context.getUptime().toMillis();
    }

    @Override
    public String getManagementStatisticsLevel() {
        if (context.getManagementStrategy().getManagementAgent() != null) {
            return context.getManagementStrategy().getManagementAgent().getStatisticsLevel().name();
        } else {
            return null;
        }
    }

    @Override
    public String getClassResolver() {
        return context.getClassResolver().getClass().getName();
    }

    @Override
    public String getPackageScanClassResolver() {
        return PluginHelper.getPackageScanClassResolver(context).getClass().getName();
    }

    @Override
    public String getApplicationContextClassName() {
        if (context.getApplicationContextClassLoader() != null) {
            return context.getApplicationContextClassLoader().getClass().getName();
        } else {
            return null;
        }
    }

    @Override
    public String getHeadersMapFactoryClassName() {
        return context.getCamelContextExtension().getHeadersMapFactory().getClass().getName();
    }

    @Override
    public Map getGlobalOptions() {
        if (context.getGlobalOptions().isEmpty()) {
            return null;
        }
        return context.getGlobalOptions();
    }

    @Override
    public String getGlobalOption(String key) throws Exception {
        return context.getGlobalOption(key);
    }

    @Override
    public void setGlobalOption(String key, String value) throws Exception {
        context.getGlobalOptions().put(key, value);
    }

    @Override
    public Boolean getTracing() {
        return context.isTracing();
    }

    @Override
    public void setTracing(Boolean tracing) {
        context.setTracing(tracing);
    }

    public Integer getInflightExchanges() {
        return (int) super.getExchangesInflight();
    }

    @Override
    public Integer getTotalRoutes() {
        return context.getRoutesSize();
    }

    @Override
    public Integer getStartedRoutes() {
        int started = 0;
        for (Route route : context.getRoutes()) {
            if (context.getRouteController().getRouteStatus(route.getId()).isStarted()) {
                started++;
            }
        }
        return started;
    }

    @Override
    public void setTimeout(long timeout) {
        context.getShutdownStrategy().setTimeout(timeout);
    }

    @Override
    public long getTimeout() {
        return context.getShutdownStrategy().getTimeout();
    }

    @Override
    public void setTimeUnit(TimeUnit timeUnit) {
        context.getShutdownStrategy().setTimeUnit(timeUnit);
    }

    @Override
    public TimeUnit getTimeUnit() {
        return context.getShutdownStrategy().getTimeUnit();
    }

    @Override
    public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) {
        context.getShutdownStrategy().setShutdownNowOnTimeout(shutdownNowOnTimeout);
    }

    @Override
    public boolean isShutdownNowOnTimeout() {
        return context.getShutdownStrategy().isShutdownNowOnTimeout();
    }

    @Override
    public String getLoad01() {
        double load1 = load.getLoad1();
        if (Double.isNaN(load1)) {
            // empty string if load statistics is disabled
            return "";
        } else {
            return String.format("%.2f", load1);
        }
    }

    @Override
    public String getLoad05() {
        double load5 = load.getLoad5();
        if (Double.isNaN(load5)) {
            // empty string if load statistics is disabled
            return "";
        } else {
            return String.format("%.2f", load5);
        }
    }

    @Override
    public String getLoad15() {
        double load15 = load.getLoad15();
        if (Double.isNaN(load15)) {
            // empty string if load statistics is disabled
            return "";
        } else {
            return String.format("%.2f", load15);
        }
    }

    @Override
    public String getThroughput() {
        double d = thp.getThroughput();
        if (Double.isNaN(d)) {
            // empty string if load statistics is disabled
            return "";
        } else {
            return String.format("%.2f", d);
        }
    }

    @Override
    public long getRemoteExchangesTotal() {
        return remoteExchangesTotal.getValue();
    }

    @Override
    public long getRemoteExchangesCompleted() {
        return remoteExchangesCompleted.getValue();
    }

    @Override
    public long getRemoteExchangesFailed() {
        return remoteExchangesFailed.getValue();
    }

    @Override
    public long getRemoteExchangesInflight() {
        return remoteExchangesInflight.getValue();
    }

    @Override
    public boolean isUseBreadcrumb() {
        return context.isUseBreadcrumb();
    }

    @Override
    public boolean isAllowUseOriginalMessage() {
        return context.isAllowUseOriginalMessage();
    }

    @Override
    public boolean isMessageHistory() {
        return context.isMessageHistory() != null ? context.isMessageHistory() : false;
    }

    @Override
    public boolean isLogMask() {
        return context.isLogMask() != null ? context.isLogMask() : false;
    }

    @Override
    public boolean isUseMDCLogging() {
        return context.isUseMDCLogging();
    }

    @Override
    public boolean isUseDataType() {
        return context.isUseDataType();
    }

    @Override
    public void onTimer() {
        load.update(getInflightExchanges());
        thp.update(getExchangesTotal());
    }

    @Override
    public void start() throws Exception {
        if (context.isSuspended()) {
            context.resume();
        } else {
            context.start();
        }
    }

    @Override
    public void stop() throws Exception {
        context.stop();
    }

    @Override
    public void restart() throws Exception {
        context.stop();
        context.start();
    }

    @Override
    public void suspend() throws Exception {
        context.suspend();
    }

    @Override
    public void resume() throws Exception {
        if (context.isSuspended()) {
            context.resume();
        } else {
            throw new IllegalStateException("CamelContext is not suspended");
        }
    }

    @Override
    public void startAllRoutes() throws Exception {
        context.getRouteController().startAllRoutes();
    }

    @Override
    public boolean canSendToEndpoint(String endpointUri) {
        try {
            Endpoint endpoint = context.getEndpoint(endpointUri);
            if (endpoint != null) {
                try (Producer producer = endpoint.createProducer()) {
                    return producer != null;
                }
            }
        } catch (Exception e) {
            // ignore
        }

        return false;
    }

    @Override
    public void sendBody(String endpointUri, Object body) throws Exception {
        try (ProducerTemplate template = context.createProducerTemplate()) {
            template.sendBody(endpointUri, body);
        }
    }

    @Override
    public void sendStringBody(String endpointUri, String body) throws Exception {
        sendBody(endpointUri, body);
    }

    @Override
    public void sendBodyAndHeaders(String endpointUri, Object body, Map headers) throws Exception {
        try (ProducerTemplate template = context.createProducerTemplate()) {
            template.sendBodyAndHeaders(endpointUri, body, headers);
        }
    }

    @Override
    public Object requestBody(String endpointUri, Object body) throws Exception {
        try (ProducerTemplate template = context.createProducerTemplate()) {
            return template.requestBody(endpointUri, body);
        }
    }

    @Override
    public Object requestStringBody(String endpointUri, String body) throws Exception {
        return requestBody(endpointUri, body);
    }

    @Override
    public Object requestBodyAndHeaders(String endpointUri, Object body, Map headers) throws Exception {
        try (ProducerTemplate template = context.createProducerTemplate()) {
            return template.requestBodyAndHeaders(endpointUri, body, headers);
        }
    }

    @Override
    public String dumpRestsAsXml() throws Exception {
        return dumpRestsAsXml(false);
    }

    @Override
    public String dumpRestsAsXml(boolean resolvePlaceholders) throws Exception {
        List rests = context.getCamelContextExtension().getContextPlugin(Model.class).getRestDefinitions();
        if (rests.isEmpty()) {
            return null;
        }

        RestsDefinition def = new RestsDefinition();
        def.setRests(rests);

        return PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, def, resolvePlaceholders, true);
    }

    @Override
    public String dumpRoutesAsXml() throws Exception {
        return dumpRoutesAsXml(false, true);
    }

    @Override
    public String dumpRoutesAsXml(boolean resolvePlaceholders) throws Exception {
        return dumpRoutesAsXml(resolvePlaceholders, true);
    }

    @Override
    public String dumpRoutesAsXml(boolean resolvePlaceholders, boolean generatedIds) throws Exception {
        List routes = context.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinitions();
        if (routes.isEmpty()) {
            return null;
        }

        // use routes definition to dump the routes
        RoutesDefinition def = new RoutesDefinition();
        def.setRoutes(routes);

        // if we are debugging then ids is needed for the debugger
        if (context.isDebugging()) {
            generatedIds = true;
        }
        return PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, def, resolvePlaceholders, generatedIds);
    }

    @Override
    public String dumpRoutesAsYaml() throws Exception {
        return dumpRoutesAsYaml(false, false);
    }

    @Override
    public String dumpRoutesAsYaml(boolean resolvePlaceholders) throws Exception {
        return dumpRoutesAsYaml(resolvePlaceholders, false, true);
    }

    @Override
    public String dumpRoutesAsYaml(boolean resolvePlaceholders, boolean uriAsParameters) throws Exception {
        return dumpRoutesAsYaml(resolvePlaceholders, uriAsParameters, true);
    }

    @Override
    public String dumpRoutesAsYaml(boolean resolvePlaceholders, boolean uriAsParameters, boolean generatedIds)
            throws Exception {
        List routes = context.getCamelContextExtension().getContextPlugin(Model.class).getRouteDefinitions();
        if (routes.isEmpty()) {
            return null;
        }

        // use routes definition to dump the routes
        RoutesDefinition def = new RoutesDefinition();
        def.setRoutes(routes);

        // if we are debugging then ids is needed for the debugger
        if (context.isDebugging()) {
            generatedIds = true;
        }

        return PluginHelper.getModelToYAMLDumper(context).dumpModelAsYaml(context, def, resolvePlaceholders, uriAsParameters,
                generatedIds);
    }

    @Override
    public String dumpRouteTemplatesAsXml() throws Exception {
        List templates
                = context.getCamelContextExtension().getContextPlugin(Model.class).getRouteTemplateDefinitions();
        if (templates.isEmpty()) {
            return null;
        }

        // use a route templates definition to dump the templates
        RouteTemplatesDefinition def = new RouteTemplatesDefinition();
        def.setRouteTemplates(templates);

        return PluginHelper.getModelToXMLDumper(context).dumpModelAsXml(context, def);
    }

    @Override
    public String dumpRoutesStatsAsXml(boolean fullStats, boolean includeProcessors) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("\n");

        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
        if (server != null) {
            // gather all the routes for this CamelContext, which requires JMX
            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
            ObjectName query = ObjectName
                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
            Set routes = server.queryNames(query, null);

            List processors = new ArrayList<>();
            if (includeProcessors) {
                // gather all the processors for this CamelContext, which requires JMX
                query = ObjectName.getInstance(
                        jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=processors,*");
                Set names = server.queryNames(query, null);
                for (ObjectName on : names) {
                    ManagedProcessorMBean processor = context.getManagementStrategy().getManagementAgent().newProxyClient(on,
                            ManagedProcessorMBean.class);
                    processors.add(processor);
                }
            }
            processors.sort(new OrderProcessorMBeans());

            // loop the routes, and append the processor stats if needed
            sb.append("  \n");
            for (ObjectName on : routes) {
                ManagedRouteMBean route
                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class);
                sb.append("    \n");

                // add processor details if needed
                if (includeProcessors) {
                    sb.append("      \n");
                    for (ManagedProcessorMBean processor : processors) {
                        int line = processor.getSourceLineNumber() != null ? processor.getSourceLineNumber() : -1;
                        // the processor must belong to this route
                        if (route.getRouteId().equals(processor.getRouteId())) {
                            sb.append("        \n");
                }
                sb.append("    \n");
            }
            sb.append("  \n");
        }

        sb.append("");
        return sb.toString();
    }

    @Override
    public String dumpStepStatsAsXml(boolean fullStats) throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("\n");

        MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
        if (server != null) {
            // gather all the routes for this CamelContext, which requires JMX
            String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
            ObjectName query = ObjectName
                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
            Set routes = server.queryNames(query, null);

            List steps = new ArrayList<>();
            // gather all the steps for this CamelContext, which requires JMX
            query = ObjectName
                    .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=steps,*");
            Set names = server.queryNames(query, null);
            for (ObjectName on : names) {
                ManagedStepMBean step
                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedStepMBean.class);
                steps.add(step);
            }
            steps.sort(new OrderProcessorMBeans());

            // loop the routes, and append the processor stats if needed
            sb.append("  \n");
            for (ObjectName on : routes) {
                ManagedRouteMBean route
                        = context.getManagementStrategy().getManagementAgent().newProxyClient(on, ManagedRouteMBean.class);
                sb.append("    \n");

                // add steps details if needed
                sb.append("      \n");
                for (ManagedProcessorMBean step : steps) {
                    // the step must belong to this route
                    if (route.getRouteId().equals(step.getRouteId())) {
                        int line = step.getSourceLineNumber() != null ? step.getSourceLineNumber() : -1;
                        sb.append("        \n");
                }
                sb.append("    \n");
            }
            sb.append("  \n");
        }

        sb.append("");
        return sb.toString();
    }

    @Override
    public String dumpRoutesCoverageAsXml() throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("\n");

        String xml = dumpRoutesAsXml(false, true);
        if (xml != null) {
            // use the coverage xml parser to dump the routes and enrich with coverage stats
            Document dom = RouteCoverageXmlParser.parseXml(context, new ByteArrayInputStream(xml.getBytes()));
            // convert dom back to xml
            String converted = context.getTypeConverter().convertTo(String.class, dom);
            sb.append(converted);
        }

        sb.append("\n");
        return sb.toString();
    }

    @Override
    public boolean createEndpoint(String uri) throws Exception {
        if (context.hasEndpoint(uri) != null) {
            // endpoint already exists
            return false;
        }

        Endpoint endpoint = context.getEndpoint(uri);
        if (endpoint != null) {
            // ensure endpoint is registered, as the management strategy could have been configured to not always
            // register new endpoints in JMX, so we need to check if its registered, and if not register it manually
            ObjectName on
                    = context.getManagementStrategy().getManagementObjectNameStrategy().getObjectNameForEndpoint(endpoint);
            if (on != null && !context.getManagementStrategy().getManagementAgent().isRegistered(on)) {
                // register endpoint as mbean
                Object me = context.getManagementStrategy().getManagementObjectStrategy().getManagedObjectForEndpoint(context,
                        endpoint);
                context.getManagementStrategy().getManagementAgent().register(me, on);
            }
            return true;
        } else {
            return false;
        }
    }

    @Override
    public int removeEndpoints(String pattern) throws Exception {
        // endpoints is always removed from JMX if removed from context
        Collection removed = context.removeEndpoints(pattern);
        return removed.size();
    }

    @Override
    public void reset(boolean includeRoutes) throws Exception {
        reset();
        load.reset();
        thp.reset();

        // and now reset all routes for this route
        if (includeRoutes) {
            MBeanServer server = getContext().getManagementStrategy().getManagementAgent().getMBeanServer();
            if (server != null) {
                String prefix = getContext().getManagementStrategy().getManagementAgent().getIncludeHostName() ? "*/" : "";
                ObjectName query = ObjectName
                        .getInstance(jmxDomain + ":context=" + prefix + getContext().getManagementName() + ",type=routes,*");
                Set names = server.queryNames(query, null);
                for (ObjectName name : names) {
                    server.invoke(name, "reset", new Object[] { true }, new String[] { "boolean" });
                }
            }
        }
    }

    @Override
    public Set componentNames() throws Exception {
        return context.getComponentNames();
    }

    @Override
    public Set languageNames() throws Exception {
        return context.getLanguageNames();
    }

    @Override
    public Set dataFormatNames() throws Exception {
        return context.getDataFormatNames();
    }

    /**
     * Used for sorting the processor mbeans accordingly to their index.
     */
    private static final class OrderProcessorMBeans implements Comparator {

        @Override
        public int compare(ManagedProcessorMBean o1, ManagedProcessorMBean o2) {
            return o1.getIndex().compareTo(o2.getIndex());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy