/*-
 * ~~~~~~licensing~~~~~~
 * object-management
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.management.object.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.CollectionType;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.platform.base.objects.EntaxyObject.OBJECT_RELATION;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.core.management.object.EntaxyRuntimeItemMBean;
import ru.entaxy.platform.core.management.object.EntaxyRuntimeObjectMBean;
import ru.entaxy.platform.core.management.object.EntaxyRuntimeObjectMBeanCustomizer;
import ru.entaxy.platform.core.management.object.EntaxyRuntimeObjectMBeanCustomizer.Customized;
import ru.entaxy.platform.core.management.object.EntaxyRuntimeObjectMBeanFactory;
import ru.entaxy.platform.core.management.object.factory.impl.EntaxyObjectMBeanFactory;
import ru.entaxy.platform.core.management.object.impl.EntaxyRuntimeItemMBeanInfo.ItemStatus;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectManager;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectService;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeRelation;

@Component(service = EntaxyRuntimeObjectMBeanManager.class, immediate = true)
public class EntaxyRuntimeObjectMBeanManager implements EntaxyRuntimeObjectService.Listener {

    private static final Logger log = LoggerFactory.getLogger(EntaxyRuntimeObjectMBeanManager.class);

    private static final String TYPE_WILDCARD = "*";

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY)
    volatile EntaxyRuntimeObjectService entaxyRuntimeObjectService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY)
    volatile EntaxyRuntimeObjectManager entaxyRuntimeObjectManager;

    protected static class FactorySearchResult {

        public static FactorySearchResult create(EntaxyRuntimeObjectMBeanFactory factory, String pattern) {
            return new FactorySearchResult(factory, pattern);
        }

        EntaxyRuntimeObjectMBeanFactory factory;
        String pattern;

        protected FactorySearchResult(EntaxyRuntimeObjectMBeanFactory factory, String pattern) {
            this.factory = factory;
            this.pattern = pattern;
        }

    }

    protected Map<String, EntaxyRuntimeObjectMBeanFactory> factoriesMap = new HashMap<>();

    protected Map<String, EntaxyRuntimeItemMBeanInfo<?>> infoById = new HashMap<>();

    protected Map<String, EntaxyRuntimeObjectMBeanCustomizerHolder> customizersMap =
            new HashMap<>();

    // protected Map<String, EntaxyRuntimeItemMBeanInfo<?>> infoByJMXObjectName = new HashMap<>();

    protected List<EntaxyRuntimeObjectMBeanInfo> toRemove = new ArrayList<>();

    protected BundleContext bundleContext;

    protected boolean activated = false;

    protected Object lock = new Object();

    @Activate
    public EntaxyRuntimeObjectMBeanManager(BundleContext bundleContext) {
        super();
        this.bundleContext = bundleContext;

        // add root factory
        addFactory(new EntaxyObjectMBeanFactory(this));
    }


    @Activate
    public void activate(ComponentContext componentContext) {
        // this.bundleContext = componentContext.getBundleContext();
        activated = true;
        synchronized (lock) {
            entaxyRuntimeObjectService.addListener(this);
            loadCurrentObjects();
        }

    }


    @Deactivate
    public void deactivate() {
        entaxyRuntimeObjectService.removeListener(this);
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY,
            collectionType = CollectionType.SERVICE, unbind = "removeFactory")
    public void addFactory(EntaxyRuntimeObjectMBeanFactory factory) {
        synchronized (lock) {
            for (String pattern : factory.getSupportedTypes()) {
                factoriesMap.put(pattern, factory);
            }
            List<? extends EntaxyRuntimeItemMBean> mbeans = factory.createMBeans();
            if (mbeans != null)
                for (EntaxyRuntimeItemMBean mbean : mbeans) {
                    EntaxyRuntimeItemMBeanInfo<?> mbeanInfo = MBeanUtils.createMBeanInfo(mbean);
                    if (!mbeanInfo.isCreated()) {
                        log.error("Error creating MBeanInfo for mbean [{}]", mbean.getId());
                        continue;
                    }
                    if (infoById.containsKey(mbeanInfo.getId())) {
                        try {
                            infoById.get(mbeanInfo.getId()).unregister();
                        } catch (Exception ignore) {
                            // ignore
                        }
                    }
                    infoById.put(mbeanInfo.getId(), mbeanInfo);
                    mbeanInfo.register(bundleContext);
                }


            // review all created mbeans if they suit newly added factory

            List<EntaxyRuntimeObjectMBeanInfo> infoToRecreate = new ArrayList<>();
            List<EntaxyRuntimeItemMBeanInfo<?>> infoToReregister = new ArrayList<>();

            for (EntaxyRuntimeItemMBeanInfo<?> itemInfo : infoById.values()) {
                if (!(itemInfo instanceof EntaxyRuntimeObjectMBeanInfo))
                    continue;
                EntaxyRuntimeObjectMBeanInfo mBeanInfo = (EntaxyRuntimeObjectMBeanInfo) itemInfo;
                FactorySearchResult result = findFactory(mBeanInfo.runtimeObject);
                if (result.factory.getId().equals(factory.getId()))
                    if (!infoToRecreate.contains(mBeanInfo))
                        infoToRecreate.add(mBeanInfo);
            }

            List<EntaxyRuntimeItemMBean> itemsToReregister = new ArrayList<>();

            for (EntaxyRuntimeObjectMBeanInfo mBeanInfo : infoToRecreate)
                collectCascadeChildren(itemsToReregister, mBeanInfo.mbeanHolder.mbean);

            infoToReregister.addAll(itemsToReregister.stream()
                    .map(item -> infoById.get(item.getId()))
                    .filter(item -> item != null)
                    .collect(Collectors.toList()));
            infoToReregister.removeAll(infoToRecreate);

            List<EntaxyRuntimeObject> runtimeObjects = infoToRecreate.stream()
                    .map(inf -> inf.runtimeObject)
                    .collect(Collectors.toList());

            ObjectHelper helper = new ObjectHelper(runtimeObjects);

            List<EntaxyRuntimeObjectMBeanInfo> recreatedInfos = helper.load();

            List<EntaxyRuntimeObjectMBeanInfo> toRegister = new ArrayList<>();

            for (EntaxyRuntimeObjectMBeanInfo newInfo : recreatedInfos) {

                EntaxyRuntimeObjectMBeanInfo oldInfo = (EntaxyRuntimeObjectMBeanInfo) infoById.get(newInfo.getId());

                if (oldInfo == null) {

                    // just add

                    infoById.put(newInfo.getId(), newInfo);
                    newInfo.setBundleContext(bundleContext);
                    newInfo.status(ItemStatus.REREGISTER);
                    toRegister.add(newInfo);
                } else {

                    // mbean info already exists

                    if (newInfo.isAutoLoaded()) {

                        // object loaded only as someone's parent
                        // so we just switch the children links
                        switchChildren(newInfo.mbeanHolder.mbean, oldInfo.mbeanHolder.mbean);

                        if (oldInfo.isInStatus(ItemStatus.REMOVE))
                            oldInfo.status(ItemStatus.REREGISTER);

                    } else if (newInfo.isLoaded()) {

                        // unregister existing mbean
                        oldInfo.unregister();

                        updateObjectMBeanInfo(newInfo, oldInfo, toRegister);

                        switchChildren(oldInfo.mbeanHolder.mbean, newInfo.mbeanHolder.mbean);

                        // detach old secondaries from parent
                        for (String key : oldInfo.secondaryBeans.keySet()) {
                            List<EntaxyRuntimeObjectMBeanInfo> list = oldInfo.secondaryBeans.get(key);
                            for (EntaxyRuntimeObjectMBeanInfo bi : list)
                                bi.mbeanHolder.mbean.setParentMBean(null);
                        }

                        // remove all secondaries
                        oldInfo.secondaryBeans.clear();

                        // add new secondaries
                        oldInfo.secondaryBeans.putAll(newInfo.secondaryBeans);

                        // set new mbean
                        oldInfo.mbeanHolder.mbean = newInfo.mbeanHolder.mbean;

                        oldInfo.status(ItemStatus.REREGISTER);

                        // schedule existing to re-register
                        toRegister.add(oldInfo);

                    }

                }

            }

            /*
             * for (EntaxyRuntimeObjectMBeanInfo mBeanInfo : infoToRecreate) { FactorySearchResult
             * searchResult = findFactory(mBeanInfo.runtimeObject); EntaxyRuntimeObjectMBean
             * newMBean = searchResult.factory.createMBean(mBeanInfo.runtimeObject);
             * EntaxyRuntimeObjectMBeanInfo newInfo = (EntaxyRuntimeObjectMBeanInfo)
             * MBeanUtils.createMBeanInfo(newMBean);
             * 
             * mBeanInfo.mbeanHolder.unregister();
             * 
             * if (newInfo.mbeanHolder.mbean.getParentMBean() == null) {
             * newInfo.mbeanHolder.mbean.setParentMBean(mBeanInfo.mbeanHolder.mbean.getParentMBean()
             * ); }
             * 
             * // unlink from parent and switch children
             * mBeanInfo.mbeanHolder.mbean.setParentMBean(null);
             * switchChildren(mBeanInfo.mbeanHolder.mbean, newInfo.mbeanHolder.mbean);
             * 
             * infoById.put(newInfo.getId(), newInfo); newInfo.mbeanHolder.bundleContext =
             * bundleContext; newInfo.mbeanHolder.register(); }
             */

            for (EntaxyRuntimeItemMBeanInfo<?> toReg : toRegister) {
                toReg.unregister();
                toReg.register();
            }

            infoToReregister.removeAll(toRegister);

            for (EntaxyRuntimeItemMBeanInfo<?> toReg : infoToReregister) {
                toReg.unregister();
                toReg.register();
            }
        }


    }

    public void refresh() {
        synchronized (lock) {
            toRemove.addAll(infoById.values().stream()
                    .filter(v -> (v instanceof EntaxyRuntimeObjectMBeanInfo))
                    .map(v -> EntaxyRuntimeObjectMBeanInfo.class.cast(v))
                    .collect(Collectors.toList()));
            tryToRemove();
            loadCurrentObjects();
        }
    }

    public void removeFactory(EntaxyRuntimeObjectMBeanFactory factory) {
        for (String pattern : factory.getSupportedTypes()) {
            factoriesMap.remove(pattern);
        }
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY,
            collectionType = CollectionType.SERVICE, unbind = "removeCustomizer")
    public void addCustomizer(EntaxyRuntimeObjectMBeanCustomizer customizer) {

        synchronized (lock) {

            EntaxyRuntimeObjectMBeanCustomizerHolder holder = customizersMap.get(customizer.getId());

            if (holder != null) {
                // just switch to new customizer
                holder.customizer = customizer;
            } else {
                holder = new EntaxyRuntimeObjectMBeanCustomizerHolder(customizer);

                customizersMap.put(customizer.getId(), holder);

                for (EntaxyRuntimeItemMBeanInfo<?> itemInfo : infoById.values()) {

                    if ((itemInfo.mbeanHolder == null)
                            || !(itemInfo.mbeanHolder.mbean instanceof EntaxyRuntimeObjectMBean))
                        continue;

                    if (!customizer.isAccepted((EntaxyRuntimeObjectMBean) itemInfo.mbeanHolder.mbean))
                        continue;

                    EntaxyRuntimeObjectMBeanInfo objectInfo = (EntaxyRuntimeObjectMBeanInfo) itemInfo;

                    Customized customized = customizer.customize(objectInfo.mbeanHolder.mbean);

                    if (customized.getAdditionalMBeans() == null || customized.getAdditionalMBeans().isEmpty())
                        continue;

                    List<EntaxyRuntimeObjectMBeanInfo> secondaries = customized.getAdditionalMBeans().stream()
                            .map(b -> (EntaxyRuntimeObjectMBeanInfo) MBeanUtils.createMBeanInfo(b)).map(bi -> {
                                bi.status(ItemStatus.NEW);
                                bi.runtimeObject = objectInfo.runtimeObject;
                                return bi;
                            }).collect(Collectors.toList());

                    for (EntaxyRuntimeObjectMBeanInfo bi : secondaries) {
                        bi.register(bundleContext);
                    }

                    objectInfo.secondaryBeans.put(customizer.getId(), secondaries);

                }

            }

        }

    }

    public void removeCustomizer(EntaxyRuntimeObjectMBeanCustomizer customizer) {
        synchronized (lock) {

            for (EntaxyRuntimeItemMBeanInfo<?> itemInfo : infoById.values()) {

                if ((itemInfo.mbeanHolder == null)
                        || !(itemInfo.mbeanHolder.mbean instanceof EntaxyRuntimeObjectMBean))
                    continue;

                EntaxyRuntimeObjectMBeanInfo objectInfo = (EntaxyRuntimeObjectMBeanInfo) itemInfo;

                if (objectInfo.secondaryBeans.containsKey(customizer.getId())) {
                    for (EntaxyRuntimeObjectMBeanInfo sBean : objectInfo.secondaryBeans.get(customizer.getId()))
                        sBean.unregister();
                    objectInfo.secondaryBeans.remove(customizer.getId());
                }

            }

            customizersMap.remove(customizer.getId());
        }

    }

    protected void loadCurrentObjects() {

        ObjectHelper helper = new ObjectHelper(entaxyRuntimeObjectService.getObjects());
        List<EntaxyRuntimeObjectMBeanInfo> mBeanInfoList = helper.load();

        for (EntaxyRuntimeObjectMBeanInfo mBeanInfo : mBeanInfoList) {
            infoById.put(mBeanInfo.getId(), mBeanInfo);
            mBeanInfo.register(bundleContext);
            mBeanInfo.resetStatus();
        }

    }

    protected FactorySearchResult findFactory(EntaxyRuntimeObject entaxyRuntimeObject) {
        EntaxyRuntimeObjectMBeanFactory result = null;
        String type = entaxyRuntimeObject.getType();
        while (CommonUtils.isValid(type)) {
            result = factoriesMap.get(type);
            if (result != null)
                return FactorySearchResult.create(result, type);
            int index = type.lastIndexOf('.');
            if (index < 0) {
                if (type.equals(TYPE_WILDCARD))
                    type = "";
                else
                    type = TYPE_WILDCARD;
            } else {
                if (type.endsWith(TYPE_WILDCARD))
                    type = type.substring(0, index);
                else {
                    type = type.substring(0, index).concat(".").concat(TYPE_WILDCARD);
                }
            }
        }

        return FactorySearchResult.create(null, "");
    }

    protected void findFactory(EntaxyRuntimeObjectMBeanInfo mBeanInfo) {
        FactorySearchResult searchResult = findFactory(mBeanInfo.runtimeObject);
        if (searchResult != null) {
            mBeanInfo.factory = searchResult.factory;
            mBeanInfo.foundPattern = searchResult.pattern;
        }
    }


    // EntaxyRuntimeObjectService.Listener

    @Override
    public void objectAdded(EntaxyRuntimeObject object) {
        if (object == null)
            return;
        synchronized (lock) {
            ObjectHelper helper = new ObjectHelper(Collections.singletonList(object));
            List<EntaxyRuntimeObjectMBeanInfo> mBeanInfoList = helper.load();

            List<EntaxyRuntimeObjectMBeanInfo> toRegister = new ArrayList<>();

            for (EntaxyRuntimeObjectMBeanInfo mBeanInfo : mBeanInfoList) {

                if (!infoById.containsKey(mBeanInfo.getId())) {

                    // just add

                    infoById.put(mBeanInfo.getId(), mBeanInfo);
                    mBeanInfo.setBundleContext(bundleContext);;
                    mBeanInfo.status(ItemStatus.REREGISTER);
                    toRegister.add(mBeanInfo);
                } else {

                    // mbean info already exists

                    EntaxyRuntimeObjectMBeanInfo existing =
                            (EntaxyRuntimeObjectMBeanInfo) infoById.get(mBeanInfo.getId());

                    if (mBeanInfo.isAutoLoaded()) {

                        // object loaded only as someone's parent
                        // so we just switch the children links
                        // switchChildren(mBeanInfo.mbeanHolder.mbean, existing.mbeanHolder.mbean);

                        switchChildren(mBeanInfo.mbeanHolder.mbean, existing.mbeanHolder.mbean,
                                existing.secondaryBeans.values().stream().flatMap(list -> list.stream())
                                        .map(bi -> bi.mbeanHolder.mbean).collect(Collectors.toList()));

                        if (existing.isInStatus(ItemStatus.REMOVE))
                            existing.status(ItemStatus.REREGISTER);

                    } else if (mBeanInfo.isLoaded()) {

                        // unregister existing mbean
                        existing.unregister();

                        updateObjectMBeanInfo(mBeanInfo, existing, toRegister);

                        // switchChildren(existing.mbeanHolder.mbean, mBeanInfo.mbeanHolder.mbean);

                        switchChildren(existing.mbeanHolder.mbean, mBeanInfo.mbeanHolder.mbean,
                                existing.secondaryBeans.values().stream().flatMap(list -> list.stream())
                                        .map(bi -> bi.mbeanHolder.mbean).collect(Collectors.toList()));

                        // detach old secondaries from parent
                        for (String key : existing.secondaryBeans.keySet()) {
                            List<EntaxyRuntimeObjectMBeanInfo> list = existing.secondaryBeans.get(key);
                            for (EntaxyRuntimeObjectMBeanInfo bi : list)
                                bi.mbeanHolder.mbean.setParentMBean(null);
                        }

                        // remove all secondaries
                        existing.secondaryBeans.clear();

                        // add new secondaries
                        existing.secondaryBeans.putAll(mBeanInfo.secondaryBeans);

                        // set new mbean
                        existing.mbeanHolder.mbean = mBeanInfo.mbeanHolder.mbean;

                        existing.status(ItemStatus.REREGISTER);

                        // schedule existing to re-register
                        toRegister.add(existing);

                    }

                }

            }

            for (EntaxyRuntimeObjectMBeanInfo mbean : toRegister) {
                if (mbean.isRegistered())
                    mbean.unregister();
                mbean.register();
            }

        }

    }

    protected void switchChildren(EntaxyRuntimeItemMBean fromMBean, EntaxyRuntimeItemMBean toMBean,
            List<EntaxyRuntimeItemMBean> ignore) {

        // avoid cuncurrent modifications
        List<EntaxyRuntimeItemMBean> children = new ArrayList<>(fromMBean.getChildMBeans().values());

        for (EntaxyRuntimeItemMBean child : children)
            if (!ignore.contains(child))
                child.setParentMBean(toMBean);

    }

    protected void switchChildren(EntaxyRuntimeItemMBean fromMBean, EntaxyRuntimeItemMBean toMBean) {

        // avoid cuncurrent modifications
        List<EntaxyRuntimeItemMBean> children = new ArrayList<>(fromMBean.getChildMBeans().values());

        for (EntaxyRuntimeItemMBean child : children)
            child.setParentMBean(toMBean);
    }

    protected void updateObjectMBeanInfo(EntaxyRuntimeObjectMBeanInfo fromMBeanInfo,
            EntaxyRuntimeObjectMBeanInfo toMBeanInfo, List<EntaxyRuntimeObjectMBeanInfo> affectedItems) {

        // switch existing children to new mbean
        // and schedule them to re-register
        /*
         * List<EntaxyRuntimeItemMBean> children = new
         * ArrayList<>(fromMBeanInfo.mbeanHolder.mbean.getChildMBeans().values()); for
         * (EntaxyRuntimeItemMBean child : children) {
         * child.setParentMBean(toMBeanInfo.mbeanHolder.mbean); if
         * (!affectedItems.contains(infoById.get(child.getId())))
         * affectedItems.add((EntaxyRuntimeObjectMBeanInfo) infoById.get(child.getId()));
         * 
         * List<EntaxyRuntimeItemMBean> cascadeChildren = new ArrayList<>();
         * collectCascadeChildren(cascadeChildren, child); affectedItems.addAll(
         * cascadeChildren.stream() .map(c -> (EntaxyRuntimeObjectMBeanInfo)
         * infoById.get(c.getId())) .collect(Collectors.toList())); }
         */
        // set parent to new mbean
        // existing.mbeanHolder.mbean.setParentMBean(mBeanInfo.mbeanHolder.mbean.getParentMBean());
        // toMBeanInfo.mbeanHolder.mbean.setParentMBean(null);

        // update existing
        toMBeanInfo.factory = fromMBeanInfo.factory;
        toMBeanInfo.foundPattern = fromMBeanInfo.foundPattern;
        toMBeanInfo.relationFromParent = fromMBeanInfo.relationFromParent;
        toMBeanInfo.runtimeObject = fromMBeanInfo.runtimeObject;

    }

    protected void collectCascadeChildren(List<EntaxyRuntimeItemMBean> result, EntaxyRuntimeItemMBean startMBean) {
        for (EntaxyRuntimeItemMBean child : startMBean.getChildMBeans().values()) {
            result.add(child);
            collectCascadeChildren(result, child);
        }
    }

    @Override
    public void objectModified(EntaxyRuntimeObject object) {
        // we'll do the same as when an object is added
        objectAdded(object);
    }

    @Override
    public void objectRemoved(EntaxyRuntimeObject object) {
        synchronized (lock) {
            if (!infoById.containsKey(object.getObjectFullId()))
                return;
            toRemove.add((EntaxyRuntimeObjectMBeanInfo) infoById.get(object.getObjectFullId()));
            tryToRemove();
        }
    }

    protected void tryToRemove() {
        int currentSize = -1;
        while (currentSize != toRemove.size()) {
            currentSize = toRemove.size();
            List<EntaxyRuntimeObjectMBeanInfo> removed = new ArrayList<>();
            for (EntaxyRuntimeObjectMBeanInfo item : toRemove) {
                if (item.isRegistered())
                    item.unregister();
                // if (item.mbeanHolder.mbean.getChildMBeans().isEmpty()) {
                if (item.getPrimaryChildMBeans().isEmpty()) {
                    // remove from parent
                    item.mbeanHolder.mbean.setParentMBean(null);
                    // remove
                    infoById.remove(item.getId());

                    removed.add(item);
                }
            }
            toRemove.removeAll(removed);
        }
    }

    // Util classes

    protected class ObjectHelper {

        List<EntaxyRuntimeObject> objects;

        Map<String, EntaxyRuntimeObjectMBeanInfo> createdInfo = new HashMap<>();

        public ObjectHelper(List<EntaxyRuntimeObject> objects) {
            this.objects = objects;
        }

        public List<EntaxyRuntimeObjectMBeanInfo> load() {

            // create Mbean info objects
            for (EntaxyRuntimeObject object : this.objects) {
                String objectId = object.getObjectFullId();

                EntaxyRuntimeObjectMBeanInfo mBeanInfo = getOrCreateMBeanInfo(object);
                if (mBeanInfo == null)
                    continue;

                mBeanInfo.makeLoaded();

                EntaxyRuntimeObjectMBean mBean = mBeanInfo.mbeanHolder.mbean;

                List<EntaxyRuntimeRelation> relations = object.getIncomiingRelations(OBJECT_RELATION.COMPOSITION);
                if (relations.isEmpty())
                    relations = object.getIncomiingRelations(OBJECT_RELATION.AGGREGATION);
                if (relations.isEmpty())
                    continue;

                EntaxyRuntimeRelation parentLink = relations.get(0);
                EntaxyRuntimeObject parentObject = parentLink.getMain();
                String parentObjectId = parentObject.getObjectFullId();
                EntaxyRuntimeObjectMBeanInfo mBeanInfoParent = getOrCreateMBeanInfo(parentObject);

                if (!mBeanInfoParent.isLoaded())
                    mBeanInfoParent.makeAutoLoaded();

                mBean.setParentMBean(mBeanInfoParent.mbeanHolder.mbean);
                if (CommonUtils.isValid(parentLink.getName()))
                    mBean.setJMXSubQualifierName(parentLink.getName());
                mBean.setParentSubPath(parentLink.getGroup());
            }

            return new ArrayList<>(createdInfo.values());

        }

        protected EntaxyRuntimeObjectMBeanInfo getOrCreateMBeanInfo(EntaxyRuntimeObject object) {
            String objectId = object.getObjectFullId();
            if (!createdInfo.containsKey(objectId)) {
                FactorySearchResult searchResult = findFactory(object);
                if (searchResult.factory == null) {
                    log.warn("No MBean factory found for object [{}]", objectId);
                    return null;
                }
                EntaxyRuntimeObjectMBean mBean = searchResult.factory.createMBean(object);
                EntaxyRuntimeObjectMBeanInfo mBeanInfo =
                        (EntaxyRuntimeObjectMBeanInfo) MBeanUtils.createMBeanInfo(mBean);

                for (EntaxyRuntimeObjectMBeanCustomizerHolder holder : customizersMap.values()) {
                    EntaxyRuntimeObjectMBeanCustomizer customizer = holder.customizer;
                    if (customizer.isAccepted(mBean)) {
                        Customized customized = customizer.customize(mBean);
                        if (customized.getAdditionalMBeans() != null && !customized.getAdditionalMBeans().isEmpty())
                            mBeanInfo.secondaryBeans.put(customizer.getId(),
                                    customized.getAdditionalMBeans().stream()
                                            .map(b -> (EntaxyRuntimeObjectMBeanInfo) MBeanUtils.createMBeanInfo(b))
                                            .map(bi -> {
                                                bi.status(ItemStatus.NEW);
                                                bi.runtimeObject = object;
                                                return bi;
                                            })
                                            .collect(Collectors.toList()));
                    }

                }

                mBeanInfo.status(ItemStatus.NEW);
                mBeanInfo.runtimeObject = object;
                createdInfo.put(objectId, mBeanInfo);
                return mBeanInfo;
            } else {
                return createdInfo.get(objectId);
            }

        }
    }

    public Map<String, EntaxyRuntimeObjectMBeanFactory> getFactoriesMap() {
        return Collections.unmodifiableMap(factoriesMap);
    }


    public Map<String, EntaxyRuntimeItemMBeanInfo<?>> getInfoById() {
        return Collections.unmodifiableMap(infoById);
    }

    public List<EntaxyRuntimeObject> findObjects(String osgiFilter) throws InvalidSyntaxException {
        return entaxyRuntimeObjectService.findObjects(osgiFilter);
    }

}
