package org.opennms.netmgt.ncs.persistence; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; import org.hibernate.criterion.Restrictions; import org.opennms.core.utils.LogUtils; import org.opennms.netmgt.dao.AlarmDao; import org.opennms.netmgt.dao.EventDao; import org.opennms.netmgt.dao.support.UpsertTemplate; import org.opennms.netmgt.model.OnmsAlarm; import org.opennms.netmgt.model.OnmsCriteria; import org.opennms.netmgt.model.OnmsEvent; import org.opennms.netmgt.model.events.EventProxy; import org.opennms.netmgt.model.events.EventProxyException; import org.opennms.netmgt.model.ncs.NCSComponent; import org.opennms.netmgt.ncs.rest.NCSRestService.ComponentList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.ObjectRetrievalFailureException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; public class NCSComponentServiceImpl implements NCSComponentService { private static final Set<NCSComponent> EMPTY_COMPONENT_SET = Collections.unmodifiableSet(new HashSet<NCSComponent>()); @Autowired NCSComponentDao m_componentDao; @Autowired AlarmDao m_alarmDao; @Autowired EventDao m_eventDao; @Autowired private PlatformTransactionManager m_transactionManager; EventProxy m_eventProxy; public void setEventProxy(final EventProxy proxy) throws Exception { m_eventProxy = proxy; } @Override @Transactional public NCSComponent getComponent(final String type, final String foreignSource, final String foreignId) { LogUtils.debugf(this, "getComponent(%s, %s, %s)", type, foreignSource, foreignId); return getComponent(new ComponentIdentifier(null, type, foreignSource, foreignId, null, null)); } @Override @Transactional public ComponentList findComponentsWithAttribute(final String attrKey, final String attrValue) { LogUtils.debugf(this, "findComponentsWithAttribute(%s, %s)", attrKey, attrValue); return new ComponentList(m_componentDao.findComponentsWithAttribute(attrKey, attrValue)); } @Override @Transactional public NCSComponent addOrUpdateComponents(final NCSComponent component, final boolean deleteOrphans) { final ComponentIdentifier componentId = getIdentifier(component); LogUtils.debugf(this, "addOrUpdateComponents(%s, %s)", componentId, Boolean.valueOf(deleteOrphans)); final ComponentEventQueue ceq = new ComponentEventQueue(); final NCSComponent updatedComponent = addOrUpdateComponents(componentId, component, ceq, deleteOrphans); try { ceq.sendAll(m_eventProxy); } catch (final EventProxyException e) { LogUtils.warnf(this, e, "Component %s added, but an error occured while sending add/delete/update events.", componentId); } return updatedComponent; } @Override @Transactional public NCSComponent addSubcomponent(final String type, final String foreignSource, final String foreignId, final NCSComponent subComponent, final boolean deleteOrphans) { final ComponentIdentifier subComponentId = getIdentifier(subComponent); LogUtils.debugf(this, "addSubcomponent(%s, %s, %s, %s, %s)", type, foreignSource, foreignId, subComponentId, Boolean.valueOf(deleteOrphans)); final NCSComponent component = getComponent(type, foreignSource, foreignId); final ComponentIdentifier id = getIdentifier(component); final ComponentEventQueue ceq = new ComponentEventQueue(); if (component == null) { throw new ObjectRetrievalFailureException(NCSComponent.class, "Unable to locate component with type=" + type + ", foreignSource=" + foreignSource + ", foreignId=" + foreignId); } final NCSComponent updatedSubComponent = addOrUpdateComponents(subComponentId, subComponent, ceq, deleteOrphans); component.addSubcomponent(updatedSubComponent); m_componentDao.update(component); ceq.componentUpdated(id); try { ceq.sendAll(m_eventProxy); } catch (final EventProxyException e) { LogUtils.warnf(this, e, "Component %s added to %s, but an error occured while sending add/delete/update events.", subComponentId, id); } return getComponent(id); } @Override @Transactional public void deleteComponent(final String type, final String foreignSource, final String foreignId, final boolean deleteOrphans) { LogUtils.debugf(this, "deleteSubcomponent(%s, %s, %s, %s)", type, foreignSource, foreignId, Boolean.valueOf(deleteOrphans)); final NCSComponent component = getComponent(type, foreignSource, foreignId); final ComponentIdentifier id = getIdentifier(component); final ComponentEventQueue ceq = new ComponentEventQueue(); deleteComponent(id, ceq, deleteOrphans); try { ceq.sendAll(m_eventProxy); } catch (final EventProxyException e) { LogUtils.warnf(this, e, "Component %s deleted, but an error occured while sending delete/update events.", id); } } private Set<ComponentIdentifier> getIdentifiers(final Collection<NCSComponent> components) { final Set<ComponentIdentifier> identifiers = new HashSet<ComponentIdentifier>(); for (final NCSComponent component : components) { identifiers.add(getIdentifier(component)); } return identifiers; } private ComponentIdentifier getIdentifier(final NCSComponent component) { return new ComponentIdentifier(component.getId(), component.getType(), component.getForeignSource(), component.getForeignId(), component.getName(), component.getDependenciesRequired()); } private NCSComponent getComponent(final ComponentIdentifier id) { return m_componentDao.findByTypeAndForeignIdentity(id.getType(), id.getForeignSource(), id.getForeignId()); } private NCSComponent addOrUpdateComponents(final ComponentIdentifier id, final NCSComponent component, final ComponentEventQueue ceq, final boolean deleteOrphans) { final Set<NCSComponent> subcomponents = new LinkedHashSet<NCSComponent>(); final NCSComponent existing = new UpsertTemplate<NCSComponent, NCSComponentDao>(m_transactionManager, m_componentDao) { @Override protected NCSComponent query() { return getComponent(id); } @Override protected NCSComponent doInsert() { for (final NCSComponent subcomponent : component.getSubcomponents()) { final NCSComponent updatedComponent = addOrUpdateComponents(getIdentifier(subcomponent), subcomponent, ceq, deleteOrphans); subcomponents.add(updatedComponent); } component.setSubcomponents(subcomponents); m_componentDao.save(component); ceq.componentAdded(getIdentifier(component)); return component; } @Override protected NCSComponent doUpdate(final NCSComponent dbObj) { for (final NCSComponent subcomponent : component.getSubcomponents()) { final NCSComponent updatedComponent = addOrUpdateComponents(getIdentifier(subcomponent), subcomponent, ceq, deleteOrphans); subcomponents.add(updatedComponent); } if (deleteOrphans) deleteOrphanedComponents(getIdentifiers(dbObj.getSubcomponents()), getIdentifiers(subcomponents), ceq); dbObj.setName(component.getName()); dbObj.setVersion(component.getVersion()); dbObj.setDependenciesRequired(component.getDependenciesRequired()); dbObj.setNodeIdentification(component.getNodeIdentification()); dbObj.setUpEventUei(component.getUpEventUei()); dbObj.setDownEventUei(component.getDownEventUei()); dbObj.setAttributes(component.getAttributes()); dbObj.setSubcomponents(subcomponents); m_componentDao.update(dbObj); ceq.componentUpdated(getIdentifier(dbObj)); return dbObj; } }.execute(); return existing; } private void deleteComponent(final ComponentIdentifier id, final ComponentEventQueue ceq, final boolean deleteOrphans) { final NCSComponent component = getComponent(id); if (component == null) { throw new WebApplicationException(Status.BAD_REQUEST); } final Set<NCSComponent> parentComponents = component.getParentcomponents(); final Set<ComponentIdentifier> childrenIdentifiers = getIdentifiers(component.getSubcomponents()); // first, we deal with orphans if (deleteOrphans) { for (final ComponentIdentifier subId : childrenIdentifiers) { handleOrphanedComponents(component, subId, ceq, deleteOrphans); } } // first, we remove this component from each of its parents for(final NCSComponent parent : parentComponents) { parent.getSubcomponents().remove(component); m_componentDao.update(parent); } // then we delete this component component.setSubcomponents(EMPTY_COMPONENT_SET); m_componentDao.delete(component); // and any events or alarms depending on it deleteEvents(id.getForeignSource(), id.getForeignId()); deleteAlarms(id.getForeignSource(), id.getForeignId()); // alert that the component is deleted ceq.componentDeleted(getIdentifier(component)); // then alert about the parents sendUpdateEvents(ceq, getIdentifiers(parentComponents)); } private void handleOrphanedComponents(final NCSComponent parent, final ComponentIdentifier child, final ComponentEventQueue ceq, final boolean deleteOrphans) { final ComponentIdentifier parentId = getIdentifier(parent); final NCSComponent childComponent = getComponent(child); final Set<ComponentIdentifier> childChildren = getIdentifiers(childComponent.getSubcomponents()); final Set<ComponentIdentifier> childParents = getIdentifiers(childComponent.getParentcomponents()); LogUtils.tracef(this, "handleOrphanedComponents: parent: %s", parentId); LogUtils.tracef(this, "handleOrphanedComponents: child: %s", child); LogUtils.tracef(this, "handleOrphanedComponents: child's children: %s", childChildren); LogUtils.tracef(this, "handleOrphanedComponents: child's parents: %s", childParents); if (childParents.size() == 1) { final ComponentIdentifier childParent = childParents.iterator().next(); if (childParent.equals(parentId)) { LogUtils.tracef(this, "handleOrphanedComponents: child (%s) has only one parent (%s) and it's being deleted.", child, childParent); deleteComponent(child, ceq, deleteOrphans); } else { LogUtils.tracef(this, "handleOrphanedComponents: child (%s) has only one parent (%s) but it's not the one we expected. This is weird.", child, childParent); ceq.componentUpdated(childParent); } } else { LogUtils.tracef(this, "handleOrphanedComponents: child (%s) has more than one parent, sending updates for remaining parents.", child); for (final ComponentIdentifier childParent : childParents) { ceq.componentUpdated(childParent); } } } private void sendUpdateEvents(final ComponentEventQueue ceq, final Collection<ComponentIdentifier> parentIds) { LogUtils.debugf(this, "sendUpdateEvents: parents = %s", parentIds); for (final ComponentIdentifier parentId : parentIds) { ceq.componentUpdated(parentId); } } private void deleteOrphanedComponents(final Set<ComponentIdentifier> oldComponents, final Set<ComponentIdentifier> newComponents, final ComponentEventQueue ceq) { for (final ComponentIdentifier id : oldComponents) { if (!newComponents.contains(id)) { deleteComponent(id, ceq, true); } } } private void deleteAlarms(final String foreignSource, final String foreignId) { final OnmsCriteria alarmCriteria = new OnmsCriteria(OnmsAlarm.class) .add(Restrictions.like("eventParms", "%componentForeignSource=" + foreignSource +"%")) .add(Restrictions.like("eventParms", "%componentForeignId=" + foreignId +"%")); for(final OnmsAlarm alarm : m_alarmDao.findMatching(alarmCriteria)) { m_alarmDao.delete(alarm); } } private void deleteEvents(final String foreignSource, final String foreignId) { final OnmsCriteria eventCriteria = new OnmsCriteria(OnmsEvent.class) .add(Restrictions.like("eventParms", "%componentForeignSource=" + foreignSource +"%")) .add(Restrictions.like("eventParms", "%componentForeignId=" + foreignId +"%")); for(final OnmsEvent event : m_eventDao.findMatching(eventCriteria)) { m_eventDao.delete(event); } } }